Polish spring-security-oauth2-client main code

Manually polish `spring-security-oauth-cleint` following the
formatting and checkstyle fixes.

Issue gh-8945
This commit is contained in:
Phillip Webb 2020-07-31 19:24:40 -07:00 committed by Rob Winch
parent ad1dbf425f
commit 7a715f9086
71 changed files with 603 additions and 914 deletions

View File

@ -46,7 +46,6 @@ public final class AuthorizationCodeOAuth2AuthorizedClientProvider implements OA
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
context.getClientRegistration().getAuthorizationGrantType()) && context.getAuthorizedClient() == null) {
// ClientAuthorizationRequiredException is caught by

View File

@ -47,7 +47,6 @@ public final class AuthorizationCodeReactiveOAuth2AuthorizedClientProvider
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(
context.getClientRegistration().getAuthorizationGrantType()) && context.getAuthorizedClient() == null) {
// ClientAuthorizationRequiredException is caught by

View File

@ -115,11 +115,9 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
@Override
public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
Authentication principal = authorizeRequest.getPrincipal();
OAuth2AuthorizationContext.Builder contextBuilder;
if (authorizedClient != null) {
contextBuilder = OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient);
@ -138,14 +136,8 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
contextBuilder = OAuth2AuthorizationContext.withClientRegistration(clientRegistration);
}
}
OAuth2AuthorizationContext authorizationContext = contextBuilder.principal(principal)
.attributes((attributes) -> {
Map<String, Object> contextAttributes = this.contextAttributesMapper.apply(authorizeRequest);
if (!CollectionUtils.isEmpty(contextAttributes)) {
attributes.putAll(contextAttributes);
}
}).build();
OAuth2AuthorizationContext authorizationContext = buildAuthorizationContext(authorizeRequest, principal,
contextBuilder);
try {
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
}
@ -153,7 +145,6 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
this.authorizationFailureHandler.onAuthorizationFailure(ex, principal, Collections.emptyMap());
throw ex;
}
if (authorizedClient != null) {
this.authorizationSuccessHandler.onAuthorizationSuccess(authorizedClient, principal,
Collections.emptyMap());
@ -167,10 +158,21 @@ public final class AuthorizedClientServiceOAuth2AuthorizedClientManager implemen
return authorizationContext.getAuthorizedClient();
}
}
return authorizedClient;
}
private OAuth2AuthorizationContext buildAuthorizationContext(OAuth2AuthorizeRequest authorizeRequest,
Authentication principal, OAuth2AuthorizationContext.Builder contextBuilder) {
OAuth2AuthorizationContext authorizationContext = contextBuilder.principal(principal)
.attributes((attributes) -> {
Map<String, Object> contextAttributes = this.contextAttributesMapper.apply(authorizeRequest);
if (!CollectionUtils.isEmpty(contextAttributes)) {
attributes.putAll(contextAttributes);
}
}).build();
return authorizationContext;
}
/**
* Sets the {@link OAuth2AuthorizedClientProvider} used for authorizing (or
* re-authorizing) an OAuth 2.0 Client.

View File

@ -120,7 +120,6 @@ public final class AuthorizedClientServiceReactiveOAuth2AuthorizedClientManager
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
return createAuthorizationContext(authorizeRequest)
.flatMap((authorizationContext) -> authorize(authorizationContext, authorizeRequest.getPrincipal()));
}

View File

@ -64,39 +64,37 @@ public final class ClientCredentialsOAuth2AuthorizedClientProvider implements OA
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
return null;
}
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
// If client is already authorized but access token is NOT expired than no
// need for re-authorization
return null;
}
// As per spec, in section 4.4.3 Access Token Response
// https://tools.ietf.org/html/rfc6749#section-4.4.3
// A refresh token SHOULD NOT be included.
//
// Therefore, renewing an expired access token (re-authorization)
// is the same as acquiring a new access token (authorization).
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest = new OAuth2ClientCredentialsGrantRequest(
clientRegistration);
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, clientCredentialsGrantRequest);
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
tokenResponse.getAccessToken());
}
OAuth2AccessTokenResponse tokenResponse;
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
try {
tokenResponse = this.accessTokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
return this.accessTokenResponseClient.getTokenResponse(clientCredentialsGrantRequest);
}
catch (OAuth2AuthorizationException ex) {
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
}
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
tokenResponse.getAccessToken());
}
private boolean hasTokenExpired(AbstractOAuth2Token token) {

View File

@ -64,31 +64,27 @@ public final class ClientCredentialsReactiveOAuth2AuthorizedClientProvider
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
if (!AuthorizationGrantType.CLIENT_CREDENTIALS.equals(clientRegistration.getAuthorizationGrantType())) {
return Mono.empty();
}
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
// If client is already authorized but access token is NOT expired than no
// need for re-authorization
return Mono.empty();
}
// As per spec, in section 4.4.3 Access Token Response
// https://tools.ietf.org/html/rfc6749#section-4.4.3
// A refresh token SHOULD NOT be included.
//
// Therefore, renewing an expired access token (re-authorization)
// is the same as acquiring a new access token (authorization).
return Mono.just(new OAuth2ClientCredentialsGrantRequest(clientRegistration))
.flatMap(this.accessTokenResponseClient::getTokenResponse)
.onErrorMap(OAuth2AuthorizationException.class,
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(),
e))
(ex) -> new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(),
ex))
.map((tokenResponse) -> new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
tokenResponse.getAccessToken()));
}

View File

@ -99,7 +99,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
*/
public JdbcOAuth2AuthorizedClientService(JdbcOperations jdbcOperations,
ClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(jdbcOperations, "jdbcOperations cannot be null");
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.jdbcOperations = jdbcOperations;
@ -113,15 +112,12 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
String principalName) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
SqlParameterValue[] parameters = new SqlParameterValue[] {
new SqlParameterValue(Types.VARCHAR, clientRegistrationId),
new SqlParameterValue(Types.VARCHAR, principalName) };
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
List<OAuth2AuthorizedClient> result = this.jdbcOperations.query(LOAD_AUTHORIZED_CLIENT_SQL, pss,
this.authorizedClientRowMapper);
return !result.isEmpty() ? (T) result.get(0) : null;
}
@ -129,10 +125,8 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
public void saveAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
Assert.notNull(authorizedClient, "authorizedClient cannot be null");
Assert.notNull(principal, "principal cannot be null");
boolean existsAuthorizedClient = null != this.loadAuthorizedClient(
authorizedClient.getClientRegistration().getRegistrationId(), principal.getName());
if (existsAuthorizedClient) {
updateAuthorizedClient(authorizedClient, principal);
}
@ -149,14 +143,11 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
private void updateAuthorizedClient(OAuth2AuthorizedClient authorizedClient, Authentication principal) {
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper
.apply(new OAuth2AuthorizedClientHolder(authorizedClient, principal));
SqlParameterValue clientRegistrationIdParameter = parameters.remove(0);
SqlParameterValue principalNameParameter = parameters.remove(0);
parameters.add(clientRegistrationIdParameter);
parameters.add(principalNameParameter);
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
this.jdbcOperations.update(UPDATE_AUTHORIZED_CLIENT_SQL, pss);
}
@ -164,7 +155,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
List<SqlParameterValue> parameters = this.authorizedClientParametersMapper
.apply(new OAuth2AuthorizedClientHolder(authorizedClient, principal));
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters.toArray());
this.jdbcOperations.update(SAVE_AUTHORIZED_CLIENT_SQL, pss);
}
@ -172,12 +162,10 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
public void removeAuthorizedClient(String clientRegistrationId, String principalName) {
Assert.hasText(clientRegistrationId, "clientRegistrationId cannot be empty");
Assert.hasText(principalName, "principalName cannot be empty");
SqlParameterValue[] parameters = new SqlParameterValue[] {
new SqlParameterValue(Types.VARCHAR, clientRegistrationId),
new SqlParameterValue(Types.VARCHAR, principalName) };
PreparedStatementSetter pss = new ArgumentPreparedStatementSetter(parameters);
this.jdbcOperations.update(REMOVE_AUTHORIZED_CLIENT_SQL, pss);
}
@ -229,7 +217,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
"The ClientRegistration with id '" + clientRegistrationId + "' exists in the data source, "
+ "however, it was not found in the ClientRegistrationRepository.");
}
OAuth2AccessToken.TokenType tokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue().equalsIgnoreCase(rs.getString("access_token_type"))) {
tokenType = OAuth2AccessToken.TokenType.BEARER;
@ -243,7 +230,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
scopes = StringUtils.commaDelimitedListToSet(accessTokenScopes);
}
OAuth2AccessToken accessToken = new OAuth2AccessToken(tokenType, tokenValue, issuedAt, expiresAt, scopes);
OAuth2RefreshToken refreshToken = null;
byte[] refreshTokenValue = rs.getBytes("refresh_token_value");
if (refreshTokenValue != null) {
@ -255,9 +241,7 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
}
refreshToken = new OAuth2RefreshToken(tokenValue, issuedAt);
}
String principalName = rs.getString("principal_name");
return new OAuth2AuthorizedClient(clientRegistration, principalName, accessToken, refreshToken);
}
@ -277,7 +261,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
ClientRegistration clientRegistration = authorizedClient.getClientRegistration();
OAuth2AccessToken accessToken = authorizedClient.getAccessToken();
OAuth2RefreshToken refreshToken = authorizedClient.getRefreshToken();
List<SqlParameterValue> parameters = new ArrayList<>();
parameters.add(new SqlParameterValue(Types.VARCHAR, clientRegistration.getRegistrationId()));
parameters.add(new SqlParameterValue(Types.VARCHAR, principal.getName()));
@ -301,7 +284,6 @@ public class JdbcOAuth2AuthorizedClientService implements OAuth2AuthorizedClient
}
parameters.add(new SqlParameterValue(Types.BLOB, refreshTokenValue));
parameters.add(new SqlParameterValue(Types.TIMESTAMP, refreshTokenIssuedAt));
return parameters;
}

View File

@ -157,8 +157,8 @@ public final class OAuth2AuthorizeRequest {
private static Authentication createAuthentication(final String principalName) {
Assert.hasText(principalName, "principalName cannot be empty");
return new AbstractAuthenticationToken(null) {
@Override
public Object getCredentials() {
return "";
@ -168,6 +168,7 @@ public final class OAuth2AuthorizeRequest {
public Object getPrincipal() {
return principalName;
}
};
}

View File

@ -77,48 +77,43 @@ public final class PasswordOAuth2AuthorizedClientProvider implements OAuth2Autho
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
return null;
}
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
return null;
}
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
// If client is already authorized and access token is NOT expired than no
// need for re-authorization
return null;
}
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
&& authorizedClient.getRefreshToken() != null) {
// If client is already authorized and access token is expired and a refresh
// token is available,
// than return and allow RefreshTokenOAuth2AuthorizedClientProvider to handle
// the refresh
// token is available, than return and allow
// RefreshTokenOAuth2AuthorizedClientProvider to handle the refresh
return null;
}
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
password);
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(clientRegistration, passwordGrantRequest);
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
}
OAuth2AccessTokenResponse tokenResponse;
private OAuth2AccessTokenResponse getTokenResponse(ClientRegistration clientRegistration,
OAuth2PasswordGrantRequest passwordGrantRequest) {
try {
tokenResponse = this.accessTokenResponseClient.getTokenResponse(passwordGrantRequest);
return this.accessTokenResponseClient.getTokenResponse(passwordGrantRequest);
}
catch (OAuth2AuthorizationException ex) {
throw new ClientAuthorizationException(ex.getError(), clientRegistration.getRegistrationId(), ex);
}
return new OAuth2AuthorizedClient(clientRegistration, context.getPrincipal().getName(),
tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
}
private boolean hasTokenExpired(AbstractOAuth2Token token) {

View File

@ -77,26 +77,21 @@ public final class PasswordReactiveOAuth2AuthorizedClientProvider implements Rea
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
ClientRegistration clientRegistration = context.getClientRegistration();
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (!AuthorizationGrantType.PASSWORD.equals(clientRegistration.getAuthorizationGrantType())) {
return Mono.empty();
}
String username = context.getAttribute(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME);
String password = context.getAttribute(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME);
if (!StringUtils.hasText(username) || !StringUtils.hasText(password)) {
return Mono.empty();
}
if (authorizedClient != null && !hasTokenExpired(authorizedClient.getAccessToken())) {
// If client is already authorized and access token is NOT expired than no
// need for re-authorization
return Mono.empty();
}
if (authorizedClient != null && hasTokenExpired(authorizedClient.getAccessToken())
&& authorizedClient.getRefreshToken() != null) {
// If client is already authorized and access token is expired and a refresh
@ -105,10 +100,8 @@ public final class PasswordReactiveOAuth2AuthorizedClientProvider implements Rea
// handle the refresh
return Mono.empty();
}
OAuth2PasswordGrantRequest passwordGrantRequest = new OAuth2PasswordGrantRequest(clientRegistration, username,
password);
return Mono.just(passwordGrantRequest).flatMap(this.accessTokenResponseClient::getTokenResponse)
.onErrorMap(OAuth2AuthorizationException.class,
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(),

View File

@ -75,13 +75,11 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A
@Nullable
public OAuth2AuthorizedClient authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (authorizedClient == null || authorizedClient.getRefreshToken() == null
|| !hasTokenExpired(authorizedClient.getAccessToken())) {
return null;
}
Object requestScope = context.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
Set<String> scopes = Collections.emptySet();
if (requestScope != null) {
@ -89,22 +87,23 @@ public final class RefreshTokenOAuth2AuthorizedClientProvider implements OAuth2A
+ OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME + "'");
scopes = new HashSet<>(Arrays.asList((String[]) requestScope));
}
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(
authorizedClient.getClientRegistration(), authorizedClient.getAccessToken(),
authorizedClient.getRefreshToken(), scopes);
OAuth2AccessTokenResponse tokenResponse = getTokenResponse(authorizedClient, refreshTokenGrantRequest);
return new OAuth2AuthorizedClient(context.getAuthorizedClient().getClientRegistration(),
context.getPrincipal().getName(), tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
}
OAuth2AccessTokenResponse tokenResponse;
private OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizedClient authorizedClient,
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
try {
tokenResponse = this.accessTokenResponseClient.getTokenResponse(refreshTokenGrantRequest);
return this.accessTokenResponseClient.getTokenResponse(refreshTokenGrantRequest);
}
catch (OAuth2AuthorizationException ex) {
throw new ClientAuthorizationException(ex.getError(),
authorizedClient.getClientRegistration().getRegistrationId(), ex);
}
return new OAuth2AuthorizedClient(context.getAuthorizedClient().getClientRegistration(),
context.getPrincipal().getName(), tokenResponse.getAccessToken(), tokenResponse.getRefreshToken());
}
private boolean hasTokenExpired(AbstractOAuth2Token token) {

View File

@ -77,13 +77,11 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext context) {
Assert.notNull(context, "context cannot be null");
OAuth2AuthorizedClient authorizedClient = context.getAuthorizedClient();
if (authorizedClient == null || authorizedClient.getRefreshToken() == null
|| !hasTokenExpired(authorizedClient.getAccessToken())) {
return Mono.empty();
}
Object requestScope = context.getAttribute(OAuth2AuthorizationContext.REQUEST_SCOPE_ATTRIBUTE_NAME);
Set<String> scopes = Collections.emptySet();
if (requestScope != null) {
@ -92,10 +90,8 @@ public final class RefreshTokenReactiveOAuth2AuthorizedClientProvider
scopes = new HashSet<>(Arrays.asList((String[]) requestScope));
}
ClientRegistration clientRegistration = context.getClientRegistration();
OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest = new OAuth2RefreshTokenGrantRequest(clientRegistration,
authorizedClient.getAccessToken(), authorizedClient.getRefreshToken(), scopes);
return Mono.just(refreshTokenGrantRequest).flatMap(this.accessTokenResponseClient::getTokenResponse)
.onErrorMap(OAuth2AuthorizationException.class,
(e) -> new ClientAuthorizationException(e.getError(), clientRegistration.getRegistrationId(),

View File

@ -16,9 +16,9 @@
package org.springframework.security.oauth2.client;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@ -47,26 +47,20 @@ public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements
* {@link OAuth2AuthorizedClient}.
* @see OAuth2ErrorCodes
*/
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(
/*
* Returned from Resource Servers when an access token provided is
* expired, revoked, malformed, or invalid for other reasons.
*
* Note that this is needed because
* ServletOAuth2AuthorizedClientExchangeFilterFunction delegates this
* type of failure received from a Resource Server to this failure
* handler.
*/
OAuth2ErrorCodes.INVALID_TOKEN,
/*
* Returned from Authorization Servers when the authorization grant or
* refresh token is invalid, expired, revoked, does not match the
* redirection URI used in the authorization request, or was issued to
* another client.
*/
OAuth2ErrorCodes.INVALID_GRANT)));
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES;
static {
Set<String> codes = new LinkedHashSet<>();
// Returned from Resource Servers when an access token provided is expired,
// revoked, malformed, or invalid for other reasons. Note that this is needed
// because ServletOAuth2AuthorizedClientExchangeFilterFunction delegates this type
// of failure received from a Resource Server to this failure handler.
codes.add(OAuth2ErrorCodes.INVALID_TOKEN);
// Returned from Authorization Servers when the authorization grant or refresh
// token is invalid, expired, revoked, does not match the redirection URI used in
// the authorization request, or was issued to another client.
codes.add(OAuth2ErrorCodes.INVALID_GRANT);
DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections.unmodifiableSet(codes);
}
/**
* The OAuth 2.0 error codes which will trigger removal of an
@ -116,10 +110,8 @@ public class RemoveAuthorizedClientOAuth2AuthorizationFailureHandler implements
@Override
public void onAuthorizationFailure(OAuth2AuthorizationException authorizationException, Authentication principal,
Map<String, Object> attributes) {
if (authorizationException instanceof ClientAuthorizationException
&& hasRemovalErrorCode(authorizationException)) {
ClientAuthorizationException clientAuthorizationException = (ClientAuthorizationException) authorizationException;
this.delegate.removeAuthorizedClient(clientAuthorizationException.getClientRegistrationId(), principal,
attributes);

View File

@ -16,9 +16,9 @@
package org.springframework.security.oauth2.client;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
@ -47,24 +47,20 @@ public class RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
* client.
* @see OAuth2ErrorCodes
*/
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections
.unmodifiableSet(new HashSet<>(Arrays.asList(
/*
* Returned from resource servers when an access token provided is
* expired, revoked, malformed, or invalid for other reasons.
*
* Note that this is needed because the
* ServerOAuth2AuthorizedClientExchangeFilterFunction delegates this
* type of failure received from a resource server to this failure
* handler.
*/
OAuth2ErrorCodes.INVALID_TOKEN,
/*
* Returned from authorization servers when a refresh token is
* invalid, expired, revoked, does not match the redirection URI used
* in the authorization request, or was issued to another client.
*/
OAuth2ErrorCodes.INVALID_GRANT)));
public static final Set<String> DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES;
static {
Set<String> codes = new LinkedHashSet<>();
// Returned from resource servers when an access token provided is expired,
// revoked, malformed, or invalid for other reasons. Note that this is needed
// because the ServerOAuth2AuthorizedClientExchangeFilterFunction delegates this
// type of failure received from a resource server to this failure handler.
codes.add(OAuth2ErrorCodes.INVALID_TOKEN);
// Returned from authorization servers when a refresh token is invalid, expired,
// revoked, does not match the redirection URI used in the authorization request,
// or was issued to another client.
codes.add(OAuth2ErrorCodes.INVALID_GRANT);
DEFAULT_REMOVE_AUTHORIZED_CLIENT_ERROR_CODES = Collections.unmodifiableSet(codes);
}
/**
* A delegate that removes an {@link OAuth2AuthorizedClient} from a
@ -116,17 +112,13 @@ public class RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler
@Override
public Mono<Void> onAuthorizationFailure(OAuth2AuthorizationException authorizationException,
Authentication principal, Map<String, Object> attributes) {
if (authorizationException instanceof ClientAuthorizationException
&& hasRemovalErrorCode(authorizationException)) {
ClientAuthorizationException clientAuthorizationException = (ClientAuthorizationException) authorizationException;
return this.delegate.removeAuthorizedClient(clientAuthorizationException.getClientRegistrationId(),
principal, attributes);
}
else {
return Mono.empty();
}
return Mono.empty();
}
/**

View File

@ -64,7 +64,6 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
*/
public OAuth2AuthorizationCodeAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient) {
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
this.accessTokenResponseClient = accessTokenResponseClient;
}
@ -72,30 +71,25 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthorizationException(authorizationResponse.getError());
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthorizationException(oauth2Error);
}
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult = new OAuth2AuthorizationCodeAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), accessTokenResponse.getAccessToken(),
accessTokenResponse.getRefreshToken(), accessTokenResponse.getAdditionalParameters());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}

View File

@ -84,23 +84,19 @@ public class OAuth2AuthorizationCodeReactiveAuthenticationManager implements Rea
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.defer(() -> {
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
OAuth2AuthorizationResponse authorizationResponse = token.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
return Mono.error(new OAuth2AuthorizationException(authorizationResponse.getError()));
}
OAuth2AuthorizationRequest authorizationRequest = token.getAuthorizationExchange()
.getAuthorizationRequest();
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
return Mono.error(new OAuth2AuthorizationException(oauth2Error));
}
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
token.getClientRegistration(), token.getAuthorizationExchange());
return this.accessTokenResponseClient.getTokenResponse(authzRequest).map(onSuccess(token));
});
}

View File

@ -83,7 +83,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
public OAuth2LoginAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
OAuth2UserService<OAuth2UserRequest, OAuth2User> userService) {
Assert.notNull(userService, "userService cannot be null");
this.authorizationCodeAuthenticationProvider = new OAuth2AuthorizationCodeAuthenticationProvider(
accessTokenResponseClient);
@ -93,10 +92,8 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken loginAuthenticationToken = (OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
if (loginAuthenticationToken.getAuthorizationExchange().getAuthorizationRequest().getScopes()
.contains("openid")) {
@ -104,7 +101,6 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
// and let OidcAuthorizationCodeAuthenticationProvider handle it instead
return null;
}
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthenticationToken;
try {
authorizationCodeAuthenticationToken = (OAuth2AuthorizationCodeAuthenticationToken) this.authorizationCodeAuthenticationProvider
@ -116,21 +112,16 @@ public class OAuth2LoginAuthenticationProvider implements AuthenticationProvider
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AccessToken accessToken = authorizationCodeAuthenticationToken.getAccessToken();
Map<String, Object> additionalParameters = authorizationCodeAuthenticationToken.getAdditionalParameters();
OAuth2User oauth2User = this.userService.loadUser(new OAuth2UserRequest(
loginAuthenticationToken.getClientRegistration(), accessToken, additionalParameters));
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oauth2User.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
loginAuthenticationToken.getClientRegistration(), loginAuthenticationToken.getAuthorizationExchange(),
oauth2User, mappedAuthorities, accessToken, authorizationCodeAuthenticationToken.getRefreshToken());
authenticationResult.setDetails(loginAuthenticationToken.getDetails());
return authenticationResult;
}

View File

@ -66,7 +66,6 @@ public class OAuth2LoginAuthenticationToken extends AbstractAuthenticationToken
*/
public OAuth2LoginAuthenticationToken(ClientRegistration clientRegistration,
OAuth2AuthorizationExchange authorizationExchange) {
super(Collections.emptyList());
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
Assert.notNull(authorizationExchange, "authorizationExchange cannot be null");

View File

@ -88,18 +88,15 @@ public class OAuth2LoginReactiveAuthenticationManager implements ReactiveAuthent
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.defer(() -> {
OAuth2AuthorizationCodeAuthenticationToken token = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope REQUIRED. OpenID Connect requests MUST contain the "openid" scope
// value.
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope value.
if (token.getAuthorizationExchange().getAuthorizationRequest().getScopes().contains("openid")) {
// This is an OpenID Connect Authentication Request so return null
// and let OidcAuthorizationCodeReactiveAuthenticationManager handle it
// instead once one is created
return Mono.empty();
}
return this.authorizationCodeManager.authenticate(token)
.onErrorMap(OAuth2AuthorizationException.class,
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
@ -128,7 +125,6 @@ public class OAuth2LoginReactiveAuthenticationManager implements ReactiveAuthent
return this.userService.loadUser(userRequest).map((oauth2User) -> {
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oauth2User.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
authentication.getClientRegistration(), authentication.getAuthorizationExchange(), oauth2User,
mappedAuthorities, accessToken, authentication.getRefreshToken());

View File

@ -74,23 +74,9 @@ public final class DefaultAuthorizationCodeTokenResponseClient
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
Assert.notNull(authorizationCodeGrantRequest, "authorizationCodeGrantRequest cannot be null");
RequestEntity<?> request = this.requestEntityConverter.convert(authorizationCodeGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response;
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
OAuth2AccessTokenResponse tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
@ -99,10 +85,22 @@ public final class DefaultAuthorizationCodeTokenResponseClient
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
.scopes(authorizationCodeGrantRequest.getClientRegistration().getScopes()).build();
}
return tokenResponse;
}
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
try {
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
}
/**
* Sets the {@link Converter} used for converting the
* {@link OAuth2AuthorizationCodeGrantRequest} to a {@link RequestEntity}

View File

@ -74,23 +74,9 @@ public final class DefaultClientCredentialsTokenResponseClient
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
Assert.notNull(clientCredentialsGrantRequest, "clientCredentialsGrantRequest cannot be null");
RequestEntity<?> request = this.requestEntityConverter.convert(clientCredentialsGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response;
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
OAuth2AccessTokenResponse tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
@ -99,10 +85,22 @@ public final class DefaultClientCredentialsTokenResponseClient
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
.scopes(clientCredentialsGrantRequest.getClientRegistration().getScopes()).build();
}
return tokenResponse;
}
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
try {
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
}
/**
* Sets the {@link Converter} used for converting the
* {@link OAuth2ClientCredentialsGrantRequest} to a {@link RequestEntity}

View File

@ -73,23 +73,9 @@ public final class DefaultPasswordTokenResponseClient
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest passwordGrantRequest) {
Assert.notNull(passwordGrantRequest, "passwordGrantRequest cannot be null");
RequestEntity<?> request = this.requestEntityConverter.convert(passwordGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response;
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
OAuth2AccessTokenResponse tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
@ -98,10 +84,22 @@ public final class DefaultPasswordTokenResponseClient
tokenResponse = OAuth2AccessTokenResponse.withResponse(tokenResponse)
.scopes(passwordGrantRequest.getClientRegistration().getScopes()).build();
}
return tokenResponse;
}
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
try {
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the OAuth 2.0 Access Token Response: "
+ ex.getMessage(),
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
}
/**
* Sets the {@link Converter} used for converting the
* {@link OAuth2PasswordGrantRequest} to a {@link RequestEntity} representation of the

View File

@ -69,12 +69,32 @@ public final class DefaultRefreshTokenTokenResponseClient
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
Assert.notNull(refreshTokenGrantRequest, "refreshTokenGrantRequest cannot be null");
RequestEntity<?> request = this.requestEntityConverter.convert(refreshTokenGrantRequest);
ResponseEntity<OAuth2AccessTokenResponse> response = getResponse(request);
OAuth2AccessTokenResponse tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())
|| tokenResponse.getRefreshToken() == null) {
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse
.withResponse(tokenResponse);
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
// If AccessTokenResponse.scope is empty, then default to the scope
// originally requested by the client in the Token Request
tokenResponseBuilder.scopes(refreshTokenGrantRequest.getAccessToken().getScopes());
}
if (tokenResponse.getRefreshToken() == null) {
// Reuse existing refresh token
tokenResponseBuilder.refreshToken(refreshTokenGrantRequest.getRefreshToken().getTokenValue());
}
tokenResponse = tokenResponseBuilder.build();
}
return tokenResponse;
}
ResponseEntity<OAuth2AccessTokenResponse> response;
private ResponseEntity<OAuth2AccessTokenResponse> getResponse(RequestEntity<?> request) {
try {
response = this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
return this.restOperations.exchange(request, OAuth2AccessTokenResponse.class);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
@ -83,31 +103,6 @@ public final class DefaultRefreshTokenTokenResponseClient
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
OAuth2AccessTokenResponse tokenResponse = response.getBody();
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())
|| tokenResponse.getRefreshToken() == null) {
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse
.withResponse(tokenResponse);
if (CollectionUtils.isEmpty(tokenResponse.getAccessToken().getScopes())) {
// As per spec, in Section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
// If AccessTokenResponse.scope is empty, then default to the scope
// originally requested by the client in the Token Request
tokenResponseBuilder.scopes(refreshTokenGrantRequest.getAccessToken().getScopes());
}
if (tokenResponse.getRefreshToken() == null) {
// Reuse existing refresh token
tokenResponseBuilder.refreshToken(refreshTokenGrantRequest.getRefreshToken().getTokenValue());
}
tokenResponse = tokenResponseBuilder.build();
}
return tokenResponse;
}
/**

View File

@ -81,7 +81,6 @@ public class NimbusAuthorizationCodeTokenResponseClient
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
ClientRegistration clientRegistration = authorizationGrantRequest.getClientRegistration();
// Build the authorization code grant request for the token endpoint
AuthorizationCode authorizationCode = new AuthorizationCode(
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationResponse().getCode());
@ -89,19 +88,43 @@ public class NimbusAuthorizationCodeTokenResponseClient
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getRedirectUri());
AuthorizationGrant authorizationCodeGrant = new AuthorizationCodeGrant(authorizationCode, redirectUri);
URI tokenUri = toURI(clientRegistration.getProviderDetails().getTokenUri());
// Set the credentials to authenticate the client at the token endpoint
ClientID clientId = new ClientID(clientRegistration.getClientId());
Secret clientSecret = new Secret(clientRegistration.getClientSecret());
ClientAuthentication clientAuthentication;
if (ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod())) {
clientAuthentication = new ClientSecretPost(clientId, clientSecret);
boolean isPost = ClientAuthenticationMethod.POST.equals(clientRegistration.getClientAuthenticationMethod());
ClientAuthentication clientAuthentication = isPost ? new ClientSecretPost(clientId, clientSecret)
: new ClientSecretBasic(clientId, clientSecret);
com.nimbusds.oauth2.sdk.TokenResponse tokenResponse = getTokenResponse(authorizationCodeGrant, tokenUri,
clientAuthentication);
if (!tokenResponse.indicatesSuccess()) {
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse;
ErrorObject errorObject = tokenErrorResponse.getErrorObject();
throw new OAuth2AuthorizationException(getOAuthError(errorObject));
}
else {
clientAuthentication = new ClientSecretBasic(clientId, clientSecret);
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue();
OAuth2AccessToken.TokenType accessTokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue()
.equalsIgnoreCase(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) {
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
}
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime();
// As per spec, in section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
// If AccessTokenResponse.scope is empty, then default to the scope
// originally requested by the client in the Authorization Request
Set<String> scopes = getScopes(authorizationGrantRequest, accessTokenResponse);
String refreshToken = null;
if (accessTokenResponse.getTokens().getRefreshToken() != null) {
refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
}
Map<String, Object> additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters());
return OAuth2AccessTokenResponse.withToken(accessToken).tokenType(accessTokenType).expiresIn(expiresIn)
.scopes(scopes).refreshToken(refreshToken).additionalParameters(additionalParameters).build();
}
com.nimbusds.oauth2.sdk.TokenResponse tokenResponse;
private com.nimbusds.oauth2.sdk.TokenResponse getTokenResponse(AuthorizationGrant authorizationCodeGrant,
URI tokenUri, ClientAuthentication clientAuthentication) {
try {
// Send the Access Token request
TokenRequest tokenRequest = new TokenRequest(tokenUri, clientAuthentication, authorizationCodeGrant);
@ -109,7 +132,7 @@ public class NimbusAuthorizationCodeTokenResponseClient
httpRequest.setAccept(MediaType.APPLICATION_JSON_VALUE);
httpRequest.setConnectTimeout(30000);
httpRequest.setReadTimeout(30000);
tokenResponse = com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
return com.nimbusds.oauth2.sdk.TokenResponse.parse(httpRequest.send());
}
catch (ParseException | IOException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_TOKEN_RESPONSE_ERROR_CODE,
@ -118,55 +141,25 @@ public class NimbusAuthorizationCodeTokenResponseClient
null);
throw new OAuth2AuthorizationException(oauth2Error, ex);
}
}
if (!tokenResponse.indicatesSuccess()) {
TokenErrorResponse tokenErrorResponse = (TokenErrorResponse) tokenResponse;
ErrorObject errorObject = tokenErrorResponse.getErrorObject();
OAuth2Error oauth2Error;
if (errorObject == null) {
oauth2Error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR);
}
else {
oauth2Error = new OAuth2Error(
(errorObject.getCode() != null) ? errorObject.getCode() : OAuth2ErrorCodes.SERVER_ERROR,
errorObject.getDescription(),
(errorObject.getURI() != null) ? errorObject.getURI().toString() : null);
}
throw new OAuth2AuthorizationException(oauth2Error);
}
AccessTokenResponse accessTokenResponse = (AccessTokenResponse) tokenResponse;
String accessToken = accessTokenResponse.getTokens().getAccessToken().getValue();
OAuth2AccessToken.TokenType accessTokenType = null;
if (OAuth2AccessToken.TokenType.BEARER.getValue()
.equalsIgnoreCase(accessTokenResponse.getTokens().getAccessToken().getType().getValue())) {
accessTokenType = OAuth2AccessToken.TokenType.BEARER;
}
long expiresIn = accessTokenResponse.getTokens().getAccessToken().getLifetime();
// As per spec, in section 5.1 Successful Access Token Response
// https://tools.ietf.org/html/rfc6749#section-5.1
// If AccessTokenResponse.scope is empty, then default to the scope
// originally requested by the client in the Authorization Request
Set<String> scopes;
private Set<String> getScopes(OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest,
AccessTokenResponse accessTokenResponse) {
if (CollectionUtils.isEmpty(accessTokenResponse.getTokens().getAccessToken().getScope())) {
scopes = new LinkedHashSet<>(
return new LinkedHashSet<>(
authorizationGrantRequest.getAuthorizationExchange().getAuthorizationRequest().getScopes());
}
else {
scopes = new LinkedHashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
return new LinkedHashSet<>(accessTokenResponse.getTokens().getAccessToken().getScope().toStringList());
}
private OAuth2Error getOAuthError(ErrorObject errorObject) {
if (errorObject == null) {
return new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR);
}
String refreshToken = null;
if (accessTokenResponse.getTokens().getRefreshToken() != null) {
refreshToken = accessTokenResponse.getTokens().getRefreshToken().getValue();
}
Map<String, Object> additionalParameters = new LinkedHashMap<>(accessTokenResponse.getCustomParameters());
return OAuth2AccessTokenResponse.withToken(accessToken).tokenType(accessTokenType).expiresIn(expiresIn)
.scopes(scopes).refreshToken(refreshToken).additionalParameters(additionalParameters).build();
String errorCode = (errorObject.getCode() != null) ? errorObject.getCode() : OAuth2ErrorCodes.SERVER_ERROR;
String description = errorObject.getDescription();
String uri = (errorObject.getURI() != null) ? errorObject.getURI().toString() : null;
return new OAuth2Error(errorCode, description, uri);
}
private static URI toURI(String uriStr) {

View File

@ -53,12 +53,10 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverter
@Override
public RequestEntity<?> convert(OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = this.buildFormParameters(authorizationCodeGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
.toUri();
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
@ -73,7 +71,6 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverter
OAuth2AuthorizationCodeGrantRequest authorizationCodeGrantRequest) {
ClientRegistration clientRegistration = authorizationCodeGrantRequest.getClientRegistration();
OAuth2AuthorizationExchange authorizationExchange = authorizationCodeGrantRequest.getAuthorizationExchange();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, authorizationCodeGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.CODE, authorizationExchange.getAuthorizationResponse().getCode());
@ -92,7 +89,6 @@ public class OAuth2AuthorizationCodeGrantRequestEntityConverter
if (codeVerifier != null) {
formParameters.add(PkceParameterNames.CODE_VERIFIER, codeVerifier);
}
return formParameters;
}

View File

@ -53,12 +53,10 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverter
@Override
public RequestEntity<?> convert(OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration();
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = this.buildFormParameters(clientCredentialsGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
.toUri();
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
@ -72,7 +70,6 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverter
private MultiValueMap<String, String> buildFormParameters(
OAuth2ClientCredentialsGrantRequest clientCredentialsGrantRequest) {
ClientRegistration clientRegistration = clientCredentialsGrantRequest.getClientRegistration();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, clientCredentialsGrantRequest.getGrantType().getValue());
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())) {
@ -83,7 +80,6 @@ public class OAuth2ClientCredentialsGrantRequestEntityConverter
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
return formParameters;
}

View File

@ -53,12 +53,10 @@ public class OAuth2PasswordGrantRequestEntityConverter
@Override
public RequestEntity<?> convert(OAuth2PasswordGrantRequest passwordGrantRequest) {
ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = buildFormParameters(passwordGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
.toUri();
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
@ -71,7 +69,6 @@ public class OAuth2PasswordGrantRequestEntityConverter
*/
private MultiValueMap<String, String> buildFormParameters(OAuth2PasswordGrantRequest passwordGrantRequest) {
ClientRegistration clientRegistration = passwordGrantRequest.getClientRegistration();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, passwordGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.USERNAME, passwordGrantRequest.getUsername());
@ -84,7 +81,6 @@ public class OAuth2PasswordGrantRequestEntityConverter
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
return formParameters;
}

View File

@ -53,12 +53,10 @@ public class OAuth2RefreshTokenGrantRequestEntityConverter
@Override
public RequestEntity<?> convert(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
HttpHeaders headers = OAuth2AuthorizationGrantRequestEntityUtils.getTokenRequestHeaders(clientRegistration);
MultiValueMap<String, String> formParameters = buildFormParameters(refreshTokenGrantRequest);
URI uri = UriComponentsBuilder.fromUriString(clientRegistration.getProviderDetails().getTokenUri()).build()
.toUri();
return new RequestEntity<>(formParameters, headers, HttpMethod.POST, uri);
}
@ -71,7 +69,6 @@ public class OAuth2RefreshTokenGrantRequestEntityConverter
*/
private MultiValueMap<String, String> buildFormParameters(OAuth2RefreshTokenGrantRequest refreshTokenGrantRequest) {
ClientRegistration clientRegistration = refreshTokenGrantRequest.getClientRegistration();
MultiValueMap<String, String> formParameters = new LinkedMultiValueMap<>();
formParameters.add(OAuth2ParameterNames.GRANT_TYPE, refreshTokenGrantRequest.getGrantType().getValue());
formParameters.add(OAuth2ParameterNames.REFRESH_TOKEN,
@ -84,7 +81,6 @@ public class OAuth2RefreshTokenGrantRequestEntityConverter
formParameters.add(OAuth2ParameterNames.CLIENT_ID, clientRegistration.getClientId());
formParameters.add(OAuth2ParameterNames.CLIENT_SECRET, clientRegistration.getClientSecret());
}
return formParameters;
}

View File

@ -68,12 +68,10 @@ public final class WebClientReactiveRefreshTokenTokenResponseClient
@Override
OAuth2AccessTokenResponse populateTokenResponse(OAuth2RefreshTokenGrantRequest grantRequest,
OAuth2AccessTokenResponse accessTokenResponse) {
if (!CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes())
&& accessTokenResponse.getRefreshToken() != null) {
return accessTokenResponse;
}
OAuth2AccessTokenResponse.Builder tokenResponseBuilder = OAuth2AccessTokenResponse
.withResponse(accessTokenResponse);
if (CollectionUtils.isEmpty(accessTokenResponse.getAccessToken().getScopes())) {

View File

@ -55,14 +55,12 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
if (!HttpStatus.BAD_REQUEST.equals(response.getStatusCode())) {
this.defaultErrorHandler.handleError(response);
}
// A Bearer Token Error may be in the WWW-Authenticate response header
// See https://tools.ietf.org/html/rfc6750#section-3
OAuth2Error oauth2Error = this.readErrorFromWwwAuthenticate(response.getHeaders());
if (oauth2Error == null) {
oauth2Error = this.oauth2ErrorConverter.read(OAuth2Error.class, response);
}
throw new OAuth2AuthorizationException(oauth2Error);
}
@ -71,21 +69,21 @@ public class OAuth2ErrorResponseErrorHandler implements ResponseErrorHandler {
if (!StringUtils.hasText(wwwAuthenticateHeader)) {
return null;
}
BearerTokenError bearerTokenError;
try {
bearerTokenError = BearerTokenError.parse(wwwAuthenticateHeader);
}
catch (Exception ex) {
return null;
}
BearerTokenError bearerTokenError = getBearerToken(wwwAuthenticateHeader);
String errorCode = (bearerTokenError.getCode() != null) ? bearerTokenError.getCode()
: OAuth2ErrorCodes.SERVER_ERROR;
String errorDescription = bearerTokenError.getDescription();
String errorUri = (bearerTokenError.getURI() != null) ? bearerTokenError.getURI().toString() : null;
return new OAuth2Error(errorCode, errorDescription, errorUri);
}
private BearerTokenError getBearerToken(String wwwAuthenticateHeader) {
try {
return BearerTokenError.parse(wwwAuthenticateHeader);
}
catch (Exception ex) {
return null;
}
}
}

View File

@ -52,7 +52,6 @@ final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegist
JsonNode clientRegistrationNode = mapper.readTree(parser);
JsonNode providerDetailsNode = JsonNodeUtils.findObjectNode(clientRegistrationNode, "providerDetails");
JsonNode userInfoEndpointNode = JsonNodeUtils.findObjectNode(providerDetailsNode, "userInfoEndpoint");
return ClientRegistration
.withRegistrationId(JsonNodeUtils.findStringValue(clientRegistrationNode, "registrationId"))
.clientId(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientId"))
@ -62,8 +61,7 @@ final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegist
.authorizationGrantType(AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(clientRegistrationNode, "authorizationGrantType")))
.redirectUri(JsonNodeUtils.findStringValue(clientRegistrationNode, "redirectUri"))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.SET_TYPE_REFERENCE,
mapper))
.scope(JsonNodeUtils.findValue(clientRegistrationNode, "scopes", JsonNodeUtils.STRING_SET, mapper))
.clientName(JsonNodeUtils.findStringValue(clientRegistrationNode, "clientName"))
.authorizationUri(JsonNodeUtils.findStringValue(providerDetailsNode, "authorizationUri"))
.tokenUri(JsonNodeUtils.findStringValue(providerDetailsNode, "tokenUri"))
@ -74,7 +72,7 @@ final class ClientRegistrationDeserializer extends JsonDeserializer<ClientRegist
.jwkSetUri(JsonNodeUtils.findStringValue(providerDetailsNode, "jwkSetUri"))
.issuerUri(JsonNodeUtils.findStringValue(providerDetailsNode, "issuerUri"))
.providerConfigurationMetadata(JsonNodeUtils.findValue(providerDetailsNode, "configurationMetadata",
JsonNodeUtils.MAP_TYPE_REFERENCE, mapper))
JsonNodeUtils.STRING_OBJECT_MAP, mapper))
.build();
}

View File

@ -31,20 +31,18 @@ import com.fasterxml.jackson.databind.ObjectMapper;
*/
abstract class JsonNodeUtils {
static final TypeReference<Set<String>> SET_TYPE_REFERENCE = new TypeReference<Set<String>>() {
static final TypeReference<Set<String>> STRING_SET = new TypeReference<Set<String>>() {
};
static final TypeReference<Map<String, Object>> MAP_TYPE_REFERENCE = new TypeReference<Map<String, Object>>() {
static final TypeReference<Map<String, Object>> STRING_OBJECT_MAP = new TypeReference<Map<String, Object>>() {
};
static String findStringValue(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode nodeValue = jsonNode.findValue(fieldName);
if (nodeValue != null && nodeValue.isTextual()) {
return nodeValue.asText();
}
return null;
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isTextual()) ? value.asText() : null;
}
static <T> T findValue(JsonNode jsonNode, String fieldName, TypeReference<T> valueTypeReference,
@ -52,22 +50,16 @@ abstract class JsonNodeUtils {
if (jsonNode == null) {
return null;
}
JsonNode nodeValue = jsonNode.findValue(fieldName);
if (nodeValue != null && nodeValue.isContainerNode()) {
return mapper.convertValue(nodeValue, valueTypeReference);
}
return null;
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isContainerNode()) ? mapper.convertValue(value, valueTypeReference) : null;
}
static JsonNode findObjectNode(JsonNode jsonNode, String fieldName) {
if (jsonNode == null) {
return null;
}
JsonNode nodeValue = jsonNode.findValue(fieldName);
if (nodeValue != null && nodeValue.isObject()) {
return nodeValue;
}
return null;
JsonNode value = jsonNode.findValue(fieldName);
return (value != null && value.isObject()) ? value : null;
}
}

View File

@ -28,6 +28,7 @@ import com.fasterxml.jackson.databind.util.StdConverter;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest.Builder;
/**
* A {@code JsonDeserializer} for {@link OAuth2AuthorizationRequest}.
@ -45,35 +46,36 @@ final class OAuth2AuthorizationRequestDeserializer extends JsonDeserializer<OAut
public OAuth2AuthorizationRequest deserialize(JsonParser parser, DeserializationContext context)
throws IOException {
ObjectMapper mapper = (ObjectMapper) parser.getCodec();
JsonNode authorizationRequestNode = mapper.readTree(parser);
JsonNode root = mapper.readTree(parser);
return deserialize(parser, mapper, root);
}
private OAuth2AuthorizationRequest deserialize(JsonParser parser, ObjectMapper mapper, JsonNode root)
throws JsonParseException {
AuthorizationGrantType authorizationGrantType = AUTHORIZATION_GRANT_TYPE_CONVERTER
.convert(JsonNodeUtils.findObjectNode(authorizationRequestNode, "authorizationGrantType"));
.convert(JsonNodeUtils.findObjectNode(root, "authorizationGrantType"));
Builder builder = getBuilder(parser, authorizationGrantType);
builder.authorizationUri(JsonNodeUtils.findStringValue(root, "authorizationUri"));
builder.clientId(JsonNodeUtils.findStringValue(root, "clientId"));
builder.redirectUri(JsonNodeUtils.findStringValue(root, "redirectUri"));
builder.scopes(JsonNodeUtils.findValue(root, "scopes", JsonNodeUtils.STRING_SET, mapper));
builder.state(JsonNodeUtils.findStringValue(root, "state"));
builder.additionalParameters(
JsonNodeUtils.findValue(root, "additionalParameters", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
builder.authorizationRequestUri(JsonNodeUtils.findStringValue(root, "authorizationRequestUri"));
builder.attributes(JsonNodeUtils.findValue(root, "attributes", JsonNodeUtils.STRING_OBJECT_MAP, mapper));
return builder.build();
}
OAuth2AuthorizationRequest.Builder builder;
private OAuth2AuthorizationRequest.Builder getBuilder(JsonParser parser,
AuthorizationGrantType authorizationGrantType) throws JsonParseException {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationGrantType)) {
builder = OAuth2AuthorizationRequest.authorizationCode();
return OAuth2AuthorizationRequest.authorizationCode();
}
else if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
builder = OAuth2AuthorizationRequest.implicit();
if (AuthorizationGrantType.IMPLICIT.equals(authorizationGrantType)) {
return OAuth2AuthorizationRequest.implicit();
}
else {
throw new JsonParseException(parser, "Invalid authorizationGrantType");
}
return builder.authorizationUri(JsonNodeUtils.findStringValue(authorizationRequestNode, "authorizationUri"))
.clientId(JsonNodeUtils.findStringValue(authorizationRequestNode, "clientId"))
.redirectUri(JsonNodeUtils.findStringValue(authorizationRequestNode, "redirectUri"))
.scopes(JsonNodeUtils
.findValue(authorizationRequestNode, "scopes", JsonNodeUtils.SET_TYPE_REFERENCE, mapper))
.state(JsonNodeUtils.findStringValue(authorizationRequestNode, "state"))
.additionalParameters(JsonNodeUtils.findValue(authorizationRequestNode, "additionalParameters",
JsonNodeUtils.MAP_TYPE_REFERENCE, mapper))
.authorizationRequestUri(
JsonNodeUtils.findStringValue(authorizationRequestNode, "authorizationRequestUri"))
.attributes(JsonNodeUtils.findValue(authorizationRequestNode, "attributes",
JsonNodeUtils.MAP_TYPE_REFERENCE, mapper))
.build();
throw new JsonParseException(parser, "Invalid authorizationGrantType");
}
}

View File

@ -53,10 +53,10 @@ abstract class StdConverters {
if (ClientAuthenticationMethod.BASIC.getValue().equalsIgnoreCase(value)) {
return ClientAuthenticationMethod.BASIC;
}
else if (ClientAuthenticationMethod.POST.getValue().equalsIgnoreCase(value)) {
if (ClientAuthenticationMethod.POST.getValue().equalsIgnoreCase(value)) {
return ClientAuthenticationMethod.POST;
}
else if (ClientAuthenticationMethod.NONE.getValue().equalsIgnoreCase(value)) {
if (ClientAuthenticationMethod.NONE.getValue().equalsIgnoreCase(value)) {
return ClientAuthenticationMethod.NONE;
}
return null;
@ -72,13 +72,13 @@ abstract class StdConverters {
if (AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.AUTHORIZATION_CODE;
}
else if (AuthorizationGrantType.IMPLICIT.getValue().equalsIgnoreCase(value)) {
if (AuthorizationGrantType.IMPLICIT.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.IMPLICIT;
}
else if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
if (AuthorizationGrantType.CLIENT_CREDENTIALS.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.CLIENT_CREDENTIALS;
}
else if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
if (AuthorizationGrantType.PASSWORD.getValue().equalsIgnoreCase(value)) {
return AuthorizationGrantType.PASSWORD;
}
return null;
@ -94,10 +94,10 @@ abstract class StdConverters {
if (AuthenticationMethod.HEADER.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.HEADER;
}
else if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
if (AuthenticationMethod.FORM.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.FORM;
}
else if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
if (AuthenticationMethod.QUERY.getValue().equalsIgnoreCase(value)) {
return AuthenticationMethod.QUERY;
}
return null;

View File

@ -110,7 +110,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
public OidcAuthorizationCodeAuthenticationProvider(
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
OAuth2UserService<OidcUserRequest, OidcUser> userService) {
Assert.notNull(accessTokenResponseClient, "accessTokenResponseClient cannot be null");
Assert.notNull(userService, "userService cannot be null");
this.accessTokenResponseClient = accessTokenResponseClient;
@ -120,7 +119,6 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
@Override
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
OAuth2LoginAuthenticationToken authorizationCodeAuthentication = (OAuth2LoginAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
@ -131,35 +129,20 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
// and let OAuth2LoginAuthenticationProvider handle it instead
return null;
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationResponse();
if (authorizationResponse.statusError()) {
throw new OAuth2AuthenticationException(authorizationResponse.getError(),
authorizationResponse.getError().toString());
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AccessTokenResponse accessTokenResponse;
try {
accessTokenResponse = this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AccessTokenResponse accessTokenResponse = getResponse(authorizationCodeAuthentication);
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
@ -169,39 +152,54 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
}
OidcIdToken idToken = createOidcToken(clientRegistration, accessTokenResponse);
// Validate nonce
String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
if (requestNonce != null) {
String nonceHash;
try {
nonceHash = createHash(requestNonce);
}
catch (NoSuchAlgorithmException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String nonceHashClaim = idToken.getNonce();
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
validateNonce(authorizationRequest, idToken);
OidcUser oidcUser = this.userService.loadUser(new OidcUserRequest(clientRegistration,
accessTokenResponse.getAccessToken(), idToken, additionalParameters));
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oidcUser.getAuthorities());
OAuth2LoginAuthenticationToken authenticationResult = new OAuth2LoginAuthenticationToken(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), oidcUser, mappedAuthorities,
accessTokenResponse.getAccessToken(), accessTokenResponse.getRefreshToken());
authenticationResult.setDetails(authorizationCodeAuthentication.getDetails());
return authenticationResult;
}
private OAuth2AccessTokenResponse getResponse(OAuth2LoginAuthenticationToken authorizationCodeAuthentication) {
try {
return this.accessTokenResponseClient.getTokenResponse(
new OAuth2AuthorizationCodeGrantRequest(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange()));
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
private void validateNonce(OAuth2AuthorizationRequest authorizationRequest, OidcIdToken idToken) {
String requestNonce = authorizationRequest.getAttribute(OidcParameterNames.NONCE);
if (requestNonce == null) {
return;
}
String nonceHash = getNonceHash(requestNonce);
String nonceHashClaim = idToken.getNonce();
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
private String getNonceHash(String requestNonce) {
try {
return createHash(requestNonce);
}
catch (NoSuchAlgorithmException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
/**
* Sets the {@link JwtDecoderFactory} used for {@link OidcIdToken} signature
* verification. The factory returns a {@link JwtDecoder} associated to the provided
@ -235,18 +233,21 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
private OidcIdToken createOidcToken(ClientRegistration clientRegistration,
OAuth2AccessTokenResponse accessTokenResponse) {
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
Jwt jwt;
Jwt jwt = getJwt(accessTokenResponse, jwtDecoder);
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
jwt.getClaims());
return idToken;
}
private Jwt getJwt(OAuth2AccessTokenResponse accessTokenResponse, JwtDecoder jwtDecoder) {
try {
jwt = jwtDecoder
.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
Map<String, Object> parameters = accessTokenResponse.getAdditionalParameters();
return jwtDecoder.decode((String) parameters.get(OidcParameterNames.ID_TOKEN));
}
catch (JwtException ex) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
}
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(),
jwt.getClaims());
return idToken;
}
static String createHash(String nonce) throws NoSuchAlgorithmException {

View File

@ -114,7 +114,6 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
public Mono<Authentication> authenticate(Authentication authentication) {
return Mono.defer(() -> {
OAuth2AuthorizationCodeAuthenticationToken authorizationCodeAuthentication = (OAuth2AuthorizationCodeAuthenticationToken) authentication;
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope REQUIRED. OpenID Connect requests MUST contain the "openid" scope
@ -125,26 +124,21 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
// and let OAuth2LoginReactiveAuthenticationManager handle it instead
return Mono.empty();
}
OAuth2AuthorizationRequest authorizationRequest = authorizationCodeAuthentication.getAuthorizationExchange()
.getAuthorizationRequest();
OAuth2AuthorizationResponse authorizationResponse = authorizationCodeAuthentication
.getAuthorizationExchange().getAuthorizationResponse();
if (authorizationResponse.statusError()) {
return Mono.error(new OAuth2AuthenticationException(authorizationResponse.getError(),
authorizationResponse.getError().toString()));
}
if (!authorizationResponse.getState().equals(authorizationRequest.getState())) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_STATE_PARAMETER_ERROR_CODE);
return Mono.error(new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString()));
}
OAuth2AuthorizationCodeGrantRequest authzRequest = new OAuth2AuthorizationCodeGrantRequest(
authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange());
return this.accessTokenResponseClient.getTokenResponse(authzRequest).flatMap(
(accessTokenResponse) -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse))
.onErrorMap(OAuth2AuthorizationException.class,
@ -190,7 +184,6 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
OAuth2AccessToken accessToken = accessTokenResponse.getAccessToken();
ClientRegistration clientRegistration = authorizationCodeAuthentication.getClientRegistration();
Map<String, Object> additionalParameters = accessTokenResponse.getAdditionalParameters();
if (!additionalParameters.containsKey(OidcParameterNames.ID_TOKEN)) {
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE,
"Missing (required) ID Token in Token Response for Client Registration: "
@ -198,14 +191,12 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
null);
return Mono.error(new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString()));
}
return createOidcToken(clientRegistration, accessTokenResponse)
.doOnNext((idToken) -> validateNonce(authorizationCodeAuthentication, idToken))
.map((idToken) -> new OidcUserRequest(clientRegistration, accessToken, idToken, additionalParameters))
.flatMap(this.userService::loadUser).map((oauth2User) -> {
Collection<? extends GrantedAuthority> mappedAuthorities = this.authoritiesMapper
.mapAuthorities(oauth2User.getAuthorities());
return new OAuth2LoginAuthenticationToken(authorizationCodeAuthentication.getClientRegistration(),
authorizationCodeAuthentication.getAuthorizationExchange(), oauth2User, mappedAuthorities,
accessToken, accessTokenResponse.getRefreshToken());
@ -225,14 +216,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
String requestNonce = authorizationCodeAuthentication.getAuthorizationExchange().getAuthorizationRequest()
.getAttribute(OidcParameterNames.NONCE);
if (requestNonce != null) {
String nonceHash;
try {
nonceHash = createHash(requestNonce);
}
catch (NoSuchAlgorithmException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String nonceHash = getNonceHash(requestNonce);
String nonceHashClaim = idToken.getNonce();
if (nonceHashClaim == null || !nonceHashClaim.equals(nonceHash)) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
@ -243,6 +227,16 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements React
return Mono.just(idToken);
}
private static String getNonceHash(String requestNonce) {
try {
return createHash(requestNonce);
}
catch (NoSuchAlgorithmException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_NONCE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
static String createHash(String nonce) throws NoSuchAlgorithmException {
MessageDigest md = MessageDigest.getInstance("SHA-256");
byte[] digest = md.digest(nonce.getBytes(StandardCharsets.US_ASCII));

View File

@ -20,6 +20,7 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -66,15 +67,16 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
private static Map<JwsAlgorithm, String> jcaAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
{
put(MacAlgorithm.HS256, "HmacSHA256");
put(MacAlgorithm.HS384, "HmacSHA384");
put(MacAlgorithm.HS512, "HmacSHA512");
}
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
static {
Map<JwsAlgorithm, String> mappings = new HashMap<>();
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
};
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
private static final ClaimTypeConverter DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
createDefaultClaimTypeConverters());
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
@ -100,23 +102,22 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
return claimTypeConverters;
Map<String, Converter<Object, ?>> converters = new HashMap<>();
converters.put(IdTokenClaimNames.ISS, urlConverter);
converters.put(IdTokenClaimNames.AUD, collectionStringConverter);
converters.put(IdTokenClaimNames.NONCE, stringConverter);
converters.put(IdTokenClaimNames.EXP, instantConverter);
converters.put(IdTokenClaimNames.IAT, instantConverter);
converters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
converters.put(IdTokenClaimNames.AMR, collectionStringConverter);
converters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
converters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
converters.put(StandardClaimNames.UPDATED_AT, instantConverter);
return converters;
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
targetDescriptor);
}
@ -165,7 +166,7 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
}
return NimbusJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm).build();
}
else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
//
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as
@ -176,7 +177,6 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
// For MAC based algorithms, the behavior is unspecified if the aud is
// multi-valued or
// if an azp value is present that is different than the aud value.
String clientSecret = clientRegistration.getClientSecret();
if (!StringUtils.hasText(clientSecret)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
@ -187,10 +187,9 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
jcaAlgorithmMappings.get(jwsAlgorithm));
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
return NimbusJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm).build();
}
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '"
+ clientRegistration.getRegistrationId()

View File

@ -68,21 +68,17 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
public OAuth2TokenValidatorResult validate(Jwt idToken) {
// 3.1.3.7 ID Token Validation
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
Map<String, Object> invalidClaims = validateRequiredClaims(idToken);
if (!invalidClaims.isEmpty()) {
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
}
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained
// during Discovery)
// MUST exactly match the value of the iss (issuer) Claim.
String metadataIssuer = this.clientRegistration.getProviderDetails().getIssuerUri();
if (metadataIssuer != null && !Objects.equals(metadataIssuer, idToken.getIssuer().toExternalForm())) {
invalidClaims.put(IdTokenClaimNames.ISS, idToken.getIssuer());
}
// 3. The Client MUST validate that the aud (audience) Claim contains its
// client_id value
// registered at the Issuer identified by the iss (issuer) Claim as an audience.
@ -93,31 +89,26 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
if (!idToken.getAudience().contains(this.clientRegistration.getClientId())) {
invalidClaims.put(IdTokenClaimNames.AUD, idToken.getAudience());
}
// 4. If the ID Token contains multiple audiences,
// the Client SHOULD verify that an azp Claim is present.
String authorizedParty = idToken.getClaimAsString(IdTokenClaimNames.AZP);
if (idToken.getAudience().size() > 1 && authorizedParty == null) {
invalidClaims.put(IdTokenClaimNames.AZP, authorizedParty);
}
// 5. If an azp (authorized party) Claim is present,
// the Client SHOULD verify that its client_id is the Claim Value.
if (authorizedParty != null && !authorizedParty.equals(this.clientRegistration.getClientId())) {
invalidClaims.put(IdTokenClaimNames.AZP, authorizedParty);
}
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the
// Client
// in the id_token_signed_response_alg parameter during Registration.
// TODO Depends on gh-4413
// 9. The current time MUST be before the time represented by the exp Claim.
Instant now = Instant.now(this.clock);
if (now.minus(this.clockSkew).isAfter(idToken.getExpiresAt())) {
invalidClaims.put(IdTokenClaimNames.EXP, idToken.getExpiresAt());
}
// 10. The iat Claim can be used to reject tokens that were issued too far away
// from the current time,
// limiting the amount of time that nonces need to be stored to prevent attacks.
@ -125,11 +116,9 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
if (now.plus(this.clockSkew).isBefore(idToken.getIssuedAt())) {
invalidClaims.put(IdTokenClaimNames.IAT, idToken.getIssuedAt());
}
if (!invalidClaims.isEmpty()) {
return OAuth2TokenValidatorResult.failure(invalidIdToken(invalidClaims));
}
return OAuth2TokenValidatorResult.success();
}
@ -164,7 +153,6 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
private static Map<String, Object> validateRequiredClaims(Jwt idToken) {
Map<String, Object> requiredClaims = new HashMap<>();
URL issuer = idToken.getIssuer();
if (issuer == null) {
requiredClaims.put(IdTokenClaimNames.ISS, issuer);
@ -185,7 +173,6 @@ public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
if (issuedAt == null) {
requiredClaims.put(IdTokenClaimNames.IAT, issuedAt);
}
return requiredClaims;
}

View File

@ -20,6 +20,7 @@ import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.time.Instant;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@ -66,15 +67,16 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
private static Map<JwsAlgorithm, String> jcaAlgorithmMappings = new HashMap<JwsAlgorithm, String>() {
{
put(MacAlgorithm.HS256, "HmacSHA256");
put(MacAlgorithm.HS384, "HmacSHA384");
put(MacAlgorithm.HS512, "HmacSHA512");
}
};
private static final Map<JwsAlgorithm, String> JCA_ALGORITHM_MAPPINGS;
static {
Map<JwsAlgorithm, String> mappings = new HashMap<JwsAlgorithm, String>();
mappings.put(MacAlgorithm.HS256, "HmacSHA256");
mappings.put(MacAlgorithm.HS384, "HmacSHA384");
mappings.put(MacAlgorithm.HS512, "HmacSHA512");
JCA_ALGORITHM_MAPPINGS = Collections.unmodifiableMap(mappings);
}
private static final Converter<Map<String, Object>, Map<String, Object>> DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
private static final ClaimTypeConverter DEFAULT_CLAIM_TYPE_CONVERTER = new ClaimTypeConverter(
createDefaultClaimTypeConverters());
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
@ -100,19 +102,18 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
Converter<Object, ?> stringConverter = getConverter(TypeDescriptor.valueOf(String.class));
Converter<Object, ?> collectionStringConverter = getConverter(
TypeDescriptor.collection(Collection.class, TypeDescriptor.valueOf(String.class)));
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put(IdTokenClaimNames.ISS, urlConverter);
claimTypeConverters.put(IdTokenClaimNames.AUD, collectionStringConverter);
claimTypeConverters.put(IdTokenClaimNames.NONCE, stringConverter);
claimTypeConverters.put(IdTokenClaimNames.EXP, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.IAT, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
claimTypeConverters.put(IdTokenClaimNames.AMR, collectionStringConverter);
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.UPDATED_AT, instantConverter);
return claimTypeConverters;
Map<String, Converter<Object, ?>> converters = new HashMap<>();
converters.put(IdTokenClaimNames.ISS, urlConverter);
converters.put(IdTokenClaimNames.AUD, collectionStringConverter);
converters.put(IdTokenClaimNames.NONCE, stringConverter);
converters.put(IdTokenClaimNames.EXP, instantConverter);
converters.put(IdTokenClaimNames.IAT, instantConverter);
converters.put(IdTokenClaimNames.AUTH_TIME, instantConverter);
converters.put(IdTokenClaimNames.AMR, collectionStringConverter);
converters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
converters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
converters.put(StandardClaimNames.UPDATED_AT, instantConverter);
return converters;
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
@ -153,7 +154,6 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by
// the Client
// in the id_token_signed_response_alg parameter during Registration.
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
if (!StringUtils.hasText(jwkSetUri)) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
@ -166,7 +166,7 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
return NimbusReactiveJwtDecoder.withJwkSetUri(jwkSetUri).jwsAlgorithm((SignatureAlgorithm) jwsAlgorithm)
.build();
}
else if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
if (jwsAlgorithm != null && MacAlgorithm.class.isAssignableFrom(jwsAlgorithm.getClass())) {
// https://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
//
// 8. If the JWT alg Header Parameter uses a MAC based algorithm such as
@ -188,11 +188,10 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
SecretKeySpec secretKeySpec = new SecretKeySpec(clientSecret.getBytes(StandardCharsets.UTF_8),
jcaAlgorithmMappings.get(jwsAlgorithm));
JCA_ALGORITHM_MAPPINGS.get(jwsAlgorithm));
return NimbusReactiveJwtDecoder.withSecretKey(secretKeySpec).macAlgorithm((MacAlgorithm) jwsAlgorithm)
.build();
}
OAuth2Error oauth2Error = new OAuth2Error(MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
"Failed to find a Signature Verifier for Client Registration: '"
+ clientRegistration.getRegistrationId()

View File

@ -81,7 +81,6 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
@ -113,9 +112,7 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo,
userNameAttributeName);
}
else {
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
}
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
});
}
@ -123,7 +120,6 @@ public class OidcReactiveOAuth2UserService implements ReactiveOAuth2UserService<
if (!OidcUserRequestUtils.shouldRetrieveUserInfo(userRequest)) {
return Mono.empty();
}
return this.oauth2UserService.loadUser(userRequest).map(OAuth2User::getAttributes)
.map((claims) -> convertClaims(claims, userRequest.getClientRegistration())).map(OidcUserInfo::new)
.doOnNext((userInfo) -> {

View File

@ -47,7 +47,6 @@ public class OidcUserRequest extends OAuth2UserRequest {
* @param idToken the ID Token
*/
public OidcUserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken, OidcIdToken idToken) {
this(clientRegistration, accessToken, idToken, Collections.emptyMap());
}
@ -61,7 +60,6 @@ public class OidcUserRequest extends OAuth2UserRequest {
*/
public OidcUserRequest(ClientRegistration clientRegistration, OAuth2AccessToken accessToken, OidcIdToken idToken,
Map<String, Object> additionalParameters) {
super(clientRegistration, accessToken, additionalParameters);
Assert.notNull(idToken, "idToken cannot be null");
this.idToken = idToken;

View File

@ -46,10 +46,8 @@ final class OidcUserRequestUtils {
// Auto-disabled if UserInfo Endpoint URI is not provided
ClientRegistration clientRegistration = userRequest.getClientRegistration();
if (StringUtils.isEmpty(clientRegistration.getProviderDetails().getUserInfoEndpoint().getUri())) {
return false;
}
// The Claims requested by the profile, email, address, and phone scope values
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
// when a response_type value is used that results in an Access Token being
@ -60,13 +58,11 @@ final class OidcUserRequestUtils {
// The Authorization Code Grant Flow, which is response_type=code, results in an
// Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
// Return true if there is at least one match between the authorized scope(s)
// and UserInfo scope(s)
return CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(),
userRequest.getClientRegistration().getScopes());
}
return false;
}

View File

@ -30,6 +30,7 @@ import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
@ -87,7 +88,6 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
public static Map<String, Converter<Object, ?>> createDefaultClaimTypeConverters() {
Converter<Object, ?> booleanConverter = getConverter(TypeDescriptor.valueOf(Boolean.class));
Converter<Object, ?> instantConverter = getConverter(TypeDescriptor.valueOf(Instant.class));
Map<String, Converter<Object, ?>> claimTypeConverters = new HashMap<>();
claimTypeConverters.put(StandardClaimNames.EMAIL_VERIFIED, booleanConverter);
claimTypeConverters.put(StandardClaimNames.PHONE_NUMBER_VERIFIED, booleanConverter);
@ -96,7 +96,7 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
}
private static Converter<Object, ?> getConverter(TypeDescriptor targetDescriptor) {
final TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
TypeDescriptor sourceDescriptor = TypeDescriptor.valueOf(Object.class);
return (source) -> ClaimConversionService.getSharedInstance().convert(source, sourceDescriptor,
targetDescriptor);
}
@ -107,26 +107,14 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
OidcUserInfo userInfo = null;
if (this.shouldRetrieveUserInfo(userRequest)) {
OAuth2User oauth2User = this.oauth2UserService.loadUser(userRequest);
Map<String, Object> claims;
Converter<Map<String, Object>, Map<String, Object>> claimTypeConverter = this.claimTypeConverterFactory
.apply(userRequest.getClientRegistration());
if (claimTypeConverter != null) {
claims = claimTypeConverter.convert(oauth2User.getAttributes());
}
else {
claims = DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
}
Map<String, Object> claims = getClaims(userRequest, oauth2User);
userInfo = new OidcUserInfo(claims);
// https://openid.net/specs/openid-connect-core-1_0.html#UserInfoResponse
// 1) The sub (subject) Claim MUST always be returned in the UserInfo Response
if (userInfo.getSubject() == null) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
// 2) Due to the possibility of token substitution attacks (see Section
// 16.11),
// the UserInfo Response is not guaranteed to be about the End-User
@ -139,36 +127,39 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
}
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OidcUserAuthority(userRequest.getIdToken(), userInfo));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return getUser(userRequest, userInfo, authorities);
}
OidcUser user;
private Map<String, Object> getClaims(OidcUserRequest userRequest, OAuth2User oauth2User) {
Converter<Map<String, Object>, Map<String, Object>> converter = this.claimTypeConverterFactory
.apply(userRequest.getClientRegistration());
if (converter != null) {
return converter.convert(oauth2User.getAttributes());
}
return DEFAULT_CLAIM_TYPE_CONVERTER.convert(oauth2User.getAttributes());
}
String userNameAttributeName = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUserNameAttributeName();
private OidcUser getUser(OidcUserRequest userRequest, OidcUserInfo userInfo, Set<GrantedAuthority> authorities) {
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
String userNameAttributeName = providerDetails.getUserInfoEndpoint().getUserNameAttributeName();
if (StringUtils.hasText(userNameAttributeName)) {
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo, userNameAttributeName);
}
else {
user = new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
}
return user;
return new DefaultOidcUser(authorities, userRequest.getIdToken(), userInfo);
}
private boolean shouldRetrieveUserInfo(OidcUserRequest userRequest) {
// Auto-disabled if UserInfo Endpoint URI is not provided
if (StringUtils
.isEmpty(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
ProviderDetails providerDetails = userRequest.getClientRegistration().getProviderDetails();
if (StringUtils.isEmpty(providerDetails.getUserInfoEndpoint().getUri())) {
return false;
}
// The Claims requested by the profile, email, address, and phone scope values
// are returned from the UserInfo Endpoint (as described in Section 5.3.2),
// when a response_type value is used that results in an Access Token being
@ -180,13 +171,11 @@ public class OidcUserService implements OAuth2UserService<OidcUserRequest, OidcU
// Access Token being issued.
if (AuthorizationGrantType.AUTHORIZATION_CODE
.equals(userRequest.getClientRegistration().getAuthorizationGrantType())) {
// Return true if there is at least one match between the authorized scope(s)
// and accessible scope(s)
return this.accessibleScopes.isEmpty()
|| CollectionUtils.containsAny(userRequest.getAccessToken().getScopes(), this.accessibleScopes);
}
return false;
}

View File

@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistration.ProviderDetails;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.web.authentication.logout.SimpleUrlLogoutSuccessHandler;
@ -59,36 +60,29 @@ public final class OidcClientInitiatedLogoutSuccessHandler extends SimpleUrlLogo
protected String determineTargetUrl(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
String targetUrl = null;
URI endSessionEndpoint;
if (authentication instanceof OAuth2AuthenticationToken && authentication.getPrincipal() instanceof OidcUser) {
String registrationId = ((OAuth2AuthenticationToken) authentication).getAuthorizedClientRegistrationId();
ClientRegistration clientRegistration = this.clientRegistrationRepository
.findByRegistrationId(registrationId);
endSessionEndpoint = this.endSessionEndpoint(clientRegistration);
URI endSessionEndpoint = this.endSessionEndpoint(clientRegistration);
if (endSessionEndpoint != null) {
String idToken = idToken(authentication);
URI postLogoutRedirectUri = postLogoutRedirectUri(request);
targetUrl = endpointUri(endSessionEndpoint, idToken, postLogoutRedirectUri);
}
}
if (targetUrl == null) {
targetUrl = super.determineTargetUrl(request, response);
}
return targetUrl;
return (targetUrl != null) ? targetUrl : super.determineTargetUrl(request, response);
}
private URI endSessionEndpoint(ClientRegistration clientRegistration) {
URI result = null;
if (clientRegistration != null) {
Object endSessionEndpoint = clientRegistration.getProviderDetails().getConfigurationMetadata()
.get("end_session_endpoint");
ProviderDetails providerDetails = clientRegistration.getProviderDetails();
Object endSessionEndpoint = providerDetails.getConfigurationMetadata().get("end_session_endpoint");
if (endSessionEndpoint != null) {
result = URI.create(endSessionEndpoint.toString());
return URI.create(endSessionEndpoint.toString());
}
}
return result;
return null;
}
private String idToken(Authentication authentication) {

View File

@ -66,14 +66,10 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
*/
public OidcClientInitiatedServerLogoutSuccessHandler(
ReactiveClientRegistrationRepository clientRegistrationRepository) {
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
this.clientRegistrationRepository = clientRegistrationRepository;
}
/**
* {@inheritDoc}
*/
@Override
public Mono<Void> onLogoutSuccess(WebFilterExchange exchange, Authentication authentication) {
return Mono.just(authentication).filter(OAuth2AuthenticationToken.class::isInstance)
@ -95,16 +91,14 @@ public class OidcClientInitiatedServerLogoutSuccessHandler implements ServerLogo
}
private URI endSessionEndpoint(ClientRegistration clientRegistration) {
URI result = null;
if (clientRegistration != null) {
Object endSessionEndpoint = clientRegistration.getProviderDetails().getConfigurationMetadata()
.get("end_session_endpoint");
if (endSessionEndpoint != null) {
result = URI.create(endSessionEndpoint.toString());
return URI.create(endSessionEndpoint.toString());
}
}
return result;
return null;
}
private URI endpointUri(URI endSessionEndpoint, String idToken, URI postLogoutRedirectUri) {

View File

@ -620,26 +620,29 @@ public final class ClientRegistration implements Serializable {
private ClientRegistration create() {
ClientRegistration clientRegistration = new ClientRegistration();
clientRegistration.registrationId = this.registrationId;
clientRegistration.clientId = this.clientId;
clientRegistration.clientSecret = StringUtils.hasText(this.clientSecret) ? this.clientSecret : "";
if (this.clientAuthenticationMethod != null) {
clientRegistration.clientAuthenticationMethod = this.clientAuthenticationMethod;
}
else {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& !StringUtils.hasText(this.clientSecret)) {
clientRegistration.clientAuthenticationMethod = ClientAuthenticationMethod.NONE;
}
else {
clientRegistration.clientAuthenticationMethod = ClientAuthenticationMethod.BASIC;
}
}
clientRegistration.clientAuthenticationMethod = (this.clientAuthenticationMethod != null)
? this.clientAuthenticationMethod : deduceClientAuthenticationMethod(clientRegistration);
clientRegistration.authorizationGrantType = this.authorizationGrantType;
clientRegistration.redirectUri = this.redirectUri;
clientRegistration.scopes = this.scopes;
clientRegistration.providerDetails = createProviderDetails(clientRegistration);
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
: this.registrationId;
return clientRegistration;
}
private ClientAuthenticationMethod deduceClientAuthenticationMethod(ClientRegistration clientRegistration) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(this.authorizationGrantType)
&& !StringUtils.hasText(this.clientSecret)) {
return ClientAuthenticationMethod.NONE;
}
return ClientAuthenticationMethod.BASIC;
}
private ProviderDetails createProviderDetails(ClientRegistration clientRegistration) {
ProviderDetails providerDetails = clientRegistration.new ProviderDetails();
providerDetails.authorizationUri = this.authorizationUri;
providerDetails.tokenUri = this.tokenUri;
@ -649,12 +652,7 @@ public final class ClientRegistration implements Serializable {
providerDetails.jwkSetUri = this.jwkSetUri;
providerDetails.issuerUri = this.issuerUri;
providerDetails.configurationMetadata = Collections.unmodifiableMap(this.configurationMetadata);
clientRegistration.providerDetails = providerDetails;
clientRegistration.clientName = StringUtils.hasText(this.clientName) ? this.clientName
: this.registrationId;
return clientRegistration;
return providerDetails;
}
private void validateAuthorizationCodeGrantType() {
@ -696,7 +694,6 @@ public final class ClientRegistration implements Serializable {
if (this.scopes == null) {
return;
}
for (String scope : this.scopes) {
Assert.isTrue(validateScope(scope), "scope \"" + scope + "\" contains invalid characters");
}

View File

@ -150,7 +150,6 @@ public final class ClientRegistrations {
private static Supplier<ClientRegistration.Builder> oidc(URI issuer) {
URI uri = UriComponentsBuilder.fromUri(issuer).replacePath(issuer.getPath() + OIDC_METADATA_PATH)
.build(Collections.emptyMap());
return () -> {
RequestEntity<Void> request = RequestEntity.get(uri).build();
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
@ -182,12 +181,10 @@ public final class ClientRegistrations {
Map<String, Object> configuration = rest.exchange(request, typeReference).getBody();
AuthorizationServerMetadata metadata = parse(configuration, AuthorizationServerMetadata::parse);
ClientRegistration.Builder builder = withProviderConfiguration(metadata, issuer.toASCIIString());
URI jwkSetUri = metadata.getJWKSetURI();
if (jwkSetUri != null) {
builder.jwkSetUri(jwkSetUri.toASCIIString());
}
String userinfoEndpoint = (String) configuration.get("userinfo_endpoint");
if (userinfoEndpoint != null) {
builder.userInfoUri(userinfoEndpoint);
@ -221,7 +218,6 @@ public final class ClientRegistrations {
}
private static <T> T parse(Map<String, Object> body, ThrowingFunction<JSONObject, T, ParseException> parser) {
try {
return parser.apply(new JSONObject(body));
}
@ -233,25 +229,19 @@ public final class ClientRegistrations {
private static ClientRegistration.Builder withProviderConfiguration(AuthorizationServerMetadata metadata,
String issuer) {
String metadataIssuer = metadata.getIssuer().getValue();
if (!issuer.equals(metadataIssuer)) {
throw new IllegalStateException(
"The Issuer \"" + metadataIssuer + "\" provided in the configuration metadata did "
+ "not match the requested issuer \"" + issuer + "\"");
}
Assert.state(issuer.equals(metadataIssuer),
() -> "The Issuer \"" + metadataIssuer + "\" provided in the configuration metadata did "
+ "not match the requested issuer \"" + issuer + "\"");
String name = URI.create(issuer).getHost();
ClientAuthenticationMethod method = getClientAuthenticationMethod(issuer,
metadata.getTokenEndpointAuthMethods());
List<GrantType> grantTypes = metadata.getGrantTypes();
// If null, the default includes authorization_code
if (grantTypes != null && !grantTypes.contains(GrantType.AUTHORIZATION_CODE)) {
throw new IllegalArgumentException(
"Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + issuer
+ "\" returned a configuration of " + grantTypes);
}
Assert.isTrue(grantTypes == null || grantTypes.contains(GrantType.AUTHORIZATION_CODE),
"Only AuthorizationGrantType.AUTHORIZATION_CODE is supported. The issuer \"" + issuer
+ "\" returned a configuration of " + grantTypes);
List<String> scopes = getScopes(metadata);
Map<String, Object> configurationMetadata = new LinkedHashMap<>(metadata.toJSONObject());
return ClientRegistration.withRegistrationId(name).userNameAttributeName(IdTokenClaimNames.SUB).scope(scopes)
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE).clientAuthenticationMethod(method)
.redirectUri("{baseUrl}/{action}/oauth2/code/{registrationId}")
@ -284,9 +274,7 @@ public final class ClientRegistrations {
// If null, default to "openid" which must be supported
return Collections.singletonList(OidcScopes.OPENID);
}
else {
return scope.toStringList();
}
return scope.toStringList();
}
private interface ThrowingFunction<S, T, E extends Throwable> {

View File

@ -67,9 +67,8 @@ public final class InMemoryClientRegistrationRepository
private static Map<String, ClientRegistration> toUnmodifiableConcurrentMap(List<ClientRegistration> registrations) {
ConcurrentHashMap<String, ClientRegistration> result = new ConcurrentHashMap<>();
for (ClientRegistration registration : registrations) {
if (result.containsKey(registration.getRegistrationId())) {
throw new IllegalStateException(String.format("Duplicate key %s", registration.getRegistrationId()));
}
Assert.state(!result.containsKey(registration.getRegistrationId()),
() -> String.format("Duplicate key %s", registration.getRegistrationId()));
result.put(registration.getRegistrationId(), registration);
}
return Collections.unmodifiableMap(result);

View File

@ -88,22 +88,22 @@ public class CustomUserTypesOAuth2UserService implements OAuth2UserService<OAuth
if (customUserType == null) {
return null;
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<? extends OAuth2User> response = getResponse(customUserType, request);
OAuth2User oauth2User = response.getBody();
return oauth2User;
}
ResponseEntity<? extends OAuth2User> response;
private ResponseEntity<? extends OAuth2User> getResponse(Class<? extends OAuth2User> customUserType,
RequestEntity<?> request) {
try {
response = this.restOperations.exchange(request, customUserType);
return this.restOperations.exchange(request, customUserType);
}
catch (RestClientException ex) {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
OAuth2User oauth2User = response.getBody();
return oauth2User;
}
/**

View File

@ -87,7 +87,6 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
@Override
public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
Assert.notNull(userRequest, "userRequest cannot be null");
if (!StringUtils
.hasText(userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint().getUri())) {
OAuth2Error oauth2Error = new OAuth2Error(MISSING_USER_INFO_URI_ERROR_CODE,
@ -105,12 +104,21 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
RequestEntity<?> request = this.requestEntityConverter.convert(userRequest);
ResponseEntity<Map<String, Object>> response = getResponse(userRequest, request);
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
ResponseEntity<Map<String, Object>> response;
private ResponseEntity<Map<String, Object>> getResponse(OAuth2UserRequest userRequest, RequestEntity<?> request) {
try {
response = this.restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE);
return this.restOperations.exchange(request, PARAMETERIZED_RESPONSE_TYPE);
}
catch (OAuth2AuthorizationException ex) {
OAuth2Error oauth2Error = ex.getError();
@ -145,16 +153,6 @@ public class DefaultOAuth2UserService implements OAuth2UserService<OAuth2UserReq
"An error occurred while attempting to retrieve the UserInfo Resource: " + ex.getMessage(), null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}
Map<String, Object> userAttributes = response.getBody();
Set<GrantedAuthority> authorities = new LinkedHashSet<>();
authorities.add(new OAuth2UserAuthority(userAttributes));
OAuth2AccessToken token = userRequest.getAccessToken();
for (String authority : token.getScopes()) {
authorities.add(new SimpleGrantedAuthority("SCOPE_" + authority));
}
return new DefaultOAuth2User(authorities, userAttributes, userNameAttributeName);
}
/**

View File

@ -74,13 +74,18 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
private static final String MISSING_USER_NAME_ATTRIBUTE_ERROR_CODE = "missing_user_name_attribute";
private static final ParameterizedTypeReference<Map<String, Object>> STRING_OBJECT_MAP = new ParameterizedTypeReference<Map<String, Object>>() {
};
private static final ParameterizedTypeReference<Map<String, String>> STRING_STRING_MAP = new ParameterizedTypeReference<Map<String, String>>() {
};
private WebClient webClient = WebClient.create();
@Override
public Mono<OAuth2User> loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
return Mono.defer(() -> {
Assert.notNull(userRequest, "userRequest cannot be null");
String userInfoUri = userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUri();
if (!StringUtils.hasText(userInfoUri)) {
@ -99,32 +104,17 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
ParameterizedTypeReference<Map<String, Object>> typeReference = new ParameterizedTypeReference<Map<String, Object>>() {
};
AuthenticationMethod authenticationMethod = userRequest.getClientRegistration().getProviderDetails()
.getUserInfoEndpoint().getAuthenticationMethod();
WebClient.RequestHeadersSpec<?> requestHeadersSpec;
if (AuthenticationMethod.FORM.equals(authenticationMethod)) {
requestHeadersSpec = this.webClient.post().uri(userInfoUri)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.syncBody("access_token=" + userRequest.getAccessToken().getTokenValue());
}
else {
requestHeadersSpec = this.webClient.get().uri(userInfoUri)
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.headers((headers) -> headers.setBearerAuth(userRequest.getAccessToken().getTokenValue()));
}
WebClient.RequestHeadersSpec<?> requestHeadersSpec = getRequestHeaderSpec(userRequest, userInfoUri,
authenticationMethod);
Mono<Map<String, Object>> userAttributes = requestHeadersSpec.retrieve()
.onStatus((s) -> s != HttpStatus.OK, (response) -> parse(response).map((userInfoErrorResponse) -> {
String description = userInfoErrorResponse.getErrorObject().getDescription();
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, description,
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
})).bodyToMono(typeReference);
})).bodyToMono(DefaultReactiveOAuth2UserService.STRING_OBJECT_MAP);
return userAttributes.map((attrs) -> {
GrantedAuthority authority = new OAuth2UserAuthority(attrs);
Set<GrantedAuthority> authorities = new HashSet<>();
@ -136,13 +126,13 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
return new DefaultOAuth2User(authorities, attrs, userNameAttributeName);
}).onErrorMap(IOException.class,
(e) -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri,
e))
.onErrorMap(UnsupportedMediaTypeException.class, (e) -> {
(ex) -> new AuthenticationServiceException("Unable to access the userInfoEndpoint " + userInfoUri,
ex))
.onErrorMap(UnsupportedMediaTypeException.class, (ex) -> {
String errorMessage = "An error occurred while attempting to retrieve the UserInfo Resource from '"
+ userRequest.getClientRegistration().getProviderDetails().getUserInfoEndpoint()
.getUri()
+ "': response contains invalid content type '" + e.getContentType().toString() + "'. "
+ "': response contains invalid content type '" + ex.getContentType().toString() + "'. "
+ "The UserInfo Response should return a JSON object (content type 'application/json') "
+ "that contains a collection of name and value pairs of the claims about the authenticated End-User. "
+ "Please ensure the UserInfo Uri in UserInfoEndpoint for Client Registration '"
@ -151,7 +141,7 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
+ "as defined in OpenID Connect 1.0: 'https://openid.net/specs/openid-connect-core-1_0.html#UserInfo'";
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE, errorMessage,
null);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), e);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString(), ex);
}).onErrorMap((t) -> !(t instanceof AuthenticationServiceException), (t) -> {
OAuth2Error oauth2Error = new OAuth2Error(INVALID_USER_INFO_RESPONSE_ERROR_CODE,
"An error occurred reading the UserInfo Success response: " + t.getMessage(), null);
@ -160,6 +150,17 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
});
}
private WebClient.RequestHeadersSpec<?> getRequestHeaderSpec(OAuth2UserRequest userRequest, String userInfoUri,
AuthenticationMethod authenticationMethod) {
if (AuthenticationMethod.FORM.equals(authenticationMethod)) {
return this.webClient.post().uri(userInfoUri).header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE)
.syncBody("access_token=" + userRequest.getAccessToken().getTokenValue());
}
return this.webClient.get().uri(userInfoUri).header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE)
.headers((headers) -> headers.setBearerAuth(userRequest.getAccessToken().getTokenValue()));
}
/**
* Sets the {@link WebClient} used for retrieving the user endpoint
* @param webClient the client to use
@ -170,18 +171,13 @@ public class DefaultReactiveOAuth2UserService implements ReactiveOAuth2UserServi
}
private static Mono<UserInfoErrorResponse> parse(ClientResponse httpResponse) {
String wwwAuth = httpResponse.headers().asHttpHeaders().getFirst(HttpHeaders.WWW_AUTHENTICATE);
if (!StringUtils.isEmpty(wwwAuth)) {
// Bearer token error?
return Mono.fromCallable(() -> UserInfoErrorResponse.parse(wwwAuth));
}
ParameterizedTypeReference<Map<String, String>> typeReference = new ParameterizedTypeReference<Map<String, String>>() {
};
// Other error?
return httpResponse.bodyToMono(typeReference)
return httpResponse.bodyToMono(STRING_STRING_MAP)
.map((body) -> new UserInfoErrorResponse(ErrorObject.parse(new JSONObject(body))));
}

View File

@ -54,12 +54,7 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq
@Override
public RequestEntity<?> convert(OAuth2UserRequest userRequest) {
ClientRegistration clientRegistration = userRequest.getClientRegistration();
HttpMethod httpMethod = HttpMethod.GET;
if (AuthenticationMethod.FORM
.equals(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())) {
httpMethod = HttpMethod.POST;
}
HttpMethod httpMethod = getHttpMethod(clientRegistration);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
URI uri = UriComponentsBuilder
@ -80,4 +75,12 @@ public class OAuth2UserRequestEntityConverter implements Converter<OAuth2UserReq
return request;
}
private HttpMethod getHttpMethod(ClientRegistration clientRegistration) {
if (AuthenticationMethod.FORM
.equals(clientRegistration.getProviderDetails().getUserInfoEndpoint().getAuthenticationMethod())) {
return HttpMethod.POST;
}
return HttpMethod.GET;
}
}

View File

@ -79,10 +79,7 @@ public final class AuthenticatedPrincipalOAuth2AuthorizedClientRepository implem
if (this.isPrincipalAuthenticated(principal)) {
return this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName());
}
else {
return this.anonymousAuthorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal,
request);
}
return this.anonymousAuthorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, request);
}
@Override

View File

@ -143,41 +143,13 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
if (registrationId == null) {
return null;
}
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
throw new IllegalArgumentException("Invalid Client Registration with Id: " + registrationId);
}
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
OAuth2AuthorizationRequest.Builder builder;
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
Map<String, Object> additionalParameters = new HashMap<>();
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())
&& clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest
// scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope
// value.
addNonceParameters(attributes, additionalParameters);
}
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
addPkceParameters(attributes, additionalParameters);
}
builder.additionalParameters(additionalParameters);
}
else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
}
else {
throw new IllegalArgumentException(
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration, attributes);
String redirectUriStr = expandRedirectUri(request, clientRegistration, redirectUriAction);
@ -191,6 +163,33 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
return builder.build();
}
private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientRegistration,
Map<String, Object> attributes) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode();
Map<String, Object> additionalParameters = new HashMap<>();
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())
&& clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
// Section 3.1.2.1 Authentication Request -
// https://openid.net/specs/openid-connect-core-1_0.html#AuthRequest scope
// REQUIRED. OpenID Connect requests MUST contain the "openid" scope
// value.
addNonceParameters(attributes, additionalParameters);
}
if (ClientAuthenticationMethod.NONE.equals(clientRegistration.getClientAuthenticationMethod())) {
addPkceParameters(attributes, additionalParameters);
}
builder.additionalParameters(additionalParameters);
return builder;
}
if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
return OAuth2AuthorizationRequest.implicit();
}
throw new IllegalArgumentException(
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
private String resolveRegistrationId(HttpServletRequest request) {
if (this.authorizationRequestMatcher.matches(request)) {
return this.authorizationRequestMatcher.matcher(request).getVariables()
@ -220,7 +219,6 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
String action) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
.replacePath(request.getContextPath()).replaceQuery(null).fragment(null).build();
String scheme = uriComponents.getScheme();
@ -238,9 +236,7 @@ public final class DefaultOAuth2AuthorizationRequestResolver implements OAuth2Au
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
uriVariables.put("action", (action != null) ? action : "");
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()).buildAndExpand(uriVariables)
.toUriString();
}

View File

@ -131,16 +131,13 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
@Override
public OAuth2AuthorizedClient authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
OAuth2AuthorizedClient authorizedClient = authorizeRequest.getAuthorizedClient();
Authentication principal = authorizeRequest.getPrincipal();
HttpServletRequest servletRequest = getHttpServletRequestOrDefault(authorizeRequest.getAttributes());
Assert.notNull(servletRequest, "servletRequest cannot be null");
HttpServletResponse servletResponse = getHttpServletResponseOrDefault(authorizeRequest.getAttributes());
Assert.notNull(servletResponse, "servletResponse cannot be null");
OAuth2AuthorizationContext.Builder contextBuilder;
if (authorizedClient != null) {
contextBuilder = OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient);
@ -166,7 +163,6 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
attributes.putAll(contextAttributes);
}
}).build();
try {
authorizedClient = this.authorizedClientProvider.authorize(authorizationContext);
}
@ -175,7 +171,6 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
createAttributes(servletRequest, servletResponse));
throw ex;
}
if (authorizedClient != null) {
this.authorizationSuccessHandler.onAuthorizationSuccess(authorizedClient, principal,
createAttributes(servletRequest, servletResponse));
@ -189,7 +184,6 @@ public final class DefaultOAuth2AuthorizedClientManager implements OAuth2Authori
return authorizationContext.getAuthorizedClient();
}
}
return authorizedClient;
}

View File

@ -136,10 +136,8 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
Authentication principal = authorizeRequest.getPrincipal();
return Mono.justOrEmpty(authorizeRequest.<ServerWebExchange>getAttribute(ServerWebExchange.class.getName()))
.switchIfEmpty(currentServerWebExchangeMono)
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException("serverWebExchange cannot be null")))
@ -183,7 +181,6 @@ public final class DefaultReactiveOAuth2AuthorizedClientManager implements React
*/
private Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext authorizationContext,
Authentication principal, ServerWebExchange serverWebExchange) {
return this.authorizedClientProvider.authorize(authorizationContext)
// Delegate to the authorizationSuccessHandler of the successful
// authorization

View File

@ -161,12 +161,10 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
if (matchesAuthorizationResponse(request)) {
processAuthorizationResponse(request, response);
return;
}
filterChain.doFilter(request, response);
}
@ -180,7 +178,6 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
if (authorizationRequest == null) {
return false;
}
// Compare redirect_uri
UriComponents requestUri = UriComponentsBuilder.fromUriString(UrlUtils.buildFullRequestUrl(request)).build();
UriComponents redirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getRedirectUri()).build();
@ -193,7 +190,6 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
// before doing an exact comparison with the authorizationRequest.getRedirectUri()
// parameters (if any)
requestUriParameters.retainAll(redirectUriParameters);
if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme())
&& Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo())
&& Objects.equals(requestUri.getHost(), redirectUri.getHost())
@ -207,24 +203,18 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
private void processAuthorizationResponse(HttpServletRequest request, HttpServletResponse response)
throws IOException {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
String redirectUri = UrlUtils.buildFullRequestUrl(request);
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
OAuth2AuthorizationCodeAuthenticationToken authenticationRequest = new OAuth2AuthorizationCodeAuthenticationToken(
clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
OAuth2AuthorizationCodeAuthenticationToken authenticationResult;
try {
authenticationResult = (OAuth2AuthorizationCodeAuthenticationToken) this.authenticationManager
.authenticate(authenticationRequest);
@ -242,24 +232,19 @@ public class OAuth2AuthorizationCodeGrantFilter extends OncePerRequestFilter {
this.redirectStrategy.sendRedirect(request, response, uriBuilder.build().encode().toString());
return;
}
Authentication currentAuthentication = SecurityContextHolder.getContext().getAuthentication();
String principalName = (currentAuthentication != null) ? currentAuthentication.getName() : "anonymousUser";
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), principalName, authenticationResult.getAccessToken(),
authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, currentAuthentication, request,
response);
String redirectUrl = authorizationRequest.getRedirectUri();
SavedRequest savedRequest = this.requestCache.getRequest(request, response);
if (savedRequest != null) {
redirectUrl = savedRequest.getRedirectUrl();
this.requestCache.removeRequest(request, response);
}
this.redirectStrategy.sendRedirect(request, response, redirectUrl);
}

View File

@ -23,6 +23,7 @@ import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.core.log.LogMessage;
import org.springframework.http.HttpStatus;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
@ -162,7 +163,6 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
try {
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestResolver.resolve(request);
if (authorizationRequest != null) {
@ -170,11 +170,10 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
return;
}
}
catch (Exception failed) {
this.unsuccessfulRedirectForAuthorization(request, response, failed);
catch (Exception ex) {
this.unsuccessfulRedirectForAuthorization(request, response, ex);
return;
}
try {
filterChain.doFilter(request, response);
}
@ -201,22 +200,18 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
}
return;
}
if (ex instanceof ServletException) {
throw (ServletException) ex;
}
else if (ex instanceof RuntimeException) {
if (ex instanceof RuntimeException) {
throw (RuntimeException) ex;
}
else {
throw new RuntimeException(ex);
}
throw new RuntimeException(ex);
}
}
private void sendRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
OAuth2AuthorizationRequest authorizationRequest) throws IOException {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(authorizationRequest.getGrantType())) {
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
}
@ -225,11 +220,8 @@ public class OAuth2AuthorizationRequestRedirectFilter extends OncePerRequestFilt
}
private void unsuccessfulRedirectForAuthorization(HttpServletRequest request, HttpServletResponse response,
Exception failed) throws IOException {
if (this.logger.isErrorEnabled()) {
this.logger.error("Authorization Request failed: " + failed.toString(), failed);
}
Exception ex) throws IOException {
this.logger.error(LogMessage.format("Authorization Request failed: %s", ex, ex));
response.sendError(HttpStatus.INTERNAL_SERVER_ERROR.value(),
HttpStatus.INTERNAL_SERVER_ERROR.getReasonPhrase());
}

View File

@ -66,16 +66,13 @@ final class OAuth2AuthorizationResponseUtils {
String code = request.getFirst(OAuth2ParameterNames.CODE);
String errorCode = request.getFirst(OAuth2ParameterNames.ERROR);
String state = request.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(code)) {
return OAuth2AuthorizationResponse.success(code).redirectUri(redirectUri).state(state).build();
}
else {
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
return OAuth2AuthorizationResponse.error(errorCode).redirectUri(redirectUri)
.errorDescription(errorDescription).errorUri(errorUri).state(state).build();
}
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
return OAuth2AuthorizationResponse.error(errorCode).redirectUri(redirectUri).errorDescription(errorDescription)
.errorUri(errorUri).state(state).build();
}
}

View File

@ -158,20 +158,17 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
@Override
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response)
throws AuthenticationException {
MultiValueMap<String, String> params = OAuth2AuthorizationResponseUtils.toMultiMap(request.getParameterMap());
if (!OAuth2AuthorizationResponseUtils.isAuthorizationResponse(params)) {
OAuth2Error oauth2Error = new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
OAuth2AuthorizationRequest authorizationRequest = this.authorizationRequestRepository
.removeAuthorizationRequest(request, response);
if (authorizationRequest == null) {
OAuth2Error oauth2Error = new OAuth2Error(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE);
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
}
String registrationId = authorizationRequest.getAttribute(OAuth2ParameterNames.REGISTRATION_ID);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId(registrationId);
if (clientRegistration == null) {
@ -183,26 +180,21 @@ public class OAuth2LoginAuthenticationFilter extends AbstractAuthenticationProce
.build().toUriString();
OAuth2AuthorizationResponse authorizationResponse = OAuth2AuthorizationResponseUtils.convert(params,
redirectUri);
Object authenticationDetails = this.authenticationDetailsSource.buildDetails(request);
OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(clientRegistration,
new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
authenticationRequest.setDetails(authenticationDetails);
OAuth2LoginAuthenticationToken authenticationResult = (OAuth2LoginAuthenticationToken) this
.getAuthenticationManager().authenticate(authenticationRequest);
OAuth2AuthenticationToken oauth2Authentication = new OAuth2AuthenticationToken(
authenticationResult.getPrincipal(), authenticationResult.getAuthorities(),
authenticationResult.getClientRegistration().getRegistrationId());
oauth2Authentication.setDetails(authenticationDetails);
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(
authenticationResult.getClientRegistration(), oauth2Authentication.getName(),
authenticationResult.getAccessToken(), authenticationResult.getRefreshToken());
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, oauth2Authentication, request, response);
return oauth2Authentication;
}

View File

@ -114,46 +114,38 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
@Override
public Object resolveArgument(MethodParameter parameter, @Nullable ModelAndViewContainer mavContainer,
NativeWebRequest webRequest, @Nullable WebDataBinderFactory binderFactory) {
String clientRegistrationId = this.resolveClientRegistrationId(parameter);
if (StringUtils.isEmpty(clientRegistrationId)) {
throw new IllegalArgumentException("Unable to resolve the Client Registration Identifier. "
+ "It must be provided via @RegisteredOAuth2AuthorizedClient(\"client1\") or "
+ "@RegisteredOAuth2AuthorizedClient(registrationId = \"client1\").");
}
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
if (principal == null) {
principal = ANONYMOUS_AUTHENTICATION;
}
HttpServletRequest servletRequest = webRequest.getNativeRequest(HttpServletRequest.class);
HttpServletResponse servletResponse = webRequest.getNativeResponse(HttpServletResponse.class);
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
.principal(principal).attribute(HttpServletRequest.class.getName(), servletRequest)
.attribute(HttpServletResponse.class.getName(), servletResponse).build();
return this.authorizedClientManager.authorize(authorizeRequest);
}
private String resolveClientRegistrationId(MethodParameter parameter) {
RegisteredOAuth2AuthorizedClient authorizedClientAnnotation = AnnotatedElementUtils
.findMergedAnnotation(parameter.getParameter(), RegisteredOAuth2AuthorizedClient.class);
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
String clientRegistrationId = null;
if (!StringUtils.isEmpty(authorizedClientAnnotation.registrationId())) {
clientRegistrationId = authorizedClientAnnotation.registrationId();
return authorizedClientAnnotation.registrationId();
}
else if (!StringUtils.isEmpty(authorizedClientAnnotation.value())) {
clientRegistrationId = authorizedClientAnnotation.value();
if (!StringUtils.isEmpty(authorizedClientAnnotation.value())) {
return authorizedClientAnnotation.value();
}
else if (principal != null && OAuth2AuthenticationToken.class.isAssignableFrom(principal.getClass())) {
clientRegistrationId = ((OAuth2AuthenticationToken) principal).getAuthorizedClientRegistrationId();
if (principal != null && OAuth2AuthenticationToken.class.isAssignableFrom(principal.getClass())) {
return ((OAuth2AuthenticationToken) principal).getAuthorizedClientRegistrationId();
}
return clientRegistrationId;
return null;
}
/**
@ -184,7 +176,6 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
private void updateDefaultAuthorizedClientManager(
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient) {
OAuth2AuthorizedClientProvider authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
.authorizationCode().refreshToken()
.clientCredentials(

View File

@ -206,12 +206,10 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
public ServerOAuth2AuthorizedClientExchangeFilterFunction(
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository) {
ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler = new RemoveAuthorizedClientReactiveOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) -> authorizedClientRepository.removeAuthorizedClient(
clientRegistrationId, principal,
(ServerWebExchange) attributes.get(ServerWebExchange.class.getName())));
this.authorizedClientManager = createDefaultAuthorizedClientManager(clientRegistrationRepository,
authorizedClientRepository, authorizationFailureHandler);
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
@ -222,7 +220,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
ReactiveClientRegistrationRepository clientRegistrationRepository,
ServerOAuth2AuthorizedClientRepository authorizedClientRepository,
ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
// gh-7544
if (authorizedClientRepository instanceof UnAuthenticatedServerOAuth2AuthorizedClientRepository) {
UnAuthenticatedReactiveOAuth2AuthorizedClientManager unauthenticatedAuthorizedClientManager = new UnAuthenticatedReactiveOAuth2AuthorizedClientManager(
@ -234,11 +231,9 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
.authorizationCode().refreshToken().clientCredentials().password().build());
return unauthenticatedAuthorizedClientManager;
}
DefaultReactiveOAuth2AuthorizedClientManager authorizedClientManager = new DefaultReactiveOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
authorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
return authorizedClientManager;
}
@ -444,9 +439,7 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
private Mono<OAuth2AuthorizeRequest> authorizeRequest(ClientRequest request) {
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request);
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request);
return Mono.zip(clientRegistrationId, this.currentAuthenticationMono, serverWebExchange).map((t3) -> {
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withClientRegistrationId(t3.getT1())
.principal(t3.getT2());
@ -488,7 +481,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
private Mono<OAuth2AuthorizeRequest> reauthorizeRequest(ClientRequest request,
OAuth2AuthorizedClient authorizedClient) {
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request);
return Mono.zip(this.currentAuthenticationMono, serverWebExchange).map((t2) -> {
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withAuthorizedClient(authorizedClient)
.principal(t2.getT1());
@ -561,27 +553,39 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
@Override
public Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizeRequest authorizeRequest) {
Assert.notNull(authorizeRequest, "authorizeRequest cannot be null");
String clientRegistrationId = authorizeRequest.getClientRegistrationId();
Authentication principal = authorizeRequest.getPrincipal();
return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient())
.switchIfEmpty(loadAuthorizedClient(clientRegistrationId, principal))
.flatMap((authorizedClient) -> reauthorize(authorizedClient, authorizeRequest, principal))
.switchIfEmpty(findAndAuthorize(clientRegistrationId, principal));
}
return Mono.justOrEmpty(authorizeRequest.getAuthorizedClient()).switchIfEmpty(Mono.defer(
() -> this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, null)))
.flatMap((authorizedClient) -> // Re-authorize
Mono.just(OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient).principal(principal)
.build()).flatMap((authorizationContext) -> authorize(authorizationContext, principal))
// Default to the existing authorizedClient if the client
// was not re-authorized
.defaultIfEmpty((authorizeRequest.getAuthorizedClient() != null)
? authorizeRequest.getAuthorizedClient() : authorizedClient))
.switchIfEmpty(Mono.defer(() ->
// Authorize
this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
"Could not find ClientRegistration with id '" + clientRegistrationId + "'")))
.flatMap((clientRegistration) -> Mono.just(OAuth2AuthorizationContext
.withClientRegistration(clientRegistration).principal(principal).build()))
.flatMap((authorizationContext) -> authorize(authorizationContext, principal))));
private Mono<OAuth2AuthorizedClient> loadAuthorizedClient(String clientRegistrationId,
Authentication principal) {
return Mono.defer(
() -> this.authorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, null));
}
private Mono<OAuth2AuthorizedClient> reauthorize(OAuth2AuthorizedClient authorizedClient,
OAuth2AuthorizeRequest authorizeRequest, Authentication principal) {
return Mono
.just(OAuth2AuthorizationContext.withAuthorizedClient(authorizedClient).principal(principal)
.build())
.flatMap((authorizationContext) -> authorize(authorizationContext, principal))
// Default to the existing authorizedClient if the client was not
// re-authorized
.defaultIfEmpty((authorizeRequest.getAuthorizedClient() != null)
? authorizeRequest.getAuthorizedClient() : authorizedClient);
}
private Mono<OAuth2AuthorizedClient> findAndAuthorize(String clientRegistrationId, Authentication principal) {
return Mono.defer(() -> this.clientRegistrationRepository.findByRegistrationId(clientRegistrationId)
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
"Could not find ClientRegistration with id '" + clientRegistrationId + "'")))
.flatMap((clientRegistration) -> Mono.just(OAuth2AuthorizationContext
.withClientRegistration(clientRegistration).principal(principal).build()))
.flatMap((authorizationContext) -> authorize(authorizationContext, principal)));
}
/**
@ -597,7 +601,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
*/
private Mono<OAuth2AuthorizedClient> authorize(OAuth2AuthorizationContext authorizationContext,
Authentication principal) {
return this.authorizedClientProvider.authorize(authorizationContext)
// Delegates to the authorizationSuccessHandler of the successful
// authorization
@ -642,7 +645,6 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
private AuthorizationFailureForwarder(ReactiveOAuth2AuthorizationFailureHandler authorizationFailureHandler) {
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
this.authorizationFailureHandler = authorizationFailureHandler;
Map<Integer, String> httpStatusToOAuth2Error = new HashMap<>();
httpStatusToOAuth2Error.put(HttpStatus.UNAUTHORIZED.value(), OAuth2ErrorCodes.INVALID_TOKEN);
httpStatusToOAuth2Error.put(HttpStatus.FORBIDDEN.value(), OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
@ -661,17 +663,12 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
private Mono<Void> handleResponse(ClientRequest request, ClientResponse response) {
return Mono.justOrEmpty(resolveErrorIfPossible(response)).flatMap((oauth2Error) -> {
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request);
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request);
return Mono
.zip(ServerOAuth2AuthorizedClientExchangeFilterFunction.this.currentAuthenticationMono,
serverWebExchange, clientRegistrationId)
.flatMap((tuple3) -> handleAuthorizationFailure(tuple3.getT1(), // Authentication
// principal
tuple3.getT2().orElse(null), // ServerWebExchange exchange
new ClientAuthorizationException(oauth2Error, tuple3.getT3()))); // String
// clientRegistrationId
.flatMap((zipped) -> handleAuthorizationFailure(zipped.getT1(), zipped.getT2(),
new ClientAuthorizationException(oauth2Error, zipped.getT3())));
});
}
@ -720,18 +717,12 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
WebClientResponseException exception) {
return Mono.justOrEmpty(resolveErrorIfPossible(exception.getRawStatusCode())).flatMap((oauth2Error) -> {
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request);
Mono<String> clientRegistrationId = effectiveClientRegistrationId(request);
return Mono
.zip(ServerOAuth2AuthorizedClientExchangeFilterFunction.this.currentAuthenticationMono,
serverWebExchange, clientRegistrationId)
.flatMap((tuple3) -> handleAuthorizationFailure(tuple3.getT1(), // Authentication
// principal
tuple3.getT2().orElse(null), // ServerWebExchange exchange
new ClientAuthorizationException(oauth2Error, tuple3.getT3(), // String
// clientRegistrationId
exception)));
.flatMap((zipped) -> handleAuthorizationFailure(zipped.getT1(), zipped.getT2(),
new ClientAuthorizationException(oauth2Error, zipped.getT3(), exception)));
});
}
@ -745,14 +736,10 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
*/
private Mono<Void> handleAuthorizationException(ClientRequest request, OAuth2AuthorizationException exception) {
Mono<Optional<ServerWebExchange>> serverWebExchange = effectiveServerWebExchange(request);
return Mono.zip(ServerOAuth2AuthorizedClientExchangeFilterFunction.this.currentAuthenticationMono,
serverWebExchange).flatMap(
(tuple2) -> handleAuthorizationFailure(tuple2.getT1(), // Authentication
// principal
tuple2.getT2().orElse(null), // ServerWebExchange
// exchange
exception));
return Mono
.zip(ServerOAuth2AuthorizedClientExchangeFilterFunction.this.currentAuthenticationMono,
serverWebExchange)
.flatMap((zipped) -> handleAuthorizationFailure(zipped.getT1(), zipped.getT2(), exception));
}
/**
@ -763,11 +750,10 @@ public final class ServerOAuth2AuthorizedClientExchangeFilterFunction implements
* @return a {@link Mono} that completes empty after the authorization failure
* handler completes.
*/
private Mono<Void> handleAuthorizationFailure(Authentication principal, ServerWebExchange exchange,
private Mono<Void> handleAuthorizationFailure(Authentication principal, Optional<ServerWebExchange> exchange,
OAuth2AuthorizationException exception) {
return this.authorizationFailureHandler.onAuthorizationFailure(exception, principal,
createAttributes(exchange));
createAttributes(exchange.orElse(null)));
}
private Map<String, Object> createAttributes(ServerWebExchange exchange) {

View File

@ -218,12 +218,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
public ServletOAuth2AuthorizedClientExchangeFilterFunction(
ClientRegistrationRepository clientRegistrationRepository,
OAuth2AuthorizedClientRepository authorizedClientRepository) {
OAuth2AuthorizationFailureHandler authorizationFailureHandler = new RemoveAuthorizedClientOAuth2AuthorizationFailureHandler(
(clientRegistrationId, principal, attributes) -> authorizedClientRepository.removeAuthorizedClient(
clientRegistrationId, principal,
(HttpServletRequest) attributes.get(HttpServletRequest.class.getName()),
(HttpServletResponse) attributes.get(HttpServletResponse.class.getName())));
(clientRegistrationId, principal, attributes) -> removeAuthorizedClient(authorizedClientRepository,
clientRegistrationId, principal, attributes));
DefaultOAuth2AuthorizedClientManager defaultAuthorizedClientManager = new DefaultOAuth2AuthorizedClientManager(
clientRegistrationRepository, authorizedClientRepository);
defaultAuthorizedClientManager.setAuthorizationFailureHandler(authorizationFailureHandler);
@ -232,6 +229,13 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
this.clientResponseHandler = new AuthorizationFailureForwarder(authorizationFailureHandler);
}
private void removeAuthorizedClient(OAuth2AuthorizedClientRepository authorizedClientRepository,
String clientRegistrationId, Authentication principal, Map<String, Object> attributes) {
HttpServletRequest request = getRequest(attributes);
HttpServletResponse response = getResponse(attributes);
authorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal, request, response);
}
/**
* Sets the {@link OAuth2AccessTokenResponseClient} used for getting an
* {@link OAuth2AuthorizedClient} for the client_credentials grant.
@ -453,9 +457,7 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
|| !request.attribute(AUTHENTICATION_ATTR_NAME).isPresent()) {
return mergeRequestAttributesFromContext(request);
}
else {
return Mono.just(request);
}
return Mono.just(request);
}
private Mono<ClientRequest> mergeRequestAttributesFromContext(ClientRequest request) {
@ -530,23 +532,13 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
}
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withClientRegistrationId(clientRegistrationId)
.principal(authentication);
builder.attributes((attributes) -> {
if (servletRequest != null) {
attributes.put(HttpServletRequest.class.getName(), servletRequest);
}
if (servletResponse != null) {
attributes.put(HttpServletResponse.class.getName(), servletResponse);
}
});
builder.attributes((attributes) -> addToAttributes(attributes, servletRequest, servletResponse));
OAuth2AuthorizeRequest authorizeRequest = builder.build();
// NOTE:
// 'authorizedClientManager.authorize()' needs to be executed
// on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
// since it performs a blocking I/O operation using RestTemplate internally
// NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated
// thread via subscribeOn(Schedulers.boundedElastic()) since it performs a
// blocking I/O operation using RestTemplate internally
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(authorizeRequest))
.subscribeOn(Schedulers.boundedElastic());
}
@ -563,27 +555,27 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
}
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
OAuth2AuthorizeRequest.Builder builder = OAuth2AuthorizeRequest.withAuthorizedClient(authorizedClient)
.principal(authentication);
builder.attributes((attributes) -> {
if (servletRequest != null) {
attributes.put(HttpServletRequest.class.getName(), servletRequest);
}
if (servletResponse != null) {
attributes.put(HttpServletResponse.class.getName(), servletResponse);
}
});
builder.attributes((attributes) -> addToAttributes(attributes, servletRequest, servletResponse));
OAuth2AuthorizeRequest reauthorizeRequest = builder.build();
// NOTE:
// 'authorizedClientManager.authorize()' needs to be executed
// on a dedicated thread via subscribeOn(Schedulers.boundedElastic())
// since it performs a blocking I/O operation using RestTemplate internally
// NOTE: 'authorizedClientManager.authorize()' needs to be executed on a dedicated
// thread via subscribeOn(Schedulers.boundedElastic()) since it performs a
// blocking I/O operation using RestTemplate internally
return Mono.fromSupplier(() -> this.authorizedClientManager.authorize(reauthorizeRequest))
.subscribeOn(Schedulers.boundedElastic());
}
private void addToAttributes(Map<String, Object> attributes, HttpServletRequest servletRequest,
HttpServletResponse servletResponse) {
if (servletRequest != null) {
attributes.put(HTTP_SERVLET_REQUEST_ATTR_NAME, servletRequest);
}
if (servletResponse != null) {
attributes.put(HTTP_SERVLET_RESPONSE_ATTR_NAME, servletResponse);
}
}
private ClientRequest bearer(ClientRequest request, OAuth2AuthorizedClient authorizedClient) {
return ClientRequest.from(request)
.headers((headers) -> headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue()))
@ -612,8 +604,8 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private static Authentication createAuthentication(final String principalName) {
Assert.hasText(principalName, "principalName cannot be empty");
return new AbstractAuthenticationToken(null) {
@Override
public Object getCredentials() {
return "";
@ -656,7 +648,6 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
private AuthorizationFailureForwarder(OAuth2AuthorizationFailureHandler authorizationFailureHandler) {
Assert.notNull(authorizationFailureHandler, "authorizationFailureHandler cannot be null");
this.authorizationFailureHandler = authorizationFailureHandler;
Map<Integer, String> httpStatusToOAuth2Error = new HashMap<>();
httpStatusToOAuth2Error.put(HttpStatus.UNAUTHORIZED.value(), OAuth2ErrorCodes.INVALID_TOKEN);
httpStatusToOAuth2Error.put(HttpStatus.FORBIDDEN.value(), OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
@ -679,14 +670,11 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
if (authorizedClient == null) {
return Mono.empty();
}
ClientAuthorizationException authorizationException = new ClientAuthorizationException(oauth2Error,
authorizedClient.getClientRegistration().getRegistrationId());
Authentication principal = createAuthentication(authorizedClient.getPrincipalName());
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
});
}
@ -740,14 +728,11 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
if (authorizedClient == null) {
return Mono.empty();
}
ClientAuthorizationException authorizationException = new ClientAuthorizationException(oauth2Error,
authorizedClient.getClientRegistration().getRegistrationId(), exception);
Authentication principal = createAuthentication(authorizedClient.getPrincipalName());
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
});
}
@ -769,11 +754,9 @@ public final class ServletOAuth2AuthorizedClientExchangeFilterFunction implement
if (authorizedClient == null) {
return Mono.empty();
}
Authentication principal = createAuthentication(authorizedClient.getPrincipalName());
HttpServletRequest servletRequest = getRequest(attrs);
HttpServletResponse servletResponse = getResponse(attrs);
return handleAuthorizationFailure(authorizationException, principal, servletRequest, servletResponse);
});
}

View File

@ -105,28 +105,24 @@ public final class OAuth2AuthorizedClientArgumentResolver implements HandlerMeth
return Mono.defer(() -> {
RegisteredOAuth2AuthorizedClient authorizedClientAnnotation = AnnotatedElementUtils
.findMergedAnnotation(parameter.getParameter(), RegisteredOAuth2AuthorizedClient.class);
String clientRegistrationId = StringUtils.hasLength(authorizedClientAnnotation.registrationId())
? authorizedClientAnnotation.registrationId() : null;
return authorizeRequest(clientRegistrationId, exchange).flatMap(this.authorizedClientManager::authorize);
});
}
private Mono<OAuth2AuthorizeRequest> authorizeRequest(String registrationId, ServerWebExchange exchange) {
Mono<Authentication> defaultedAuthentication = currentAuthentication();
Mono<String> defaultedRegistrationId = Mono.justOrEmpty(registrationId)
.switchIfEmpty(clientRegistrationId(defaultedAuthentication))
.switchIfEmpty(Mono.error(() -> new IllegalArgumentException(
"The clientRegistrationId could not be resolved. Please provide one")));
Mono<ServerWebExchange> defaultedExchange = Mono.justOrEmpty(exchange)
.switchIfEmpty(currentServerWebExchange());
return Mono.zip(defaultedRegistrationId, defaultedAuthentication, defaultedExchange)
.map((t3) -> OAuth2AuthorizeRequest.withClientRegistrationId(t3.getT1()).principal(t3.getT2())
.attribute(ServerWebExchange.class.getName(), t3.getT3()).build());
.map((zipped) -> OAuth2AuthorizeRequest.withClientRegistrationId(zipped.getT1())
.principal(zipped.getT2()).attribute(ServerWebExchange.class.getName(), zipped.getT3())
.build());
}
private Mono<Authentication> currentAuthentication() {

View File

@ -83,10 +83,7 @@ public final class AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository
if (this.isPrincipalAuthenticated(principal)) {
return this.authorizedClientService.loadAuthorizedClient(clientRegistrationId, principal.getName());
}
else {
return this.anonymousAuthorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal,
exchange);
}
return this.anonymousAuthorizedClientRepository.loadAuthorizedClient(clientRegistrationId, principal, exchange);
}
@Override
@ -95,9 +92,7 @@ public final class AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository
if (this.isPrincipalAuthenticated(principal)) {
return this.authorizedClientService.saveAuthorizedClient(authorizedClient, principal);
}
else {
return this.anonymousAuthorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, exchange);
}
return this.anonymousAuthorizedClientRepository.saveAuthorizedClient(authorizedClient, principal, exchange);
}
@Override
@ -106,10 +101,8 @@ public final class AuthenticatedPrincipalServerOAuth2AuthorizedClientRepository
if (this.isPrincipalAuthenticated(principal)) {
return this.authorizedClientService.removeAuthorizedClient(clientRegistrationId, principal.getName());
}
else {
return this.anonymousAuthorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal,
exchange);
}
return this.anonymousAuthorizedClientRepository.removeAuthorizedClient(clientRegistrationId, principal,
exchange);
}
private boolean isPrincipalAuthenticated(Authentication authentication) {

View File

@ -153,13 +153,23 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
private OAuth2AuthorizationRequest authorizationRequest(ServerWebExchange exchange,
ClientRegistration clientRegistration) {
String redirectUriStr = expandRedirectUri(exchange.getRequest(), clientRegistration);
Map<String, Object> attributes = new HashMap<>();
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, clientRegistration.getRegistrationId());
OAuth2AuthorizationRequest.Builder builder = getBuilder(clientRegistration, attributes);
builder.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(redirectUriStr).scopes(clientRegistration.getScopes())
.state(this.stateGenerator.generateKey()).attributes(attributes);
OAuth2AuthorizationRequest.Builder builder;
this.authorizationRequestCustomizer.accept(builder);
return builder.build();
}
private OAuth2AuthorizationRequest.Builder getBuilder(ClientRegistration clientRegistration,
Map<String, Object> attributes) {
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.authorizationCode();
OAuth2AuthorizationRequest.Builder builder = OAuth2AuthorizationRequest.authorizationCode();
Map<String, Object> additionalParameters = new HashMap<>();
if (!CollectionUtils.isEmpty(clientRegistration.getScopes())
&& clientRegistration.getScopes().contains(OidcScopes.OPENID)) {
@ -174,23 +184,14 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
addPkceParameters(attributes, additionalParameters);
}
builder.additionalParameters(additionalParameters);
return builder;
}
else if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
builder = OAuth2AuthorizationRequest.implicit();
if (AuthorizationGrantType.IMPLICIT.equals(clientRegistration.getAuthorizationGrantType())) {
return OAuth2AuthorizationRequest.implicit();
}
else {
throw new IllegalArgumentException(
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
builder.clientId(clientRegistration.getClientId())
.authorizationUri(clientRegistration.getProviderDetails().getAuthorizationUri())
.redirectUri(redirectUriStr).scopes(clientRegistration.getScopes())
.state(this.stateGenerator.generateKey()).attributes(attributes);
this.authorizationRequestCustomizer.accept(builder);
return builder.build();
throw new IllegalArgumentException(
"Invalid Authorization Grant Type (" + clientRegistration.getAuthorizationGrantType().getValue()
+ ") for Client Registration with Id: " + clientRegistration.getRegistrationId());
}
/**
@ -213,7 +214,6 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
private static String expandRedirectUri(ServerHttpRequest request, ClientRegistration clientRegistration) {
Map<String, String> uriVariables = new HashMap<>();
uriVariables.put("registrationId", clientRegistration.getRegistrationId());
UriComponents uriComponents = UriComponentsBuilder.fromUri(request.getURI())
.replacePath(request.getPath().contextPath().value()).replaceQuery(null).fragment(null).build();
String scheme = uriComponents.getScheme();
@ -231,13 +231,11 @@ public class DefaultServerOAuth2AuthorizationRequestResolver implements ServerOA
}
uriVariables.put("basePath", (path != null) ? path : "");
uriVariables.put("baseUrl", uriComponents.toUriString());
String action = "";
if (AuthorizationGrantType.AUTHORIZATION_CODE.equals(clientRegistration.getAuthorizationGrantType())) {
action = "login";
}
uriVariables.put("action", action);
return UriComponentsBuilder.fromUriString(clientRegistration.getRedirectUri()).buildAndExpand(uriVariables)
.toUriString();
}

View File

@ -206,7 +206,7 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
.filter(ServerWebExchangeMatcher.MatchResult::isMatch)
.flatMap((matchResult) -> this.authenticationConverter.convert(exchange).onErrorMap(
OAuth2AuthorizationException.class,
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString())))
(ex) -> new OAuth2AuthenticationException(ex.getError(), ex.getError().toString())))
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
.flatMap((token) -> authenticate(exchange, chain, token))
.onErrorResume(AuthenticationException.class, (e) -> this.authenticationFailureHandler
@ -217,7 +217,7 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
WebFilterExchange webFilterExchange = new WebFilterExchange(exchange, chain);
return this.authenticationManager.authenticate(token)
.onErrorMap(OAuth2AuthorizationException.class,
(e) -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
(ex) -> new OAuth2AuthenticationException(ex.getError(), ex.getError().toString()))
.switchIfEmpty(Mono.defer(
() -> Mono.error(new IllegalStateException("No provider found for " + token.getClass()))))
.flatMap((authentication) -> onAuthenticationSuccess(authentication, webFilterExchange))
@ -258,7 +258,6 @@ public class OAuth2AuthorizationCodeGrantWebFilter implements WebFilter {
// before doing an exact comparison with the authorizationRequest.getRedirectUri()
// parameters (if any)
requestUriParameters.retainAll(redirectUriParameters);
if (Objects.equals(requestUri.getScheme(), redirectUri.getScheme())
&& Objects.equals(requestUri.getUserInfo(), redirectUri.getUserInfo())
&& Objects.equals(requestUri.getHost(), redirectUri.getHost())

View File

@ -130,8 +130,8 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
return this.authorizationRequestResolver.resolve(exchange)
.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
.onErrorResume(ClientAuthorizationRequiredException.class,
(e) -> this.requestCache.saveRequest(exchange)
.then(this.authorizationRequestResolver.resolve(exchange, e.getClientRegistrationId())))
(ex) -> this.requestCache.saveRequest(exchange).then(
this.authorizationRequestResolver.resolve(exchange, ex.getClientRegistrationId())))
.flatMap((clientRegistration) -> sendRedirectForAuthorization(exchange, clientRegistration));
}
@ -143,7 +143,6 @@ public class OAuth2AuthorizationRequestRedirectWebFilter implements WebFilter {
saveAuthorizationRequest = this.authorizationRequestRepository
.saveAuthorizationRequest(authorizationRequest, exchange);
}
URI redirectUri = UriComponentsBuilder.fromUriString(authorizationRequest.getAuthorizationRequestUri())
.build(true).toUri();
return saveAuthorizationRequest

View File

@ -66,16 +66,13 @@ final class OAuth2AuthorizationResponseUtils {
String code = request.getFirst(OAuth2ParameterNames.CODE);
String errorCode = request.getFirst(OAuth2ParameterNames.ERROR);
String state = request.getFirst(OAuth2ParameterNames.STATE);
if (StringUtils.hasText(code)) {
return OAuth2AuthorizationResponse.success(code).redirectUri(redirectUri).state(state).build();
}
else {
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
return OAuth2AuthorizationResponse.error(errorCode).redirectUri(redirectUri)
.errorDescription(errorDescription).errorUri(errorUri).state(state).build();
}
String errorDescription = request.getFirst(OAuth2ParameterNames.ERROR_DESCRIPTION);
String errorUri = request.getFirst(OAuth2ParameterNames.ERROR_URI);
return OAuth2AuthorizationResponse.error(errorCode).redirectUri(redirectUri).errorDescription(errorDescription)
.errorUri(errorUri).state(state).build();
}
}

View File

@ -53,7 +53,6 @@ public class UnAuthenticatedServerOAuth2AuthorizedClientRepository implements Se
Assert.notNull(clientRegistrationId, "clientRegistrationId cannot be null");
Assert.isNull(serverWebExchange, "serverWebExchange must be null");
Assert.isTrue(isUnauthenticated(authentication), "The user " + authentication + " should not be authenticated");
return Mono.fromSupplier(() -> (T) this.clientRegistrationIdToAuthorizedClient.get(clientRegistrationId));
}

View File

@ -121,14 +121,11 @@ public final class WebSessionOAuth2ServerAuthorizationRequestRepository
private Mono<Map<String, OAuth2AuthorizationRequest>> saveStateToAuthorizationRequest(ServerWebExchange exchange) {
Assert.notNull(exchange, "exchange cannot be null");
return getSessionAttributes(exchange).doOnNext((sessionAttrs) -> {
Object stateToAuthzRequest = sessionAttrs.get(this.sessionAttributeName);
if (stateToAuthzRequest == null) {
stateToAuthzRequest = new HashMap<String, OAuth2AuthorizationRequest>();
}
// No matter stateToAuthzRequest was in session or not, we should always put
// it into session again
// in case of redis or hazelcast session. #6215