Decompose OAuth2AuthorizationCodeRequestAuthenticationProvider
Closes gh-896
This commit is contained in:
@@ -32,9 +32,9 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
|
||||
return http.build();
|
||||
}
|
||||
----
|
||||
<1> `authorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken`.
|
||||
<1> `authorizationRequestConverter()`: Adds an `AuthenticationConverter` (_pre-processor_) used when attempting to extract an https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1[OAuth2 authorization request] (or consent) from `HttpServletRequest` to an instance of `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
|
||||
<2> `authorizationRequestConverters()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationConverter``'s allowing the ability to add, remove, or customize a specific `AuthenticationConverter`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken`.
|
||||
<3> `authenticationProvider()`: Adds an `AuthenticationProvider` (_main processor_) used for authenticating the `OAuth2AuthorizationCodeRequestAuthenticationToken` or `OAuth2AuthorizationConsentAuthenticationToken`.
|
||||
<4> `authenticationProviders()`: Sets the `Consumer` providing access to the `List` of default and (optionally) added ``AuthenticationProvider``'s allowing the ability to add, remove, or customize a specific `AuthenticationProvider`.
|
||||
<5> `authorizationResponseHandler()`: The `AuthenticationSuccessHandler` (_post-processor_) used for handling an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2[OAuth2AuthorizationResponse].
|
||||
<6> `errorResponseHandler()`: The `AuthenticationFailureHandler` (_post-processor_) used for handling an `OAuth2AuthorizationCodeRequestAuthenticationException` and returning the https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1[OAuth2Error response].
|
||||
@@ -45,8 +45,8 @@ public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity h
|
||||
|
||||
`OAuth2AuthorizationEndpointFilter` is configured with the following defaults:
|
||||
|
||||
* `*AuthenticationConverter*` -- An `OAuth2AuthorizationCodeRequestAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider`.
|
||||
* `*AuthenticationConverter*` -- A `DelegatingAuthenticationConverter` composed of `OAuth2AuthorizationCodeRequestAuthenticationConverter` and `OAuth2AuthorizationConsentAuthenticationConverter`.
|
||||
* `*AuthenticationManager*` -- An `AuthenticationManager` composed of `OAuth2AuthorizationCodeRequestAuthenticationProvider` and `OAuth2AuthorizationConsentAuthenticationProvider`.
|
||||
* `*AuthenticationSuccessHandler*` -- An internal implementation that handles an "`authenticated`" `OAuth2AuthorizationCodeRequestAuthenticationToken` and returns the `OAuth2AuthorizationResponse`.
|
||||
* `*AuthenticationFailureHandler*` -- An internal implementation that uses the `OAuth2Error` associated with the `OAuth2AuthorizationCodeRequestAuthenticationException` and returns the `OAuth2Error` response.
|
||||
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2TokenGenerator} that generates an {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.4.0
|
||||
* @see OAuth2TokenGenerator
|
||||
* @see OAuth2AuthorizationCode
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider
|
||||
*/
|
||||
final class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator<OAuth2AuthorizationCode> {
|
||||
private final StringKeyGenerator authorizationCodeGenerator =
|
||||
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
|
||||
if (context.getTokenType() == null ||
|
||||
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {
|
||||
return null;
|
||||
}
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
|
||||
return new OAuth2AuthorizationCode(this.authorizationCodeGenerator.generateKey(), issuedAt, expiresAt);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -16,19 +16,14 @@
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.time.Instant;
|
||||
import java.util.Base64;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.crypto.keygen.Base64StringKeyGenerator;
|
||||
import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
@@ -54,7 +49,7 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request (and Consent)
|
||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Request
|
||||
* used in the Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
@@ -63,6 +58,7 @@ import org.springframework.util.StringUtils;
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationToken
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationValidator
|
||||
* @see OAuth2AuthorizationCodeAuthenticationProvider
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider
|
||||
* @see RegisteredClientRepository
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
@@ -71,7 +67,6 @@ import org.springframework.util.StringUtils;
|
||||
public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implements AuthenticationProvider {
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
|
||||
private static final String PKCE_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc7636#section-4.4.1";
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private static final StringKeyGenerator DEFAULT_STATE_GENERATOR =
|
||||
new Base64StringKeyGenerator(Base64.getUrlEncoder());
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
@@ -80,7 +75,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
|
||||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationValidator();
|
||||
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using the provided parameters.
|
||||
@@ -104,73 +98,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
|
||||
|
||||
return authorizationCodeRequestAuthentication.isConsent() ?
|
||||
authenticateAuthorizationConsent(authentication) :
|
||||
authenticateAuthorizationRequest(authentication);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
|
||||
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
|
||||
this.authorizationCodeGenerator = authorizationCodeGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
|
||||
* and is responsible for validating specific OAuth 2.0 Authorization Request parameters
|
||||
* associated in the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
* The default authentication validator is {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
|
||||
*
|
||||
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for validating specific OAuth 2.0 Authorization Request parameters
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public void setAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
|
||||
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
|
||||
this.authenticationValidator = authenticationValidator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationConsentAuthenticationContext}
|
||||
* containing an {@link OAuth2AuthorizationConsent.Builder} and additional context information.
|
||||
*
|
||||
* <p>
|
||||
* The following context attributes are available:
|
||||
* <ul>
|
||||
* <li>The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization consent
|
||||
* prior to {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.</li>
|
||||
* <li>The {@link Authentication} of type
|
||||
* {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.</li>
|
||||
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
|
||||
* <li>The {@link OAuth2Authorization} associated with the state token presented in the
|
||||
* authorization consent request.</li>
|
||||
* <li>The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param authorizationConsentCustomizer the {@code Consumer} providing access to the
|
||||
* {@link OAuth2AuthorizationConsentAuthenticationContext} containing an {@link OAuth2AuthorizationConsent.Builder}
|
||||
*/
|
||||
public void setAuthorizationConsentCustomizer(Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer) {
|
||||
Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null");
|
||||
this.authorizationConsentCustomizer = authorizationConsentCustomizer;
|
||||
}
|
||||
|
||||
private Authentication authenticateAuthorizationRequest(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
|
||||
authorizationCodeRequestAuthentication.getClientId());
|
||||
if (registeredClient == null) {
|
||||
@@ -234,12 +161,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
|
||||
currentAuthorizationConsent.getScopes() : null;
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri(authorizationRequest.getAuthorizationUri())
|
||||
.scopes(currentAuthorizedScopes)
|
||||
.state(state)
|
||||
.consentRequired(true)
|
||||
.build();
|
||||
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationRequest.getAuthorizationUri(),
|
||||
registeredClient.getClientId(), principal, state, currentAuthorizedScopes, null);
|
||||
}
|
||||
|
||||
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
|
||||
@@ -262,136 +185,42 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
redirectUri = registeredClient.getRedirectUris().iterator().next();
|
||||
}
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri(authorizationRequest.getAuthorizationUri())
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(authorizationRequest.getScopes())
|
||||
.state(authorizationRequest.getState())
|
||||
.authorizationCode(authorizationCode)
|
||||
.build();
|
||||
return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationRequest.getAuthorizationUri(),
|
||||
registeredClient.getClientId(), principal, authorizationCode, redirectUri,
|
||||
authorizationRequest.getState(), authorizationRequest.getScopes());
|
||||
}
|
||||
|
||||
private Authentication authenticateAuthorizationConsent(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication;
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||
authorizationCodeRequestAuthentication.getState(), STATE_TOKEN_TYPE);
|
||||
if (authorization == null) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
|
||||
authorizationCodeRequestAuthentication, null, null);
|
||||
}
|
||||
/**
|
||||
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
|
||||
* @since 0.2.3
|
||||
*/
|
||||
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
|
||||
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
|
||||
this.authorizationCodeGenerator = authorizationCodeGenerator;
|
||||
}
|
||||
|
||||
// The 'in-flight' authorization must be associated to the current principal
|
||||
Authentication principal = (Authentication) authorizationCodeRequestAuthentication.getPrincipal();
|
||||
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
|
||||
authorizationCodeRequestAuthentication, null, null);
|
||||
}
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
|
||||
authorizationCodeRequestAuthentication.getClientId());
|
||||
if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> requestedScopes = authorizationRequest.getScopes();
|
||||
Set<String> authorizedScopes = new HashSet<>(authorizationCodeRequestAuthentication.getScopes());
|
||||
if (!requestedScopes.containsAll(authorizedScopes)) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
|
||||
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
|
||||
currentAuthorizationConsent.getScopes() : Collections.emptySet();
|
||||
|
||||
if (!currentAuthorizedScopes.isEmpty()) {
|
||||
for (String requestedScope : requestedScopes) {
|
||||
if (currentAuthorizedScopes.contains(requestedScope)) {
|
||||
authorizedScopes.add(requestedScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!authorizedScopes.isEmpty() && requestedScopes.contains(OidcScopes.OPENID)) {
|
||||
// 'openid' scope is auto-approved as it does not require consent
|
||||
authorizedScopes.add(OidcScopes.OPENID);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder;
|
||||
if (currentAuthorizationConsent != null) {
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent);
|
||||
} else {
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
}
|
||||
authorizedScopes.forEach(authorizationConsentBuilder::scope);
|
||||
|
||||
if (this.authorizationConsentCustomizer != null) {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext =
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(authorizationCodeRequestAuthentication)
|
||||
.authorizationConsent(authorizationConsentBuilder)
|
||||
.registeredClient(registeredClient)
|
||||
.authorization(authorization)
|
||||
.authorizationRequest(authorizationRequest)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext);
|
||||
}
|
||||
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorizationConsentBuilder.authorities(authorities::addAll);
|
||||
|
||||
if (authorities.isEmpty()) {
|
||||
// Authorization consent denied (or revoked)
|
||||
if (currentAuthorizationConsent != null) {
|
||||
this.authorizationConsentService.remove(currentAuthorizationConsent);
|
||||
}
|
||||
this.authorizationService.remove(authorization);
|
||||
throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,
|
||||
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
|
||||
if (!authorizationConsent.equals(currentAuthorizationConsent)) {
|
||||
this.authorizationConsentService.save(authorizationConsent);
|
||||
}
|
||||
|
||||
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
|
||||
authorizationCodeRequestAuthentication, registeredClient, authorization, authorizedScopes);
|
||||
OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);
|
||||
if (authorizationCode == null) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"The token generator failed to generate the authorization code.", ERROR_URI);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
|
||||
}
|
||||
|
||||
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
|
||||
.authorizedScopes(authorizedScopes)
|
||||
.token(authorizationCode)
|
||||
.attributes(attrs -> {
|
||||
attrs.remove(OAuth2ParameterNames.STATE);
|
||||
})
|
||||
.build();
|
||||
this.authorizationService.save(updatedAuthorization);
|
||||
|
||||
String redirectUri = authorizationRequest.getRedirectUri();
|
||||
if (!StringUtils.hasText(redirectUri)) {
|
||||
redirectUri = registeredClient.getRedirectUris().iterator().next();
|
||||
}
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri(authorizationRequest.getAuthorizationUri())
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(authorizedScopes)
|
||||
.state(authorizationRequest.getState())
|
||||
.authorizationCode(authorizationCode)
|
||||
.build();
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext}
|
||||
* and is responsible for validating specific OAuth 2.0 Authorization Request parameters
|
||||
* associated in the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
* The default authentication validator is {@link OAuth2AuthorizationCodeRequestAuthenticationValidator}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2AuthorizationCodeRequestAuthenticationException} if validation fails.
|
||||
*
|
||||
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2AuthorizationCodeRequestAuthenticationContext} and is responsible for validating specific OAuth 2.0 Authorization Request parameters
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public void setAuthenticationValidator(Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator) {
|
||||
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
|
||||
this.authenticationValidator = authenticationValidator;
|
||||
}
|
||||
|
||||
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal,
|
||||
@@ -454,14 +283,7 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
private static void throwError(String errorCode, String parameterName,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient) {
|
||||
throwError(errorCode, parameterName, authorizationCodeRequestAuthentication, registeredClient, null);
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
throwError(errorCode, parameterName, ERROR_URI,
|
||||
authorizationCodeRequestAuthentication, registeredClient, authorizationRequest);
|
||||
throwError(errorCode, parameterName, ERROR_URI, authorizationCodeRequestAuthentication, registeredClient, null);
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName, String errorUri,
|
||||
@@ -475,30 +297,19 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
|
||||
boolean redirectOnError = true;
|
||||
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
|
||||
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
|
||||
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
|
||||
parameterName.equals(OAuth2ParameterNames.STATE))) {
|
||||
redirectOnError = false;
|
||||
redirectUri = null; // Prevent redirects
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = authorizationCodeRequestAuthentication;
|
||||
|
||||
if (redirectOnError && !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
|
||||
String state = authorizationCodeRequestAuthentication.isConsent() && authorizationRequest != null ?
|
||||
authorizationRequest.getState() : authorizationCodeRequestAuthentication.getState();
|
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
|
||||
.redirectUri(redirectUri)
|
||||
.state(state)
|
||||
.build();
|
||||
} else if (!redirectOnError && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
|
||||
.redirectUri(null) // Prevent redirects
|
||||
.build();
|
||||
}
|
||||
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationCodeRequestAuthentication.getClientId(),
|
||||
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
|
||||
authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(),
|
||||
authorizationCodeRequestAuthentication.getAdditionalParameters());
|
||||
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
|
||||
}
|
||||
@@ -513,32 +324,4 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
return null;
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder from(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(authorizationCodeRequestAuthentication.getClientId(), (Authentication) authorizationCodeRequestAuthentication.getPrincipal())
|
||||
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri())
|
||||
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri())
|
||||
.scopes(authorizationCodeRequestAuthentication.getScopes())
|
||||
.state(authorizationCodeRequestAuthentication.getState())
|
||||
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters())
|
||||
.authorizationCode(authorizationCodeRequestAuthentication.getAuthorizationCode());
|
||||
}
|
||||
|
||||
private static class OAuth2AuthorizationCodeGenerator implements OAuth2TokenGenerator<OAuth2AuthorizationCode> {
|
||||
private final StringKeyGenerator authorizationCodeGenerator =
|
||||
new Base64StringKeyGenerator(Base64.getUrlEncoder().withoutPadding(), 96);
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public OAuth2AuthorizationCode generate(OAuth2TokenContext context) {
|
||||
if (context.getTokenType() == null ||
|
||||
!OAuth2ParameterNames.CODE.equals(context.getTokenType().getValue())) {
|
||||
return null;
|
||||
}
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(context.getRegisteredClient().getTokenSettings().getAuthorizationCodeTimeToLive());
|
||||
return new OAuth2AuthorizationCode(this.authorizationCodeGenerator.generateKey(), issuedAt, expiresAt);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -15,45 +15,104 @@
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Request (and Consent)
|
||||
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Request
|
||||
* used in the Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.2
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider
|
||||
*/
|
||||
public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken {
|
||||
public class OAuth2AuthorizationCodeRequestAuthenticationToken extends AbstractAuthenticationToken {
|
||||
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
|
||||
private String authorizationUri;
|
||||
private String clientId;
|
||||
private Authentication principal;
|
||||
private String redirectUri;
|
||||
private Set<String> scopes;
|
||||
private String state;
|
||||
private Map<String, Object> additionalParameters;
|
||||
private boolean consentRequired;
|
||||
private boolean consent;
|
||||
private OAuth2AuthorizationCode authorizationCode;
|
||||
private final String authorizationUri;
|
||||
private final String clientId;
|
||||
private final Authentication principal;
|
||||
private final String redirectUri;
|
||||
private final String state;
|
||||
private final Set<String> scopes;
|
||||
private final Map<String, Object> additionalParameters;
|
||||
private final OAuth2AuthorizationCode authorizationCode;
|
||||
|
||||
private OAuth2AuthorizationCodeRequestAuthenticationToken() {
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the provided parameters.
|
||||
*
|
||||
* @param authorizationUri the authorization URI
|
||||
* @param clientId the client identifier
|
||||
* @param principal the {@code Principal} (Resource Owner)
|
||||
* @param redirectUri the redirect uri
|
||||
* @param state the state
|
||||
* @param scopes the requested scope(s)
|
||||
* @param additionalParameters the additional parameters
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
|
||||
@Nullable String redirectUri, @Nullable String state, @Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
|
||||
super(Collections.emptyList());
|
||||
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
|
||||
Assert.hasText(clientId, "clientId cannot be empty");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
this.authorizationUri = authorizationUri;
|
||||
this.clientId = clientId;
|
||||
this.principal = principal;
|
||||
this.redirectUri = redirectUri;
|
||||
this.state = state;
|
||||
this.scopes = Collections.unmodifiableSet(
|
||||
scopes != null ?
|
||||
new HashSet<>(scopes) :
|
||||
Collections.emptySet());
|
||||
this.additionalParameters = Collections.unmodifiableMap(
|
||||
additionalParameters != null ?
|
||||
new HashMap<>(additionalParameters) :
|
||||
Collections.emptyMap());
|
||||
this.authorizationCode = null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationToken} using the provided parameters.
|
||||
*
|
||||
* @param authorizationUri the authorization URI
|
||||
* @param clientId the client identifier
|
||||
* @param principal the {@code Principal} (Resource Owner)
|
||||
* @param authorizationCode the {@link OAuth2AuthorizationCode}
|
||||
* @param redirectUri the redirect uri
|
||||
* @param state the state
|
||||
* @param scopes the authorized scope(s)
|
||||
* @since 0.4.0
|
||||
*/
|
||||
public OAuth2AuthorizationCodeRequestAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
|
||||
OAuth2AuthorizationCode authorizationCode, @Nullable String redirectUri, @Nullable String state, @Nullable Set<String> scopes) {
|
||||
super(Collections.emptyList());
|
||||
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
|
||||
Assert.hasText(clientId, "clientId cannot be empty");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
Assert.notNull(authorizationCode, "authorizationCode cannot be null");
|
||||
this.authorizationUri = authorizationUri;
|
||||
this.clientId = clientId;
|
||||
this.principal = principal;
|
||||
this.authorizationCode = authorizationCode;
|
||||
this.redirectUri = redirectUri;
|
||||
this.state = state;
|
||||
this.scopes = Collections.unmodifiableSet(
|
||||
scopes != null ?
|
||||
new HashSet<>(scopes) :
|
||||
Collections.emptySet());
|
||||
this.additionalParameters = Collections.emptyMap();
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -94,15 +153,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
|
||||
return this.redirectUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested (or authorized) scope(s).
|
||||
*
|
||||
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state.
|
||||
*
|
||||
@@ -113,34 +163,24 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested (or authorized) scope(s).
|
||||
*
|
||||
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional parameters.
|
||||
*
|
||||
* @return the additional parameters
|
||||
* @return the additional parameters, or an empty {@code Map} if not available
|
||||
*/
|
||||
public Map<String, Object> getAdditionalParameters() {
|
||||
return this.additionalParameters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if authorization consent is required, {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if authorization consent is required, {@code false} otherwise
|
||||
*/
|
||||
public boolean isConsentRequired() {
|
||||
return this.consentRequired;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if this {@code Authentication} represents an authorization consent request,
|
||||
* {@code false} otherwise.
|
||||
*
|
||||
* @return {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise
|
||||
*/
|
||||
public boolean isConsent() {
|
||||
return this.consent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
@@ -151,170 +191,4 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationToken extends Abs
|
||||
return this.authorizationCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a new {@link Builder}, initialized with the given client identifier
|
||||
* and {@code Principal} (Resource Owner).
|
||||
*
|
||||
* @param clientId the client identifier
|
||||
* @param principal the {@code Principal} (Resource Owner)
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder with(@NonNull String clientId, @NonNull Authentication principal) {
|
||||
Assert.hasText(clientId, "clientId cannot be empty");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
return new Builder(clientId, principal);
|
||||
}
|
||||
|
||||
/**
|
||||
* A builder for {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
*/
|
||||
public static final class Builder implements Serializable {
|
||||
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
|
||||
private String authorizationUri;
|
||||
private String clientId;
|
||||
private Authentication principal;
|
||||
private String redirectUri;
|
||||
private Set<String> scopes;
|
||||
private String state;
|
||||
private Map<String, Object> additionalParameters;
|
||||
private boolean consentRequired;
|
||||
private boolean consent;
|
||||
private OAuth2AuthorizationCode authorizationCode;
|
||||
|
||||
private Builder(String clientId, Authentication principal) {
|
||||
this.clientId = clientId;
|
||||
this.principal = principal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the authorization URI.
|
||||
*
|
||||
* @param authorizationUri the authorization URI
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder authorizationUri(String authorizationUri) {
|
||||
this.authorizationUri = authorizationUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the redirect uri.
|
||||
*
|
||||
* @param redirectUri the redirect uri
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder redirectUri(String redirectUri) {
|
||||
this.redirectUri = redirectUri;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the requested (or authorized) scope(s).
|
||||
*
|
||||
* @param scopes the requested (or authorized) scope(s)
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder scopes(Set<String> scopes) {
|
||||
if (scopes != null) {
|
||||
this.scopes = new HashSet<>(scopes);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the state.
|
||||
*
|
||||
* @param state the state
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder state(String state) {
|
||||
this.state = state;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the additional parameters.
|
||||
*
|
||||
* @param additionalParameters the additional parameters
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder additionalParameters(Map<String, Object> additionalParameters) {
|
||||
if (additionalParameters != null) {
|
||||
this.additionalParameters = new HashMap<>(additionalParameters);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if authorization consent is required, {@code false} otherwise.
|
||||
*
|
||||
* @param consentRequired {@code true} if authorization consent is required, {@code false} otherwise
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder consentRequired(boolean consentRequired) {
|
||||
this.consentRequired = consentRequired;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise.
|
||||
*
|
||||
* @param consent {@code true} if this {@code Authentication} represents an authorization consent request, {@code false} otherwise
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder consent(boolean consent) {
|
||||
this.consent = consent;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
* @param authorizationCode the {@link OAuth2AuthorizationCode}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public Builder authorizationCode(OAuth2AuthorizationCode authorizationCode) {
|
||||
this.authorizationCode = authorizationCode;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
*/
|
||||
public OAuth2AuthorizationCodeRequestAuthenticationToken build() {
|
||||
Assert.hasText(this.authorizationUri, "authorizationUri cannot be empty");
|
||||
if (this.consent) {
|
||||
Assert.hasText(this.state, "state cannot be empty");
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken();
|
||||
|
||||
authentication.authorizationUri = this.authorizationUri;
|
||||
authentication.clientId = this.clientId;
|
||||
authentication.principal = this.principal;
|
||||
authentication.redirectUri = this.redirectUri;
|
||||
authentication.scopes = Collections.unmodifiableSet(
|
||||
!CollectionUtils.isEmpty(this.scopes) ?
|
||||
this.scopes :
|
||||
Collections.emptySet());
|
||||
authentication.state = this.state;
|
||||
authentication.additionalParameters = Collections.unmodifiableMap(
|
||||
!CollectionUtils.isEmpty(this.additionalParameters) ?
|
||||
this.additionalParameters :
|
||||
Collections.emptyMap());
|
||||
authentication.consentRequired = this.consentRequired;
|
||||
authentication.consent = this.consent;
|
||||
authentication.authorizationCode = this.authorizationCode;
|
||||
if (this.authorizationCode != null || this.consentRequired) {
|
||||
authentication.setAuthenticated(true);
|
||||
}
|
||||
|
||||
return authentication;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -189,38 +189,23 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationValidator impleme
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
RegisteredClient registeredClient) {
|
||||
|
||||
boolean redirectOnError = true;
|
||||
String redirectUri = StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri()) ?
|
||||
authorizationCodeRequestAuthentication.getRedirectUri() :
|
||||
registeredClient.getRedirectUris().iterator().next();
|
||||
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
|
||||
parameterName.equals(OAuth2ParameterNames.REDIRECT_URI)) {
|
||||
redirectOnError = false;
|
||||
redirectUri = null; // Prevent redirects
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult = authorizationCodeRequestAuthentication;
|
||||
|
||||
if (redirectOnError && !StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||
String redirectUri = registeredClient.getRedirectUris().iterator().next();
|
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
|
||||
.redirectUri(redirectUri)
|
||||
.build();
|
||||
} else if (!redirectOnError && StringUtils.hasText(authorizationCodeRequestAuthentication.getRedirectUri())) {
|
||||
authorizationCodeRequestAuthenticationResult = from(authorizationCodeRequestAuthentication)
|
||||
.redirectUri(null) // Prevent redirects
|
||||
.build();
|
||||
}
|
||||
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(authorizationCodeRequestAuthentication.isAuthenticated());
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
authorizationCodeRequestAuthentication.getAuthorizationUri(), authorizationCodeRequestAuthentication.getClientId(),
|
||||
(Authentication) authorizationCodeRequestAuthentication.getPrincipal(), redirectUri,
|
||||
authorizationCodeRequestAuthentication.getState(), authorizationCodeRequestAuthentication.getScopes(),
|
||||
authorizationCodeRequestAuthentication.getAdditionalParameters());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder from(OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(authorizationCodeRequestAuthentication.getClientId(), (Authentication) authorizationCodeRequestAuthentication.getPrincipal())
|
||||
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri())
|
||||
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri())
|
||||
.scopes(authorizationCodeRequestAuthentication.getScopes())
|
||||
.state(authorizationCodeRequestAuthentication.getState())
|
||||
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters())
|
||||
.authorizationCode(authorizationCodeRequestAuthentication.getAuthorizationCode());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import org.springframework.util.Assert;
|
||||
* @since 0.2.1
|
||||
* @see OAuth2AuthenticationContext
|
||||
* @see OAuth2AuthorizationConsent
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider#setAuthorizationConsentCustomizer(Consumer)
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider#setAuthorizationConsentCustomizer(Consumer)
|
||||
*/
|
||||
public final class OAuth2AuthorizationConsentAuthenticationContext implements OAuth2AuthenticationContext {
|
||||
private final Map<Object, Object> context;
|
||||
@@ -95,12 +95,12 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationConsentAuthenticationToken}.
|
||||
*
|
||||
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
* @param authentication the {@link OAuth2AuthorizationConsentAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder with(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
|
||||
public static Builder with(OAuth2AuthorizationConsentAuthenticationToken authentication) {
|
||||
return new Builder(authentication);
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ public final class OAuth2AuthorizationConsentAuthenticationContext implements OA
|
||||
*/
|
||||
public static final class Builder extends AbstractBuilder<OAuth2AuthorizationConsentAuthenticationContext, Builder> {
|
||||
|
||||
private Builder(OAuth2AuthorizationCodeRequestAuthenticationToken authentication) {
|
||||
private Builder(OAuth2AuthorizationConsentAuthenticationToken authentication) {
|
||||
super(authentication);
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,316 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationProvider;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.AuthenticationException;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Authorization Consent
|
||||
* used in the Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.4.0
|
||||
* @see OAuth2AuthorizationConsentAuthenticationToken
|
||||
* @see OAuth2AuthorizationConsent
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
|
||||
* @see RegisteredClientRepository
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2AuthorizationConsentService
|
||||
*/
|
||||
public final class OAuth2AuthorizationConsentAuthenticationProvider implements AuthenticationProvider {
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
private final OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
|
||||
private Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationConsentAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* @param registeredClientRepository the repository of registered clients
|
||||
* @param authorizationService the authorization service
|
||||
* @param authorizationConsentService the authorization consent service
|
||||
*/
|
||||
public OAuth2AuthorizationConsentAuthenticationProvider(RegisteredClientRepository registeredClientRepository,
|
||||
OAuth2AuthorizationService authorizationService, OAuth2AuthorizationConsentService authorizationConsentService) {
|
||||
Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null");
|
||||
Assert.notNull(authorizationService, "authorizationService cannot be null");
|
||||
Assert.notNull(authorizationConsentService, "authorizationConsentService cannot be null");
|
||||
this.registeredClientRepository = registeredClientRepository;
|
||||
this.authorizationService = authorizationService;
|
||||
this.authorizationConsentService = authorizationConsentService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
|
||||
(OAuth2AuthorizationConsentAuthenticationToken) authentication;
|
||||
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(
|
||||
authorizationConsentAuthentication.getState(), STATE_TOKEN_TYPE);
|
||||
if (authorization == null) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
|
||||
authorizationConsentAuthentication, null, null);
|
||||
}
|
||||
|
||||
// The 'in-flight' authorization must be associated to the current principal
|
||||
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
|
||||
if (!isPrincipalAuthenticated(principal) || !principal.getName().equals(authorization.getPrincipalName())) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE,
|
||||
authorizationConsentAuthentication, null, null);
|
||||
}
|
||||
|
||||
RegisteredClient registeredClient = this.registeredClientRepository.findByClientId(
|
||||
authorizationConsentAuthentication.getClientId());
|
||||
if (registeredClient == null || !registeredClient.getId().equals(authorization.getRegisteredClientId())) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID,
|
||||
authorizationConsentAuthentication, registeredClient, null);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> requestedScopes = authorizationRequest.getScopes();
|
||||
Set<String> authorizedScopes = new HashSet<>(authorizationConsentAuthentication.getScopes());
|
||||
if (!requestedScopes.containsAll(authorizedScopes)) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE,
|
||||
authorizationConsentAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
Set<String> currentAuthorizedScopes = currentAuthorizationConsent != null ?
|
||||
currentAuthorizationConsent.getScopes() : Collections.emptySet();
|
||||
|
||||
if (!currentAuthorizedScopes.isEmpty()) {
|
||||
for (String requestedScope : requestedScopes) {
|
||||
if (currentAuthorizedScopes.contains(requestedScope)) {
|
||||
authorizedScopes.add(requestedScope);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!authorizedScopes.isEmpty() && requestedScopes.contains(OidcScopes.OPENID)) {
|
||||
// 'openid' scope is auto-approved as it does not require consent
|
||||
authorizedScopes.add(OidcScopes.OPENID);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder;
|
||||
if (currentAuthorizationConsent != null) {
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.from(currentAuthorizationConsent);
|
||||
} else {
|
||||
authorizationConsentBuilder = OAuth2AuthorizationConsent.withId(
|
||||
authorization.getRegisteredClientId(), authorization.getPrincipalName());
|
||||
}
|
||||
authorizedScopes.forEach(authorizationConsentBuilder::scope);
|
||||
|
||||
if (this.authorizationConsentCustomizer != null) {
|
||||
// @formatter:off
|
||||
OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext =
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(authorizationConsentAuthentication)
|
||||
.authorizationConsent(authorizationConsentBuilder)
|
||||
.registeredClient(registeredClient)
|
||||
.authorization(authorization)
|
||||
.authorizationRequest(authorizationRequest)
|
||||
.build();
|
||||
// @formatter:on
|
||||
this.authorizationConsentCustomizer.accept(authorizationConsentAuthenticationContext);
|
||||
}
|
||||
|
||||
Set<GrantedAuthority> authorities = new HashSet<>();
|
||||
authorizationConsentBuilder.authorities(authorities::addAll);
|
||||
|
||||
if (authorities.isEmpty()) {
|
||||
// Authorization consent denied (or revoked)
|
||||
if (currentAuthorizationConsent != null) {
|
||||
this.authorizationConsentService.remove(currentAuthorizationConsent);
|
||||
}
|
||||
this.authorizationService.remove(authorization);
|
||||
throwError(OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID,
|
||||
authorizationConsentAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentBuilder.build();
|
||||
if (!authorizationConsent.equals(currentAuthorizationConsent)) {
|
||||
this.authorizationConsentService.save(authorizationConsent);
|
||||
}
|
||||
|
||||
OAuth2TokenContext tokenContext = createAuthorizationCodeTokenContext(
|
||||
authorizationConsentAuthentication, registeredClient, authorization, authorizedScopes);
|
||||
OAuth2AuthorizationCode authorizationCode = this.authorizationCodeGenerator.generate(tokenContext);
|
||||
if (authorizationCode == null) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"The token generator failed to generate the authorization code.", ERROR_URI);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
|
||||
}
|
||||
|
||||
OAuth2Authorization updatedAuthorization = OAuth2Authorization.from(authorization)
|
||||
.authorizedScopes(authorizedScopes)
|
||||
.token(authorizationCode)
|
||||
.attributes(attrs -> {
|
||||
attrs.remove(OAuth2ParameterNames.STATE);
|
||||
})
|
||||
.build();
|
||||
this.authorizationService.save(updatedAuthorization);
|
||||
|
||||
String redirectUri = authorizationRequest.getRedirectUri();
|
||||
if (!StringUtils.hasText(redirectUri)) {
|
||||
redirectUri = registeredClient.getRedirectUris().iterator().next();
|
||||
}
|
||||
|
||||
return new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
authorizationRequest.getAuthorizationUri(), registeredClient.getClientId(), principal, authorizationCode,
|
||||
redirectUri, authorizationRequest.getState(), authorizedScopes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supports(Class<?> authentication) {
|
||||
return OAuth2AuthorizationConsentAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}.
|
||||
*
|
||||
* @param authorizationCodeGenerator the {@link OAuth2TokenGenerator} that generates the {@link OAuth2AuthorizationCode}
|
||||
*/
|
||||
public void setAuthorizationCodeGenerator(OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator) {
|
||||
Assert.notNull(authorizationCodeGenerator, "authorizationCodeGenerator cannot be null");
|
||||
this.authorizationCodeGenerator = authorizationCodeGenerator;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AuthorizationConsentAuthenticationContext}
|
||||
* containing an {@link OAuth2AuthorizationConsent.Builder} and additional context information.
|
||||
*
|
||||
* <p>
|
||||
* The following context attributes are available:
|
||||
* <ul>
|
||||
* <li>The {@link OAuth2AuthorizationConsent.Builder} used to build the authorization consent
|
||||
* prior to {@link OAuth2AuthorizationConsentService#save(OAuth2AuthorizationConsent)}.</li>
|
||||
* <li>The {@link Authentication} of type
|
||||
* {@link OAuth2AuthorizationConsentAuthenticationToken}.</li>
|
||||
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
|
||||
* <li>The {@link OAuth2Authorization} associated with the state token presented in the
|
||||
* authorization consent request.</li>
|
||||
* <li>The {@link OAuth2AuthorizationRequest} associated with the authorization consent request.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param authorizationConsentCustomizer the {@code Consumer} providing access to the
|
||||
* {@link OAuth2AuthorizationConsentAuthenticationContext} containing an {@link OAuth2AuthorizationConsent.Builder}
|
||||
*/
|
||||
public void setAuthorizationConsentCustomizer(Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer) {
|
||||
Assert.notNull(authorizationConsentCustomizer, "authorizationConsentCustomizer cannot be null");
|
||||
this.authorizationConsentCustomizer = authorizationConsentCustomizer;
|
||||
}
|
||||
|
||||
private static OAuth2TokenContext createAuthorizationCodeTokenContext(
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2Authorization authorization, Set<String> authorizedScopes) {
|
||||
|
||||
// @formatter:off
|
||||
return DefaultOAuth2TokenContext.builder()
|
||||
.registeredClient(registeredClient)
|
||||
.principal((Authentication) authorizationConsentAuthentication.getPrincipal())
|
||||
.authorization(authorization)
|
||||
.authorizationServerContext(AuthorizationServerContextHolder.getContext())
|
||||
.tokenType(new OAuth2TokenType(OAuth2ParameterNames.CODE))
|
||||
.authorizedScopes(authorizedScopes)
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
.authorizationGrant(authorizationConsentAuthentication)
|
||||
.build();
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
private static boolean isPrincipalAuthenticated(Authentication principal) {
|
||||
return principal != null &&
|
||||
!AnonymousAuthenticationToken.class.isAssignableFrom(principal.getClass()) &&
|
||||
principal.isAuthenticated();
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName,
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, ERROR_URI);
|
||||
throwError(error, parameterName, authorizationConsentAuthentication, registeredClient, authorizationRequest);
|
||||
}
|
||||
|
||||
private static void throwError(OAuth2Error error, String parameterName,
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication,
|
||||
RegisteredClient registeredClient, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
|
||||
String redirectUri = resolveRedirectUri(authorizationRequest, registeredClient);
|
||||
if (error.getErrorCode().equals(OAuth2ErrorCodes.INVALID_REQUEST) &&
|
||||
(parameterName.equals(OAuth2ParameterNames.CLIENT_ID) ||
|
||||
parameterName.equals(OAuth2ParameterNames.STATE))) {
|
||||
redirectUri = null; // Prevent redirects
|
||||
}
|
||||
|
||||
String state = authorizationRequest != null ?
|
||||
authorizationRequest.getState() :
|
||||
authorizationConsentAuthentication.getState();
|
||||
Set<String> requestedScopes = authorizationRequest != null ?
|
||||
authorizationRequest.getScopes() :
|
||||
authorizationConsentAuthentication.getScopes();
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
authorizationConsentAuthentication.getAuthorizationUri(), authorizationConsentAuthentication.getClientId(),
|
||||
(Authentication) authorizationConsentAuthentication.getPrincipal(), redirectUri,
|
||||
state, requestedScopes, null);
|
||||
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthenticationResult);
|
||||
}
|
||||
|
||||
private static String resolveRedirectUri(OAuth2AuthorizationRequest authorizationRequest, RegisteredClient registeredClient) {
|
||||
if (authorizationRequest != null && StringUtils.hasText(authorizationRequest.getRedirectUri())) {
|
||||
return authorizationRequest.getRedirectUri();
|
||||
}
|
||||
if (registeredClient != null) {
|
||||
return registeredClient.getRedirectUris().iterator().next();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.util.SpringAuthorizationServerVersion;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link Authentication} implementation for the OAuth 2.0 Authorization Consent
|
||||
* used in the Authorization Code Grant.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.4.0
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
|
||||
*/
|
||||
public class OAuth2AuthorizationConsentAuthenticationToken extends AbstractAuthenticationToken {
|
||||
private static final long serialVersionUID = SpringAuthorizationServerVersion.SERIAL_VERSION_UID;
|
||||
private final String authorizationUri;
|
||||
private final String clientId;
|
||||
private final Authentication principal;
|
||||
private final String state;
|
||||
private final Set<String> scopes;
|
||||
private final Map<String, Object> additionalParameters;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationConsentAuthenticationToken} using the provided parameters.
|
||||
*
|
||||
* @param authorizationUri the authorization URI
|
||||
* @param clientId the client identifier
|
||||
* @param principal the {@code Principal} (Resource Owner)
|
||||
* @param state the state
|
||||
* @param scopes the requested (or authorized) scope(s)
|
||||
* @param additionalParameters the additional parameters
|
||||
*/
|
||||
public OAuth2AuthorizationConsentAuthenticationToken(String authorizationUri, String clientId, Authentication principal,
|
||||
String state, @Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) {
|
||||
super(Collections.emptyList());
|
||||
Assert.hasText(authorizationUri, "authorizationUri cannot be empty");
|
||||
Assert.hasText(clientId, "clientId cannot be empty");
|
||||
Assert.notNull(principal, "principal cannot be null");
|
||||
Assert.hasText(state, "state cannot be empty");
|
||||
this.authorizationUri = authorizationUri;
|
||||
this.clientId = clientId;
|
||||
this.principal = principal;
|
||||
this.state = state;
|
||||
this.scopes = Collections.unmodifiableSet(
|
||||
scopes != null ?
|
||||
new HashSet<>(scopes) :
|
||||
Collections.emptySet());
|
||||
this.additionalParameters = Collections.unmodifiableMap(
|
||||
additionalParameters != null ?
|
||||
new HashMap<>(additionalParameters) :
|
||||
Collections.emptyMap());
|
||||
setAuthenticated(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getPrincipal() {
|
||||
return this.principal;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getCredentials() {
|
||||
return "";
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the authorization URI.
|
||||
*
|
||||
* @return the authorization URI
|
||||
*/
|
||||
public String getAuthorizationUri() {
|
||||
return this.authorizationUri;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the client identifier.
|
||||
*
|
||||
* @return the client identifier
|
||||
*/
|
||||
public String getClientId() {
|
||||
return this.clientId;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the state.
|
||||
*
|
||||
* @return the state
|
||||
*/
|
||||
public String getState() {
|
||||
return this.state;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested (or authorized) scope(s).
|
||||
*
|
||||
* @return the requested (or authorized) scope(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
return this.scopes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the additional parameters.
|
||||
*
|
||||
* @return the additional parameters, or an empty {@code Map} if not available
|
||||
*/
|
||||
public Map<String, Object> getAdditionalParameters() {
|
||||
return this.additionalParameters;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -31,10 +31,13 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
import org.springframework.security.web.authentication.AuthenticationSuccessHandler;
|
||||
@@ -72,7 +75,8 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
|
||||
/**
|
||||
* Adds an {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} or {@link OAuth2AuthorizationConsentAuthenticationToken}
|
||||
* used for authenticating the request.
|
||||
*
|
||||
* @param authorizationRequestConverter an {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* @return the {@link OAuth2AuthorizationEndpointConfigurer} for further configuration
|
||||
@@ -170,7 +174,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
*
|
||||
* <ul>
|
||||
* <li>It must be an HTTP POST</li>
|
||||
* <li>It must be submitted to {@link AuthorizationServerSettings#getAuthorizationEndpoint()} ()}</li>
|
||||
* <li>It must be submitted to {@link AuthorizationServerSettings#getAuthorizationEndpoint()}</li>
|
||||
* <li>It must include the received {@code client_id} as an HTTP parameter</li>
|
||||
* <li>It must include the received {@code state} as an HTTP parameter</li>
|
||||
* <li>It must include the list of {@code scope}s the {@code Resource Owner}
|
||||
@@ -242,6 +246,7 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
List<AuthenticationConverter> authenticationConverters = new ArrayList<>();
|
||||
|
||||
authenticationConverters.add(new OAuth2AuthorizationCodeRequestAuthenticationConverter());
|
||||
authenticationConverters.add(new OAuth2AuthorizationConsentAuthenticationConverter());
|
||||
|
||||
return authenticationConverters;
|
||||
}
|
||||
@@ -256,6 +261,13 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
|
||||
authenticationProviders.add(authorizationCodeRequestAuthenticationProvider);
|
||||
|
||||
OAuth2AuthorizationConsentAuthenticationProvider authorizationConsentAuthenticationProvider =
|
||||
new OAuth2AuthorizationConsentAuthenticationProvider(
|
||||
OAuth2ConfigurerUtils.getRegisteredClientRepository(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationService(httpSecurity),
|
||||
OAuth2ConfigurerUtils.getAuthorizationConsentService(httpSecurity));
|
||||
authenticationProviders.add(authorizationConsentAuthenticationProvider);
|
||||
|
||||
return authenticationProviders;
|
||||
}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.web;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
@@ -28,6 +29,7 @@ import javax.servlet.http.HttpServletResponse;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.authentication.AbstractAuthenticationToken;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.core.Authentication;
|
||||
@@ -40,7 +42,11 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
|
||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||
import org.springframework.security.web.RedirectStrategy;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
@@ -61,7 +67,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* A {@code Filter} for the OAuth 2.0 Authorization Code Grant,
|
||||
* which handles the processing of the OAuth 2.0 Authorization Request (and Consent).
|
||||
* which handles the processing of the OAuth 2.0 Authorization Request and Consent.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Paurav Munshi
|
||||
@@ -71,6 +77,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
* @since 0.0.1
|
||||
* @see AuthenticationManager
|
||||
* @see OAuth2AuthorizationCodeRequestAuthenticationProvider
|
||||
* @see OAuth2AuthorizationConsentAuthenticationProvider
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.1">Section 4.1.1 Authorization Request</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2">Section 4.1.2 Authorization Response</a>
|
||||
@@ -110,7 +117,10 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
||||
this.authenticationManager = authenticationManager;
|
||||
this.authorizationEndpointMatcher = createDefaultRequestMatcher(authorizationEndpointUri);
|
||||
this.authenticationConverter = new OAuth2AuthorizationCodeRequestAuthenticationConverter();
|
||||
this.authenticationConverter = new DelegatingAuthenticationConverter(
|
||||
Arrays.asList(
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationConverter(),
|
||||
new OAuth2AuthorizationConsentAuthenticationConverter()));
|
||||
}
|
||||
|
||||
private static RequestMatcher createDefaultRequestMatcher(String authorizationEndpointUri) {
|
||||
@@ -145,14 +155,14 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
}
|
||||
|
||||
try {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationConverter.convert(request);
|
||||
authorizationCodeRequestAuthentication.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
Authentication authentication = this.authenticationConverter.convert(request);
|
||||
if (authentication instanceof AbstractAuthenticationToken) {
|
||||
((AbstractAuthenticationToken) authentication)
|
||||
.setDetails(this.authenticationDetailsSource.buildDetails(request));
|
||||
}
|
||||
Authentication authenticationResult = this.authenticationManager.authenticate(authentication);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationManager.authenticate(authorizationCodeRequestAuthentication);
|
||||
|
||||
if (!authorizationCodeRequestAuthenticationResult.isAuthenticated()) {
|
||||
if (!authenticationResult.isAuthenticated()) {
|
||||
// If the Principal (Resource Owner) is not authenticated then
|
||||
// pass through the chain with the expectation that the authentication process
|
||||
// will commence via AuthenticationEntryPoint
|
||||
@@ -160,13 +170,15 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
return;
|
||||
}
|
||||
|
||||
if (authorizationCodeRequestAuthenticationResult.isConsentRequired()) {
|
||||
sendAuthorizationConsent(request, response, authorizationCodeRequestAuthentication, authorizationCodeRequestAuthenticationResult);
|
||||
if (authenticationResult instanceof OAuth2AuthorizationConsentAuthenticationToken) {
|
||||
sendAuthorizationConsent(request, response,
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) authentication,
|
||||
(OAuth2AuthorizationConsentAuthenticationToken) authenticationResult);
|
||||
return;
|
||||
}
|
||||
|
||||
this.authenticationSuccessHandler.onAuthenticationSuccess(
|
||||
request, response, authorizationCodeRequestAuthenticationResult);
|
||||
request, response, authenticationResult);
|
||||
|
||||
} catch (OAuth2AuthenticationException ex) {
|
||||
this.authenticationFailureHandler.onAuthenticationFailure(request, response, ex);
|
||||
@@ -186,7 +198,8 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
|
||||
/**
|
||||
* Sets the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
* to an instance of {@link OAuth2AuthorizationCodeRequestAuthenticationToken} or {@link OAuth2AuthorizationConsentAuthenticationToken}
|
||||
* used for authenticating the request.
|
||||
*
|
||||
* @param authenticationConverter the {@link AuthenticationConverter} used when attempting to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
*/
|
||||
@@ -229,13 +242,13 @@ public final class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilte
|
||||
|
||||
private void sendAuthorizationConsent(HttpServletRequest request, HttpServletResponse response,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult) throws IOException {
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication) throws IOException {
|
||||
|
||||
String clientId = authorizationCodeRequestAuthenticationResult.getClientId();
|
||||
Authentication principal = (Authentication) authorizationCodeRequestAuthenticationResult.getPrincipal();
|
||||
String clientId = authorizationConsentAuthentication.getClientId();
|
||||
Authentication principal = (Authentication) authorizationConsentAuthentication.getPrincipal();
|
||||
Set<String> requestedScopes = authorizationCodeRequestAuthentication.getScopes();
|
||||
Set<String> authorizedScopes = authorizationCodeRequestAuthenticationResult.getScopes();
|
||||
String state = authorizationCodeRequestAuthenticationResult.getState();
|
||||
Set<String> authorizedScopes = authorizationConsentAuthentication.getScopes();
|
||||
String state = authorizationConsentAuthentication.getState();
|
||||
|
||||
if (hasConsentUri()) {
|
||||
String redirectUri = UriComponentsBuilder.fromUriString(resolveConsentUri(request))
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -43,7 +43,7 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to extract an Authorization Request (or Consent) from {@link HttpServletRequest}
|
||||
* Attempts to extract an Authorization Request from {@link HttpServletRequest}
|
||||
* for the OAuth 2.0 Authorization Code Grant and then converts it to
|
||||
* an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} used for authenticating the request.
|
||||
*
|
||||
@@ -62,20 +62,19 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
if (!"GET".equals(request.getMethod()) && !OIDC_REQUEST_MATCHER.matches(request)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||
|
||||
boolean authorizationRequest = false;
|
||||
if ("GET".equals(request.getMethod()) || OIDC_REQUEST_MATCHER.matches(request)) {
|
||||
authorizationRequest = true;
|
||||
|
||||
// response_type (REQUIRED)
|
||||
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
if (!StringUtils.hasText(responseType) ||
|
||||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
||||
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
}
|
||||
// response_type (REQUIRED)
|
||||
String responseType = request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
if (!StringUtils.hasText(responseType) ||
|
||||
parameters.get(OAuth2ParameterNames.RESPONSE_TYPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
} else if (!responseType.equals(OAuth2AuthorizationResponseType.CODE.getValue())) {
|
||||
throwError(OAuth2ErrorCodes.UNSUPPORTED_RESPONSE_TYPE, OAuth2ParameterNames.RESPONSE_TYPE);
|
||||
}
|
||||
|
||||
String authorizationUri = request.getRequestURL().toString();
|
||||
@@ -101,37 +100,21 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
|
||||
|
||||
// scope (OPTIONAL)
|
||||
Set<String> scopes = null;
|
||||
if (authorizationRequest) {
|
||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope) &&
|
||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
|
||||
}
|
||||
if (StringUtils.hasText(scope)) {
|
||||
scopes = new HashSet<>(
|
||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
}
|
||||
} else {
|
||||
// Consent request
|
||||
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
|
||||
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
|
||||
}
|
||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope) &&
|
||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE);
|
||||
}
|
||||
if (StringUtils.hasText(scope)) {
|
||||
scopes = new HashSet<>(
|
||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
}
|
||||
|
||||
// state
|
||||
// RECOMMENDED for Authorization Request
|
||||
// state (RECOMMENDED)
|
||||
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
|
||||
if (authorizationRequest) {
|
||||
if (StringUtils.hasText(state) &&
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
} else {
|
||||
// REQUIRED for Authorization Consent Request
|
||||
if (!StringUtils.hasText(state) ||
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
if (StringUtils.hasText(state) &&
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
|
||||
// code_challenge (REQUIRED for public clients) - RFC 7636 (PKCE)
|
||||
@@ -159,14 +142,8 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationConverter impleme
|
||||
}
|
||||
});
|
||||
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, principal)
|
||||
.authorizationUri(authorizationUri)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(scopes)
|
||||
.state(state)
|
||||
.additionalParameters(additionalParameters)
|
||||
.consent(!authorizationRequest)
|
||||
.build();
|
||||
return new OAuth2AuthorizationCodeRequestAuthenticationToken(authorizationUri, clientId, principal,
|
||||
redirectUri, state, scopes, additionalParameters);
|
||||
}
|
||||
|
||||
private static RequestMatcher createOidcRequestMatcher() {
|
||||
|
||||
@@ -0,0 +1,109 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.web.authentication;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to extract an Authorization Consent from {@link HttpServletRequest}
|
||||
* for the OAuth 2.0 Authorization Code Grant and then converts it to
|
||||
* an {@link OAuth2AuthorizationConsentAuthenticationToken} used for authenticating the request.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.4.0
|
||||
* @see AuthenticationConverter
|
||||
* @see OAuth2AuthorizationConsentAuthenticationToken
|
||||
* @see OAuth2AuthorizationEndpointFilter
|
||||
*/
|
||||
public final class OAuth2AuthorizationConsentAuthenticationConverter implements AuthenticationConverter {
|
||||
private static final String DEFAULT_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-4.1.2.1";
|
||||
private static final Authentication ANONYMOUS_AUTHENTICATION = new AnonymousAuthenticationToken(
|
||||
"anonymous", "anonymousUser", AuthorityUtils.createAuthorityList("ROLE_ANONYMOUS"));
|
||||
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
if (!"POST".equals(request.getMethod()) ||
|
||||
request.getParameter(OAuth2ParameterNames.RESPONSE_TYPE) != null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request);
|
||||
|
||||
String authorizationUri = request.getRequestURL().toString();
|
||||
|
||||
// client_id (REQUIRED)
|
||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
||||
if (!StringUtils.hasText(clientId) ||
|
||||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (principal == null) {
|
||||
principal = ANONYMOUS_AUTHENTICATION;
|
||||
}
|
||||
|
||||
// state (REQUIRED)
|
||||
String state = parameters.getFirst(OAuth2ParameterNames.STATE);
|
||||
if (!StringUtils.hasText(state) ||
|
||||
parameters.get(OAuth2ParameterNames.STATE).size() != 1) {
|
||||
throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE);
|
||||
}
|
||||
|
||||
// scope (OPTIONAL)
|
||||
Set<String> scopes = null;
|
||||
if (parameters.containsKey(OAuth2ParameterNames.SCOPE)) {
|
||||
scopes = new HashSet<>(parameters.get(OAuth2ParameterNames.SCOPE));
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.CLIENT_ID) &&
|
||||
!key.equals(OAuth2ParameterNames.STATE) &&
|
||||
!key.equals(OAuth2ParameterNames.SCOPE)) {
|
||||
additionalParameters.put(key, value.get(0));
|
||||
}
|
||||
});
|
||||
|
||||
return new OAuth2AuthorizationConsentAuthenticationToken(authorizationUri, clientId, principal,
|
||||
state, scopes, additionalParameters);
|
||||
}
|
||||
|
||||
private static void throwError(String errorCode, String parameterName) {
|
||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, DEFAULT_ERROR_URI);
|
||||
throw new OAuth2AuthorizationCodeRequestAuthenticationException(error, null);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -18,7 +18,6 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
@@ -42,8 +41,6 @@ import org.springframework.security.oauth2.server.authorization.OAuth2Authorizat
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
@@ -58,7 +55,6 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -69,7 +65,8 @@ import static org.mockito.Mockito.when;
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
|
||||
private static final String STATE = "state";
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
@@ -132,19 +129,13 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.hasMessage("authenticationValidator cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentCustomizer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -160,9 +151,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https:///invalid")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https:///invalid", STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -178,9 +169,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https://example.com#fragment")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https://example.com#fragment", STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -196,9 +187,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https://localhost:5000")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https://localhost:5000", STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -216,9 +207,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https://invalid-example.com")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https://invalid-example.com", STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -236,9 +227,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https://127.0.0.1:5000")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https://127.0.0.1:5000", STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -255,9 +246,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri("https://[::1]:5000")
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
"https://[::1]:5000", STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -271,9 +262,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri(null)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
null, STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -291,9 +282,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.redirectUri(null)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
null, STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -311,8 +302,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -328,9 +320,10 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(Collections.singleton("invalid-scope"))
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE,
|
||||
Collections.singleton("invalid-scope"), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -347,8 +340,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -366,9 +360,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "unsupported");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -386,9 +380,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
@@ -405,8 +399,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
this.principal.setAuthenticated(false);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -424,11 +419,12 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2AuthorizationConsentAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationConsentAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
@@ -457,8 +453,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
|
||||
assertThat(authenticationResult.getScopes()).isEmpty();
|
||||
assertThat(authenticationResult.getState()).isEqualTo(state);
|
||||
assertThat(authenticationResult.isConsentRequired()).isTrue();
|
||||
assertThat(authenticationResult.getAuthorizationCode()).isNull();
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@@ -475,8 +469,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -500,8 +495,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -519,9 +515,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE, "code-challenge");
|
||||
additionalParameters.put(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), additionalParameters);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -540,8 +536,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
this.authenticationProvider.setAuthorizationCodeGenerator(authorizationCodeGenerator);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
@@ -563,8 +560,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
this.authenticationProvider.setAuthenticationValidator(authenticationValidator);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
@@ -611,410 +609,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestInvalidStateThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
this.principal.setAuthenticated(false);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestInvalidPrincipalThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName().concat("-other"))
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(otherRegisteredClient, this.principal)
|
||||
.state("state")
|
||||
.build();
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestDoesNotMatchClientThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(otherRegisteredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.state("state")
|
||||
.build();
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestScopeNotRequestedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = new HashSet<>(authorizationRequest.getScopes());
|
||||
authorizedScopes.add("scope-not-requested");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(authorizedScopes)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestNotApprovedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(new HashSet<>()) // No scopes approved
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
|
||||
verify(this.authorizationService).remove(eq(authorization));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestApproveAllThenReturnAuthorizationCode() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(authorizedScopes) // Approve all scopes
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthorizationConsentCustomizerThenUsed() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(authorizedScopes) // Approve all scopes
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer = mock(Consumer.class);
|
||||
this.authenticationProvider.setAuthorizationConsentCustomizer(authorizationConsentCustomizer);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsentAuthenticationContext> authenticationContextCaptor =
|
||||
ArgumentCaptor.forClass(OAuth2AuthorizationConsentAuthenticationContext.class);
|
||||
verify(authorizationConsentCustomizer).accept(authenticationContextCaptor.capture());
|
||||
|
||||
OAuth2AuthorizationConsentAuthenticationContext authenticationContext = authenticationContextCaptor.getValue();
|
||||
assertThat(authenticationContext.<Authentication>getAuthentication()).isEqualTo(authentication);
|
||||
assertThat(authenticationContext.getAuthorizationConsent()).isNotNull();
|
||||
assertThat(authenticationContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(authenticationContext.getAuthorizationRequest()).isEqualTo(authorizationRequest);
|
||||
}
|
||||
|
||||
private void assertAuthorizationConsentRequestWithAuthorizationCodeResult(
|
||||
RegisteredClient registeredClient,
|
||||
OAuth2Authorization authorization,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
|
||||
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
||||
assertThat(authorizationConsent.getAuthorities()).hasSize(authorizedScopes.size());
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
|
||||
assertThat(updatedAuthorization.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
||||
assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
||||
assertThat(updatedAuthorization.getAuthorizationGrantType()).isEqualTo(authorization.getAuthorizationGrantType());
|
||||
assertThat(updatedAuthorization.<Authentication>getAttribute(Principal.class.getName()))
|
||||
.isEqualTo(authorization.<Authentication>getAttribute(Principal.class.getName()));
|
||||
assertThat(updatedAuthorization.<OAuth2AuthorizationRequest>getAttribute(OAuth2AuthorizationRequest.class.getName()))
|
||||
.isEqualTo(authorizationRequest);
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);
|
||||
assertThat(authorizationCode).isNotNull();
|
||||
assertThat(updatedAuthorization.<String>getAttribute(OAuth2ParameterNames.STATE)).isNull();
|
||||
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(authorizedScopes);
|
||||
|
||||
assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal);
|
||||
assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
|
||||
assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri());
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(authorizedScopes);
|
||||
assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState());
|
||||
assertThat(authenticationResult.getAuthorizationCode()).isEqualTo(authorizationCode.getToken());
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestApproveNoneAndRevokePreviouslyApprovedThenAuthorizationConsentRemoved() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(new HashSet<>()) // No scopes approved
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
// Revoke all (including previously approved)
|
||||
this.authenticationProvider.setAuthorizationConsentCustomizer((authorizationConsentContext) ->
|
||||
authorizationConsentContext.getAuthorizationConsent().authorities(Set::clear));
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
|
||||
verify(this.authorizationConsentService).remove(eq(previousAuthorizationConsent));
|
||||
verify(this.authorizationService).remove(eq(authorization));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestApproveSomeAndPreviouslyApprovedThenAuthorizationConsentUpdated() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
String otherPreviouslyApprovedScope = "other.scope";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> requestedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(requestedScopes)
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.scope(otherPreviouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue();
|
||||
|
||||
assertThat(updatedAuthorizationConsent.getRegisteredClientId()).isEqualTo(previousAuthorizationConsent.getRegisteredClientId());
|
||||
assertThat(updatedAuthorizationConsent.getPrincipalName()).isEqualTo(previousAuthorizationConsent.getPrincipalName());
|
||||
assertThat(updatedAuthorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
previouslyApprovedScope, otherPreviouslyApprovedScope, requestedScope);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(requestedScopes);
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(requestedScopes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenConsentRequestApproveNoneAndPreviouslyApprovedThenAuthorizationConsentNotUpdated() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
authorizationConsentRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(new HashSet<>()) // No scopes approved
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationConsentService, never()).save(any());
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(Collections.singleton(previouslyApprovedScope));
|
||||
}
|
||||
|
||||
private static void assertAuthenticationException(OAuth2AuthorizationCodeRequestAuthenticationException authenticationException,
|
||||
String errorCode, String parameterName, String redirectUri) {
|
||||
|
||||
@@ -1025,30 +619,6 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authenticationException.getAuthorizationCodeRequestAuthentication();
|
||||
assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri);
|
||||
|
||||
// gh-595
|
||||
if (OAuth2ErrorCodes.ACCESS_DENIED.equals(errorCode)) {
|
||||
assertThat(authorizationCodeRequestAuthentication.isConsent()).isFalse();
|
||||
assertThat(authorizationCodeRequestAuthentication.isConsentRequired()).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder authorizationCodeRequestAuthentication(
|
||||
RegisteredClient registeredClient, Authentication principal) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.redirectUri(registeredClient.getRedirectUris().iterator().next())
|
||||
.scopes(registeredClient.getScopes())
|
||||
.state("state");
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder authorizationConsentRequestAuthentication(
|
||||
RegisteredClient registeredClient, Authentication principal) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.scopes(registeredClient.getScopes())
|
||||
.state("state")
|
||||
.consent(true);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -38,61 +38,57 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
*/
|
||||
public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
|
||||
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
|
||||
private static final String STATE = "state";
|
||||
private static final RegisteredClient REGISTERED_CLIENT = TestRegisteredClients.registeredClient().build();
|
||||
private static final TestingAuthenticationToken PRINCIPAL = new TestingAuthenticationToken("principalName", "password");
|
||||
private static final OAuth2AuthorizationCode AUTHORIZATION_CODE =
|
||||
new OAuth2AuthorizationCode("code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
|
||||
|
||||
@Test
|
||||
public void withWhenClientIdNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2AuthorizationCodeRequestAuthenticationToken.with(null, PRINCIPAL))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("clientId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void withWhenPrincipalNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("principal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationUriNotProvidedThenThrowIllegalArgumentException() {
|
||||
public void constructorWhenAuthorizationUriNotProvidedThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), PRINCIPAL)
|
||||
.build())
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(null, REGISTERED_CLIENT.getClientId(), PRINCIPAL,
|
||||
null, null, (Set<String>) null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationUri cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenStateNotProvidedThenThrowIllegalArgumentException() {
|
||||
public void constructorWhenClientIdNotProvidedThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(REGISTERED_CLIENT.getClientId(), PRINCIPAL)
|
||||
.authorizationUri(AUTHORIZATION_URI)
|
||||
.consent(true)
|
||||
.build())
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, null, PRINCIPAL,
|
||||
null, null, (Set<String>) null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("state cannot be empty");
|
||||
.hasMessage("clientId cannot be empty");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationCodeRequestThenValuesAreSet() {
|
||||
public void constructorWhenPrincipalNotProvidedThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, REGISTERED_CLIENT.getClientId(), null,
|
||||
null, null, (Set<String>) null, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("principal cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationCodeNotProvidedThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() ->
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(AUTHORIZATION_URI, REGISTERED_CLIENT.getClientId(), PRINCIPAL,
|
||||
null, null, null, (Set<String>) null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationCode cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationRequestThenValuesAreSet() {
|
||||
String clientId = REGISTERED_CLIENT.getClientId();
|
||||
String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next();
|
||||
String state = "state";
|
||||
Set<String> requestedScopes = REGISTERED_CLIENT.getScopes();
|
||||
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
|
||||
.authorizationUri(AUTHORIZATION_URI)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(requestedScopes)
|
||||
.state(STATE)
|
||||
.additionalParameters(additionalParameters)
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, clientId, PRINCIPAL, redirectUri, state, requestedScopes, additionalParameters);
|
||||
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
|
||||
assertThat(authentication.getCredentials()).isEqualTo("");
|
||||
@@ -100,87 +96,22 @@ public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
|
||||
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
|
||||
assertThat(authentication.getClientId()).isEqualTo(clientId);
|
||||
assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri);
|
||||
assertThat(authentication.getState()).isEqualTo(state);
|
||||
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(requestedScopes);
|
||||
assertThat(authentication.getState()).isEqualTo(STATE);
|
||||
assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters);
|
||||
assertThat(authentication.isConsentRequired()).isFalse();
|
||||
assertThat(authentication.isConsent()).isFalse();
|
||||
assertThat(authentication.getAuthorizationCode()).isNull();
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationConsentRequiredThenValuesAreSet() {
|
||||
String clientId = REGISTERED_CLIENT.getClientId();
|
||||
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
|
||||
.authorizationUri(AUTHORIZATION_URI)
|
||||
.scopes(authorizedScopes)
|
||||
.state(STATE)
|
||||
.consentRequired(true)
|
||||
.build();
|
||||
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
|
||||
assertThat(authentication.getCredentials()).isEqualTo("");
|
||||
assertThat(authentication.getAuthorities()).isEmpty();
|
||||
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
|
||||
assertThat(authentication.getClientId()).isEqualTo(clientId);
|
||||
assertThat(authentication.getRedirectUri()).isNull();
|
||||
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
|
||||
assertThat(authentication.getState()).isEqualTo(STATE);
|
||||
assertThat(authentication.getAdditionalParameters()).isEmpty();
|
||||
assertThat(authentication.isConsentRequired()).isTrue();
|
||||
assertThat(authentication.isConsent()).isFalse();
|
||||
assertThat(authentication.getAuthorizationCode()).isNull();
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationConsentRequestThenValuesAreSet() {
|
||||
String clientId = REGISTERED_CLIENT.getClientId();
|
||||
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
|
||||
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
|
||||
.authorizationUri(AUTHORIZATION_URI)
|
||||
.scopes(authorizedScopes)
|
||||
.state(STATE)
|
||||
.additionalParameters(additionalParameters)
|
||||
.consent(true)
|
||||
.build();
|
||||
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
|
||||
assertThat(authentication.getCredentials()).isEqualTo("");
|
||||
assertThat(authentication.getAuthorities()).isEmpty();
|
||||
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
|
||||
assertThat(authentication.getClientId()).isEqualTo(clientId);
|
||||
assertThat(authentication.getRedirectUri()).isNull();
|
||||
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
|
||||
assertThat(authentication.getState()).isEqualTo(STATE);
|
||||
assertThat(authentication.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(additionalParameters);
|
||||
assertThat(authentication.isConsentRequired()).isFalse();
|
||||
assertThat(authentication.isConsent()).isTrue();
|
||||
assertThat(authentication.getAuthorizationCode()).isNull();
|
||||
assertThat(authentication.isAuthenticated()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAuthorizationResponseThenValuesAreSet() {
|
||||
public void constructorWhenAuthorizationResponseThenValuesAreSet() {
|
||||
String clientId = REGISTERED_CLIENT.getClientId();
|
||||
String redirectUri = REGISTERED_CLIENT.getRedirectUris().iterator().next();
|
||||
String state = "state";
|
||||
Set<String> authorizedScopes = REGISTERED_CLIENT.getScopes();
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(clientId, PRINCIPAL)
|
||||
.authorizationUri(AUTHORIZATION_URI)
|
||||
.redirectUri(redirectUri)
|
||||
.scopes(authorizedScopes)
|
||||
.state(STATE)
|
||||
.authorizationCode(AUTHORIZATION_CODE)
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, clientId, PRINCIPAL, AUTHORIZATION_CODE, redirectUri, state, authorizedScopes);
|
||||
|
||||
assertThat(authentication.getPrincipal()).isEqualTo(PRINCIPAL);
|
||||
assertThat(authentication.getCredentials()).isEqualTo("");
|
||||
@@ -188,11 +119,9 @@ public class OAuth2AuthorizationCodeRequestAuthenticationTokenTests {
|
||||
assertThat(authentication.getAuthorizationUri()).isEqualTo(AUTHORIZATION_URI);
|
||||
assertThat(authentication.getClientId()).isEqualTo(clientId);
|
||||
assertThat(authentication.getRedirectUri()).isEqualTo(redirectUri);
|
||||
assertThat(authentication.getState()).isEqualTo(state);
|
||||
assertThat(authentication.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
|
||||
assertThat(authentication.getState()).isEqualTo(STATE);
|
||||
assertThat(authentication.getAdditionalParameters()).isEmpty();
|
||||
assertThat(authentication.isConsentRequired()).isFalse();
|
||||
assertThat(authentication.isConsent()).isFalse();
|
||||
assertThat(authentication.getAuthorizationCode()).isEqualTo(AUTHORIZATION_CODE);
|
||||
assertThat(authentication.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2020-2021 the original author or authors.
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -42,10 +42,10 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
|
||||
private final Authentication principal = this.authorization.getAttribute(Principal.class.getName());
|
||||
private final OAuth2AuthorizationRequest authorizationRequest = this.authorization.getAttribute(
|
||||
OAuth2AuthorizationRequest.class.getName());
|
||||
private final OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(this.registeredClient.getClientId(), this.principal)
|
||||
.authorizationUri(this.authorizationRequest.getAuthorizationUri())
|
||||
.build();
|
||||
private final OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
this.authorizationRequest.getAuthorizationUri(), this.registeredClient.getClientId(),
|
||||
this.principal, "state", null, null);
|
||||
private final OAuth2AuthorizationConsent.Builder authorizationConsentBuilder =
|
||||
OAuth2AuthorizationConsent.withId(this.authorization.getRegisteredClientId(), this.authorization.getPrincipalName());
|
||||
|
||||
@@ -59,7 +59,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
|
||||
@Test
|
||||
public void setWhenValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2AuthorizationConsentAuthenticationContext.Builder builder =
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication);
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication);
|
||||
|
||||
assertThatThrownBy(() -> builder.authorizationConsent(null))
|
||||
.isInstanceOf(IllegalArgumentException.class);
|
||||
@@ -76,7 +76,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
|
||||
@Test
|
||||
public void buildWhenRequiredValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2AuthorizationConsentAuthenticationContext.Builder builder =
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication);
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication);
|
||||
|
||||
assertThatThrownBy(builder::build)
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
@@ -104,7 +104,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
|
||||
@Test
|
||||
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
|
||||
OAuth2AuthorizationConsentAuthenticationContext context =
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationCodeRequestAuthentication)
|
||||
OAuth2AuthorizationConsentAuthenticationContext.with(this.authorizationConsentAuthentication)
|
||||
.authorizationConsent(this.authorizationConsentBuilder)
|
||||
.registeredClient(this.registeredClient)
|
||||
.authorization(this.authorization)
|
||||
@@ -113,7 +113,7 @@ public class OAuth2AuthorizationConsentAuthenticationContextTests {
|
||||
.context(ctx -> ctx.put("custom-key-2", "custom-value-2"))
|
||||
.build();
|
||||
|
||||
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.authorizationCodeRequestAuthentication);
|
||||
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.authorizationConsentAuthentication);
|
||||
assertThat(context.getAuthorizationConsent()).isEqualTo(this.authorizationConsentBuilder);
|
||||
assertThat(context.getRegisteredClient()).isEqualTo(this.registeredClient);
|
||||
assertThat(context.getAuthorization()).isEqualTo(this.authorization);
|
||||
|
||||
@@ -0,0 +1,548 @@
|
||||
/*
|
||||
* Copyright 2020-2022 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.springframework.security.oauth2.server.authorization.authentication;
|
||||
|
||||
import java.security.Principal;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType;
|
||||
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder;
|
||||
import org.springframework.security.oauth2.server.authorization.context.TestAuthorizationServerContext;
|
||||
import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
/**
|
||||
* Tests for {@link OAuth2AuthorizationConsentAuthenticationProvider}.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2AuthorizationConsentAuthenticationProviderTests {
|
||||
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
|
||||
private static final String STATE = "state";
|
||||
private static final OAuth2TokenType STATE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.STATE);
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
private OAuth2AuthorizationConsentService authorizationConsentService;
|
||||
private OAuth2AuthorizationConsentAuthenticationProvider authenticationProvider;
|
||||
private TestingAuthenticationToken principal;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.authorizationConsentService = mock(OAuth2AuthorizationConsentService.class);
|
||||
this.authenticationProvider = new OAuth2AuthorizationConsentAuthenticationProvider(
|
||||
this.registeredClientRepository, this.authorizationService, this.authorizationConsentService);
|
||||
this.principal = new TestingAuthenticationToken("principalName", "password");
|
||||
this.principal.setAuthenticated(true);
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().issuer("https://provider.com").build();
|
||||
AuthorizationServerContextHolder.setContext(new TestAuthorizationServerContext(authorizationServerSettings, null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(
|
||||
null, this.authorizationService, this.authorizationConsentService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClientRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(
|
||||
this.registeredClientRepository, null, this.authorizationConsentService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationConsentServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new OAuth2AuthorizationConsentAuthenticationProvider(
|
||||
this.registeredClientRepository, this.authorizationService, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void supportsWhenTypeOAuth2AuthorizationConsentAuthenticationTokenThenReturnTrue() {
|
||||
assertThat(this.authenticationProvider.supports(OAuth2AuthorizationConsentAuthenticationToken.class)).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationCodeGeneratorWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationCodeGenerator(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationCodeGenerator cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setAuthorizationConsentCustomizerWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentCustomizer(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentCustomizer cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidStateThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, registeredClient.getScopes(), null);
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, registeredClient.getScopes(), null);
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
this.principal.setAuthenticated(false);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidPrincipalThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName().concat("-other"))
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, registeredClient.getScopes(), null);
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.STATE, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenInvalidClientIdThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, otherRegisteredClient.getClientId(), principal,
|
||||
STATE, registeredClient.getScopes(), null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenDoesNotMatchClientThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
RegisteredClient otherRegisteredClient = TestRegisteredClients.registeredClient2()
|
||||
.build();
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(otherRegisteredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq("state"), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, registeredClient.getScopes(), null);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.CLIENT_ID, null)
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenScopeNotRequestedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = new HashSet<>(authorizationRequest.getScopes());
|
||||
authorizedScopes.add("scope-not-requested");
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, authorizedScopes, null);
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.INVALID_SCOPE, OAuth2ParameterNames.SCOPE, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenNotApprovedThenThrowOAuth2AuthorizationCodeRequestAuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, new HashSet<>(), null); // No scopes approved
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
|
||||
verify(this.authorizationService).remove(eq(authorization));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenApproveAllThenReturnAuthorizationCode() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, authorizedScopes, null); // Approve all scopes
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthorizationConsentCustomizerThenUsed() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, authorizedScopes, null); // Approve all scopes
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<OAuth2AuthorizationConsentAuthenticationContext> authorizationConsentCustomizer = mock(Consumer.class);
|
||||
this.authenticationProvider.setAuthorizationConsentCustomizer(authorizationConsentCustomizer);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
assertAuthorizationConsentRequestWithAuthorizationCodeResult(registeredClient, authorization, authenticationResult);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsentAuthenticationContext> authenticationContextCaptor =
|
||||
ArgumentCaptor.forClass(OAuth2AuthorizationConsentAuthenticationContext.class);
|
||||
verify(authorizationConsentCustomizer).accept(authenticationContextCaptor.capture());
|
||||
|
||||
OAuth2AuthorizationConsentAuthenticationContext authenticationContext = authenticationContextCaptor.getValue();
|
||||
assertThat(authenticationContext.<Authentication>getAuthentication()).isEqualTo(authentication);
|
||||
assertThat(authenticationContext.getAuthorizationConsent()).isNotNull();
|
||||
assertThat(authenticationContext.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationContext.getAuthorization()).isEqualTo(authorization);
|
||||
assertThat(authenticationContext.getAuthorizationRequest()).isEqualTo(authorizationRequest);
|
||||
}
|
||||
|
||||
private void assertAuthorizationConsentRequestWithAuthorizationCodeResult(
|
||||
RegisteredClient registeredClient,
|
||||
OAuth2Authorization authorization,
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult) {
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> authorizedScopes = authorizationRequest.getScopes();
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent authorizationConsent = authorizationConsentCaptor.getValue();
|
||||
|
||||
assertThat(authorizationConsent.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
||||
assertThat(authorizationConsent.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
||||
assertThat(authorizationConsent.getAuthorities()).hasSize(authorizedScopes.size());
|
||||
assertThat(authorizationConsent.getScopes()).containsExactlyInAnyOrderElementsOf(authorizedScopes);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
|
||||
assertThat(updatedAuthorization.getRegisteredClientId()).isEqualTo(authorization.getRegisteredClientId());
|
||||
assertThat(updatedAuthorization.getPrincipalName()).isEqualTo(authorization.getPrincipalName());
|
||||
assertThat(updatedAuthorization.getAuthorizationGrantType()).isEqualTo(authorization.getAuthorizationGrantType());
|
||||
assertThat(updatedAuthorization.<Authentication>getAttribute(Principal.class.getName()))
|
||||
.isEqualTo(authorization.<Authentication>getAttribute(Principal.class.getName()));
|
||||
assertThat(updatedAuthorization.<OAuth2AuthorizationRequest>getAttribute(OAuth2AuthorizationRequest.class.getName()))
|
||||
.isEqualTo(authorizationRequest);
|
||||
OAuth2Authorization.Token<OAuth2AuthorizationCode> authorizationCode = updatedAuthorization.getToken(OAuth2AuthorizationCode.class);
|
||||
assertThat(authorizationCode).isNotNull();
|
||||
assertThat(updatedAuthorization.<String>getAttribute(OAuth2ParameterNames.STATE)).isNull();
|
||||
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(authorizedScopes);
|
||||
|
||||
assertThat(authenticationResult.getClientId()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(this.principal);
|
||||
assertThat(authenticationResult.getAuthorizationUri()).isEqualTo(authorizationRequest.getAuthorizationUri());
|
||||
assertThat(authenticationResult.getRedirectUri()).isEqualTo(authorizationRequest.getRedirectUri());
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(authorizedScopes);
|
||||
assertThat(authenticationResult.getState()).isEqualTo(authorizationRequest.getState());
|
||||
assertThat(authenticationResult.getAuthorizationCode()).isEqualTo(authorizationCode.getToken());
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenApproveNoneAndRevokePreviouslyApprovedThenAuthorizationConsentRemoved() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, new HashSet<>(), null); // No scopes approved
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
// Revoke all (including previously approved)
|
||||
this.authenticationProvider.setAuthorizationConsentCustomizer((authorizationConsentContext) ->
|
||||
authorizationConsentContext.getAuthorizationConsent().authorities(Set::clear));
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthorizationCodeRequestAuthenticationException.class)
|
||||
.satisfies(ex ->
|
||||
assertAuthenticationException((OAuth2AuthorizationCodeRequestAuthenticationException) ex,
|
||||
OAuth2ErrorCodes.ACCESS_DENIED, OAuth2ParameterNames.CLIENT_ID, authorizationRequest.getRedirectUri())
|
||||
);
|
||||
|
||||
verify(this.authorizationConsentService).remove(eq(previousAuthorizationConsent));
|
||||
verify(this.authorizationService).remove(eq(authorization));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenApproveSomeAndPreviouslyApprovedThenAuthorizationConsentUpdated() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
String otherPreviouslyApprovedScope = "other.scope";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(OAuth2AuthorizationRequest.class.getName());
|
||||
Set<String> requestedScopes = authorizationRequest.getScopes();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, requestedScopes, null);
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.scope(otherPreviouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
ArgumentCaptor<OAuth2AuthorizationConsent> authorizationConsentCaptor = ArgumentCaptor.forClass(OAuth2AuthorizationConsent.class);
|
||||
verify(this.authorizationConsentService).save(authorizationConsentCaptor.capture());
|
||||
OAuth2AuthorizationConsent updatedAuthorizationConsent = authorizationConsentCaptor.getValue();
|
||||
|
||||
assertThat(updatedAuthorizationConsent.getRegisteredClientId()).isEqualTo(previousAuthorizationConsent.getRegisteredClientId());
|
||||
assertThat(updatedAuthorizationConsent.getPrincipalName()).isEqualTo(previousAuthorizationConsent.getPrincipalName());
|
||||
assertThat(updatedAuthorizationConsent.getScopes()).containsExactlyInAnyOrder(
|
||||
previouslyApprovedScope, otherPreviouslyApprovedScope, requestedScope);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||
assertThat(updatedAuthorization.getAuthorizedScopes()).isEqualTo(requestedScopes);
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(requestedScopes);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenApproveNoneAndPreviouslyApprovedThenAuthorizationConsentNotUpdated() {
|
||||
String previouslyApprovedScope = "message.read";
|
||||
String requestedScope = "message.write";
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.scopes(scopes -> {
|
||||
scopes.clear();
|
||||
scopes.add(previouslyApprovedScope);
|
||||
scopes.add(requestedScope);
|
||||
})
|
||||
.build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.principalName(this.principal.getName())
|
||||
.build();
|
||||
OAuth2AuthorizationConsentAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, new HashSet<>(), null); // No scopes approved
|
||||
when(this.authorizationService.findByToken(eq(authentication.getState()), eq(STATE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
OAuth2AuthorizationConsent previousAuthorizationConsent =
|
||||
OAuth2AuthorizationConsent.withId(authorization.getRegisteredClientId(), authorization.getPrincipalName())
|
||||
.scope(previouslyApprovedScope)
|
||||
.build();
|
||||
when(this.authorizationConsentService.findById(eq(authorization.getRegisteredClientId()), eq(authorization.getPrincipalName())))
|
||||
.thenReturn(previousAuthorizationConsent);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationConsentService, never()).save(any());
|
||||
assertThat(authenticationResult.getScopes()).isEqualTo(Collections.singleton(previouslyApprovedScope));
|
||||
}
|
||||
|
||||
private static void assertAuthenticationException(OAuth2AuthorizationCodeRequestAuthenticationException authenticationException,
|
||||
String errorCode, String parameterName, String redirectUri) {
|
||||
|
||||
OAuth2Error error = authenticationException.getError();
|
||||
assertThat(error.getErrorCode()).isEqualTo(errorCode);
|
||||
assertThat(error.getDescription()).contains(parameterName);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authenticationException.getAuthorizationCodeRequestAuthentication();
|
||||
assertThat(authorizationCodeRequestAuthentication.getRedirectUri()).isEqualTo(redirectUri);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -90,6 +90,8 @@ import org.springframework.security.oauth2.server.authorization.TestOAuth2Author
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationContext;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationProvider;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
|
||||
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
@@ -108,6 +110,7 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer;
|
||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeRequestAuthenticationConverter;
|
||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationConsentAuthenticationConverter;
|
||||
import org.springframework.security.web.SecurityFilterChain;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
|
||||
@@ -626,13 +629,9 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
OAuth2AuthorizationCode authorizationCode = new OAuth2AuthorizationCode(
|
||||
"code", Instant.now(), Instant.now().plus(5, ChronoUnit.MINUTES));
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.redirectUri(registeredClient.getRedirectUris().iterator().next())
|
||||
.scopes(registeredClient.getScopes())
|
||||
.state("state")
|
||||
.authorizationCode(authorizationCode)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
"https://provider.com/oauth2/authorize", registeredClient.getClientId(), principal, authorizationCode,
|
||||
registeredClient.getRedirectUris().iterator().next(), "state", registeredClient.getScopes());
|
||||
when(authorizationRequestConverter.convert(any())).thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
when(authorizationRequestAuthenticationProvider.supports(eq(OAuth2AuthorizationCodeRequestAuthenticationToken.class))).thenReturn(true);
|
||||
when(authorizationRequestAuthenticationProvider.authenticate(any())).thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
@@ -650,7 +649,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
List<AuthenticationConverter> authenticationConverters = authenticationConvertersCaptor.getValue();
|
||||
assertThat(authenticationConverters).allMatch((converter) ->
|
||||
converter == authorizationRequestConverter ||
|
||||
converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter);
|
||||
converter instanceof OAuth2AuthorizationCodeRequestAuthenticationConverter ||
|
||||
converter instanceof OAuth2AuthorizationConsentAuthenticationConverter);
|
||||
|
||||
verify(authorizationRequestAuthenticationProvider).authenticate(eq(authorizationCodeRequestAuthenticationResult));
|
||||
|
||||
@@ -660,7 +660,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
List<AuthenticationProvider> authenticationProviders = authenticationProvidersCaptor.getValue();
|
||||
assertThat(authenticationProviders).allMatch((provider) ->
|
||||
provider == authorizationRequestAuthenticationProvider ||
|
||||
provider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider);
|
||||
provider instanceof OAuth2AuthorizationCodeRequestAuthenticationProvider ||
|
||||
provider instanceof OAuth2AuthorizationConsentAuthenticationProvider);
|
||||
|
||||
verify(authorizationResponseHandler).onAuthenticationSuccess(any(), any(), eq(authorizationCodeRequestAuthenticationResult));
|
||||
}
|
||||
@@ -934,7 +935,7 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
new OAuth2AuthorizationServerConfigurer();
|
||||
authorizationServerConfigurer
|
||||
.authorizationEndpoint(authorizationEndpoint ->
|
||||
authorizationEndpoint.authenticationProvider(createProvider()));
|
||||
authorizationEndpoint.authenticationProviders(configureAuthenticationProviders()));
|
||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher();
|
||||
|
||||
http
|
||||
@@ -966,15 +967,14 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
};
|
||||
}
|
||||
|
||||
private AuthenticationProvider createProvider() {
|
||||
OAuth2AuthorizationCodeRequestAuthenticationProvider authorizationCodeRequestAuthenticationProvider =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationProvider(
|
||||
this.registeredClientRepository,
|
||||
this.authorizationService,
|
||||
this.authorizationConsentService);
|
||||
authorizationCodeRequestAuthenticationProvider.setAuthorizationConsentCustomizer(new AuthorizationConsentCustomizer());
|
||||
|
||||
return authorizationCodeRequestAuthenticationProvider;
|
||||
private Consumer<List<AuthenticationProvider>> configureAuthenticationProviders() {
|
||||
return (authenticationProviders) ->
|
||||
authenticationProviders.forEach((authenticationProvider) -> {
|
||||
if (authenticationProvider instanceof OAuth2AuthorizationConsentAuthenticationProvider) {
|
||||
((OAuth2AuthorizationConsentAuthenticationProvider) authenticationProvider)
|
||||
.setAuthorizationConsentCustomizer(new AuthorizationConsentCustomizer());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
static class AuthorizationConsentCustomizer implements Consumer<OAuth2AuthorizationConsentAuthenticationContext> {
|
||||
@@ -983,10 +983,10 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
public void accept(OAuth2AuthorizationConsentAuthenticationContext authorizationConsentAuthenticationContext) {
|
||||
OAuth2AuthorizationConsent.Builder authorizationConsentBuilder =
|
||||
authorizationConsentAuthenticationContext.getAuthorizationConsent();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthentication =
|
||||
authorizationConsentAuthenticationContext.getAuthentication();
|
||||
Map<String, Object> additionalParameters =
|
||||
authorizationCodeRequestAuthentication.getAdditionalParameters();
|
||||
authorizationConsentAuthentication.getAdditionalParameters();
|
||||
RegisteredClient registeredClient = authorizationConsentAuthenticationContext.getRegisteredClient();
|
||||
ClientSettings clientSettings = registeredClient.getClientSettings();
|
||||
|
||||
|
||||
@@ -40,7 +40,6 @@ import org.springframework.mock.web.MockHttpServletResponse;
|
||||
import org.springframework.security.authentication.AuthenticationDetailsSource;
|
||||
import org.springframework.security.authentication.AuthenticationManager;
|
||||
import org.springframework.security.authentication.TestingAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||
@@ -52,6 +51,7 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationException;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeRequestAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationConsentAuthenticationToken;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||
import org.springframework.security.web.authentication.AuthenticationConverter;
|
||||
@@ -82,6 +82,8 @@ import static org.mockito.Mockito.when;
|
||||
*/
|
||||
public class OAuth2AuthorizationEndpointFilterTests {
|
||||
private static final String DEFAULT_AUTHORIZATION_ENDPOINT_URI = "/oauth2/authorize";
|
||||
private static final String AUTHORIZATION_URI = "https://provider.com/oauth2/authorize";
|
||||
private static final String STATE = "state";
|
||||
private static final String REMOTE_ADDRESS = "remote-address";
|
||||
private AuthenticationManager authenticationManager;
|
||||
private OAuth2AuthorizationEndpointFilter filter;
|
||||
@@ -280,8 +282,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenAuthorizationRequestAuthenticationExceptionThenErrorResponse() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri");
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenThrow(new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthentication));
|
||||
@@ -304,8 +307,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenCustomAuthenticationConverterThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
|
||||
AuthenticationConverter authenticationConverter = mock(AuthenticationConverter.class);
|
||||
when(authenticationConverter.convert(any())).thenReturn(authorizationCodeRequestAuthentication);
|
||||
@@ -329,9 +333,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenCustomAuthenticationSuccessHandlerThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.authorizationCode(this.authorizationCode)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal, this.authorizationCode,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
@@ -354,8 +358,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenCustomAuthenticationFailureHandlerThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
OAuth2Error error = new OAuth2Error("errorCode", "errorDescription", "errorUri");
|
||||
OAuth2AuthorizationCodeRequestAuthenticationException authenticationException =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationException(error, authorizationCodeRequestAuthentication);
|
||||
@@ -380,7 +385,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthentication =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal).build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
|
||||
AuthenticationDetailsSource<HttpServletRequest, WebAuthenticationDetails> authenticationDetailsSource =
|
||||
@@ -407,8 +414,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
this.principal.setAuthenticated(false);
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes(), null);
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(false);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
@@ -432,14 +440,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
scopes.addAll(requestedScopes);
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(new HashSet<>()) // No scopes previously approved
|
||||
.consentRequired(true)
|
||||
.build();
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, new HashSet<>(), null); // No scopes previously approved
|
||||
authorizationConsentAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
.thenReturn(authorizationConsentAuthenticationResult);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -464,14 +471,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
scopes.addAll(requestedScopes);
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(new HashSet<>()) // No scopes previously approved
|
||||
.consentRequired(true)
|
||||
.build();
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, new HashSet<>(), null); // No scopes previously approved
|
||||
authorizationConsentAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
.thenReturn(authorizationConsentAuthenticationResult);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -500,14 +506,13 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
scopes.addAll(requestedScopes);
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.scopes(approvedScopes)
|
||||
.consentRequired(true)
|
||||
.build();
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
OAuth2AuthorizationConsentAuthenticationToken authorizationConsentAuthenticationResult =
|
||||
new OAuth2AuthorizationConsentAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
STATE, approvedScopes, null);
|
||||
authorizationConsentAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
.thenReturn(authorizationConsentAuthenticationResult);
|
||||
|
||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
@@ -532,9 +537,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
public void doFilterWhenAuthorizationRequestAuthenticatedThenAuthorizationResponse() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.authorizationCode(this.authorizationCode)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal, this.authorizationCode,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
@@ -568,9 +573,9 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
})
|
||||
.build();
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authorizationCodeRequestAuthenticationResult =
|
||||
authorizationCodeRequestAuthentication(registeredClient, this.principal)
|
||||
.authorizationCode(this.authorizationCode)
|
||||
.build();
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal, this.authorizationCode,
|
||||
registeredClient.getRedirectUris().iterator().next(), STATE, registeredClient.getScopes());
|
||||
authorizationCodeRequestAuthenticationResult.setAuthenticated(true);
|
||||
when(this.authenticationManager.authenticate(any()))
|
||||
.thenReturn(authorizationCodeRequestAuthenticationResult);
|
||||
@@ -647,15 +652,6 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
||||
return request;
|
||||
}
|
||||
|
||||
private static OAuth2AuthorizationCodeRequestAuthenticationToken.Builder authorizationCodeRequestAuthentication(
|
||||
RegisteredClient registeredClient, Authentication principal) {
|
||||
return OAuth2AuthorizationCodeRequestAuthenticationToken.with(registeredClient.getClientId(), principal)
|
||||
.authorizationUri("https://provider.com/oauth2/authorize")
|
||||
.redirectUri(registeredClient.getRedirectUris().iterator().next())
|
||||
.scopes(registeredClient.getScopes())
|
||||
.state("state");
|
||||
}
|
||||
|
||||
private static String scopeCheckbox(String scope) {
|
||||
return MessageFormat.format(
|
||||
"<input class=\"form-check-input\" type=\"checkbox\" name=\"scope\" value=\"{0}\" id=\"{0}\">",
|
||||
|
||||
Reference in New Issue
Block a user