Apply Spring formatting to 1.3.x
Issue gh-1616
This commit is contained in:
@@ -38,10 +38,16 @@ import org.springframework.util.Assert;
|
||||
* @author Joe Grandja
|
||||
* @see OAuth2AuthorizationServerMetadataClaimAccessor
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2. Authorization Server Metadata Response</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2. OpenID Provider Configuration Response</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-3.2">3.2.
|
||||
* Authorization Server Metadata Response</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderConfigurationResponse">4.2.
|
||||
* OpenID Provider Configuration Response</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4.
|
||||
* Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client
|
||||
* Certificate-Bound Access Tokens Metadata</a>
|
||||
*/
|
||||
public abstract class AbstractOAuth2AuthorizationServerMetadata
|
||||
implements OAuth2AuthorizationServerMetadataClaimAccessor, Serializable {
|
||||
@@ -358,14 +364,16 @@ public abstract class AbstractOAuth2AuthorizationServerMetadata
|
||||
}
|
||||
|
||||
/**
|
||||
* Use this {@code tls_client_certificate_bound_access_tokens} in the resulting {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
*
|
||||
* @param tlsClientCertificateBoundAccessTokens {@code true} to indicate support for mutual-TLS client certificate-bound access tokens
|
||||
* Use this {@code tls_client_certificate_bound_access_tokens} in the resulting
|
||||
* {@link AbstractOAuth2AuthorizationServerMetadata}, OPTIONAL.
|
||||
* @param tlsClientCertificateBoundAccessTokens {@code true} to indicate support
|
||||
* for mutual-TLS client certificate-bound access tokens
|
||||
* @return the {@link AbstractBuilder} for further configuration
|
||||
* @since 1.3
|
||||
*/
|
||||
public B tlsClientCertificateBoundAccessTokens(boolean tlsClientCertificateBoundAccessTokens) {
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS, tlsClientCertificateBoundAccessTokens);
|
||||
return claim(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS,
|
||||
tlsClientCertificateBoundAccessTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -30,10 +30,16 @@ import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||
* @since 0.1.1
|
||||
* @see ClaimAccessor
|
||||
* @see OAuth2AuthorizationServerMetadataClaimNames
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2.
|
||||
* Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID
|
||||
* Provider Metadata</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4.
|
||||
* Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client
|
||||
* Certificate-Bound Access Tokens Metadata</a>
|
||||
*/
|
||||
public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAccessor {
|
||||
|
||||
@@ -176,13 +182,15 @@ public interface OAuth2AuthorizationServerMetadataClaimAccessor extends ClaimAcc
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} to indicate support for mutual-TLS client certificate-bound access tokens {@code (tls_client_certificate_bound_access_tokens)}.
|
||||
*
|
||||
* @return {@code true} to indicate support for mutual-TLS client certificate-bound access tokens, {@code false} otherwise
|
||||
* Returns {@code true} to indicate support for mutual-TLS client certificate-bound
|
||||
* access tokens {@code (tls_client_certificate_bound_access_tokens)}.
|
||||
* @return {@code true} to indicate support for mutual-TLS client certificate-bound
|
||||
* access tokens, {@code false} otherwise
|
||||
* @since 1.3
|
||||
*/
|
||||
default boolean isTlsClientCertificateBoundAccessTokens() {
|
||||
return Boolean.TRUE.equals(getClaimAsBoolean(OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS));
|
||||
return Boolean.TRUE.equals(getClaimAsBoolean(
|
||||
OAuth2AuthorizationServerMetadataClaimNames.TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -22,10 +22,16 @@ package org.springframework.security.oauth2.server.authorization;
|
||||
* @author Daniel Garnier-Moiroux
|
||||
* @author Joe Grandja
|
||||
* @since 0.1.1
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2. Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href="https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID Provider Metadata</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4. Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client Certificate-Bound Access Tokens Metadata</a>
|
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc8414#section-2">2.
|
||||
* Authorization Server Metadata</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://openid.net/specs/openid-connect-discovery-1_0.html#ProviderMetadata">3. OpenID
|
||||
* Provider Metadata</a>
|
||||
* @see <a target="_blank" href="https://www.rfc-editor.org/rfc/rfc8628.html#section-4">4.
|
||||
* Device Authorization Grant Metadata</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc8705#section-3.3">3.3 Mutual-TLS Client
|
||||
* Certificate-Bound Access Tokens Metadata</a>
|
||||
*/
|
||||
public class OAuth2AuthorizationServerMetadataClaimNames {
|
||||
|
||||
@@ -118,7 +124,8 @@ public class OAuth2AuthorizationServerMetadataClaimNames {
|
||||
public static final String CODE_CHALLENGE_METHODS_SUPPORTED = "code_challenge_methods_supported";
|
||||
|
||||
/**
|
||||
* {@code tls_client_certificate_bound_access_tokens} - {@code true} to indicate support for mutual-TLS client certificate-bound access tokens
|
||||
* {@code tls_client_certificate_bound_access_tokens} - {@code true} to indicate
|
||||
* support for mutual-TLS client certificate-bound access tokens
|
||||
* @since 1.3
|
||||
*/
|
||||
public static final String TLS_CLIENT_CERTIFICATE_BOUND_ACCESS_TOKENS = "tls_client_certificate_bound_access_tokens";
|
||||
|
||||
@@ -150,14 +150,14 @@ class OAuth2AuthorizationServerBeanRegistrationAotProcessor implements BeanRegis
|
||||
loadClass("org.springframework.security.jackson2.UserMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||
loadClass("org.springframework.security.jackson2.SimpleGrantedAuthorityMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.OAuth2ActorAuthenticationTokenMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.OAuth2CompositeAuthenticationTokenMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(),
|
||||
loadClass("org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2ActorAuthenticationTokenMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2AuthorizationRequestMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2CompositeAuthenticationTokenMixin"));
|
||||
this.reflectionHintsRegistrar.registerReflectionHints(hints.reflection(), loadClass(
|
||||
"org.springframework.security.oauth2.server.authorization.jackson2.OAuth2TokenFormatMixin"));
|
||||
|
||||
// Check if Spring Security OAuth2 Client is on classpath
|
||||
if (ClassUtils.isPresent("org.springframework.security.oauth2.client.registration.ClientRegistration",
|
||||
|
||||
@@ -26,8 +26,9 @@ import org.springframework.security.oauth2.server.authorization.web.authenticati
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2AccessTokenAuthenticationToken} and additional information
|
||||
* and is used when customizing the {@link OAuth2AccessTokenResponse}.
|
||||
* An {@link OAuth2AuthenticationContext} that holds an
|
||||
* {@link OAuth2AccessTokenAuthenticationToken} and additional information and is used
|
||||
* when customizing the {@link OAuth2AccessTokenResponse}.
|
||||
*
|
||||
* @author Dmitriy Dubson
|
||||
* @since 1.3
|
||||
@@ -37,6 +38,7 @@ import org.springframework.util.Assert;
|
||||
* @see OAuth2AccessTokenResponseAuthenticationSuccessHandler#setAccessTokenResponseCustomizer(Consumer)
|
||||
*/
|
||||
public final class OAuth2AccessTokenAuthenticationContext implements OAuth2AuthenticationContext {
|
||||
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
private OAuth2AccessTokenAuthenticationContext(Map<Object, Object> context) {
|
||||
@@ -57,8 +59,8 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AccessTokenResponse.Builder access token response builder}.
|
||||
*
|
||||
* Returns the {@link OAuth2AccessTokenResponse.Builder access token response
|
||||
* builder}.
|
||||
* @return the {@link OAuth2AccessTokenResponse.Builder}
|
||||
*/
|
||||
public OAuth2AccessTokenResponse.Builder getAccessTokenResponse() {
|
||||
@@ -66,8 +68,8 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2AccessTokenAuthenticationToken}.
|
||||
*
|
||||
* Constructs a new {@link Builder} with the provided
|
||||
* {@link OAuth2AccessTokenAuthenticationToken}.
|
||||
* @param authentication the {@link OAuth2AccessTokenAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -85,8 +87,8 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AccessTokenResponse.Builder access token response builder}.
|
||||
*
|
||||
* Sets the {@link OAuth2AccessTokenResponse.Builder access token response
|
||||
* builder}.
|
||||
* @param accessTokenResponse the {@link OAuth2AccessTokenResponse.Builder}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
@@ -96,7 +98,6 @@ public final class OAuth2AccessTokenAuthenticationContext implements OAuth2Authe
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2AccessTokenAuthenticationContext}.
|
||||
*
|
||||
* @return the {@link OAuth2AccessTokenAuthenticationContext}
|
||||
*/
|
||||
public OAuth2AccessTokenAuthenticationContext build() {
|
||||
|
||||
@@ -81,11 +81,11 @@ final class OAuth2AuthenticationProviderUtils {
|
||||
static <T extends OAuth2Token> OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token,
|
||||
OAuth2TokenContext accessTokenContext) {
|
||||
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
token.getTokenValue(), token.getIssuedAt(), token.getExpiresAt(),
|
||||
accessTokenContext.getAuthorizedScopes());
|
||||
OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient().getTokenSettings()
|
||||
.getAccessTokenFormat();
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token.getTokenValue(),
|
||||
token.getIssuedAt(), token.getExpiresAt(), accessTokenContext.getAuthorizedScopes());
|
||||
OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient()
|
||||
.getTokenSettings()
|
||||
.getAccessTokenFormat();
|
||||
builder.token(accessToken, (metadata) -> {
|
||||
if (token instanceof ClaimAccessor claimAccessor) {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims());
|
||||
|
||||
@@ -157,11 +157,12 @@ public final class OAuth2AuthorizationCodeAuthenticationProvider implements Auth
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
if (StringUtils.hasText(authorizationRequest.getRedirectUri()) &&
|
||||
!authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
|
||||
if (StringUtils.hasText(authorizationRequest.getRedirectUri())
|
||||
&& !authorizationRequest.getRedirectUri().equals(authorizationCodeAuthentication.getRedirectUri())) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: redirect_uri does not match" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: redirect_uri does not match" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
@@ -28,8 +28,10 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and additional information
|
||||
* and is used when validating the OAuth 2.0 Authorization Request parameters, as well as, determining if authorization consent is required.
|
||||
* An {@link OAuth2AuthenticationContext} that holds an
|
||||
* {@link OAuth2AuthorizationCodeRequestAuthenticationToken} and additional information
|
||||
* and is used when validating the OAuth 2.0 Authorization Request parameters, as well as,
|
||||
* determining if authorization consent is required.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 0.4.0
|
||||
@@ -69,7 +71,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationRequest authorization request}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationRequest}
|
||||
* @since 1.3
|
||||
*/
|
||||
@@ -80,7 +81,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement
|
||||
|
||||
/**
|
||||
* Returns the {@link OAuth2AuthorizationConsent authorization consent}.
|
||||
*
|
||||
* @return the {@link OAuth2AuthorizationConsent}
|
||||
* @since 1.3
|
||||
*/
|
||||
@@ -89,10 +89,9 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement
|
||||
return get(OAuth2AuthorizationConsent.class);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
*
|
||||
* Constructs a new {@link Builder} with the provided
|
||||
* {@link OAuth2AuthorizationCodeRequestAuthenticationToken}.
|
||||
* @param authentication the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -121,7 +120,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizationRequest authorization request}.
|
||||
*
|
||||
* @param authorizationRequest the {@link OAuth2AuthorizationRequest}
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 1.3
|
||||
@@ -132,7 +130,6 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationContext implement
|
||||
|
||||
/**
|
||||
* Sets the {@link OAuth2AuthorizationConsent authorization consent}.
|
||||
*
|
||||
* @param authorizationConsent the {@link OAuth2AuthorizationConsent}
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 1.3
|
||||
|
||||
@@ -90,10 +90,9 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
|
||||
private OAuth2TokenGenerator<OAuth2AuthorizationCode> authorizationCodeGenerator = new OAuth2AuthorizationCodeGenerator();
|
||||
|
||||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationValidator();
|
||||
private Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationProvider::isAuthorizationConsentRequired;
|
||||
private Consumer<OAuth2AuthorizationCodeRequestAuthenticationContext> authenticationValidator = new OAuth2AuthorizationCodeRequestAuthenticationValidator();
|
||||
|
||||
private Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired = OAuth2AuthorizationCodeRequestAuthenticationProvider::isAuthorizationConsentRequired;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2AuthorizationCodeRequestAuthenticationProvider} using
|
||||
@@ -128,15 +127,16 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
this.logger.trace("Retrieved registered client");
|
||||
}
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder =
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext.with(authorizationCodeRequestAuthentication)
|
||||
.registeredClient(registeredClient);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext.Builder authenticationContextBuilder = OAuth2AuthorizationCodeRequestAuthenticationContext
|
||||
.with(authorizationCodeRequestAuthentication)
|
||||
.registeredClient(registeredClient);
|
||||
this.authenticationValidator.accept(authenticationContextBuilder.build());
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: requested grant_type is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: requested grant_type is not allowed" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID,
|
||||
authorizationCodeRequestAuthentication, registeredClient);
|
||||
@@ -176,17 +176,17 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
}
|
||||
|
||||
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
|
||||
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri())
|
||||
.clientId(registeredClient.getClientId())
|
||||
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri())
|
||||
.scopes(authorizationCodeRequestAuthentication.getScopes())
|
||||
.state(authorizationCodeRequestAuthentication.getState())
|
||||
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters())
|
||||
.build();
|
||||
.authorizationUri(authorizationCodeRequestAuthentication.getAuthorizationUri())
|
||||
.clientId(registeredClient.getClientId())
|
||||
.redirectUri(authorizationCodeRequestAuthentication.getRedirectUri())
|
||||
.scopes(authorizationCodeRequestAuthentication.getScopes())
|
||||
.state(authorizationCodeRequestAuthentication.getState())
|
||||
.additionalParameters(authorizationCodeRequestAuthentication.getAdditionalParameters())
|
||||
.build();
|
||||
authenticationContextBuilder.authorizationRequest(authorizationRequest);
|
||||
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService.findById(
|
||||
registeredClient.getId(), principal.getName());
|
||||
OAuth2AuthorizationConsent currentAuthorizationConsent = this.authorizationConsentService
|
||||
.findById(registeredClient.getId(), principal.getName());
|
||||
if (currentAuthorizationConsent != null) {
|
||||
authenticationContextBuilder.authorizationConsent(currentAuthorizationConsent);
|
||||
}
|
||||
@@ -295,42 +295,48 @@ public final class OAuth2AuthorizationCodeRequestAuthenticationProvider implemen
|
||||
* Sets the {@code Predicate} used to determine if authorization consent is required.
|
||||
*
|
||||
* <p>
|
||||
* The {@link OAuth2AuthorizationCodeRequestAuthenticationContext} gives the predicate access to the {@link OAuth2AuthorizationCodeRequestAuthenticationToken},
|
||||
* as well as, the following context attributes:
|
||||
* The {@link OAuth2AuthorizationCodeRequestAuthenticationContext} gives the predicate
|
||||
* access to the {@link OAuth2AuthorizationCodeRequestAuthenticationToken}, as well
|
||||
* as, the following context attributes:
|
||||
* <ul>
|
||||
* <li>The {@link RegisteredClient} associated with the authorization request.</li>
|
||||
* <li>The {@link OAuth2AuthorizationRequest} containing the authorization request parameters.</li>
|
||||
* <li>The {@link OAuth2AuthorizationConsent} previously granted to the {@link RegisteredClient}, or {@code null} if not available.</li>
|
||||
* <li>The {@link OAuth2AuthorizationRequest} containing the authorization request
|
||||
* parameters.</li>
|
||||
* <li>The {@link OAuth2AuthorizationConsent} previously granted to the
|
||||
* {@link RegisteredClient}, or {@code null} if not available.</li>
|
||||
* </ul>
|
||||
*
|
||||
* @param authorizationConsentRequired the {@code Predicate} used to determine if authorization consent is required
|
||||
* @param authorizationConsentRequired the {@code Predicate} used to determine if
|
||||
* authorization consent is required
|
||||
* @since 1.3
|
||||
*/
|
||||
public void setAuthorizationConsentRequired(Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired) {
|
||||
public void setAuthorizationConsentRequired(
|
||||
Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired) {
|
||||
Assert.notNull(authorizationConsentRequired, "authorizationConsentRequired cannot be null");
|
||||
this.authorizationConsentRequired = authorizationConsentRequired;
|
||||
}
|
||||
|
||||
private static boolean isAuthorizationConsentRequired(OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
|
||||
private static boolean isAuthorizationConsentRequired(
|
||||
OAuth2AuthorizationCodeRequestAuthenticationContext authenticationContext) {
|
||||
if (!authenticationContext.getRegisteredClient().getClientSettings().isRequireAuthorizationConsent()) {
|
||||
return false;
|
||||
}
|
||||
// 'openid' scope does not require consent
|
||||
if (authenticationContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID) &&
|
||||
authenticationContext.getAuthorizationRequest().getScopes().size() == 1) {
|
||||
if (authenticationContext.getAuthorizationRequest().getScopes().contains(OidcScopes.OPENID)
|
||||
&& authenticationContext.getAuthorizationRequest().getScopes().size() == 1) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (authenticationContext.getAuthorizationConsent() != null &&
|
||||
authenticationContext.getAuthorizationConsent().getScopes().containsAll(authenticationContext.getAuthorizationRequest().getScopes())) {
|
||||
if (authenticationContext.getAuthorizationConsent() != null && authenticationContext.getAuthorizationConsent()
|
||||
.getScopes()
|
||||
.containsAll(authenticationContext.getAuthorizationRequest().getScopes())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient, Authentication principal,
|
||||
OAuth2AuthorizationRequest authorizationRequest) {
|
||||
private static OAuth2Authorization.Builder authorizationBuilder(RegisteredClient registeredClient,
|
||||
Authentication principal, OAuth2AuthorizationRequest authorizationRequest) {
|
||||
return OAuth2Authorization.withRegisteredClient(registeredClient)
|
||||
.principalName(principal.getName())
|
||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||
|
||||
@@ -25,8 +25,9 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2ClientAuthenticationToken} and additional information
|
||||
* and is used when validating an OAuth 2.0 Client Authentication.
|
||||
* An {@link OAuth2AuthenticationContext} that holds an
|
||||
* {@link OAuth2ClientAuthenticationToken} and additional information and is used when
|
||||
* validating an OAuth 2.0 Client Authentication.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
@@ -35,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
* @see X509ClientCertificateAuthenticationProvider#setCertificateVerifier(Consumer)
|
||||
*/
|
||||
public final class OAuth2ClientAuthenticationContext implements OAuth2AuthenticationContext {
|
||||
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
private OAuth2ClientAuthenticationContext(Map<Object, Object> context) {
|
||||
@@ -56,7 +58,6 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica
|
||||
|
||||
/**
|
||||
* Returns the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @return the {@link RegisteredClient}
|
||||
*/
|
||||
public RegisteredClient getRegisteredClient() {
|
||||
@@ -64,8 +65,8 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2ClientAuthenticationToken}.
|
||||
*
|
||||
* Constructs a new {@link Builder} with the provided
|
||||
* {@link OAuth2ClientAuthenticationToken}.
|
||||
* @param authentication the {@link OAuth2ClientAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -84,7 +85,6 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica
|
||||
|
||||
/**
|
||||
* Sets the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @param registeredClient the {@link RegisteredClient}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
@@ -94,7 +94,6 @@ public final class OAuth2ClientAuthenticationContext implements OAuth2Authentica
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2ClientAuthenticationContext}.
|
||||
*
|
||||
* @return the {@link OAuth2ClientAuthenticationContext}
|
||||
*/
|
||||
public OAuth2ClientAuthenticationContext build() {
|
||||
|
||||
@@ -25,8 +25,9 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link OAuth2AuthenticationContext} that holds an {@link OAuth2ClientCredentialsAuthenticationToken} and additional information
|
||||
* and is used when validating the OAuth 2.0 Client Credentials Grant Request.
|
||||
* An {@link OAuth2AuthenticationContext} that holds an
|
||||
* {@link OAuth2ClientCredentialsAuthenticationToken} and additional information and is
|
||||
* used when validating the OAuth 2.0 Client Credentials Grant Request.
|
||||
*
|
||||
* @author Adam Pilling
|
||||
* @since 1.3
|
||||
@@ -35,6 +36,7 @@ import org.springframework.util.Assert;
|
||||
* @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer)
|
||||
*/
|
||||
public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth2AuthenticationContext {
|
||||
|
||||
private final Map<Object, Object> context;
|
||||
|
||||
private OAuth2ClientCredentialsAuthenticationContext(Map<Object, Object> context) {
|
||||
@@ -56,7 +58,6 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth
|
||||
|
||||
/**
|
||||
* Returns the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @return the {@link RegisteredClient}
|
||||
*/
|
||||
public RegisteredClient getRegisteredClient() {
|
||||
@@ -64,8 +65,8 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth
|
||||
}
|
||||
|
||||
/**
|
||||
* Constructs a new {@link Builder} with the provided {@link OAuth2ClientCredentialsAuthenticationToken}.
|
||||
*
|
||||
* Constructs a new {@link Builder} with the provided
|
||||
* {@link OAuth2ClientCredentialsAuthenticationToken}.
|
||||
* @param authentication the {@link OAuth2ClientCredentialsAuthenticationToken}
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
@@ -84,7 +85,6 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth
|
||||
|
||||
/**
|
||||
* Sets the {@link RegisteredClient registered client}.
|
||||
*
|
||||
* @param registeredClient the {@link RegisteredClient}
|
||||
* @return the {@link Builder} for further configuration
|
||||
*/
|
||||
@@ -94,7 +94,6 @@ public final class OAuth2ClientCredentialsAuthenticationContext implements OAuth
|
||||
|
||||
/**
|
||||
* Builds a new {@link OAuth2ClientCredentialsAuthenticationContext}.
|
||||
*
|
||||
* @return the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
*/
|
||||
public OAuth2ClientCredentialsAuthenticationContext build() {
|
||||
|
||||
@@ -71,8 +71,8 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
private final OAuth2AuthorizationService authorizationService;
|
||||
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
private Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator =
|
||||
new OAuth2ClientCredentialsAuthenticationValidator();
|
||||
|
||||
private Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator = new OAuth2ClientCredentialsAuthenticationValidator();
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2ClientCredentialsAuthenticationProvider} using the
|
||||
@@ -103,16 +103,17 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.CLIENT_CREDENTIALS)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: requested grant_type is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: requested grant_type is not allowed" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
|
||||
OAuth2ClientCredentialsAuthenticationContext authenticationContext =
|
||||
OAuth2ClientCredentialsAuthenticationContext.with(clientCredentialsAuthentication)
|
||||
.registeredClient(registeredClient)
|
||||
.build();
|
||||
OAuth2ClientCredentialsAuthenticationContext authenticationContext = OAuth2ClientCredentialsAuthenticationContext
|
||||
.with(clientCredentialsAuthentication)
|
||||
.registeredClient(registeredClient)
|
||||
.build();
|
||||
this.authenticationValidator.accept(authenticationContext);
|
||||
|
||||
Set<String> authorizedScopes = new LinkedHashSet<>(clientCredentialsAuthentication.getScopes());
|
||||
@@ -173,18 +174,22 @@ public final class OAuth2ClientCredentialsAuthenticationProvider implements Auth
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
* and is responsible for validating specific OAuth 2.0 Client Credentials Grant Request parameters
|
||||
* associated in the {@link OAuth2ClientCredentialsAuthenticationToken}.
|
||||
* The default authentication validator is {@link OAuth2ClientCredentialsAuthenticationValidator}.
|
||||
* Sets the {@code Consumer} providing access to the
|
||||
* {@link OAuth2ClientCredentialsAuthenticationContext} and is responsible for
|
||||
* validating specific OAuth 2.0 Client Credentials Grant Request parameters
|
||||
* associated in the {@link OAuth2ClientCredentialsAuthenticationToken}. The default
|
||||
* authentication validator is {@link OAuth2ClientCredentialsAuthenticationValidator}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> The authentication validator MUST throw {@link OAuth2AuthenticationException} if validation fails.
|
||||
*
|
||||
* @param authenticationValidator the {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext} and is responsible for validating specific OAuth 2.0 Client Credentials Grant Request parameters
|
||||
* <b>NOTE:</b> The authentication validator MUST throw
|
||||
* {@link OAuth2AuthenticationException} if validation fails.
|
||||
* @param authenticationValidator the {@code Consumer} providing access to the
|
||||
* {@link OAuth2ClientCredentialsAuthenticationContext} and is responsible for
|
||||
* validating specific OAuth 2.0 Client Credentials Grant Request parameters
|
||||
* @since 1.3
|
||||
*/
|
||||
public void setAuthenticationValidator(Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator) {
|
||||
public void setAuthenticationValidator(
|
||||
Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator) {
|
||||
Assert.notNull(authenticationValidator, "authenticationValidator cannot be null");
|
||||
this.authenticationValidator = authenticationValidator;
|
||||
}
|
||||
|
||||
@@ -27,14 +27,17 @@ import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||
|
||||
/**
|
||||
* A {@code Consumer} providing access to the {@link OAuth2ClientCredentialsAuthenticationContext}
|
||||
* containing an {@link OAuth2ClientCredentialsAuthenticationToken}
|
||||
* and is the default {@link OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer) authentication validator}
|
||||
* used for validating specific OAuth 2.0 Client Credentials Grant Request parameters.
|
||||
* A {@code Consumer} providing access to the
|
||||
* {@link OAuth2ClientCredentialsAuthenticationContext} containing an
|
||||
* {@link OAuth2ClientCredentialsAuthenticationToken} and is the default
|
||||
* {@link OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer)
|
||||
* authentication validator} used for validating specific OAuth 2.0 Client Credentials
|
||||
* Grant Request parameters.
|
||||
*
|
||||
* <p>
|
||||
* The default implementation validates {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}.
|
||||
* If validation fails, an {@link OAuth2AuthenticationException} is thrown.
|
||||
* The default implementation validates
|
||||
* {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}. If validation fails, an
|
||||
* {@link OAuth2AuthenticationException} is thrown.
|
||||
*
|
||||
* @author Adam Pilling
|
||||
* @since 1.3
|
||||
@@ -42,14 +45,16 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
||||
* @see OAuth2ClientCredentialsAuthenticationToken
|
||||
* @see OAuth2ClientCredentialsAuthenticationProvider#setAuthenticationValidator(Consumer)
|
||||
*/
|
||||
public final class OAuth2ClientCredentialsAuthenticationValidator implements Consumer<OAuth2ClientCredentialsAuthenticationContext> {
|
||||
public final class OAuth2ClientCredentialsAuthenticationValidator
|
||||
implements Consumer<OAuth2ClientCredentialsAuthenticationContext> {
|
||||
|
||||
private static final Log LOGGER = LogFactory.getLog(OAuth2ClientCredentialsAuthenticationValidator.class);
|
||||
|
||||
/**
|
||||
* The default validator for {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}.
|
||||
* The default validator for
|
||||
* {@link OAuth2ClientCredentialsAuthenticationToken#getScopes()}.
|
||||
*/
|
||||
public static final Consumer<OAuth2ClientCredentialsAuthenticationContext> DEFAULT_SCOPE_VALIDATOR =
|
||||
OAuth2ClientCredentialsAuthenticationValidator::validateScope;
|
||||
public static final Consumer<OAuth2ClientCredentialsAuthenticationContext> DEFAULT_SCOPE_VALIDATOR = OAuth2ClientCredentialsAuthenticationValidator::validateScope;
|
||||
|
||||
private final Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator = DEFAULT_SCOPE_VALIDATOR;
|
||||
|
||||
@@ -59,16 +64,17 @@ public final class OAuth2ClientCredentialsAuthenticationValidator implements Con
|
||||
}
|
||||
|
||||
private static void validateScope(OAuth2ClientCredentialsAuthenticationContext authenticationContext) {
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||
authenticationContext.getAuthentication();
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = authenticationContext
|
||||
.getAuthentication();
|
||||
RegisteredClient registeredClient = authenticationContext.getRegisteredClient();
|
||||
|
||||
Set<String> requestedScopes = clientCredentialsAuthentication.getScopes();
|
||||
Set<String> allowedScopes = registeredClient.getScopes();
|
||||
if (!requestedScopes.isEmpty() && !allowedScopes.containsAll(requestedScopes)) {
|
||||
if (LOGGER.isDebugEnabled()) {
|
||||
LOGGER.debug(LogMessage.format("Invalid request: requested scope is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
LOGGER.debug(LogMessage.format(
|
||||
"Invalid request: requested scope is not allowed" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE);
|
||||
}
|
||||
|
||||
@@ -108,8 +108,9 @@ public final class OAuth2DeviceAuthorizationRequestAuthenticationProvider implem
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.DEVICE_CODE)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: requested grant_type is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: requested grant_type is not allowed" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throwError(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, OAuth2ParameterNames.CLIENT_ID);
|
||||
}
|
||||
|
||||
@@ -127,8 +127,9 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
|
||||
|
||||
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN)) {
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: requested grant_type is not allowed" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: requested grant_type is not allowed" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
@@ -137,10 +138,12 @@ public final class OAuth2RefreshTokenAuthenticationProvider implements Authentic
|
||||
if (!refreshToken.isActive()) {
|
||||
// As per https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
// invalid_grant: The provided authorization grant (e.g., authorization code,
|
||||
// resource owner credentials) or refresh token is invalid, expired, revoked [...].
|
||||
// resource owner credentials) or refresh token is invalid, expired, revoked
|
||||
// [...].
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(LogMessage.format("Invalid request: refresh_token is not active" +
|
||||
" for registered client '%s'", registeredClient.getId()));
|
||||
this.logger.debug(LogMessage.format(
|
||||
"Invalid request: refresh_token is not active" + " for registered client '%s'",
|
||||
registeredClient.getId()));
|
||||
}
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
@@ -25,8 +25,9 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A {@link ClaimAccessor} used for the OAuth 2.0 Token Exchange Grant to represent an actor in a
|
||||
* {@link OAuth2TokenExchangeCompositeAuthenticationToken} (e.g. the "delegation" use case).
|
||||
* A {@link ClaimAccessor} used for the OAuth 2.0 Token Exchange Grant to represent an
|
||||
* actor in a {@link OAuth2TokenExchangeCompositeAuthenticationToken} (e.g. the
|
||||
* "delegation" use case).
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
|
||||
@@ -55,7 +55,8 @@ import org.springframework.util.StringUtils;
|
||||
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Token Exchange Grant.
|
||||
* An {@link AuthenticationProvider} implementation for the OAuth 2.0 Token Exchange
|
||||
* Grant.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
@@ -63,8 +64,10 @@ import static org.springframework.security.oauth2.server.authorization.authentic
|
||||
* @see OAuth2AccessTokenAuthenticationToken
|
||||
* @see OAuth2AuthorizationService
|
||||
* @see OAuth2TokenGenerator
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc8693#section-1">Section 1 Introduction</a>
|
||||
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc8693#section-2.1">Section 2.1 Request</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc8693#section-1">Section 1 Introduction</a>
|
||||
* @see <a target="_blank" href=
|
||||
* "https://datatracker.ietf.org/doc/html/rfc8693#section-2.1">Section 2.1 Request</a>
|
||||
*/
|
||||
public final class OAuth2TokenExchangeAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
@@ -83,8 +86,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2TokenExchangeAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code OAuth2TokenExchangeAuthenticationProvider} using the provided
|
||||
* parameters.
|
||||
* @param authorizationService the authorization service
|
||||
* @param tokenGenerator the token generator
|
||||
*/
|
||||
@@ -98,11 +101,10 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication =
|
||||
(OAuth2TokenExchangeAuthenticationToken) authentication;
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthentication = (OAuth2TokenExchangeAuthenticationToken) authentication;
|
||||
|
||||
OAuth2ClientAuthenticationToken clientPrincipal =
|
||||
getAuthenticatedClientElseThrowInvalidClient(tokenExchangeAuthentication);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(
|
||||
tokenExchangeAuthentication);
|
||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient();
|
||||
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
@@ -113,13 +115,14 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||
}
|
||||
|
||||
if (JWT_TOKEN_TYPE_VALUE.equals(tokenExchangeAuthentication.getRequestedTokenType()) &&
|
||||
!OAuth2TokenFormat.SELF_CONTAINED.equals(registeredClient.getTokenSettings().getAccessTokenFormat())) {
|
||||
if (JWT_TOKEN_TYPE_VALUE.equals(tokenExchangeAuthentication.getRequestedTokenType())
|
||||
&& !OAuth2TokenFormat.SELF_CONTAINED
|
||||
.equals(registeredClient.getTokenSettings().getAccessTokenFormat())) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
OAuth2Authorization subjectAuthorization = this.authorizationService.findByToken(
|
||||
tokenExchangeAuthentication.getSubjectToken(), OAuth2TokenType.ACCESS_TOKEN);
|
||||
OAuth2Authorization subjectAuthorization = this.authorizationService
|
||||
.findByToken(tokenExchangeAuthentication.getSubjectToken(), OAuth2TokenType.ACCESS_TOKEN);
|
||||
if (subjectAuthorization == null) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
@@ -128,12 +131,13 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
this.logger.trace("Retrieved authorization with subject token");
|
||||
}
|
||||
|
||||
OAuth2Authorization.Token<OAuth2Token> subjectToken = subjectAuthorization.getToken(
|
||||
tokenExchangeAuthentication.getSubjectToken());
|
||||
OAuth2Authorization.Token<OAuth2Token> subjectToken = subjectAuthorization
|
||||
.getToken(tokenExchangeAuthentication.getSubjectToken());
|
||||
if (!subjectToken.isActive()) {
|
||||
// As per https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
// invalid_grant: The provided authorization grant (e.g., authorization code,
|
||||
// resource owner credentials) or refresh token is invalid, expired, revoked [...].
|
||||
// resource owner credentials) or refresh token is invalid, expired, revoked
|
||||
// [...].
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
@@ -152,16 +156,15 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
// The may_act claim makes a statement that one party is authorized to
|
||||
// become the actor and act on behalf of another party.
|
||||
Map<String, Object> authorizedActorClaims = null;
|
||||
if (subjectToken.getClaims() != null &&
|
||||
subjectToken.getClaims().containsKey(MAY_ACT) &&
|
||||
subjectToken.getClaims().get(MAY_ACT) instanceof Map<?, ?> mayAct) {
|
||||
if (subjectToken.getClaims() != null && subjectToken.getClaims().containsKey(MAY_ACT)
|
||||
&& subjectToken.getClaims().get(MAY_ACT) instanceof Map<?, ?> mayAct) {
|
||||
authorizedActorClaims = (Map<String, Object>) mayAct;
|
||||
}
|
||||
|
||||
OAuth2Authorization actorAuthorization = null;
|
||||
if (StringUtils.hasText(tokenExchangeAuthentication.getActorToken())) {
|
||||
actorAuthorization = this.authorizationService.findByToken(
|
||||
tokenExchangeAuthentication.getActorToken(), OAuth2TokenType.ACCESS_TOKEN);
|
||||
actorAuthorization = this.authorizationService.findByToken(tokenExchangeAuthentication.getActorToken(),
|
||||
OAuth2TokenType.ACCESS_TOKEN);
|
||||
if (actorAuthorization == null) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
@@ -170,12 +173,14 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
this.logger.trace("Retrieved authorization with actor token");
|
||||
}
|
||||
|
||||
OAuth2Authorization.Token<OAuth2Token> actorToken = actorAuthorization.getToken(
|
||||
tokenExchangeAuthentication.getActorToken());
|
||||
OAuth2Authorization.Token<OAuth2Token> actorToken = actorAuthorization
|
||||
.getToken(tokenExchangeAuthentication.getActorToken());
|
||||
if (!actorToken.isActive()) {
|
||||
// As per https://tools.ietf.org/html/rfc6749#section-5.2
|
||||
// invalid_grant: The provided authorization grant (e.g., authorization code,
|
||||
// resource owner credentials) or refresh token is invalid, expired, revoked [...].
|
||||
// invalid_grant: The provided authorization grant (e.g., authorization
|
||||
// code,
|
||||
// resource owner credentials) or refresh token is invalid, expired,
|
||||
// revoked [...].
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
@@ -187,14 +192,16 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
validateClaims(authorizedActorClaims, actorToken.getClaims(), OAuth2TokenClaimNames.ISS,
|
||||
OAuth2TokenClaimNames.SUB);
|
||||
}
|
||||
} else if (authorizedActorClaims != null) {
|
||||
}
|
||||
else if (authorizedActorClaims != null) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
|
||||
Set<String> authorizedScopes = Collections.emptySet();
|
||||
if (!CollectionUtils.isEmpty(tokenExchangeAuthentication.getScopes())) {
|
||||
authorizedScopes = validateRequestedScopes(registeredClient, tokenExchangeAuthentication.getScopes());
|
||||
} else if (!CollectionUtils.isEmpty(subjectAuthorization.getAuthorizedScopes())) {
|
||||
}
|
||||
else if (!CollectionUtils.isEmpty(subjectAuthorization.getAuthorizedScopes())) {
|
||||
authorizedScopes = validateRequestedScopes(registeredClient, subjectAuthorization.getAuthorizedScopes());
|
||||
}
|
||||
|
||||
@@ -248,21 +255,21 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
additionalParameters.put(OAuth2ParameterNames.ISSUED_TOKEN_TYPE, tokenExchangeAuthentication.getRequestedTokenType());
|
||||
additionalParameters.put(OAuth2ParameterNames.ISSUED_TOKEN_TYPE,
|
||||
tokenExchangeAuthentication.getRequestedTokenType());
|
||||
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Authenticated token request");
|
||||
}
|
||||
|
||||
return new OAuth2AccessTokenAuthenticationToken(
|
||||
registeredClient, clientPrincipal, accessToken, null, additionalParameters);
|
||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, null,
|
||||
additionalParameters);
|
||||
}
|
||||
|
||||
private static boolean isValidTokenType(String tokenType, OAuth2Authorization.Token<OAuth2Token> token) {
|
||||
String tokenFormat = token.getMetadata(OAuth2TokenFormat.class.getName());
|
||||
return ACCESS_TOKEN_TYPE_VALUE.equals(tokenType) ||
|
||||
JWT_TOKEN_TYPE_VALUE.equals(tokenType) &&
|
||||
OAuth2TokenFormat.SELF_CONTAINED.getValue().equals(tokenFormat);
|
||||
return ACCESS_TOKEN_TYPE_VALUE.equals(tokenType) || JWT_TOKEN_TYPE_VALUE.equals(tokenType)
|
||||
&& OAuth2TokenFormat.SELF_CONTAINED.getValue().equals(tokenFormat);
|
||||
}
|
||||
|
||||
private static Set<String> validateRequestedScopes(RegisteredClient registeredClient, Set<String> requestedScopes) {
|
||||
@@ -275,7 +282,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
return new LinkedHashSet<>(requestedScopes);
|
||||
}
|
||||
|
||||
private static void validateClaims(Map<String, Object> expectedClaims, Map<String, Object> actualClaims, String... claimNames) {
|
||||
private static void validateClaims(Map<String, Object> expectedClaims, Map<String, Object> actualClaims,
|
||||
String... claimNames) {
|
||||
if (actualClaims == null) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_GRANT);
|
||||
}
|
||||
@@ -287,7 +295,8 @@ public final class OAuth2TokenExchangeAuthenticationProvider implements Authenti
|
||||
}
|
||||
}
|
||||
|
||||
private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization, OAuth2Authorization actorAuthorization) {
|
||||
private static Authentication getPrincipal(OAuth2Authorization subjectAuthorization,
|
||||
OAuth2Authorization actorAuthorization) {
|
||||
Authentication subjectPrincipal = subjectAuthorization.getAttribute(Principal.class.getName());
|
||||
if (actorAuthorization == null) {
|
||||
if (subjectPrincipal instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
|
||||
|
||||
@@ -53,8 +53,8 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
private final Set<String> scopes;
|
||||
|
||||
/**
|
||||
* Constructs an {@code OAuth2TokenExchangeAuthenticationToken} using the provided parameters.
|
||||
*
|
||||
* Constructs an {@code OAuth2TokenExchangeAuthenticationToken} using the provided
|
||||
* parameters.
|
||||
* @param requestedTokenType the requested token type
|
||||
* @param subjectToken the subject token
|
||||
* @param subjectTokenType the subject token type
|
||||
@@ -79,17 +79,15 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
this.subjectTokenType = subjectTokenType;
|
||||
this.actorToken = actorToken;
|
||||
this.actorTokenType = actorTokenType;
|
||||
this.resources = Collections.unmodifiableSet(
|
||||
resources != null ? new LinkedHashSet<>(resources) : Collections.emptySet());
|
||||
this.audiences = Collections.unmodifiableSet(
|
||||
audiences != null ? new LinkedHashSet<>(audiences) : Collections.emptySet());
|
||||
this.scopes = Collections.unmodifiableSet(
|
||||
scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
|
||||
this.resources = Collections
|
||||
.unmodifiableSet(resources != null ? new LinkedHashSet<>(resources) : Collections.emptySet());
|
||||
this.audiences = Collections
|
||||
.unmodifiableSet(audiences != null ? new LinkedHashSet<>(audiences) : Collections.emptySet());
|
||||
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the requested token type.
|
||||
*
|
||||
* @return the requested token type
|
||||
*/
|
||||
public String getRequestedTokenType() {
|
||||
@@ -98,7 +96,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the subject token.
|
||||
*
|
||||
* @return the subject token
|
||||
*/
|
||||
public String getSubjectToken() {
|
||||
@@ -107,7 +104,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the subject token type.
|
||||
*
|
||||
* @return the subject token type
|
||||
*/
|
||||
public String getSubjectTokenType() {
|
||||
@@ -116,7 +112,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the actor token.
|
||||
*
|
||||
* @return the actor token
|
||||
*/
|
||||
public String getActorToken() {
|
||||
@@ -125,7 +120,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the actor token type.
|
||||
*
|
||||
* @return the actor token type
|
||||
*/
|
||||
public String getActorTokenType() {
|
||||
@@ -134,7 +128,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the requested resource URI(s).
|
||||
*
|
||||
* @return the requested resource URI(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getResources() {
|
||||
@@ -143,7 +136,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the requested audience value(s).
|
||||
*
|
||||
* @return the requested audience value(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getAudiences() {
|
||||
@@ -152,7 +144,6 @@ public class OAuth2TokenExchangeAuthenticationToken extends OAuth2AuthorizationG
|
||||
|
||||
/**
|
||||
* Returns the requested scope(s).
|
||||
*
|
||||
* @return the requested scope(s), or an empty {@code Set} if not available
|
||||
*/
|
||||
public Set<String> getScopes() {
|
||||
|
||||
@@ -26,8 +26,8 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* An {@link Authentication} implementation used for the OAuth 2.0 Token Exchange Grant
|
||||
* to represent the principal in a composite token (e.g. the "delegation" use case).
|
||||
* An {@link Authentication} implementation used for the OAuth 2.0 Token Exchange Grant to
|
||||
* represent the principal in a composite token (e.g. the "delegation" use case).
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
@@ -39,7 +39,8 @@ public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAut
|
||||
|
||||
private final List<OAuth2TokenExchangeActor> actors;
|
||||
|
||||
public OAuth2TokenExchangeCompositeAuthenticationToken(Authentication subject, List<OAuth2TokenExchangeActor> actors) {
|
||||
public OAuth2TokenExchangeCompositeAuthenticationToken(Authentication subject,
|
||||
List<OAuth2TokenExchangeActor> actors) {
|
||||
super(subject != null ? subject.getAuthorities() : null);
|
||||
Assert.notNull(subject, "subject cannot be null");
|
||||
Assert.notNull(actors, "actors cannot be null");
|
||||
@@ -72,8 +73,8 @@ public class OAuth2TokenExchangeCompositeAuthenticationToken extends AbstractAut
|
||||
if (!(obj instanceof OAuth2TokenExchangeCompositeAuthenticationToken other)) {
|
||||
return false;
|
||||
}
|
||||
return super.equals(obj) && Objects.equals(this.subject, other.subject) &&
|
||||
Objects.equals(this.actors, other.actors);
|
||||
return super.equals(obj) && Objects.equals(this.subject, other.subject)
|
||||
&& Objects.equals(this.actors, other.actors);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -37,9 +37,10 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client Authentication,
|
||||
* which authenticates the client {@code X509Certificate} received
|
||||
* when the {@code tls_client_auth} or {@code self_signed_tls_client_auth} authentication method is used.
|
||||
* An {@link AuthenticationProvider} implementation used for OAuth 2.0 Client
|
||||
* Authentication, which authenticates the client {@code X509Certificate} received when
|
||||
* the {@code tls_client_auth} or {@code self_signed_tls_client_auth} authentication
|
||||
* method is used.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
@@ -49,17 +50,22 @@ import org.springframework.util.StringUtils;
|
||||
* @see OAuth2AuthorizationService
|
||||
*/
|
||||
public final class X509ClientCertificateAuthenticationProvider implements AuthenticationProvider {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
private final RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
private final CodeVerifierAuthenticator codeVerifierAuthenticator;
|
||||
private final Consumer<OAuth2ClientAuthenticationContext> selfSignedCertificateVerifier =
|
||||
new X509SelfSignedCertificateVerifier();
|
||||
|
||||
private final Consumer<OAuth2ClientAuthenticationContext> selfSignedCertificateVerifier = new X509SelfSignedCertificateVerifier();
|
||||
|
||||
private Consumer<OAuth2ClientAuthenticationContext> certificateVerifier = this::verifyX509Certificate;
|
||||
|
||||
/**
|
||||
* Constructs a {@code X509ClientCertificateAuthenticationProvider} using the provided parameters.
|
||||
*
|
||||
* Constructs a {@code X509ClientCertificateAuthenticationProvider} using the provided
|
||||
* parameters.
|
||||
* @param registeredClientRepository the repository of registered clients
|
||||
* @param authorizationService the authorization service
|
||||
*/
|
||||
@@ -73,11 +79,11 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
|
||||
@Override
|
||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException {
|
||||
OAuth2ClientAuthenticationToken clientAuthentication =
|
||||
(OAuth2ClientAuthenticationToken) authentication;
|
||||
OAuth2ClientAuthenticationToken clientAuthentication = (OAuth2ClientAuthenticationToken) authentication;
|
||||
|
||||
if (!ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) &&
|
||||
!ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())) {
|
||||
if (!ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())
|
||||
&& !ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH
|
||||
.equals(clientAuthentication.getClientAuthenticationMethod())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -91,8 +97,8 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
this.logger.trace("Retrieved registered client");
|
||||
}
|
||||
|
||||
if (!registeredClient.getClientAuthenticationMethods().contains(
|
||||
clientAuthentication.getClientAuthenticationMethod())) {
|
||||
if (!registeredClient.getClientAuthenticationMethods()
|
||||
.contains(clientAuthentication.getClientAuthenticationMethod())) {
|
||||
throwInvalidClient("authentication_method");
|
||||
}
|
||||
|
||||
@@ -100,17 +106,18 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
throwInvalidClient("credentials");
|
||||
}
|
||||
|
||||
OAuth2ClientAuthenticationContext authenticationContext =
|
||||
OAuth2ClientAuthenticationContext.with(clientAuthentication)
|
||||
.registeredClient(registeredClient)
|
||||
.build();
|
||||
OAuth2ClientAuthenticationContext authenticationContext = OAuth2ClientAuthenticationContext
|
||||
.with(clientAuthentication)
|
||||
.registeredClient(registeredClient)
|
||||
.build();
|
||||
this.certificateVerifier.accept(authenticationContext);
|
||||
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
this.logger.trace("Validated client authentication parameters");
|
||||
}
|
||||
|
||||
// Validate the "code_verifier" parameter for the confidential client, if available
|
||||
// Validate the "code_verifier" parameter for the confidential client, if
|
||||
// available
|
||||
this.codeVerifierAuthenticator.authenticateIfAvailable(clientAuthentication, registeredClient);
|
||||
|
||||
if (this.logger.isTraceEnabled()) {
|
||||
@@ -127,15 +134,20 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2ClientAuthenticationContext}
|
||||
* and is responsible for verifying the client {@code X509Certificate} associated in the {@link OAuth2ClientAuthenticationToken}.
|
||||
* The default implementation for the {@code tls_client_auth} authentication method
|
||||
* verifies the {@link ClientSettings#getX509CertificateSubjectDN() expected subject distinguished name}.
|
||||
* Sets the {@code Consumer} providing access to the
|
||||
* {@link OAuth2ClientAuthenticationContext} and is responsible for verifying the
|
||||
* client {@code X509Certificate} associated in the
|
||||
* {@link OAuth2ClientAuthenticationToken}. The default implementation for the
|
||||
* {@code tls_client_auth} authentication method verifies the
|
||||
* {@link ClientSettings#getX509CertificateSubjectDN() expected subject distinguished
|
||||
* name}.
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> If verification fails, an {@link OAuth2AuthenticationException} MUST be thrown.
|
||||
*
|
||||
* @param certificateVerifier the {@code Consumer} providing access to the {@link OAuth2ClientAuthenticationContext} and is responsible for verifying the client {@code X509Certificate}
|
||||
* <b>NOTE:</b> If verification fails, an {@link OAuth2AuthenticationException} MUST
|
||||
* be thrown.
|
||||
* @param certificateVerifier the {@code Consumer} providing access to the
|
||||
* {@link OAuth2ClientAuthenticationContext} and is responsible for verifying the
|
||||
* client {@code X509Certificate}
|
||||
*/
|
||||
public void setCertificateVerifier(Consumer<OAuth2ClientAuthenticationContext> certificateVerifier) {
|
||||
Assert.notNull(certificateVerifier, "certificateVerifier cannot be null");
|
||||
@@ -144,9 +156,11 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
|
||||
private void verifyX509Certificate(OAuth2ClientAuthenticationContext clientAuthenticationContext) {
|
||||
OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication();
|
||||
if (ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())) {
|
||||
if (ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH
|
||||
.equals(clientAuthentication.getClientAuthenticationMethod())) {
|
||||
this.selfSignedCertificateVerifier.accept(clientAuthenticationContext);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
verifyX509CertificateSubjectDN(clientAuthenticationContext);
|
||||
}
|
||||
}
|
||||
@@ -157,8 +171,8 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials();
|
||||
X509Certificate clientCertificate = clientCertificateChain[0];
|
||||
String expectedSubjectDN = registeredClient.getClientSettings().getX509CertificateSubjectDN();
|
||||
if (!StringUtils.hasText(expectedSubjectDN) ||
|
||||
!clientCertificate.getSubjectX500Principal().getName().equals(expectedSubjectDN)) {
|
||||
if (!StringUtils.hasText(expectedSubjectDN)
|
||||
|| !clientCertificate.getSubjectX500Principal().getName().equals(expectedSubjectDN)) {
|
||||
throwInvalidClient("x509_certificate_subject_dn");
|
||||
}
|
||||
}
|
||||
@@ -168,11 +182,8 @@ public final class X509ClientCertificateAuthenticationProvider implements Authen
|
||||
}
|
||||
|
||||
private static void throwInvalidClient(String parameterName, Throwable cause) {
|
||||
OAuth2Error error = new OAuth2Error(
|
||||
OAuth2ErrorCodes.INVALID_CLIENT,
|
||||
"Client authentication failed: " + parameterName,
|
||||
ERROR_URI
|
||||
);
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
|
||||
"Client authentication failed: " + parameterName, ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(error, error.toString(), cause);
|
||||
}
|
||||
|
||||
|
||||
@@ -52,15 +52,20 @@ import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* The default {@code X509Certificate} verifier for the {@code self_signed_tls_client_auth} authentication method.
|
||||
* The default {@code X509Certificate} verifier for the
|
||||
* {@code self_signed_tls_client_auth} authentication method.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
* @see X509ClientCertificateAuthenticationProvider#setCertificateVerifier(Consumer)
|
||||
*/
|
||||
final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAuthenticationContext> {
|
||||
|
||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";
|
||||
private static final JWKMatcher HAS_X509_CERT_CHAIN_MATCHER = new JWKMatcher.Builder().hasX509CertChain(true).build();
|
||||
|
||||
private static final JWKMatcher HAS_X509_CERT_CHAIN_MATCHER = new JWKMatcher.Builder().hasX509CertChain(true)
|
||||
.build();
|
||||
|
||||
private final Function<RegisteredClient, JWKSet> jwkSetSupplier = new JwkSetSupplier();
|
||||
|
||||
@Override
|
||||
@@ -98,17 +103,17 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
}
|
||||
|
||||
private static void throwInvalidClient(String parameterName, Throwable cause) {
|
||||
OAuth2Error error = new OAuth2Error(
|
||||
OAuth2ErrorCodes.INVALID_CLIENT,
|
||||
"Client authentication failed: " + parameterName,
|
||||
ERROR_URI
|
||||
);
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.INVALID_CLIENT,
|
||||
"Client authentication failed: " + parameterName, ERROR_URI);
|
||||
throw new OAuth2AuthenticationException(error, error.toString(), cause);
|
||||
}
|
||||
|
||||
private static class JwkSetSupplier implements Function<RegisteredClient, JWKSet> {
|
||||
|
||||
private static final MediaType APPLICATION_JWK_SET_JSON = new MediaType("application", "jwk-set+json");
|
||||
|
||||
private final RestOperations restOperations;
|
||||
|
||||
private final Map<String, Supplier<JWKSet>> jwkSets = new ConcurrentHashMap<>();
|
||||
|
||||
private JwkSetSupplier() {
|
||||
@@ -120,13 +125,12 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
|
||||
@Override
|
||||
public JWKSet apply(RegisteredClient registeredClient) {
|
||||
Supplier<JWKSet> jwkSetSupplier = this.jwkSets.computeIfAbsent(
|
||||
registeredClient.getId(), (key) -> {
|
||||
if (!StringUtils.hasText(registeredClient.getClientSettings().getJwkSetUrl())) {
|
||||
throwInvalidClient("client_jwk_set_url");
|
||||
}
|
||||
return new JwkSetHolder(registeredClient.getClientSettings().getJwkSetUrl());
|
||||
});
|
||||
Supplier<JWKSet> jwkSetSupplier = this.jwkSets.computeIfAbsent(registeredClient.getId(), (key) -> {
|
||||
if (!StringUtils.hasText(registeredClient.getClientSettings().getJwkSetUrl())) {
|
||||
throwInvalidClient("client_jwk_set_url");
|
||||
}
|
||||
return new JwkSetHolder(registeredClient.getClientSettings().getJwkSetUrl());
|
||||
});
|
||||
return jwkSetSupplier.get();
|
||||
}
|
||||
|
||||
@@ -134,7 +138,8 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
URI jwkSetUri = null;
|
||||
try {
|
||||
jwkSetUri = new URI(jwkSetUrl);
|
||||
} catch (URISyntaxException ex) {
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
throwInvalidClient("jwk_set_uri", ex);
|
||||
}
|
||||
|
||||
@@ -144,7 +149,8 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
ResponseEntity<String> response = null;
|
||||
try {
|
||||
response = this.restOperations.exchange(request, String.class);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throwInvalidClient("jwk_set_response_error", ex);
|
||||
}
|
||||
if (response.getStatusCode().value() != 200) {
|
||||
@@ -154,7 +160,8 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
JWKSet jwkSet = null;
|
||||
try {
|
||||
jwkSet = JWKSet.parse(response.getBody());
|
||||
} catch (ParseException ex) {
|
||||
}
|
||||
catch (ParseException ex) {
|
||||
throwInvalidClient("jwk_set_response_body", ex);
|
||||
}
|
||||
|
||||
@@ -162,10 +169,15 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
}
|
||||
|
||||
private class JwkSetHolder implements Supplier<JWKSet> {
|
||||
|
||||
private final ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
|
||||
|
||||
private final Clock clock = Clock.systemUTC();
|
||||
|
||||
private final String jwkSetUrl;
|
||||
|
||||
private JWKSet jwkSet;
|
||||
|
||||
private Instant lastUpdatedAt;
|
||||
|
||||
private JwkSetHolder(String jwkSetUrl) {
|
||||
@@ -184,22 +196,24 @@ final class X509SelfSignedCertificateVerifier implements Consumer<OAuth2ClientAu
|
||||
this.lastUpdatedAt = Instant.now();
|
||||
}
|
||||
this.rwLock.readLock().lock();
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
this.rwLock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
return this.jwkSet;
|
||||
} finally {
|
||||
}
|
||||
finally {
|
||||
this.rwLock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldRefresh() {
|
||||
// Refresh every 5 minutes
|
||||
return (this.jwkSet == null ||
|
||||
this.clock.instant().isAfter(this.lastUpdatedAt.plus(5, ChronoUnit.MINUTES)));
|
||||
return (this.jwkSet == null
|
||||
|| this.clock.instant().isAfter(this.lastUpdatedAt.plus(5, ChronoUnit.MINUTES)));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,6 +48,7 @@ import org.springframework.web.util.UriComponentsBuilder;
|
||||
final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
|
||||
private final AuthorizationServerSettings authorizationServerSettings;
|
||||
|
||||
private final IssuerResolver issuerResolver;
|
||||
|
||||
AuthorizationServerContextFilter(AuthorizationServerSettings authorizationServerSettings) {
|
||||
@@ -62,8 +63,8 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
|
||||
try {
|
||||
String issuer = this.issuerResolver.resolve(request);
|
||||
AuthorizationServerContext authorizationServerContext =
|
||||
new DefaultAuthorizationServerContext(issuer, this.authorizationServerSettings);
|
||||
AuthorizationServerContext authorizationServerContext = new DefaultAuthorizationServerContext(issuer,
|
||||
this.authorizationServerSettings);
|
||||
AuthorizationServerContextHolder.setContext(authorizationServerContext);
|
||||
filterChain.doFilter(request, response);
|
||||
}
|
||||
@@ -73,14 +74,17 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private static final class IssuerResolver {
|
||||
|
||||
private final String issuer;
|
||||
|
||||
private final Set<String> endpointUris;
|
||||
|
||||
private IssuerResolver(AuthorizationServerSettings authorizationServerSettings) {
|
||||
if (authorizationServerSettings.getIssuer() != null) {
|
||||
this.issuer = authorizationServerSettings.getIssuer();
|
||||
this.endpointUris = Collections.emptySet();
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
this.issuer = null;
|
||||
this.endpointUris = new HashSet<>();
|
||||
this.endpointUris.add("/.well-known/oauth-authorization-server");
|
||||
@@ -102,7 +106,8 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
String path = request.getRequestURI();
|
||||
if (!StringUtils.hasText(path)) {
|
||||
path = "";
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
for (String endpointUri : this.endpointUris) {
|
||||
if (path.contains(endpointUri)) {
|
||||
path = path.replace(endpointUri, "");
|
||||
@@ -124,10 +129,13 @@ final class AuthorizationServerContextFilter extends OncePerRequestFilter {
|
||||
}
|
||||
|
||||
private static final class DefaultAuthorizationServerContext implements AuthorizationServerContext {
|
||||
|
||||
private final String issuer;
|
||||
|
||||
private final AuthorizationServerSettings authorizationServerSettings;
|
||||
|
||||
private DefaultAuthorizationServerContext(String issuer, AuthorizationServerSettings authorizationServerSettings) {
|
||||
private DefaultAuthorizationServerContext(String issuer,
|
||||
AuthorizationServerSettings authorizationServerSettings) {
|
||||
this.issuer = issuer;
|
||||
this.authorizationServerSettings = authorizationServerSettings;
|
||||
}
|
||||
|
||||
@@ -54,13 +54,14 @@ final class DefaultOAuth2TokenCustomizers {
|
||||
|
||||
private static void customize(OAuth2TokenContext tokenContext, Map<String, Object> claims) {
|
||||
// Add 'cnf' claim for Mutual-TLS Client Certificate-Bound Access Tokens
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenContext.getTokenType()) &&
|
||||
tokenContext.getAuthorizationGrant() != null &&
|
||||
tokenContext.getAuthorizationGrant().getPrincipal() instanceof OAuth2ClientAuthenticationToken clientAuthentication) {
|
||||
if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenContext.getTokenType())
|
||||
&& tokenContext.getAuthorizationGrant() != null && tokenContext.getAuthorizationGrant()
|
||||
.getPrincipal() instanceof OAuth2ClientAuthenticationToken clientAuthentication) {
|
||||
|
||||
if ((ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod()) ||
|
||||
ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())) &&
|
||||
tokenContext.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) {
|
||||
if ((ClientAuthenticationMethod.TLS_CLIENT_AUTH.equals(clientAuthentication.getClientAuthenticationMethod())
|
||||
|| ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH
|
||||
.equals(clientAuthentication.getClientAuthenticationMethod()))
|
||||
&& tokenContext.getRegisteredClient().getTokenSettings().isX509CertificateBoundAccessTokens()) {
|
||||
|
||||
X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials();
|
||||
try {
|
||||
@@ -68,7 +69,8 @@ final class DefaultOAuth2TokenCustomizers {
|
||||
Map<String, Object> x5tClaim = new HashMap<>();
|
||||
x5tClaim.put("x5t#S256", sha256Thumbprint);
|
||||
claims.put("cnf", x5tClaim);
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"Failed to compute SHA-256 Thumbprint for client X509Certificate.", null);
|
||||
throw new OAuth2AuthenticationException(error, ex);
|
||||
@@ -77,8 +79,10 @@ final class DefaultOAuth2TokenCustomizers {
|
||||
}
|
||||
|
||||
// Add 'act' claim for delegation use case of Token Exchange Grant.
|
||||
// If more than one actor is present, we create a chain of delegation by nesting "act" claims.
|
||||
if (tokenContext.getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
|
||||
// If more than one actor is present, we create a chain of delegation by nesting
|
||||
// "act" claims.
|
||||
if (tokenContext
|
||||
.getPrincipal() instanceof OAuth2TokenExchangeCompositeAuthenticationToken compositeAuthenticationToken) {
|
||||
Map<String, Object> currentClaims = claims;
|
||||
for (OAuth2TokenExchangeActor actor : compositeAuthenticationToken.getActors()) {
|
||||
Map<String, Object> actorClaims = actor.getClaims();
|
||||
|
||||
@@ -236,10 +236,11 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) :
|
||||
authorizationServerSettings.getAuthorizationEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint())
|
||||
: authorizationServerSettings.getAuthorizationEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(authorizationEndpointUri, HttpMethod.POST.name()));
|
||||
@@ -256,12 +257,13 @@ public final class OAuth2AuthorizationEndpointConfigurer extends AbstractOAuth2C
|
||||
@Override
|
||||
void configure(HttpSecurity httpSecurity) {
|
||||
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint()) :
|
||||
authorizationServerSettings.getAuthorizationEndpoint();
|
||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter =
|
||||
new OAuth2AuthorizationEndpointFilter(authenticationManager, authorizationEndpointUri);
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getAuthorizationEndpoint())
|
||||
: authorizationServerSettings.getAuthorizationEndpoint();
|
||||
OAuth2AuthorizationEndpointFilter authorizationEndpointFilter = new OAuth2AuthorizationEndpointFilter(
|
||||
authenticationManager, authorizationEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.authorizationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.authorizationRequestConverters);
|
||||
|
||||
@@ -326,9 +326,9 @@ public final class OAuth2AuthorizationServerConfigurer
|
||||
configurer.init(httpSecurity);
|
||||
requestMatchers.add(configurer.getRequestMatcher());
|
||||
});
|
||||
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) :
|
||||
authorizationServerSettings.getJwkSetEndpoint();
|
||||
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint())
|
||||
: authorizationServerSettings.getJwkSetEndpoint();
|
||||
requestMatchers.add(new AntPathRequestMatcher(jwkSetEndpointUri, HttpMethod.GET.name()));
|
||||
this.endpointsMatcher = new OrRequestMatcher(requestMatchers);
|
||||
|
||||
@@ -356,12 +356,13 @@ public final class OAuth2AuthorizationServerConfigurer
|
||||
|
||||
JWKSource<com.nimbusds.jose.proc.SecurityContext> jwkSource = OAuth2ConfigurerUtils.getJwkSource(httpSecurity);
|
||||
if (jwkSource != null) {
|
||||
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint()) :
|
||||
authorizationServerSettings.getJwkSetEndpoint();
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter =
|
||||
new NimbusJwkSetEndpointFilter(jwkSource, jwkSetEndpointUri);
|
||||
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter), AbstractPreAuthenticatedProcessingFilter.class);
|
||||
String jwkSetEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getJwkSetEndpoint())
|
||||
: authorizationServerSettings.getJwkSetEndpoint();
|
||||
NimbusJwkSetEndpointFilter jwkSetEndpointFilter = new NimbusJwkSetEndpointFilter(jwkSource,
|
||||
jwkSetEndpointUri);
|
||||
httpSecurity.addFilterBefore(postProcess(jwkSetEndpointFilter),
|
||||
AbstractPreAuthenticatedProcessingFilter.class);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -74,10 +74,10 @@ public final class OAuth2AuthorizationServerMetadataEndpointConfigurer extends A
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationServerMetadataEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
"/.well-known/oauth-authorization-server/**" :
|
||||
"/.well-known/oauth-authorization-server";
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String authorizationServerMetadataEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? "/.well-known/oauth-authorization-server/**" : "/.well-known/oauth-authorization-server";
|
||||
this.requestMatcher = new AntPathRequestMatcher(authorizationServerMetadataEndpointUri, HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
|
||||
@@ -180,21 +180,21 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) :
|
||||
authorizationServerSettings.getTokenEndpoint();
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) :
|
||||
authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) :
|
||||
authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) :
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()),
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint())
|
||||
: authorizationServerSettings.getTokenEndpoint();
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint())
|
||||
: authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint())
|
||||
: authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
|
||||
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name()));
|
||||
@@ -257,12 +257,12 @@ public final class OAuth2ClientAuthenticationConfigurer extends AbstractOAuth2Co
|
||||
registeredClientRepository, authorizationService);
|
||||
authenticationProviders.add(jwtClientAssertionAuthenticationProvider);
|
||||
|
||||
X509ClientCertificateAuthenticationProvider x509ClientCertificateAuthenticationProvider =
|
||||
new X509ClientCertificateAuthenticationProvider(registeredClientRepository, authorizationService);
|
||||
X509ClientCertificateAuthenticationProvider x509ClientCertificateAuthenticationProvider = new X509ClientCertificateAuthenticationProvider(
|
||||
registeredClientRepository, authorizationService);
|
||||
authenticationProviders.add(x509ClientCertificateAuthenticationProvider);
|
||||
|
||||
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider =
|
||||
new ClientSecretAuthenticationProvider(registeredClientRepository, authorizationService);
|
||||
ClientSecretAuthenticationProvider clientSecretAuthenticationProvider = new ClientSecretAuthenticationProvider(
|
||||
registeredClientRepository, authorizationService);
|
||||
PasswordEncoder passwordEncoder = OAuth2ConfigurerUtils.getOptionalBean(httpSecurity, PasswordEncoder.class);
|
||||
if (passwordEncoder != null) {
|
||||
clientSecretAuthenticationProvider.setPasswordEncoder(passwordEncoder);
|
||||
|
||||
@@ -59,9 +59,7 @@ final class OAuth2ConfigurerUtils {
|
||||
|
||||
static String withMultipleIssuersPattern(String endpointUri) {
|
||||
Assert.hasText(endpointUri, "endpointUri cannot be empty");
|
||||
return endpointUri.startsWith("/") ?
|
||||
"/**" + endpointUri :
|
||||
"/**/" + endpointUri;
|
||||
return endpointUri.startsWith("/") ? "/**" + endpointUri : "/**/" + endpointUri;
|
||||
}
|
||||
|
||||
static RegisteredClientRepository getRegisteredClientRepository(HttpSecurity httpSecurity) {
|
||||
@@ -168,8 +166,10 @@ final class OAuth2ConfigurerUtils {
|
||||
}
|
||||
|
||||
private static OAuth2TokenCustomizer<JwtEncodingContext> getJwtCustomizer(HttpSecurity httpSecurity) {
|
||||
final OAuth2TokenCustomizer<JwtEncodingContext> defaultJwtCustomizer = DefaultOAuth2TokenCustomizers.jwtCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, JwtEncodingContext.class);
|
||||
final OAuth2TokenCustomizer<JwtEncodingContext> defaultJwtCustomizer = DefaultOAuth2TokenCustomizers
|
||||
.jwtCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class,
|
||||
JwtEncodingContext.class);
|
||||
final OAuth2TokenCustomizer<JwtEncodingContext> jwtCustomizer = getOptionalBean(httpSecurity, type);
|
||||
if (jwtCustomizer == null) {
|
||||
return defaultJwtCustomizer;
|
||||
@@ -181,8 +181,10 @@ final class OAuth2ConfigurerUtils {
|
||||
}
|
||||
|
||||
private static OAuth2TokenCustomizer<OAuth2TokenClaimsContext> getAccessTokenCustomizer(HttpSecurity httpSecurity) {
|
||||
final OAuth2TokenCustomizer<OAuth2TokenClaimsContext> defaultAccessTokenCustomizer = DefaultOAuth2TokenCustomizers.accessTokenCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class, OAuth2TokenClaimsContext.class);
|
||||
final OAuth2TokenCustomizer<OAuth2TokenClaimsContext> defaultAccessTokenCustomizer = DefaultOAuth2TokenCustomizers
|
||||
.accessTokenCustomizer();
|
||||
ResolvableType type = ResolvableType.forClassWithGenerics(OAuth2TokenCustomizer.class,
|
||||
OAuth2TokenClaimsContext.class);
|
||||
OAuth2TokenCustomizer<OAuth2TokenClaimsContext> accessTokenCustomizer = getOptionalBean(httpSecurity, type);
|
||||
if (accessTokenCustomizer == null) {
|
||||
return defaultAccessTokenCustomizer;
|
||||
|
||||
@@ -197,11 +197,11 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
|
||||
|
||||
@Override
|
||||
public void init(HttpSecurity builder) {
|
||||
AuthorizationServerSettings authorizationServerSettings =
|
||||
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) :
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(builder);
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
|
||||
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
this.requestMatcher = new AntPathRequestMatcher(deviceAuthorizationEndpointUri, HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(builder);
|
||||
@@ -219,11 +219,11 @@ public final class OAuth2DeviceAuthorizationEndpointConfigurer extends AbstractO
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(builder);
|
||||
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint()) :
|
||||
authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
OAuth2DeviceAuthorizationEndpointFilter deviceAuthorizationEndpointFilter =
|
||||
new OAuth2DeviceAuthorizationEndpointFilter(authenticationManager, deviceAuthorizationEndpointUri);
|
||||
String deviceAuthorizationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getDeviceAuthorizationEndpoint())
|
||||
: authorizationServerSettings.getDeviceAuthorizationEndpoint();
|
||||
OAuth2DeviceAuthorizationEndpointFilter deviceAuthorizationEndpointFilter = new OAuth2DeviceAuthorizationEndpointFilter(
|
||||
authenticationManager, deviceAuthorizationEndpointUri);
|
||||
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.deviceAuthorizationRequestConverters.isEmpty()) {
|
||||
|
||||
@@ -232,11 +232,11 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
|
||||
|
||||
@Override
|
||||
public void init(HttpSecurity builder) {
|
||||
AuthorizationServerSettings authorizationServerSettings =
|
||||
OAuth2ConfigurerUtils.getAuthorizationServerSettings(builder);
|
||||
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) :
|
||||
authorizationServerSettings.getDeviceVerificationEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(builder);
|
||||
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint())
|
||||
: authorizationServerSettings.getDeviceVerificationEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(deviceVerificationEndpointUri, HttpMethod.POST.name()));
|
||||
@@ -256,11 +256,11 @@ public final class OAuth2DeviceVerificationEndpointConfigurer extends AbstractOA
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(builder);
|
||||
|
||||
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint()) :
|
||||
authorizationServerSettings.getDeviceVerificationEndpoint();
|
||||
OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter =
|
||||
new OAuth2DeviceVerificationEndpointFilter(authenticationManager, deviceVerificationEndpointUri);
|
||||
String deviceVerificationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getDeviceVerificationEndpoint())
|
||||
: authorizationServerSettings.getDeviceVerificationEndpoint();
|
||||
OAuth2DeviceVerificationEndpointFilter deviceVerificationEndpointFilter = new OAuth2DeviceVerificationEndpointFilter(
|
||||
authenticationManager, deviceVerificationEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.deviceVerificationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.deviceVerificationRequestConverters);
|
||||
|
||||
@@ -185,10 +185,11 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) :
|
||||
authorizationServerSettings.getTokenEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint())
|
||||
: authorizationServerSettings.getTokenEndpoint();
|
||||
this.requestMatcher = new AntPathRequestMatcher(tokenEndpointUri, HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
@@ -206,11 +207,11 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint()) :
|
||||
authorizationServerSettings.getTokenEndpoint();
|
||||
OAuth2TokenEndpointFilter tokenEndpointFilter =
|
||||
new OAuth2TokenEndpointFilter(authenticationManager, tokenEndpointUri);
|
||||
String tokenEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenEndpoint())
|
||||
: authorizationServerSettings.getTokenEndpoint();
|
||||
OAuth2TokenEndpointFilter tokenEndpointFilter = new OAuth2TokenEndpointFilter(authenticationManager,
|
||||
tokenEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.accessTokenRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.accessTokenRequestConverters);
|
||||
@@ -270,8 +271,8 @@ public final class OAuth2TokenEndpointConfigurer extends AbstractOAuth2Configure
|
||||
authorizationService, tokenGenerator);
|
||||
authenticationProviders.add(deviceCodeAuthenticationProvider);
|
||||
|
||||
OAuth2TokenExchangeAuthenticationProvider tokenExchangeAuthenticationProvider =
|
||||
new OAuth2TokenExchangeAuthenticationProvider(authorizationService, tokenGenerator);
|
||||
OAuth2TokenExchangeAuthenticationProvider tokenExchangeAuthenticationProvider = new OAuth2TokenExchangeAuthenticationProvider(
|
||||
authorizationService, tokenGenerator);
|
||||
authenticationProviders.add(tokenExchangeAuthenticationProvider);
|
||||
|
||||
return authenticationProviders;
|
||||
|
||||
@@ -180,10 +180,11 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) :
|
||||
authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint())
|
||||
: authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
this.requestMatcher = new AntPathRequestMatcher(tokenIntrospectionEndpointUri, HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
@@ -198,12 +199,13 @@ public final class OAuth2TokenIntrospectionEndpointConfigurer extends AbstractOA
|
||||
@Override
|
||||
void configure(HttpSecurity httpSecurity) {
|
||||
AuthenticationManager authenticationManager = httpSecurity.getSharedObject(AuthenticationManager.class);
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint()) :
|
||||
authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter =
|
||||
new OAuth2TokenIntrospectionEndpointFilter(authenticationManager, tokenIntrospectionEndpointUri);
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenIntrospectionEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenIntrospectionEndpoint())
|
||||
: authorizationServerSettings.getTokenIntrospectionEndpoint();
|
||||
OAuth2TokenIntrospectionEndpointFilter introspectionEndpointFilter = new OAuth2TokenIntrospectionEndpointFilter(
|
||||
authenticationManager, tokenIntrospectionEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.introspectionRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.introspectionRequestConverters);
|
||||
|
||||
@@ -179,10 +179,11 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) :
|
||||
authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint())
|
||||
: authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
this.requestMatcher = new AntPathRequestMatcher(tokenRevocationEndpointUri, HttpMethod.POST.name());
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
@@ -200,11 +201,11 @@ public final class OAuth2TokenRevocationEndpointConfigurer extends AbstractOAuth
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint()) :
|
||||
authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter =
|
||||
new OAuth2TokenRevocationEndpointFilter(authenticationManager, tokenRevocationEndpointUri);
|
||||
String tokenRevocationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getTokenRevocationEndpoint())
|
||||
: authorizationServerSettings.getTokenRevocationEndpoint();
|
||||
OAuth2TokenRevocationEndpointFilter revocationEndpointFilter = new OAuth2TokenRevocationEndpointFilter(
|
||||
authenticationManager, tokenRevocationEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.revocationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.revocationRequestConverters);
|
||||
|
||||
@@ -190,10 +190,11 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) :
|
||||
authorizationServerSettings.getOidcClientRegistrationEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint())
|
||||
: authorizationServerSettings.getOidcClientRegistrationEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.POST.name()),
|
||||
new AntPathRequestMatcher(clientRegistrationEndpointUri, HttpMethod.GET.name()));
|
||||
@@ -213,11 +214,11 @@ public final class OidcClientRegistrationEndpointConfigurer extends AbstractOAut
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
|
||||
String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint()) :
|
||||
authorizationServerSettings.getOidcClientRegistrationEndpoint();
|
||||
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter =
|
||||
new OidcClientRegistrationEndpointFilter(authenticationManager, clientRegistrationEndpointUri);
|
||||
String clientRegistrationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcClientRegistrationEndpoint())
|
||||
: authorizationServerSettings.getOidcClientRegistrationEndpoint();
|
||||
OidcClientRegistrationEndpointFilter oidcClientRegistrationEndpointFilter = new OidcClientRegistrationEndpointFilter(
|
||||
authenticationManager, clientRegistrationEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.clientRegistrationRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.clientRegistrationRequestConverters);
|
||||
|
||||
@@ -165,14 +165,13 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) :
|
||||
authorizationServerSettings.getOidcLogoutEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.POST.name())
|
||||
);
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint())
|
||||
: authorizationServerSettings.getOidcLogoutEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(logoutEndpointUri, HttpMethod.POST.name()));
|
||||
|
||||
List<AuthenticationProvider> authenticationProviders = createDefaultAuthenticationProviders(httpSecurity);
|
||||
if (!this.authenticationProviders.isEmpty()) {
|
||||
@@ -189,11 +188,11 @@ public final class OidcLogoutEndpointConfigurer extends AbstractOAuth2Configurer
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
|
||||
String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint()) :
|
||||
authorizationServerSettings.getOidcLogoutEndpoint();
|
||||
OidcLogoutEndpointFilter oidcLogoutEndpointFilter =
|
||||
new OidcLogoutEndpointFilter(authenticationManager, logoutEndpointUri);
|
||||
String logoutEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcLogoutEndpoint())
|
||||
: authorizationServerSettings.getOidcLogoutEndpoint();
|
||||
OidcLogoutEndpointFilter oidcLogoutEndpointFilter = new OidcLogoutEndpointFilter(authenticationManager,
|
||||
logoutEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.logoutRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.logoutRequestConverters);
|
||||
|
||||
@@ -74,10 +74,10 @@ public final class OidcProviderConfigurationEndpointConfigurer extends AbstractO
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String oidcProviderConfigurationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
"/**/.well-known/openid-configuration" :
|
||||
"/.well-known/openid-configuration";
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String oidcProviderConfigurationEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? "/**/.well-known/openid-configuration" : "/.well-known/openid-configuration";
|
||||
this.requestMatcher = new AntPathRequestMatcher(oidcProviderConfigurationEndpointUri, HttpMethod.GET.name());
|
||||
}
|
||||
|
||||
|
||||
@@ -208,10 +208,11 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
|
||||
|
||||
@Override
|
||||
void init(HttpSecurity httpSecurity) {
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils.getAuthorizationServerSettings(httpSecurity);
|
||||
String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) :
|
||||
authorizationServerSettings.getOidcUserInfoEndpoint();
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint())
|
||||
: authorizationServerSettings.getOidcUserInfoEndpoint();
|
||||
this.requestMatcher = new OrRequestMatcher(
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.GET.name()),
|
||||
new AntPathRequestMatcher(userInfoEndpointUri, HttpMethod.POST.name()));
|
||||
@@ -231,11 +232,11 @@ public final class OidcUserInfoEndpointConfigurer extends AbstractOAuth2Configur
|
||||
AuthorizationServerSettings authorizationServerSettings = OAuth2ConfigurerUtils
|
||||
.getAuthorizationServerSettings(httpSecurity);
|
||||
|
||||
String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed() ?
|
||||
withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint()) :
|
||||
authorizationServerSettings.getOidcUserInfoEndpoint();
|
||||
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter =
|
||||
new OidcUserInfoEndpointFilter(authenticationManager, userInfoEndpointUri);
|
||||
String userInfoEndpointUri = authorizationServerSettings.isMultipleIssuersAllowed()
|
||||
? withMultipleIssuersPattern(authorizationServerSettings.getOidcUserInfoEndpoint())
|
||||
: authorizationServerSettings.getOidcUserInfoEndpoint();
|
||||
OidcUserInfoEndpointFilter oidcUserInfoEndpointFilter = new OidcUserInfoEndpointFilter(authenticationManager,
|
||||
userInfoEndpointUri);
|
||||
List<AuthenticationConverter> authenticationConverters = createDefaultAuthenticationConverters();
|
||||
if (!this.userInfoRequestConverters.isEmpty()) {
|
||||
authenticationConverters.addAll(0, this.userInfoRequestConverters);
|
||||
|
||||
@@ -32,18 +32,24 @@ public interface AuthorizationServerContext {
|
||||
* resolves the issuer identifier from the <i>"current"</i> request.
|
||||
*
|
||||
* <p>
|
||||
* The issuer identifier may contain a path component to support {@link AuthorizationServerSettings#isMultipleIssuersAllowed() multiple issuers per host} in a multi-tenant hosting configuration.
|
||||
* The issuer identifier may contain a path component to support
|
||||
* {@link AuthorizationServerSettings#isMultipleIssuersAllowed() multiple issuers per
|
||||
* host} in a multi-tenant hosting configuration.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
* <ul>
|
||||
* <li>{@code https://example.com/issuer1/oauth2/token} — resolves the issuer to {@code https://example.com/issuer1}</li>
|
||||
* <li>{@code https://example.com/issuer2/oauth2/token} — resolves the issuer to {@code https://example.com/issuer2}</li>
|
||||
* <li>{@code https://example.com/authz/issuer1/oauth2/token} — resolves the issuer to {@code https://example.com/authz/issuer1}</li>
|
||||
* <li>{@code https://example.com/authz/issuer2/oauth2/token} — resolves the issuer to {@code https://example.com/authz/issuer2}</li>
|
||||
* <li>{@code https://example.com/issuer1/oauth2/token} — resolves the issuer to
|
||||
* {@code https://example.com/issuer1}</li>
|
||||
* <li>{@code https://example.com/issuer2/oauth2/token} — resolves the issuer to
|
||||
* {@code https://example.com/issuer2}</li>
|
||||
* <li>{@code https://example.com/authz/issuer1/oauth2/token} — resolves the
|
||||
* issuer to {@code https://example.com/authz/issuer1}</li>
|
||||
* <li>{@code https://example.com/authz/issuer2/oauth2/token} — resolves the
|
||||
* issuer to {@code https://example.com/authz/issuer2}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return {@link AuthorizationServerSettings#getIssuer()} if available, otherwise, resolves the issuer identifier from the <i>"current"</i> request
|
||||
* @return {@link AuthorizationServerSettings#getIssuer()} if available, otherwise,
|
||||
* resolves the issuer identifier from the <i>"current"</i> request
|
||||
*/
|
||||
String getIssuer();
|
||||
|
||||
|
||||
@@ -28,7 +28,8 @@ import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2TokenExchangeCompositeAuthenticationToken;
|
||||
|
||||
/**
|
||||
* This mixin class is used to serialize/deserialize {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
|
||||
* This mixin class is used to serialize/deserialize
|
||||
* {@link OAuth2TokenExchangeCompositeAuthenticationToken}.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
|
||||
@@ -67,11 +67,11 @@ final class OidcAuthenticationProviderUtils {
|
||||
static <T extends OAuth2Token> OAuth2AccessToken accessToken(OAuth2Authorization.Builder builder, T token,
|
||||
OAuth2TokenContext accessTokenContext) {
|
||||
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||
token.getTokenValue(), token.getIssuedAt(), token.getExpiresAt(),
|
||||
accessTokenContext.getAuthorizedScopes());
|
||||
OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient().getTokenSettings()
|
||||
.getAccessTokenFormat();
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, token.getTokenValue(),
|
||||
token.getIssuedAt(), token.getExpiresAt(), accessTokenContext.getAuthorizedScopes());
|
||||
OAuth2TokenFormat accessTokenFormat = accessTokenContext.getRegisteredClient()
|
||||
.getTokenSettings()
|
||||
.getAccessTokenFormat();
|
||||
builder.token(accessToken, (metadata) -> {
|
||||
if (token instanceof ClaimAccessor claimAccessor) {
|
||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, claimAccessor.getClaims());
|
||||
|
||||
@@ -63,9 +63,11 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
|
||||
private static final String DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI = "/.well-known/openid-configuration";
|
||||
|
||||
private final RequestMatcher requestMatcher = createRequestMatcher();
|
||||
private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter =
|
||||
new OidcProviderConfigurationHttpMessageConverter();
|
||||
private Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = (providerConfiguration) -> {};
|
||||
|
||||
private final OidcProviderConfigurationHttpMessageConverter providerConfigurationHttpMessageConverter = new OidcProviderConfigurationHttpMessageConverter();
|
||||
|
||||
private Consumer<OidcProviderConfiguration.Builder> providerConfigurationCustomizer = (providerConfiguration) -> {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the
|
||||
@@ -96,29 +98,29 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
|
||||
.getAuthorizationServerSettings();
|
||||
|
||||
OidcProviderConfiguration.Builder providerConfiguration = OidcProviderConfiguration.builder()
|
||||
.issuer(issuer)
|
||||
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
|
||||
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
|
||||
.userInfoEndpoint(asUrl(issuer, authorizationServerSettings.getOidcUserInfoEndpoint()))
|
||||
.endSessionEndpoint(asUrl(issuer, authorizationServerSettings.getOidcLogoutEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true)
|
||||
.subjectType("public")
|
||||
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
|
||||
.scope(OidcScopes.OPENID);
|
||||
.issuer(issuer)
|
||||
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
|
||||
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
|
||||
.userInfoEndpoint(asUrl(issuer, authorizationServerSettings.getOidcUserInfoEndpoint()))
|
||||
.endSessionEndpoint(asUrl(issuer, authorizationServerSettings.getOidcLogoutEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true)
|
||||
.subjectType("public")
|
||||
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
|
||||
.scope(OidcScopes.OPENID);
|
||||
|
||||
this.providerConfigurationCustomizer.accept(providerConfiguration);
|
||||
|
||||
@@ -132,10 +134,10 @@ public final class OidcProviderConfigurationEndpointFilter extends OncePerReques
|
||||
DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
final RequestMatcher multipleIssuersRequestMatcher = new AntPathRequestMatcher(
|
||||
"/**" + DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
return (request) ->
|
||||
AuthorizationServerContextHolder.getContext().getAuthorizationServerSettings().isMultipleIssuersAllowed() ?
|
||||
multipleIssuersRequestMatcher.matches(request) :
|
||||
defaultRequestMatcher.matches(request);
|
||||
return (request) -> AuthorizationServerContextHolder.getContext()
|
||||
.getAuthorizationServerSettings()
|
||||
.isMultipleIssuersAllowed() ? multipleIssuersRequestMatcher.matches(request)
|
||||
: defaultRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
private static Consumer<List<String>> clientAuthenticationMethods() {
|
||||
|
||||
@@ -44,8 +44,9 @@ public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if multiple issuers are allowed per host. The default is {@code false}.
|
||||
* Using path components in the URL of the issuer identifier enables supporting multiple issuers per host in a multi-tenant hosting configuration.
|
||||
* Returns {@code true} if multiple issuers are allowed per host. The default is
|
||||
* {@code false}. Using path components in the URL of the issuer identifier enables
|
||||
* supporting multiple issuers per host in a multi-tenant hosting configuration.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
@@ -53,8 +54,8 @@ public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
* <li>{@code https://example.com/issuer1}</li>
|
||||
* <li>{@code https://example.com/authz/issuer2}</li>
|
||||
* </ul>
|
||||
*
|
||||
* @return {@code true} if multiple issuers are allowed per host, {@code false} otherwise
|
||||
* @return {@code true} if multiple issuers are allowed per host, {@code false}
|
||||
* otherwise
|
||||
* @since 1.3
|
||||
* @see AuthorizationServerContext#getIssuer()
|
||||
*/
|
||||
@@ -198,8 +199,9 @@ public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if multiple issuers are allowed per host.
|
||||
* Using path components in the URL of the issuer identifier enables supporting multiple issuers per host in a multi-tenant hosting configuration.
|
||||
* Set to {@code true} if multiple issuers are allowed per host. Using path
|
||||
* components in the URL of the issuer identifier enables supporting multiple
|
||||
* issuers per host in a multi-tenant hosting configuration.
|
||||
*
|
||||
* <p>
|
||||
* For example:
|
||||
@@ -209,17 +211,20 @@ public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
* </ul>
|
||||
*
|
||||
* <p>
|
||||
* <b>NOTE:</b> Explicitly configuring the issuer identifier via {@link #issuer(String)} forces to a single-tenant configuration.
|
||||
* Avoid configuring the issuer identifier when using a multi-tenant hosting configuration,
|
||||
* allowing the issuer identifier to be resolved from the <i>"current"</i> request.
|
||||
*
|
||||
* @param multipleIssuersAllowed {@code true} if multiple issuers are allowed per host, {@code false} otherwise
|
||||
* <b>NOTE:</b> Explicitly configuring the issuer identifier via
|
||||
* {@link #issuer(String)} forces to a single-tenant configuration. Avoid
|
||||
* configuring the issuer identifier when using a multi-tenant hosting
|
||||
* configuration, allowing the issuer identifier to be resolved from the
|
||||
* <i>"current"</i> request.
|
||||
* @param multipleIssuersAllowed {@code true} if multiple issuers are allowed per
|
||||
* host, {@code false} otherwise
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 1.3
|
||||
* @see AuthorizationServerContext#getIssuer()
|
||||
*/
|
||||
public Builder multipleIssuersAllowed(boolean multipleIssuersAllowed) {
|
||||
return setting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED, multipleIssuersAllowed);
|
||||
return setting(ConfigurationSettingNames.AuthorizationServer.MULTIPLE_ISSUERS_ALLOWED,
|
||||
multipleIssuersAllowed);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -328,9 +333,10 @@ public final class AuthorizationServerSettings extends AbstractSettings {
|
||||
@Override
|
||||
public AuthorizationServerSettings build() {
|
||||
AuthorizationServerSettings authorizationServerSettings = new AuthorizationServerSettings(getSettings());
|
||||
if (authorizationServerSettings.getIssuer() != null && authorizationServerSettings.isMultipleIssuersAllowed()) {
|
||||
throw new IllegalArgumentException("The issuer identifier (" + authorizationServerSettings.getIssuer() +
|
||||
") cannot be set when isMultipleIssuersAllowed() is true.");
|
||||
if (authorizationServerSettings.getIssuer() != null
|
||||
&& authorizationServerSettings.isMultipleIssuersAllowed()) {
|
||||
throw new IllegalArgumentException("The issuer identifier (" + authorizationServerSettings.getIssuer()
|
||||
+ ") cannot be set when isMultipleIssuersAllowed() is true.");
|
||||
}
|
||||
return authorizationServerSettings;
|
||||
}
|
||||
|
||||
@@ -81,10 +81,11 @@ public final class ClientSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the expected subject distinguished name associated to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} method.
|
||||
*
|
||||
* @return the expected subject distinguished name associated to the client {@code X509Certificate} received during client authentication
|
||||
* Returns the expected subject distinguished name associated to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} method.
|
||||
* @return the expected subject distinguished name associated to the client
|
||||
* {@code X509Certificate} received during client authentication
|
||||
* @since 1.3
|
||||
*/
|
||||
public String getX509CertificateSubjectDN() {
|
||||
@@ -168,10 +169,12 @@ public final class ClientSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the expected subject distinguished name associated to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} method.
|
||||
*
|
||||
* @param x509CertificateSubjectDN the expected subject distinguished name associated to the client {@code X509Certificate} received during client authentication * @return the {@link Builder} for further configuration
|
||||
* Sets the expected subject distinguished name associated to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} method.
|
||||
* @param x509CertificateSubjectDN the expected subject distinguished name
|
||||
* associated to the client {@code X509Certificate} received during client
|
||||
* authentication * @return the {@link Builder} for further configuration
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 1.3
|
||||
*/
|
||||
|
||||
@@ -73,11 +73,13 @@ public final class ConfigurationSettingNames {
|
||||
.concat("token-endpoint-authentication-signing-algorithm");
|
||||
|
||||
/**
|
||||
* Set the expected subject distinguished name associated to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} method.
|
||||
* Set the expected subject distinguished name associated to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} method.
|
||||
* @since 1.3
|
||||
*/
|
||||
public static final String X509_CERTIFICATE_SUBJECT_DN = CLIENT_SETTINGS_NAMESPACE.concat("x509-certificate-subject-dn");
|
||||
public static final String X509_CERTIFICATE_SUBJECT_DN = CLIENT_SETTINGS_NAMESPACE
|
||||
.concat("x509-certificate-subject-dn");
|
||||
|
||||
private Client() {
|
||||
}
|
||||
@@ -101,7 +103,8 @@ public final class ConfigurationSettingNames {
|
||||
* Set to {@code true} if multiple issuers are allowed per host.
|
||||
* @since 1.3
|
||||
*/
|
||||
public static final String MULTIPLE_ISSUERS_ALLOWED = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE.concat("multiple-issuers-allowed");
|
||||
public static final String MULTIPLE_ISSUERS_ALLOWED = AUTHORIZATION_SERVER_SETTINGS_NAMESPACE
|
||||
.concat("multiple-issuers-allowed");
|
||||
|
||||
/**
|
||||
* Set the OAuth 2.0 Authorization endpoint.
|
||||
@@ -221,11 +224,13 @@ public final class ConfigurationSettingNames {
|
||||
.concat("id-token-signature-algorithm");
|
||||
|
||||
/**
|
||||
* Set to {@code true} if access tokens must be bound to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
* Set to {@code true} if access tokens must be bound to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
* @since 1.3
|
||||
*/
|
||||
public static final String X509_CERTIFICATE_BOUND_ACCESS_TOKENS = TOKEN_SETTINGS_NAMESPACE.concat("x509-certificate-bound-access-tokens");
|
||||
public static final String X509_CERTIFICATE_BOUND_ACCESS_TOKENS = TOKEN_SETTINGS_NAMESPACE
|
||||
.concat("x509-certificate-bound-access-tokens");
|
||||
|
||||
private Token() {
|
||||
}
|
||||
|
||||
@@ -101,11 +101,12 @@ public final class TokenSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@code true} if access tokens must be bound to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
* The default is {@code false}.
|
||||
*
|
||||
* @return {@code true} if access tokens must be bound to the client {@code X509Certificate}, {@code false} otherwise
|
||||
* Returns {@code true} if access tokens must be bound to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} or {@code self_signed_tls_client_auth} method. The default
|
||||
* is {@code false}.
|
||||
* @return {@code true} if access tokens must be bound to the client
|
||||
* {@code X509Certificate}, {@code false} otherwise
|
||||
* @since 1.3
|
||||
*/
|
||||
public boolean isX509CertificateBoundAccessTokens() {
|
||||
@@ -117,15 +118,14 @@ public final class TokenSettings extends AbstractSettings {
|
||||
* @return the {@link Builder}
|
||||
*/
|
||||
public static Builder builder() {
|
||||
return new Builder()
|
||||
.authorizationCodeTimeToLive(Duration.ofMinutes(5))
|
||||
.accessTokenTimeToLive(Duration.ofMinutes(5))
|
||||
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
||||
.deviceCodeTimeToLive(Duration.ofMinutes(5))
|
||||
.reuseRefreshTokens(true)
|
||||
.refreshTokenTimeToLive(Duration.ofMinutes(60))
|
||||
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
|
||||
.x509CertificateBoundAccessTokens(false);
|
||||
return new Builder().authorizationCodeTimeToLive(Duration.ofMinutes(5))
|
||||
.accessTokenTimeToLive(Duration.ofMinutes(5))
|
||||
.accessTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)
|
||||
.deviceCodeTimeToLive(Duration.ofMinutes(5))
|
||||
.reuseRefreshTokens(true)
|
||||
.refreshTokenTimeToLive(Duration.ofMinutes(60))
|
||||
.idTokenSignatureAlgorithm(SignatureAlgorithm.RS256)
|
||||
.x509CertificateBoundAccessTokens(false);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -237,15 +237,17 @@ public final class TokenSettings extends AbstractSettings {
|
||||
}
|
||||
|
||||
/**
|
||||
* Set to {@code true} if access tokens must be bound to the client {@code X509Certificate}
|
||||
* received during client authentication when using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
*
|
||||
* @param x509CertificateBoundAccessTokens {@code true} if access tokens must be bound to the client {@code X509Certificate}, {@code false} otherwise
|
||||
* Set to {@code true} if access tokens must be bound to the client
|
||||
* {@code X509Certificate} received during client authentication when using the
|
||||
* {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
* @param x509CertificateBoundAccessTokens {@code true} if access tokens must be
|
||||
* bound to the client {@code X509Certificate}, {@code false} otherwise
|
||||
* @return the {@link Builder} for further configuration
|
||||
* @since 1.3
|
||||
*/
|
||||
public Builder x509CertificateBoundAccessTokens(boolean x509CertificateBoundAccessTokens) {
|
||||
return setting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS, x509CertificateBoundAccessTokens);
|
||||
return setting(ConfigurationSettingNames.Token.X509_CERTIFICATE_BOUND_ACCESS_TOKENS,
|
||||
x509CertificateBoundAccessTokens);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -61,9 +61,12 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
|
||||
private static final String DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI = "/.well-known/oauth-authorization-server";
|
||||
|
||||
private final RequestMatcher requestMatcher = createRequestMatcher();
|
||||
private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter =
|
||||
new OAuth2AuthorizationServerMetadataHttpMessageConverter();
|
||||
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = (authorizationServerMetadata) -> {};
|
||||
|
||||
private final OAuth2AuthorizationServerMetadataHttpMessageConverter authorizationServerMetadataHttpMessageConverter = new OAuth2AuthorizationServerMetadataHttpMessageConverter();
|
||||
|
||||
private Consumer<OAuth2AuthorizationServerMetadata.Builder> authorizationServerMetadataCustomizer = (
|
||||
authorizationServerMetadata) -> {
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the
|
||||
@@ -93,25 +96,26 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
|
||||
AuthorizationServerSettings authorizationServerSettings = authorizationServerContext
|
||||
.getAuthorizationServerSettings();
|
||||
|
||||
OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer(issuer)
|
||||
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
|
||||
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true);
|
||||
OAuth2AuthorizationServerMetadata.Builder authorizationServerMetadata = OAuth2AuthorizationServerMetadata
|
||||
.builder()
|
||||
.issuer(issuer)
|
||||
.authorizationEndpoint(asUrl(issuer, authorizationServerSettings.getAuthorizationEndpoint()))
|
||||
.deviceAuthorizationEndpoint(asUrl(issuer, authorizationServerSettings.getDeviceAuthorizationEndpoint()))
|
||||
.tokenEndpoint(asUrl(issuer, authorizationServerSettings.getTokenEndpoint()))
|
||||
.tokenEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.jwkSetUrl(asUrl(issuer, authorizationServerSettings.getJwkSetEndpoint()))
|
||||
.responseType(OAuth2AuthorizationResponseType.CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||
.grantType(AuthorizationGrantType.DEVICE_CODE.getValue())
|
||||
.grantType(AuthorizationGrantType.TOKEN_EXCHANGE.getValue())
|
||||
.tokenRevocationEndpoint(asUrl(issuer, authorizationServerSettings.getTokenRevocationEndpoint()))
|
||||
.tokenRevocationEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.tokenIntrospectionEndpoint(asUrl(issuer, authorizationServerSettings.getTokenIntrospectionEndpoint()))
|
||||
.tokenIntrospectionEndpointAuthenticationMethods(clientAuthenticationMethods())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true);
|
||||
|
||||
this.authorizationServerMetadataCustomizer.accept(authorizationServerMetadata);
|
||||
|
||||
@@ -125,10 +129,10 @@ public final class OAuth2AuthorizationServerMetadataEndpointFilter extends OnceP
|
||||
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI, HttpMethod.GET.name());
|
||||
final RequestMatcher multipleIssuersRequestMatcher = new AntPathRequestMatcher(
|
||||
DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI + "/**", HttpMethod.GET.name());
|
||||
return (request) ->
|
||||
AuthorizationServerContextHolder.getContext().getAuthorizationServerSettings().isMultipleIssuersAllowed() ?
|
||||
multipleIssuersRequestMatcher.matches(request) :
|
||||
defaultRequestMatcher.matches(request);
|
||||
return (request) -> AuthorizationServerContextHolder.getContext()
|
||||
.getAuthorizationServerSettings()
|
||||
.isMultipleIssuersAllowed() ? multipleIssuersRequestMatcher.matches(request)
|
||||
: defaultRequestMatcher.matches(request);
|
||||
}
|
||||
|
||||
private static Consumer<List<String>> clientAuthenticationMethods() {
|
||||
|
||||
@@ -220,8 +220,9 @@ public final class OAuth2DeviceAuthorizationEndpointFilter extends OncePerReques
|
||||
OAuth2UserCode userCode = deviceAuthorizationRequestAuthentication.getUserCode();
|
||||
|
||||
// Generate the fully-qualified verification URI
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replacePath(this.verificationUri);
|
||||
UriComponentsBuilder uriComponentsBuilder = UriComponentsBuilder
|
||||
.fromHttpUrl(UrlUtils.buildFullRequestUrl(request))
|
||||
.replacePath(this.verificationUri);
|
||||
String verificationUri = uriComponentsBuilder.build().toUriString();
|
||||
// @formatter:off
|
||||
String verificationUriComplete = uriComponentsBuilder
|
||||
|
||||
@@ -104,10 +104,12 @@ public final class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
||||
|
||||
private final RequestMatcher tokenEndpointMatcher;
|
||||
|
||||
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
|
||||
new WebAuthenticationDetailsSource();
|
||||
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
|
||||
|
||||
private AuthenticationConverter authenticationConverter;
|
||||
|
||||
private AuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2AccessTokenResponseAuthenticationSuccessHandler();
|
||||
|
||||
private AuthenticationFailureHandler authenticationFailureHandler = new OAuth2ErrorAuthenticationFailureHandler();
|
||||
|
||||
/**
|
||||
|
||||
@@ -44,8 +44,9 @@ import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* An implementation of an {@link AuthenticationSuccessHandler} used for handling an {@link OAuth2AccessTokenAuthenticationToken}
|
||||
* and returning the {@link OAuth2AccessTokenResponse Access Token Response}.
|
||||
* An implementation of an {@link AuthenticationSuccessHandler} used for handling an
|
||||
* {@link OAuth2AccessTokenAuthenticationToken} and returning the
|
||||
* {@link OAuth2AccessTokenResponse Access Token Response}.
|
||||
*
|
||||
* @author Dmitriy Dubson
|
||||
* @since 1.3
|
||||
@@ -53,9 +54,11 @@ import org.springframework.util.CollectionUtils;
|
||||
* @see OAuth2AccessTokenResponseHttpMessageConverter
|
||||
*/
|
||||
public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler implements AuthenticationSuccessHandler {
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private Consumer<OAuth2AccessTokenAuthenticationContext> accessTokenResponseCustomizer;
|
||||
|
||||
@Override
|
||||
@@ -63,11 +66,12 @@ public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler impleme
|
||||
Authentication authentication) throws IOException, ServletException {
|
||||
if (!(authentication instanceof OAuth2AccessTokenAuthenticationToken accessTokenAuthentication)) {
|
||||
if (this.logger.isErrorEnabled()) {
|
||||
this.logger.error(Authentication.class.getSimpleName() + " must be of type " +
|
||||
OAuth2AccessTokenAuthenticationToken.class.getName() +
|
||||
" but was " + authentication.getClass().getName());
|
||||
this.logger.error(Authentication.class.getSimpleName() + " must be of type "
|
||||
+ OAuth2AccessTokenAuthenticationToken.class.getName() + " but was "
|
||||
+ authentication.getClass().getName());
|
||||
}
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, "Unable to process the access token response.", null);
|
||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR,
|
||||
"Unable to process the access token response.", null);
|
||||
throw new OAuth2AuthenticationException(error);
|
||||
}
|
||||
|
||||
@@ -75,10 +79,9 @@ public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler impleme
|
||||
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
|
||||
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
|
||||
|
||||
OAuth2AccessTokenResponse.Builder builder =
|
||||
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||
.tokenType(accessToken.getTokenType())
|
||||
.scopes(accessToken.getScopes());
|
||||
OAuth2AccessTokenResponse.Builder builder = OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||
.tokenType(accessToken.getTokenType())
|
||||
.scopes(accessToken.getScopes());
|
||||
if (accessToken.getIssuedAt() != null && accessToken.getExpiresAt() != null) {
|
||||
builder.expiresIn(ChronoUnit.SECONDS.between(accessToken.getIssuedAt(), accessToken.getExpiresAt()));
|
||||
}
|
||||
@@ -108,12 +111,15 @@ public final class OAuth2AccessTokenResponseAuthenticationSuccessHandler impleme
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@code Consumer} providing access to the {@link OAuth2AccessTokenAuthenticationContext}
|
||||
* containing an {@link OAuth2AccessTokenResponse.Builder} and additional context information.
|
||||
*
|
||||
* @param accessTokenResponseCustomizer the {@code Consumer} providing access to the {@link OAuth2AccessTokenAuthenticationContext} containing an {@link OAuth2AccessTokenResponse.Builder}
|
||||
* Sets the {@code Consumer} providing access to the
|
||||
* {@link OAuth2AccessTokenAuthenticationContext} containing an
|
||||
* {@link OAuth2AccessTokenResponse.Builder} and additional context information.
|
||||
* @param accessTokenResponseCustomizer the {@code Consumer} providing access to the
|
||||
* {@link OAuth2AccessTokenAuthenticationContext} containing an
|
||||
* {@link OAuth2AccessTokenResponse.Builder}
|
||||
*/
|
||||
public void setAccessTokenResponseCustomizer(Consumer<OAuth2AccessTokenAuthenticationContext> accessTokenResponseCustomizer) {
|
||||
public void setAccessTokenResponseCustomizer(
|
||||
Consumer<OAuth2AccessTokenAuthenticationContext> accessTokenResponseCustomizer) {
|
||||
Assert.notNull(accessTokenResponseCustomizer, "accessTokenResponseCustomizer cannot be null");
|
||||
this.accessTokenResponseCustomizer = accessTokenResponseCustomizer;
|
||||
}
|
||||
|
||||
@@ -44,8 +44,10 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to extract an Access Token Request from {@link HttpServletRequest} for the OAuth 2.0 Token Exchange Grant
|
||||
* and then converts it to an {@link OAuth2TokenExchangeAuthenticationToken} used for authenticating the authorization grant.
|
||||
* Attempts to extract an Access Token Request from {@link HttpServletRequest} for the
|
||||
* OAuth 2.0 Token Exchange Grant and then converts it to an
|
||||
* {@link OAuth2TokenExchangeAuthenticationToken} used for authenticating the
|
||||
* authorization grant.
|
||||
*
|
||||
* @author Steve Riesenberg
|
||||
* @since 1.3
|
||||
@@ -81,9 +83,7 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
|
||||
if (!CollectionUtils.isEmpty(resources)) {
|
||||
for (String resource : resources) {
|
||||
if (!isValidUri(resource)) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.RESOURCE,
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.RESOURCE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
}
|
||||
@@ -94,64 +94,52 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
|
||||
|
||||
// scope (OPTIONAL)
|
||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE);
|
||||
if (StringUtils.hasText(scope) &&
|
||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.SCOPE,
|
||||
if (StringUtils.hasText(scope) && parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SCOPE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
Set<String> requestedScopes = null;
|
||||
if (StringUtils.hasText(scope)) {
|
||||
requestedScopes = new HashSet<>(
|
||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
requestedScopes = new HashSet<>(Arrays.asList(StringUtils.delimitedListToStringArray(scope, " ")));
|
||||
}
|
||||
|
||||
// requested_token_type (OPTIONAL)
|
||||
String requestedTokenType = parameters.getFirst(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE);
|
||||
if (StringUtils.hasText(requestedTokenType)) {
|
||||
if (parameters.get(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.REQUESTED_TOKEN_TYPE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
validateTokenType(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, requestedTokenType);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
requestedTokenType = ACCESS_TOKEN_TYPE_VALUE;
|
||||
}
|
||||
|
||||
// subject_token (REQUIRED)
|
||||
String subjectToken = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN);
|
||||
if (!StringUtils.hasText(subjectToken) ||
|
||||
parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.SUBJECT_TOKEN,
|
||||
if (!StringUtils.hasText(subjectToken) || parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
// subject_token_type (REQUIRED)
|
||||
String subjectTokenType = parameters.getFirst(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE);
|
||||
if (!StringUtils.hasText(subjectTokenType) ||
|
||||
parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.SUBJECT_TOKEN_TYPE,
|
||||
if (!StringUtils.hasText(subjectTokenType)
|
||||
|| parameters.get(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.SUBJECT_TOKEN_TYPE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
validateTokenType(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, subjectTokenType);
|
||||
}
|
||||
|
||||
// actor_token (OPTIONAL, REQUIRED if actor_token_type is provided)
|
||||
String actorToken = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN);
|
||||
if (StringUtils.hasText(actorToken) &&
|
||||
parameters.get(OAuth2ParameterNames.ACTOR_TOKEN).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.ACTOR_TOKEN,
|
||||
if (StringUtils.hasText(actorToken) && parameters.get(OAuth2ParameterNames.ACTOR_TOKEN).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
@@ -159,9 +147,7 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
|
||||
String actorTokenType = parameters.getFirst(OAuth2ParameterNames.ACTOR_TOKEN_TYPE);
|
||||
if (StringUtils.hasText(actorTokenType)) {
|
||||
if (parameters.get(OAuth2ParameterNames.ACTOR_TOKEN_TYPE).size() != 1) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.ACTOR_TOKEN_TYPE,
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN_TYPE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
@@ -169,28 +155,23 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
|
||||
}
|
||||
|
||||
if (!StringUtils.hasText(actorToken) && StringUtils.hasText(actorTokenType)) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.ACTOR_TOKEN,
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
} else if (StringUtils.hasText(actorToken) && !StringUtils.hasText(actorTokenType)) {
|
||||
OAuth2EndpointUtils.throwError(
|
||||
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||
OAuth2ParameterNames.ACTOR_TOKEN_TYPE,
|
||||
}
|
||||
else if (StringUtils.hasText(actorToken) && !StringUtils.hasText(actorTokenType)) {
|
||||
OAuth2EndpointUtils.throwError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.ACTOR_TOKEN_TYPE,
|
||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI);
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = new HashMap<>();
|
||||
parameters.forEach((key, value) -> {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.RESOURCE) &&
|
||||
!key.equals(OAuth2ParameterNames.AUDIENCE) &&
|
||||
!key.equals(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.SUBJECT_TOKEN) &&
|
||||
!key.equals(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.ACTOR_TOKEN) &&
|
||||
!key.equals(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) &&
|
||||
!key.equals(OAuth2ParameterNames.SCOPE)) {
|
||||
if (!key.equals(OAuth2ParameterNames.GRANT_TYPE) && !key.equals(OAuth2ParameterNames.RESOURCE)
|
||||
&& !key.equals(OAuth2ParameterNames.AUDIENCE)
|
||||
&& !key.equals(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE)
|
||||
&& !key.equals(OAuth2ParameterNames.SUBJECT_TOKEN)
|
||||
&& !key.equals(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE)
|
||||
&& !key.equals(OAuth2ParameterNames.ACTOR_TOKEN)
|
||||
&& !key.equals(OAuth2ParameterNames.ACTOR_TOKEN_TYPE) && !key.equals(OAuth2ParameterNames.SCOPE)) {
|
||||
additionalParameters.put(key, (value.size() == 1) ? value.get(0) : value.toArray(new String[0]));
|
||||
}
|
||||
});
|
||||
@@ -219,7 +200,8 @@ public final class OAuth2TokenExchangeAuthenticationConverter implements Authent
|
||||
try {
|
||||
URI validUri = new URI(uri);
|
||||
return validUri.isAbsolute() && validUri.getFragment() == null;
|
||||
} catch (URISyntaxException ex) {
|
||||
}
|
||||
catch (URISyntaxException ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,9 +33,10 @@ import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Attempts to extract a client {@code X509Certificate} chain from {@link HttpServletRequest}
|
||||
* and then converts to an {@link OAuth2ClientAuthenticationToken} used for authenticating the client
|
||||
* using the {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
* Attempts to extract a client {@code X509Certificate} chain from
|
||||
* {@link HttpServletRequest} and then converts to an
|
||||
* {@link OAuth2ClientAuthenticationToken} used for authenticating the client using the
|
||||
* {@code tls_client_auth} or {@code self_signed_tls_client_auth} method.
|
||||
*
|
||||
* @author Joe Grandja
|
||||
* @since 1.3
|
||||
@@ -48,8 +49,8 @@ public final class X509ClientCertificateAuthenticationConverter implements Authe
|
||||
@Nullable
|
||||
@Override
|
||||
public Authentication convert(HttpServletRequest request) {
|
||||
X509Certificate[] clientCertificateChain =
|
||||
(X509Certificate[]) request.getAttribute("jakarta.servlet.request.X509Certificate");
|
||||
X509Certificate[] clientCertificateChain = (X509Certificate[]) request
|
||||
.getAttribute("jakarta.servlet.request.X509Certificate");
|
||||
if (clientCertificateChain == null || clientCertificateChain.length == 0) {
|
||||
return null;
|
||||
}
|
||||
@@ -58,21 +59,18 @@ public final class X509ClientCertificateAuthenticationConverter implements Authe
|
||||
|
||||
// client_id (REQUIRED)
|
||||
String clientId = parameters.getFirst(OAuth2ParameterNames.CLIENT_ID);
|
||||
if (!StringUtils.hasText(clientId) ||
|
||||
parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||
if (!StringUtils.hasText(clientId) || parameters.get(OAuth2ParameterNames.CLIENT_ID).size() != 1) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
Map<String, Object> additionalParameters = OAuth2EndpointUtils.getParametersIfMatchesAuthorizationCodeGrantRequest(
|
||||
request, OAuth2ParameterNames.CLIENT_ID);
|
||||
Map<String, Object> additionalParameters = OAuth2EndpointUtils
|
||||
.getParametersIfMatchesAuthorizationCodeGrantRequest(request, OAuth2ParameterNames.CLIENT_ID);
|
||||
|
||||
ClientAuthenticationMethod clientAuthenticationMethod =
|
||||
clientCertificateChain.length == 1 ?
|
||||
ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH :
|
||||
ClientAuthenticationMethod.TLS_CLIENT_AUTH;
|
||||
ClientAuthenticationMethod clientAuthenticationMethod = clientCertificateChain.length == 1
|
||||
? ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH : ClientAuthenticationMethod.TLS_CLIENT_AUTH;
|
||||
|
||||
return new OAuth2ClientAuthenticationToken(clientId, clientAuthenticationMethod,
|
||||
clientCertificateChain, additionalParameters);
|
||||
return new OAuth2ClientAuthenticationToken(clientId, clientAuthenticationMethod, clientCertificateChain,
|
||||
additionalParameters);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -48,23 +48,23 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
@Test
|
||||
public void buildWhenAllClaimsProvidedThenCreated() {
|
||||
OAuth2AuthorizationServerMetadata authorizationServerMetadata = OAuth2AuthorizationServerMetadata.builder()
|
||||
.issuer("https://example.com")
|
||||
.authorizationEndpoint("https://example.com/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/oauth2/token")
|
||||
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.jwkSetUrl("https://example.com/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenRevocationEndpoint("https://example.com/oauth2/revoke")
|
||||
.tokenRevocationEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.tokenIntrospectionEndpoint("https://example.com/oauth2/introspect")
|
||||
.tokenIntrospectionEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true)
|
||||
.claim("a-claim", "a-value")
|
||||
.build();
|
||||
.issuer("https://example.com")
|
||||
.authorizationEndpoint("https://example.com/oauth2/authorize")
|
||||
.tokenEndpoint("https://example.com/oauth2/token")
|
||||
.tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.jwkSetUrl("https://example.com/oauth2/jwks")
|
||||
.scope("openid")
|
||||
.responseType("code")
|
||||
.grantType("authorization_code")
|
||||
.grantType("client_credentials")
|
||||
.tokenRevocationEndpoint("https://example.com/oauth2/revoke")
|
||||
.tokenRevocationEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.tokenIntrospectionEndpoint("https://example.com/oauth2/introspect")
|
||||
.tokenIntrospectionEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue())
|
||||
.codeChallengeMethod("S256")
|
||||
.tlsClientCertificateBoundAccessTokens(true)
|
||||
.claim("a-claim", "a-value")
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerMetadata.getIssuer()).isEqualTo(url("https://example.com"));
|
||||
assertThat(authorizationServerMetadata.getAuthorizationEndpoint())
|
||||
|
||||
@@ -92,8 +92,8 @@ public class TestOAuth2Authorizations {
|
||||
.attribute(Principal.class.getName(),
|
||||
new TestingAuthenticationToken("principal", null, "ROLE_A", "ROLE_B"));
|
||||
if (accessToken != null) {
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken(
|
||||
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now(),
|
||||
Instant.now().plus(1, ChronoUnit.HOURS));
|
||||
builder
|
||||
.token(accessToken, (metadata) -> metadata.putAll(tokenMetadata(registeredClient, accessTokenClaims)))
|
||||
.refreshToken(refreshToken);
|
||||
@@ -102,7 +102,8 @@ public class TestOAuth2Authorizations {
|
||||
return builder;
|
||||
}
|
||||
|
||||
private static Map<String, Object> tokenMetadata(RegisteredClient registeredClient, Map<String, Object> tokenClaims) {
|
||||
private static Map<String, Object> tokenMetadata(RegisteredClient registeredClient,
|
||||
Map<String, Object> tokenClaims) {
|
||||
Map<String, Object> tokenMetadata = new HashMap<>();
|
||||
OAuth2TokenFormat accessTokenFormat = registeredClient.getTokenSettings().getAccessTokenFormat();
|
||||
tokenMetadata.put(OAuth2TokenFormat.class.getName(), accessTokenFormat.getValue());
|
||||
|
||||
@@ -34,39 +34,43 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* @author Dmitriy Dubson
|
||||
*/
|
||||
public class OAuth2AccessTokenAuthenticationContextTests {
|
||||
|
||||
private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient).build();
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
|
||||
private final OAuth2AccessTokenAuthenticationToken accessTokenAuthenticationToken =
|
||||
new OAuth2AccessTokenAuthenticationToken(this.registeredClient, this.clientPrincipal,
|
||||
this.authorization.getAccessToken().getToken(), this.authorization.getRefreshToken().getToken());
|
||||
|
||||
private final OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(this.registeredClient)
|
||||
.build();
|
||||
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
|
||||
|
||||
private final OAuth2AccessTokenAuthenticationToken accessTokenAuthenticationToken = new OAuth2AccessTokenAuthenticationToken(
|
||||
this.registeredClient, this.clientPrincipal, this.authorization.getAccessToken().getToken(),
|
||||
this.authorization.getRefreshToken().getToken());
|
||||
|
||||
@Test
|
||||
public void withWhenAuthenticationNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> OAuth2AccessTokenAuthenticationContext.with(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authentication cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authentication cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setWhenValueNullThenThrowIllegalArgumentException() {
|
||||
OAuth2AccessTokenAuthenticationContext.Builder builder =
|
||||
OAuth2AccessTokenAuthenticationContext.with(this.accessTokenAuthenticationToken);
|
||||
OAuth2AccessTokenAuthenticationContext.Builder builder = OAuth2AccessTokenAuthenticationContext
|
||||
.with(this.accessTokenAuthenticationToken);
|
||||
|
||||
assertThatThrownBy(() -> builder.accessTokenResponse(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
assertThatThrownBy(() -> builder.accessTokenResponse(null)).isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("value cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
|
||||
OAuth2AccessTokenResponse.Builder accessTokenResponseBuilder =
|
||||
OAuth2AccessTokenResponse.withToken(this.accessTokenAuthenticationToken.getAccessToken().getTokenValue());
|
||||
OAuth2AccessTokenAuthenticationContext context =
|
||||
OAuth2AccessTokenAuthenticationContext.with(this.accessTokenAuthenticationToken)
|
||||
.accessTokenResponse(accessTokenResponseBuilder)
|
||||
.build();
|
||||
OAuth2AccessTokenResponse.Builder accessTokenResponseBuilder = OAuth2AccessTokenResponse
|
||||
.withToken(this.accessTokenAuthenticationToken.getAccessToken().getTokenValue());
|
||||
OAuth2AccessTokenAuthenticationContext context = OAuth2AccessTokenAuthenticationContext
|
||||
.with(this.accessTokenAuthenticationToken)
|
||||
.accessTokenResponse(accessTokenResponseBuilder)
|
||||
.build();
|
||||
|
||||
assertThat(context.<Authentication>getAuthentication()).isEqualTo(this.accessTokenAuthenticationToken);
|
||||
assertThat(context.getAccessTokenResponse()).isEqualTo(accessTokenResponseBuilder);
|
||||
|
||||
@@ -146,8 +146,8 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
@Test
|
||||
public void setAuthorizationConsentRequiredWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthorizationConsentRequired(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentRequired cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationConsentRequired cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -486,23 +486,24 @@ public class OAuth2AuthorizationCodeRequestAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthorizationConsentRequiredThenUsed() {
|
||||
@SuppressWarnings("unchecked")
|
||||
Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired = mock(Predicate.class);
|
||||
Predicate<OAuth2AuthorizationCodeRequestAuthenticationContext> authorizationConsentRequired = mock(
|
||||
Predicate.class);
|
||||
this.authenticationProvider.setAuthorizationConsentRequired(authorizationConsentRequired);
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
String redirectUri = registeredClient.getRedirectUris().toArray(new String[0])[1];
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication =
|
||||
new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal,
|
||||
redirectUri, STATE, registeredClient.getScopes(), null);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authentication = new OAuth2AuthorizationCodeRequestAuthenticationToken(
|
||||
AUTHORIZATION_URI, registeredClient.getClientId(), principal, redirectUri, STATE,
|
||||
registeredClient.getScopes(), null);
|
||||
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult =
|
||||
(OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2AuthorizationCodeRequestAuthenticationToken authenticationResult = (OAuth2AuthorizationCodeRequestAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
|
||||
assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication, authenticationResult);
|
||||
assertAuthorizationCodeRequestWithAuthorizationCodeResult(registeredClient, authentication,
|
||||
authenticationResult);
|
||||
|
||||
verify(authorizationConsentRequired).test(any());
|
||||
}
|
||||
|
||||
@@ -145,8 +145,8 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
@Test
|
||||
public void setAuthenticationValidatorWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setAuthenticationValidator(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationValidator cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authenticationValidator cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -325,10 +325,10 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenCustomAuthenticationValidatorThenUsed() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2ClientCredentialsAuthenticationToken authentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, registeredClient.getScopes(), null);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2ClientCredentialsAuthenticationToken authentication = new OAuth2ClientCredentialsAuthenticationToken(
|
||||
clientPrincipal, registeredClient.getScopes(), null);
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
Consumer<OAuth2ClientCredentialsAuthenticationContext> authenticationValidator = mock(Consumer.class);
|
||||
|
||||
@@ -70,15 +70,24 @@ import static org.mockito.Mockito.when;
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2");
|
||||
|
||||
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1",
|
||||
"https://mydomain.com/resource2");
|
||||
|
||||
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2");
|
||||
|
||||
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
|
||||
|
||||
private static final String ACTOR_TOKEN = "JlNE_xR1f";
|
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
|
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
|
||||
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
private OAuth2TokenGenerator<OAuth2Token> tokenGenerator;
|
||||
|
||||
private OAuth2TokenExchangeAuthenticationProvider authenticationProvider;
|
||||
|
||||
@BeforeEach
|
||||
@@ -168,7 +177,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenSubjectTokenNotFoundThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(null);
|
||||
// @formatter:off
|
||||
@@ -187,10 +197,12 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenSubjectTokenNotActiveThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createExpiredAccessToken(SUBJECT_TOKEN)).build();
|
||||
.token(createExpiredAccessToken(SUBJECT_TOKEN))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
@@ -208,10 +220,12 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenSubjectTokenTypeJwtAndSubjectTokenFormatReferenceThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient);
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build();
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(authorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
@@ -229,7 +243,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenSubjectPrincipalNullThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
// @formatter:off
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
@@ -254,12 +269,14 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenActorTokenNotFoundThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, (OAuth2Authorization) null);
|
||||
.token(createAccessToken(SUBJECT_TOKEN))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, (OAuth2Authorization) null);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -277,14 +294,17 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenActorTokenNotActiveThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN)).build();
|
||||
.token(createAccessToken(SUBJECT_TOKEN))
|
||||
.build();
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createExpiredAccessToken(ACTOR_TOKEN)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createExpiredAccessToken(ACTOR_TOKEN))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -302,14 +322,17 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenActorTokenTypeJwtAndActorTokenFormatReferenceThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createJwtRequest(registeredClient);
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.SELF_CONTAINED)).build();
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withTokenFormat(OAuth2TokenFormat.SELF_CONTAINED))
|
||||
.build();
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(ACTOR_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createAccessToken(ACTOR_TOKEN), withTokenFormat(OAuth2TokenFormat.REFERENCE))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -327,7 +350,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenMayActAndActorIssClaimNotAuthorizedThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
|
||||
OAuth2TokenClaimNames.SUB, "actor");
|
||||
@@ -339,9 +363,10 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "invalid-issuer",
|
||||
OAuth2TokenClaimNames.SUB, "actor");
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -359,7 +384,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenMayActAndActorSubClaimNotAuthorizedThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
|
||||
OAuth2TokenClaimNames.SUB, "actor");
|
||||
@@ -371,9 +397,10 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
Map<String, Object> actorTokenClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer", OAuth2TokenClaimNames.SUB,
|
||||
"invalid-actor");
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createAccessToken(ACTOR_TOKEN), withClaims(actorTokenClaims))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -391,7 +418,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenMayActAndImpersonationThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
|
||||
Map<String, String> authorizedActorClaims = Map.of(OAuth2TokenClaimNames.ISS, "issuer",
|
||||
OAuth2TokenClaimNames.SUB, "actor");
|
||||
@@ -400,8 +428,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", authorizedActorClaims)))
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization);
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -418,15 +446,18 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenInvalidScopeInRequestThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient,
|
||||
Set.of("invalid"));
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN)).build();
|
||||
.token(createAccessToken(SUBJECT_TOKEN))
|
||||
.build();
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(ACTOR_TOKEN)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createAccessToken(ACTOR_TOKEN))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -444,14 +475,18 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenInvalidScopeInSubjectAuthorizationThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient, Set.of());
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN)).authorizedScopes(Set.of("invalid")).build();
|
||||
.token(createAccessToken(SUBJECT_TOKEN))
|
||||
.authorizedScopes(Set.of("invalid"))
|
||||
.build();
|
||||
OAuth2Authorization actorAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(ACTOR_TOKEN)).build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
.token(createAccessToken(ACTOR_TOKEN))
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
// @formatter:off
|
||||
assertThatExceptionOfType(OAuth2AuthenticationException.class)
|
||||
.isThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
@@ -469,7 +504,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenNoActorTokenAndValidTokenExchangeThenReturnAccessTokenForImpersonation() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
|
||||
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
|
||||
// @formatter:off
|
||||
@@ -478,19 +514,19 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
.attribute(Principal.class.getName(), userPrincipal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization);
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization);
|
||||
OAuth2AccessToken accessToken = createAccessToken("token-value");
|
||||
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult =
|
||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
|
||||
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
||||
assertThat(authenticationResult.getRefreshToken()).isNull();
|
||||
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
|
||||
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
|
||||
@@ -521,32 +557,33 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenNoActorTokenAndPreviousActorThenReturnAccessTokenForImpersonation() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createImpersonationRequest(registeredClient);
|
||||
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
|
||||
OAuth2TokenExchangeActor previousActor = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
|
||||
OAuth2TokenClaimNames.SUB, "actor"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal =
|
||||
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(previousActor));
|
||||
OAuth2TokenExchangeActor previousActor = new OAuth2TokenExchangeActor(
|
||||
Map.of(OAuth2TokenClaimNames.ISS, "issuer1", OAuth2TokenClaimNames.SUB, "actor"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
userPrincipal, List.of(previousActor));
|
||||
// @formatter:off
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN))
|
||||
.attribute(Principal.class.getName(), subjectPrincipal)
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization);
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization);
|
||||
OAuth2AccessToken accessToken = createAccessToken("token-value");
|
||||
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult =
|
||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
|
||||
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
||||
assertThat(authenticationResult.getRefreshToken()).isNull();
|
||||
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
|
||||
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
|
||||
@@ -577,15 +614,16 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
@Test
|
||||
public void authenticateWhenActorTokenAndValidTokenExchangeThenReturnAccessTokenForDelegation() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = createDelegationRequest(registeredClient);
|
||||
TestingAuthenticationToken userPrincipal = new TestingAuthenticationToken("user", null, "ROLE_USER");
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer1",
|
||||
OAuth2TokenClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(OAuth2TokenClaimNames.ISS, "issuer2",
|
||||
OAuth2TokenClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal =
|
||||
new OAuth2TokenExchangeCompositeAuthenticationToken(userPrincipal, List.of(actor1));
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(
|
||||
Map.of(OAuth2TokenClaimNames.ISS, "issuer1", OAuth2TokenClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(
|
||||
Map.of(OAuth2TokenClaimNames.ISS, "issuer2", OAuth2TokenClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken subjectPrincipal = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
userPrincipal, List.of(actor1));
|
||||
// @formatter:off
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.token(createAccessToken(SUBJECT_TOKEN), withClaims(Map.of("may_act", actor2.getClaims())))
|
||||
@@ -596,19 +634,19 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
.token(createAccessToken(ACTOR_TOKEN), withClaims(actor2.getClaims()))
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class))).thenReturn(
|
||||
subjectAuthorization, actorAuthorization);
|
||||
when(this.authorizationService.findByToken(anyString(), any(OAuth2TokenType.class)))
|
||||
.thenReturn(subjectAuthorization, actorAuthorization);
|
||||
OAuth2AccessToken accessToken = createAccessToken("token-value");
|
||||
when(this.tokenGenerator.generate(any(OAuth2TokenContext.class))).thenReturn(accessToken);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult =
|
||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2AccessTokenAuthenticationToken authenticationResult = (OAuth2AccessTokenAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getPrincipal()).isEqualTo(authentication.getPrincipal());
|
||||
assertThat(authenticationResult.getAccessToken()).isEqualTo(accessToken);
|
||||
assertThat(authenticationResult.getRefreshToken()).isNull();
|
||||
assertThat(authenticationResult.getAdditionalParameters()).hasSize(1);
|
||||
assertThat(authenticationResult.getAdditionalParameters().get(OAuth2ParameterNames.ISSUED_TOKEN_TYPE))
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
.isEqualTo(JWT_TOKEN_TYPE_VALUE);
|
||||
|
||||
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
|
||||
@@ -638,8 +676,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
assertThat(authorization.getAccessToken().getToken()).isEqualTo(accessToken);
|
||||
assertThat(authorization.getRefreshToken()).isNull();
|
||||
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken authorizationPrincipal =
|
||||
authorization.getAttribute(Principal.class.getName());
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken authorizationPrincipal = authorization
|
||||
.getAttribute(Principal.class.getName());
|
||||
assertThat(authorizationPrincipal).isNotNull();
|
||||
assertThat(authorizationPrincipal.getSubject()).isSameAs(subjectPrincipal.getSubject());
|
||||
assertThat(authorizationPrincipal.getActors()).containsExactly(actor2, actor1);
|
||||
@@ -664,7 +702,8 @@ public class OAuth2TokenExchangeAuthenticationProviderTests {
|
||||
clientPrincipal, ACTOR_TOKEN, ACCESS_TOKEN_TYPE_VALUE, RESOURCES, AUDIENCES, requestedScopes, null);
|
||||
}
|
||||
|
||||
private static OAuth2TokenExchangeAuthenticationToken createImpersonationRequest(RegisteredClient registeredClient) {
|
||||
private static OAuth2TokenExchangeAuthenticationToken createImpersonationRequest(
|
||||
RegisteredClient registeredClient) {
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, null);
|
||||
return new OAuth2TokenExchangeAuthenticationToken(JWT_TOKEN_TYPE_VALUE, SUBJECT_TOKEN, ACCESS_TOKEN_TYPE_VALUE,
|
||||
|
||||
@@ -35,17 +35,27 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2TokenExchangeAuthenticationTokenTests {
|
||||
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1", "https://mydomain.com/resource2");
|
||||
|
||||
private static final Set<String> RESOURCES = Set.of("https://mydomain.com/resource1",
|
||||
"https://mydomain.com/resource2");
|
||||
|
||||
private static final Set<String> AUDIENCES = Set.of("audience1", "audience2");
|
||||
|
||||
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
|
||||
|
||||
private static final String ACTOR_TOKEN = "JlNE_xR1f";
|
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
|
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
|
||||
|
||||
private RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
|
||||
|
||||
private OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(this.registeredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
|
||||
|
||||
private Set<String> scopes = Collections.singleton("scope1");
|
||||
|
||||
private Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||
|
||||
@Test
|
||||
|
||||
@@ -57,8 +57,8 @@ public class OAuth2TokenExchangeCompositeAuthenticationTokenTests {
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of("claim1", "value1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of("claim2", "value2"));
|
||||
List<OAuth2TokenExchangeActor> actors = List.of(actor1, actor2);
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken authentication =
|
||||
new OAuth2TokenExchangeCompositeAuthenticationToken(subject, actors);
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken authentication = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
subject, actors);
|
||||
assertThat(authentication.getSubject()).isEqualTo(subject);
|
||||
assertThat(authentication.getActors()).isEqualTo(actors);
|
||||
}
|
||||
|
||||
@@ -65,18 +65,27 @@ import static org.mockito.Mockito.when;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class X509ClientCertificateAuthenticationProviderTests {
|
||||
// See RFC 7636: Appendix B. Example for the S256 code_challenge_method
|
||||
|
||||
// See RFC 7636: Appendix B. Example for the S256 code_challenge_method
|
||||
// https://tools.ietf.org/html/rfc7636#appendix-B
|
||||
private static final String S256_CODE_VERIFIER = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk";
|
||||
|
||||
private static final String S256_CODE_CHALLENGE = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM";
|
||||
|
||||
private static final String AUTHORIZATION_CODE = "code";
|
||||
|
||||
private static final OAuth2TokenType AUTHORIZATION_CODE_TOKEN_TYPE = new OAuth2TokenType(OAuth2ParameterNames.CODE);
|
||||
|
||||
private JWKSet selfSignedCertificateJwkSet;
|
||||
|
||||
private MockWebServer server;
|
||||
|
||||
private String clientJwkSetUrl;
|
||||
|
||||
private RegisteredClientRepository registeredClientRepository;
|
||||
|
||||
private OAuth2AuthorizationService authorizationService;
|
||||
|
||||
private X509ClientCertificateAuthenticationProvider authenticationProvider;
|
||||
|
||||
@BeforeEach
|
||||
@@ -102,8 +111,8 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
|
||||
this.registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||
this.authorizationService = mock(OAuth2AuthorizationService.class);
|
||||
this.authenticationProvider = new X509ClientCertificateAuthenticationProvider(
|
||||
this.registeredClientRepository, this.authorizationService);
|
||||
this.authenticationProvider = new X509ClientCertificateAuthenticationProvider(this.registeredClientRepository,
|
||||
this.authorizationService);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -114,22 +123,22 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
@Test
|
||||
public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new X509ClientCertificateAuthenticationProvider(null, this.authorizationService))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClientRepository cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("registeredClientRepository cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> new X509ClientCertificateAuthenticationProvider(this.registeredClientRepository, null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("authorizationService cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void setCertificateVerifierWhenNullThenThrowIllegalArgumentException() {
|
||||
assertThatThrownBy(() -> this.authenticationProvider.setCertificateVerifier(null))
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("certificateVerifier cannot be null");
|
||||
.isInstanceOf(IllegalArgumentException.class)
|
||||
.hasMessage("certificateVerifier cannot be null");
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -145,36 +154,36 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId() + "-invalid", ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID);
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains(OAuth2ParameterNames.CLIENT_ID);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenUnsupportedClientAuthenticationMethodThenThrowOAuth2AuthenticationException() {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("authentication_method");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("authentication_method");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -185,17 +194,17 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH, null, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("credentials");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("credentials");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -211,18 +220,18 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("x509_certificate_subject_dn");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("x509_certificate_subject_dn");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -238,20 +247,21 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null);
|
||||
|
||||
OAuth2ClientAuthenticationToken authenticationResult =
|
||||
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -267,18 +277,19 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
// PKI Certificate will have different issuer
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null); // PKI Certificate will have different issuer
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("x509_certificate_issuer");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("x509_certificate_issuer");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -289,18 +300,18 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("client_jwk_set_url");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("client_jwk_set_url");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -316,18 +327,18 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("jwk_set_uri");
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains("jwk_set_uri");
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -352,7 +363,8 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenSelfSignedX509CertificateJwkSetResponseNoMatchingKeysThenThrowOAuth2AuthenticationException() throws Exception {
|
||||
public void authenticateWhenSelfSignedX509CertificateJwkSetResponseNoMatchingKeysThenThrowOAuth2AuthenticationException()
|
||||
throws Exception {
|
||||
// @formatter:off
|
||||
X509Certificate pkiCertificate = TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE[0];
|
||||
RSAKey pkiRSAKey = new RSAKey.Builder((RSAPublicKey) pkiCertificate.getPublicKey())
|
||||
@@ -396,18 +408,18 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null);
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains(expectedErrorDescription);
|
||||
});
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.satisfies(error -> {
|
||||
assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT);
|
||||
assertThat(error.getDescription()).contains(expectedErrorDescription);
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -423,20 +435,22 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE, null);
|
||||
|
||||
OAuth2ClientAuthenticationToken authenticationResult =
|
||||
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE);
|
||||
assertThat(authenticationResult.getCredentials())
|
||||
.isEqualTo(TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -452,13 +466,13 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||
.thenReturn(registeredClient);
|
||||
.thenReturn(registeredClient);
|
||||
|
||||
OAuth2Authorization authorization = TestOAuth2Authorizations
|
||||
.authorization(registeredClient, createPkceAuthorizationParametersS256())
|
||||
.build();
|
||||
.authorization(registeredClient, createPkceAuthorizationParametersS256())
|
||||
.build();
|
||||
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)))
|
||||
.thenReturn(authorization);
|
||||
.thenReturn(authorization);
|
||||
|
||||
Map<String, Object> parameters = createPkceTokenParameters(S256_CODE_VERIFIER);
|
||||
|
||||
@@ -466,15 +480,16 @@ public class X509ClientCertificateAuthenticationProviderTests {
|
||||
registeredClient.getClientId(), ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE, parameters);
|
||||
|
||||
OAuth2ClientAuthenticationToken authenticationResult =
|
||||
(OAuth2ClientAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||
OAuth2ClientAuthenticationToken authenticationResult = (OAuth2ClientAuthenticationToken) this.authenticationProvider
|
||||
.authenticate(authentication);
|
||||
|
||||
verify(this.authorizationService).findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE));
|
||||
assertThat(authenticationResult.isAuthenticated()).isTrue();
|
||||
assertThat(authenticationResult.getPrincipal().toString()).isEqualTo(registeredClient.getClientId());
|
||||
assertThat(authenticationResult.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
assertThat(authenticationResult.getRegisteredClient()).isEqualTo(registeredClient);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
assertThat(authenticationResult.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
}
|
||||
|
||||
private static Map<String, Object> createPkceAuthorizationParametersS256() {
|
||||
|
||||
@@ -37,10 +37,15 @@ import static org.assertj.core.api.Assertions.assertThat;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
class AuthorizationServerContextFilterTests {
|
||||
|
||||
private static final String SCHEME = "https";
|
||||
|
||||
private static final String HOST = "example.com";
|
||||
|
||||
private static final int PORT = 8443;
|
||||
|
||||
private static final String DEFAULT_ISSUER = SCHEME + "://" + HOST + ":" + PORT;
|
||||
|
||||
private AuthorizationServerContextFilter filter;
|
||||
|
||||
@Test
|
||||
@@ -60,17 +65,17 @@ class AuthorizationServerContextFilterTests {
|
||||
@Test
|
||||
public void doFilterWhenCustomEndpointsThenIssuerResolved() throws Exception {
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
|
||||
.authorizationEndpoint("/oauth2/v1/authorize")
|
||||
.deviceAuthorizationEndpoint("/oauth2/v1/device_authorization")
|
||||
.deviceVerificationEndpoint("/oauth2/v1/device_verification")
|
||||
.tokenEndpoint("/oauth2/v1/token")
|
||||
.jwkSetEndpoint("/oauth2/v1/jwks")
|
||||
.tokenRevocationEndpoint("/oauth2/v1/revoke")
|
||||
.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
|
||||
.oidcClientRegistrationEndpoint("/connect/v1/register")
|
||||
.oidcUserInfoEndpoint("/v1/userinfo")
|
||||
.oidcLogoutEndpoint("/connect/v1/logout")
|
||||
.build();
|
||||
.authorizationEndpoint("/oauth2/v1/authorize")
|
||||
.deviceAuthorizationEndpoint("/oauth2/v1/device_authorization")
|
||||
.deviceVerificationEndpoint("/oauth2/v1/device_verification")
|
||||
.tokenEndpoint("/oauth2/v1/token")
|
||||
.jwkSetEndpoint("/oauth2/v1/jwks")
|
||||
.tokenRevocationEndpoint("/oauth2/v1/revoke")
|
||||
.tokenIntrospectionEndpoint("/oauth2/v1/introspect")
|
||||
.oidcClientRegistrationEndpoint("/connect/v1/register")
|
||||
.oidcUserInfoEndpoint("/v1/userinfo")
|
||||
.oidcLogoutEndpoint("/connect/v1/logout")
|
||||
.build();
|
||||
this.filter = new AuthorizationServerContextFilter(authorizationServerSettings);
|
||||
|
||||
String issuerPath = "/issuer2";
|
||||
@@ -101,8 +106,8 @@ class AuthorizationServerContextFilterTests {
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
AtomicReference<String> resolvedIssuer = new AtomicReference<>();
|
||||
FilterChain filterChain = (req, resp) ->
|
||||
resolvedIssuer.set(AuthorizationServerContextHolder.getContext().getIssuer());
|
||||
FilterChain filterChain = (req, resp) -> resolvedIssuer
|
||||
.set(AuthorizationServerContextHolder.getContext().getIssuer());
|
||||
|
||||
this.filter.doFilter(request, response, filterChain);
|
||||
|
||||
|
||||
@@ -55,9 +55,13 @@ import static org.mockito.Mockito.when;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
class DefaultOAuth2TokenCustomizersTests {
|
||||
|
||||
private static final String ISSUER_1 = "issuer-1";
|
||||
|
||||
private static final String ISSUER_2 = "issuer-2";
|
||||
|
||||
private JwsHeader.Builder jwsHeaderBuilder;
|
||||
|
||||
private JwtClaimsSet.Builder jwtClaimsBuilder;
|
||||
|
||||
@BeforeEach
|
||||
@@ -131,10 +135,10 @@ class DefaultOAuth2TokenCustomizersTests {
|
||||
when(tokenExchangeAuthentication.getAudiences()).thenReturn(Collections.emptySet());
|
||||
|
||||
Authentication subject = new TestingAuthenticationToken("subject", null);
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(Map.of(JwtClaimNames.ISS, ISSUER_1,
|
||||
JwtClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(Map.of(JwtClaimNames.ISS, ISSUER_2,
|
||||
JwtClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeActor actor1 = new OAuth2TokenExchangeActor(
|
||||
Map.of(JwtClaimNames.ISS, ISSUER_1, JwtClaimNames.SUB, "actor1"));
|
||||
OAuth2TokenExchangeActor actor2 = new OAuth2TokenExchangeActor(
|
||||
Map.of(JwtClaimNames.ISS, ISSUER_2, JwtClaimNames.SUB, "actor2"));
|
||||
OAuth2TokenExchangeCompositeAuthenticationToken principal = new OAuth2TokenExchangeCompositeAuthenticationToken(
|
||||
subject, List.of(actor1, actor2));
|
||||
|
||||
@@ -177,11 +181,10 @@ class DefaultOAuth2TokenCustomizersTests {
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
|
||||
ClientAuthenticationMethod.TLS_CLIENT_AUTH, TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken(
|
||||
clientPrincipal, null, null);
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
@@ -215,11 +218,11 @@ class DefaultOAuth2TokenCustomizersTests {
|
||||
)
|
||||
.build();
|
||||
// @formatter:on
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
|
||||
ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH,
|
||||
TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication =
|
||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
|
||||
OAuth2ClientCredentialsAuthenticationToken clientCredentialsAuthentication = new OAuth2ClientCredentialsAuthenticationToken(
|
||||
clientPrincipal, null, null);
|
||||
// @formatter:off
|
||||
JwtEncodingContext tokenContext = JwtEncodingContext.with(this.jwsHeaderBuilder, this.jwtClaimsBuilder)
|
||||
.tokenType(OAuth2TokenType.ACCESS_TOKEN)
|
||||
|
||||
@@ -82,13 +82,13 @@ public class JwkSetTests {
|
||||
public static void init() {
|
||||
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
|
||||
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
|
||||
db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(EmbeddedDatabaseType.HSQL)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
|
||||
.build();
|
||||
db = new EmbeddedDatabaseBuilder().generateUniqueName(true)
|
||||
.setType(EmbeddedDatabaseType.HSQL)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.addScript("org/springframework/security/oauth2/server/authorization/oauth2-authorization-schema.sql")
|
||||
.addScript(
|
||||
"org/springframework/security/oauth2/server/authorization/client/oauth2-registered-client-schema.sql")
|
||||
.build();
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
@@ -188,7 +188,10 @@ public class JwkSetTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().jwkSetEndpoint("/test/jwks").multipleIssuersAllowed(true).build();
|
||||
return AuthorizationServerSettings.builder()
|
||||
.jwkSetEndpoint("/test/jwks")
|
||||
.multipleIssuersAllowed(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -918,30 +918,33 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI))
|
||||
.queryParams(getAuthorizationRequestParameters(registeredClient))
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
MvcResult mvcResult = this.mvc
|
||||
.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI))
|
||||
.queryParams(getAuthorizationRequestParameters(registeredClient))
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE, S256_CODE_CHALLENGE)
|
||||
.queryParam(PkceParameterNames.CODE_CHALLENGE_METHOD, "S256")
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
String authorizationCode = extractParameterFromRedirectUri(mvcResult.getResponse().getRedirectedUrl(), "code");
|
||||
OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode, AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
OAuth2Authorization authorizationCodeAuthorization = this.authorizationService.findByToken(authorizationCode,
|
||||
AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
this.mvc
|
||||
.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.params(getTokenRequestParameters(registeredClient, authorizationCodeAuthorization))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.param(PkceParameterNames.CODE_VERIFIER, S256_CODE_VERIFIER))
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").doesNotExist())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andReturn();
|
||||
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||
.andExpect(jsonPath("$.refresh_token").doesNotExist())
|
||||
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||
.andReturn();
|
||||
|
||||
ArgumentCaptor<OAuth2TokenContext> tokenContextCaptor = ArgumentCaptor.forClass(OAuth2TokenContext.class);
|
||||
verify(tokenGenerator).generate(tokenContextCaptor.capture());
|
||||
@@ -1333,7 +1336,8 @@ public class OAuth2AuthorizationCodeGrantTests {
|
||||
|
||||
@EnableWebSecurity
|
||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||
static class AuthorizationServerConfigurationWithMultipleIssuersAllowed extends AuthorizationServerConfigurationWithTokenGenerator {
|
||||
static class AuthorizationServerConfigurationWithMultipleIssuersAllowed
|
||||
extends AuthorizationServerConfigurationWithTokenGenerator {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
|
||||
@@ -110,13 +110,14 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(ISSUER))
|
||||
.andReturn();
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(ISSUER))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath() throws Exception {
|
||||
public void requestWhenAuthorizationServerMetadataRequestIncludesIssuerPathThenMetadataResponseHasIssuerPath()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire();
|
||||
|
||||
String host = "https://example.com:8443";
|
||||
@@ -124,23 +125,23 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
String issuerPath = "/issuer1";
|
||||
String issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
|
||||
issuerPath = "/path1/issuer2";
|
||||
issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
|
||||
issuerPath = "/path1/path2/issuer3";
|
||||
issuer = host.concat(issuerPath);
|
||||
this.mvc.perform(get(host.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI).concat(issuerPath)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath("issuer").value(issuer))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
// gh-616
|
||||
@@ -150,9 +151,9 @@ public class OAuth2AuthorizationServerMetadataTests {
|
||||
this.spring.register(AuthorizationServerConfigurationWithMetadataCustomizer.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems("scope1", "scope2")));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems("scope1", "scope2")));
|
||||
}
|
||||
|
||||
@EnableWebSecurity
|
||||
|
||||
@@ -298,14 +298,14 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
// @formatter:on
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
|
||||
.with(x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
|
||||
this.mvc
|
||||
.perform(post(DEFAULT_TOKEN_ENDPOINT_URI).with(x509(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2"))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
|
||||
|
||||
verify(jwtCustomizer).customize(any());
|
||||
}
|
||||
@@ -344,13 +344,12 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
.forClass(List.class);
|
||||
verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture());
|
||||
List<AuthenticationConverter> authenticationConverters = authenticationConvertersCaptor.getValue();
|
||||
assertThat(authenticationConverters).allMatch((converter) ->
|
||||
converter == authenticationConverter ||
|
||||
converter instanceof OAuth2AuthorizationCodeAuthenticationConverter ||
|
||||
converter instanceof OAuth2RefreshTokenAuthenticationConverter ||
|
||||
converter instanceof OAuth2ClientCredentialsAuthenticationConverter ||
|
||||
converter instanceof OAuth2DeviceCodeAuthenticationConverter ||
|
||||
converter instanceof OAuth2TokenExchangeAuthenticationConverter);
|
||||
assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter
|
||||
|| converter instanceof OAuth2AuthorizationCodeAuthenticationConverter
|
||||
|| converter instanceof OAuth2RefreshTokenAuthenticationConverter
|
||||
|| converter instanceof OAuth2ClientCredentialsAuthenticationConverter
|
||||
|| converter instanceof OAuth2DeviceCodeAuthenticationConverter
|
||||
|| converter instanceof OAuth2TokenExchangeAuthenticationConverter);
|
||||
|
||||
verify(authenticationProvider).authenticate(eq(clientCredentialsAuthentication));
|
||||
|
||||
@@ -359,13 +358,12 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
.forClass(List.class);
|
||||
verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture());
|
||||
List<AuthenticationProvider> authenticationProviders = authenticationProvidersCaptor.getValue();
|
||||
assertThat(authenticationProviders).allMatch((provider) ->
|
||||
provider == authenticationProvider ||
|
||||
provider instanceof OAuth2AuthorizationCodeAuthenticationProvider ||
|
||||
provider instanceof OAuth2RefreshTokenAuthenticationProvider ||
|
||||
provider instanceof OAuth2ClientCredentialsAuthenticationProvider ||
|
||||
provider instanceof OAuth2DeviceCodeAuthenticationProvider ||
|
||||
provider instanceof OAuth2TokenExchangeAuthenticationProvider);
|
||||
assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider
|
||||
|| provider instanceof OAuth2AuthorizationCodeAuthenticationProvider
|
||||
|| provider instanceof OAuth2RefreshTokenAuthenticationProvider
|
||||
|| provider instanceof OAuth2ClientCredentialsAuthenticationProvider
|
||||
|| provider instanceof OAuth2DeviceCodeAuthenticationProvider
|
||||
|| provider instanceof OAuth2TokenExchangeAuthenticationProvider);
|
||||
|
||||
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(accessTokenAuthentication));
|
||||
}
|
||||
@@ -395,13 +393,12 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
.forClass(List.class);
|
||||
verify(authenticationConvertersConsumer).accept(authenticationConvertersCaptor.capture());
|
||||
List<AuthenticationConverter> authenticationConverters = authenticationConvertersCaptor.getValue();
|
||||
assertThat(authenticationConverters).allMatch((converter) ->
|
||||
converter == authenticationConverter ||
|
||||
converter instanceof JwtClientAssertionAuthenticationConverter ||
|
||||
converter instanceof X509ClientCertificateAuthenticationConverter ||
|
||||
converter instanceof ClientSecretBasicAuthenticationConverter ||
|
||||
converter instanceof ClientSecretPostAuthenticationConverter ||
|
||||
converter instanceof PublicClientAuthenticationConverter);
|
||||
assertThat(authenticationConverters).allMatch((converter) -> converter == authenticationConverter
|
||||
|| converter instanceof JwtClientAssertionAuthenticationConverter
|
||||
|| converter instanceof X509ClientCertificateAuthenticationConverter
|
||||
|| converter instanceof ClientSecretBasicAuthenticationConverter
|
||||
|| converter instanceof ClientSecretPostAuthenticationConverter
|
||||
|| converter instanceof PublicClientAuthenticationConverter);
|
||||
|
||||
verify(authenticationProvider).authenticate(eq(clientPrincipal));
|
||||
|
||||
@@ -410,12 +407,11 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
.forClass(List.class);
|
||||
verify(authenticationProvidersConsumer).accept(authenticationProvidersCaptor.capture());
|
||||
List<AuthenticationProvider> authenticationProviders = authenticationProvidersCaptor.getValue();
|
||||
assertThat(authenticationProviders).allMatch((provider) ->
|
||||
provider == authenticationProvider ||
|
||||
provider instanceof JwtClientAssertionAuthenticationProvider ||
|
||||
provider instanceof X509ClientCertificateAuthenticationProvider ||
|
||||
provider instanceof ClientSecretAuthenticationProvider ||
|
||||
provider instanceof PublicClientAuthenticationProvider);
|
||||
assertThat(authenticationProviders).allMatch((provider) -> provider == authenticationProvider
|
||||
|| provider instanceof JwtClientAssertionAuthenticationProvider
|
||||
|| provider instanceof X509ClientCertificateAuthenticationProvider
|
||||
|| provider instanceof ClientSecretAuthenticationProvider
|
||||
|| provider instanceof PublicClientAuthenticationProvider);
|
||||
|
||||
verify(authenticationSuccessHandler).onAuthenticationSuccess(any(), any(), eq(clientPrincipal));
|
||||
}
|
||||
@@ -429,14 +425,15 @@ public class OAuth2ClientCredentialsGrantTests {
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
|
||||
this.mvc
|
||||
.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1 scope2")
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
"Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1 scope2"));
|
||||
|
||||
ArgumentCaptor<JwtEncodingContext> jwtEncodingContextCaptor = ArgumentCaptor.forClass(JwtEncodingContext.class);
|
||||
verify(jwtCustomizer).customize(jwtEncodingContextCaptor.capture());
|
||||
|
||||
@@ -247,9 +247,9 @@ public class OAuth2DeviceCodeGrantTests {
|
||||
String userCode = deviceAuthorizationResponse.getUserCode().getTokenValue();
|
||||
assertThat(userCode).matches("[A-Z]{4}-[A-Z]{4}");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUri())
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification");
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification");
|
||||
assertThat(deviceAuthorizationResponse.getVerificationUriComplete())
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode);
|
||||
.isEqualTo("https://example.com:8443/oauth2/device_verification?user_code=" + userCode);
|
||||
|
||||
String deviceCode = deviceAuthorizationResponse.getDeviceCode().getTokenValue();
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(deviceCode, DEVICE_CODE_TOKEN_TYPE);
|
||||
|
||||
@@ -94,18 +94,24 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
||||
*/
|
||||
@ExtendWith(SpringTestContextExtension.class)
|
||||
public class OAuth2TokenExchangeGrantTests {
|
||||
|
||||
private static final String DEFAULT_TOKEN_ENDPOINT_URI = "/oauth2/token";
|
||||
|
||||
private static final String RESOURCE = "https://mydomain.com/resource";
|
||||
|
||||
private static final String AUDIENCE = "audience";
|
||||
|
||||
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
|
||||
|
||||
private static final String ACTOR_TOKEN = "JlNE_xR1f";
|
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
|
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
|
||||
|
||||
public final SpringTestContext spring = new SpringTestContext();
|
||||
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenResponseHttpMessageConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
@Autowired
|
||||
private MockMvc mvc;
|
||||
@@ -152,7 +158,8 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
@@ -168,23 +175,27 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation() throws Exception {
|
||||
public void requestWhenAccessTokenRequestValidAndNoActorTokenThenReturnAccessTokenResponseForImpersonation()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user");
|
||||
OAuth2Authorization subjectAuthorization = TestOAuth2Authorizations.authorization(registeredClient)
|
||||
.attribute(Principal.class.getName(), userPrincipal).build();
|
||||
.attribute(Principal.class.getName(), userPrincipal)
|
||||
.build();
|
||||
this.authorizationService.save(subjectAuthorization);
|
||||
|
||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.TOKEN_EXCHANGE.getValue());
|
||||
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||
parameters.set(OAuth2ParameterNames.REQUESTED_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
|
||||
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN, subjectAuthorization.getAccessToken().getToken().getTokenValue());
|
||||
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN,
|
||||
subjectAuthorization.getAccessToken().getToken().getTokenValue());
|
||||
parameters.set(OAuth2ParameterNames.SUBJECT_TOKEN_TYPE, JWT_TOKEN_TYPE_VALUE);
|
||||
parameters.set(OAuth2ParameterNames.RESOURCE, RESOURCE);
|
||||
parameters.set(OAuth2ParameterNames.AUDIENCE, AUDIENCE);
|
||||
@@ -208,8 +219,8 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
|
||||
HttpStatus.OK);
|
||||
OAuth2AccessTokenResponse accessTokenResponse =
|
||||
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter
|
||||
.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
|
||||
String accessToken = accessTokenResponse.getAccessToken().getTokenValue();
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken,
|
||||
@@ -217,19 +228,22 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
assertThat(authorization).isNotNull();
|
||||
assertThat(authorization.getAccessToken()).isNotNull();
|
||||
assertThat(authorization.getAccessToken().getClaims()).isNotNull();
|
||||
// We do not populate claims (e.g. `aud`) based on the resource or audience parameters
|
||||
// We do not populate claims (e.g. `aud`) based on the resource or audience
|
||||
// parameters
|
||||
assertThat(authorization.getAccessToken().getClaims().get(OAuth2TokenClaimNames.AUD))
|
||||
.isEqualTo(List.of(registeredClient.getClientId()));
|
||||
.isEqualTo(List.of(registeredClient.getClientId()));
|
||||
assertThat(authorization.getRefreshToken()).isNull();
|
||||
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName())).isEqualTo(userPrincipal);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation() throws Exception {
|
||||
public void requestWhenAccessTokenRequestValidAndActorTokenThenReturnAccessTokenResponseForDelegation()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
this.registeredClientRepository.save(registeredClient);
|
||||
|
||||
UsernamePasswordAuthenticationToken userPrincipal = createUserPrincipal("user");
|
||||
@@ -284,8 +298,8 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
|
||||
HttpStatus.OK);
|
||||
OAuth2AccessTokenResponse accessTokenResponse =
|
||||
this.accessTokenResponseHttpMessageConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
OAuth2AccessTokenResponse accessTokenResponse = this.accessTokenResponseHttpMessageConverter
|
||||
.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
|
||||
String accessToken = accessTokenResponse.getAccessToken().getTokenValue();
|
||||
OAuth2Authorization authorization = this.authorizationService.findByToken(accessToken,
|
||||
@@ -296,7 +310,7 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
assertThat(authorization.getAccessToken().getClaims().get("act")).isNotNull();
|
||||
assertThat(authorization.getRefreshToken()).isNull();
|
||||
assertThat(authorization.<Authentication>getAttribute(Principal.class.getName()))
|
||||
.isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class);
|
||||
.isInstanceOf(OAuth2TokenExchangeCompositeAuthenticationToken.class);
|
||||
}
|
||||
|
||||
private static OAuth2AccessToken createAccessToken(String tokenValue) {
|
||||
@@ -363,6 +377,7 @@ public class OAuth2TokenExchangeGrantTests {
|
||||
PasswordEncoder passwordEncoder() {
|
||||
return NoOpPasswordEncoder.getInstance();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -437,11 +437,10 @@ public class OAuth2TokenIntrospectionTests {
|
||||
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
introspectRegisteredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret());
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication =
|
||||
new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(introspectRegisteredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, introspectRegisteredClient.getClientSecret());
|
||||
OAuth2TokenIntrospectionAuthenticationToken tokenIntrospectionAuthentication = new OAuth2TokenIntrospectionAuthenticationToken(
|
||||
accessToken.getTokenValue(), clientPrincipal, null, null);
|
||||
|
||||
when(authenticationConverter.convert(any())).thenReturn(tokenIntrospectionAuthentication);
|
||||
when(authenticationProvider.supports(eq(OAuth2TokenIntrospectionAuthenticationToken.class))).thenReturn(true);
|
||||
@@ -600,10 +599,12 @@ public class OAuth2TokenIntrospectionTests {
|
||||
}
|
||||
// @formatter:on
|
||||
|
||||
|
||||
@Override
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).tokenIntrospectionEndpoint("/test/introspect").build();
|
||||
return AuthorizationServerSettings.builder()
|
||||
.multipleIssuersAllowed(true)
|
||||
.tokenIntrospectionEndpoint("/test/introspect")
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -387,7 +387,8 @@ public class OidcClientRegistrationTests {
|
||||
when(authenticationProvider.authenticate(any())).thenThrow(new OAuth2AuthenticationException("error"));
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, "invalid").with(jwt()));
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, "invalid")
|
||||
.with(jwt()));
|
||||
|
||||
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any(), any());
|
||||
verifyNoInteractions(authenticationSuccessHandler);
|
||||
@@ -411,14 +412,16 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
|
||||
|
||||
this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.with(httpBasic(clientRegistrationResponse.getClientId(), clientRegistrationResponse.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1"))
|
||||
.andReturn();
|
||||
this.mvc
|
||||
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.with(httpBasic(clientRegistrationResponse.getClientId(),
|
||||
clientRegistrationResponse.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1"))
|
||||
.andReturn();
|
||||
}
|
||||
|
||||
// gh-1344
|
||||
@@ -445,12 +448,12 @@ public class OidcClientRegistrationTests {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
|
||||
.issuer(clientRegistrationResponse.getClientId())
|
||||
.subject(clientRegistrationResponse.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.build();
|
||||
.issuer(clientRegistrationResponse.getClientId())
|
||||
.subject(clientRegistrationResponse.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt)
|
||||
.build();
|
||||
|
||||
JWKSet jwkSet = new JWKSet(
|
||||
TestJwks.jwk(new SecretKeySpec(clientRegistrationResponse.getClientSecret().getBytes(), "HS256"))
|
||||
@@ -460,15 +463,17 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
|
||||
|
||||
this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE, "urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue())
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1"));
|
||||
this.mvc
|
||||
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, "scope1")
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE,
|
||||
"urn:ietf:params:oauth:client-assertion-type:jwt-bearer")
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION, jwtAssertion.getTokenValue())
|
||||
.param(OAuth2ParameterNames.CLIENT_ID, clientRegistrationResponse.getClientId()))
|
||||
.andExpect(status().isOk())
|
||||
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||
.andExpect(jsonPath("$.scope").value("scope1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -533,7 +538,8 @@ public class OidcClientRegistrationTests {
|
||||
// @formatter:on
|
||||
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
|
||||
|
||||
MvcResult mvcResult = this.mvc.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
MvcResult mvcResult = this.mvc
|
||||
.perform(post(ISSUER.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||
.param(OAuth2ParameterNames.SCOPE, clientRegistrationScope)
|
||||
.param(OAuth2ParameterNames.CLIENT_ASSERTION_TYPE,
|
||||
@@ -553,8 +559,8 @@ public class OidcClientRegistrationTests {
|
||||
httpHeaders.setBearerAuth(accessToken.getTokenValue());
|
||||
|
||||
// Register the client
|
||||
mvcResult = this.mvc.perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI))
|
||||
.headers(httpHeaders)
|
||||
mvcResult = this.mvc
|
||||
.perform(post(ISSUER.concat(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI)).headers(httpHeaders)
|
||||
.contentType(MediaType.APPLICATION_JSON)
|
||||
.content(getClientRegistrationRequestContent(clientRegistration)))
|
||||
.andExpect(status().isCreated())
|
||||
@@ -569,11 +575,11 @@ public class OidcClientRegistrationTests {
|
||||
Instant issuedAt = Instant.now();
|
||||
Instant expiresAt = issuedAt.plus(1, ChronoUnit.HOURS);
|
||||
return JwtClaimsSet.builder()
|
||||
.issuer(registeredClient.getClientId())
|
||||
.subject(registeredClient.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt);
|
||||
.issuer(registeredClient.getClientId())
|
||||
.subject(registeredClient.getClientId())
|
||||
.audience(Collections.singletonList(asUrl(ISSUER, this.authorizationServerSettings.getTokenEndpoint())))
|
||||
.issuedAt(issuedAt)
|
||||
.expiresAt(expiresAt);
|
||||
}
|
||||
|
||||
private static String asUrl(String uri, String path) {
|
||||
@@ -753,9 +759,7 @@ public class OidcClientRegistrationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.multipleIssuersAllowed(true)
|
||||
.build();
|
||||
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -80,28 +80,29 @@ public class OidcProviderConfigurationTests {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath() throws Exception {
|
||||
public void requestWhenConfigurationRequestIncludesIssuerPathThenConfigurationResponseHasIssuerPath()
|
||||
throws Exception {
|
||||
this.spring.register(AuthorizationServerConfigurationWithMultipleIssuersAllowed.class).autowire();
|
||||
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
|
||||
issuer = "https://example.com:8443/path1/issuer2";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
|
||||
issuer = "https://example.com:8443/path1/path2/issuer3";
|
||||
this.mvc.perform(get(issuer.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(issuer));
|
||||
}
|
||||
|
||||
// gh-632
|
||||
@@ -109,10 +110,9 @@ public class OidcProviderConfigurationTests {
|
||||
public void requestWhenConfigurationRequestAndUserAuthenticatedThenReturnConfigurationResponse() throws Exception {
|
||||
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
|
||||
.with(user("user")))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)).with(user("user")))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER));
|
||||
}
|
||||
|
||||
// gh-616
|
||||
@@ -122,9 +122,9 @@ public class OidcProviderConfigurationTests {
|
||||
this.spring.register(AuthorizationServerConfigurationWithProviderConfigurationCustomizer.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL)));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpect(jsonPath(OAuth2AuthorizationServerMetadataClaimNames.SCOPES_SUPPORTED,
|
||||
hasItems(OidcScopes.OPENID, OidcScopes.PROFILE, OidcScopes.EMAIL)));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -133,9 +133,10 @@ public class OidcProviderConfigurationTests {
|
||||
this.spring.register(AuthorizationServerConfigurationWithClientRegistrationEnabled.class).autowire();
|
||||
|
||||
this.mvc.perform(get(ISSUER.concat(DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI)))
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER))
|
||||
.andExpect(jsonPath("$.registration_endpoint").value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint())));
|
||||
.andExpect(status().is2xxSuccessful())
|
||||
.andExpectAll(defaultConfigurationMatchers(ISSUER))
|
||||
.andExpect(jsonPath("$.registration_endpoint")
|
||||
.value(ISSUER.concat(this.authorizationServerSettings.getOidcClientRegistrationEndpoint())));
|
||||
}
|
||||
|
||||
private ResultMatcher[] defaultConfigurationMatchers(String issuer) {
|
||||
@@ -235,9 +236,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.issuer(ISSUER)
|
||||
.build();
|
||||
return AuthorizationServerSettings.builder().issuer(ISSUER).build();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -248,9 +247,7 @@ public class OidcProviderConfigurationTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.multipleIssuersAllowed(true)
|
||||
.build();
|
||||
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -324,12 +324,13 @@ public class OidcTests {
|
||||
String issuer = "https://example.com:8443/issuer1";
|
||||
|
||||
// Login
|
||||
MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(registeredClient);
|
||||
MvcResult mvcResult = this.mvc.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI))
|
||||
.queryParams(authorizationRequestParameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
MultiValueMap<String, String> authorizationRequestParameters = getAuthorizationRequestParameters(
|
||||
registeredClient);
|
||||
MvcResult mvcResult = this.mvc
|
||||
.perform(get(issuer.concat(DEFAULT_AUTHORIZATION_ENDPOINT_URI)).queryParams(authorizationRequestParameters)
|
||||
.with(user("user")))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
|
||||
MockHttpSession session = (MockHttpSession) mvcResult.getRequest().getSession();
|
||||
assertThat(session.isNew()).isTrue();
|
||||
@@ -340,12 +341,13 @@ public class OidcTests {
|
||||
AUTHORIZATION_CODE_TOKEN_TYPE);
|
||||
|
||||
// Get ID Token
|
||||
mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
mvcResult = this.mvc
|
||||
.perform(post(issuer.concat(DEFAULT_TOKEN_ENDPOINT_URI))
|
||||
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||
.header(HttpHeaders.AUTHORIZATION,
|
||||
"Basic " + encodeBasicAuth(registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||
.andExpect(status().isOk())
|
||||
.andReturn();
|
||||
|
||||
MockHttpServletResponse servletResponse = mvcResult.getResponse();
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(servletResponse.getContentAsByteArray(),
|
||||
@@ -356,11 +358,11 @@ public class OidcTests {
|
||||
String idToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||
|
||||
// Logout
|
||||
mvcResult = this.mvc.perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI))
|
||||
.param("id_token_hint", idToken)
|
||||
.session(session))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
mvcResult = this.mvc
|
||||
.perform(post(issuer.concat(DEFAULT_OIDC_LOGOUT_ENDPOINT_URI)).param("id_token_hint", idToken)
|
||||
.session(session))
|
||||
.andExpect(status().is3xxRedirection())
|
||||
.andReturn();
|
||||
redirectedUrl = mvcResult.getResponse().getRedirectedUrl();
|
||||
|
||||
assertThat(redirectedUrl).matches("/");
|
||||
|
||||
@@ -526,9 +526,7 @@ public class OidcUserInfoTests {
|
||||
|
||||
@Bean
|
||||
AuthorizationServerSettings authorizationServerSettings() {
|
||||
return AuthorizationServerSettings.builder()
|
||||
.multipleIssuersAllowed(true)
|
||||
.build();
|
||||
return AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -63,8 +63,8 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotConfigurationRequestThenNotProcessed() throws Exception {
|
||||
AuthorizationServerContextHolder.setContext(
|
||||
new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
AuthorizationServerContextHolder
|
||||
.setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
@@ -79,8 +79,8 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenConfigurationRequestPostThenNotProcessed() throws Exception {
|
||||
AuthorizationServerContextHolder.setContext(
|
||||
new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
AuthorizationServerContextHolder
|
||||
.setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
|
||||
String requestUri = DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
@@ -137,18 +137,25 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
||||
assertThat(providerConfigurationResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"scopes_supported\":[\"openid\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(providerConfigurationResponse).contains(
|
||||
"\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]");
|
||||
assertThat(providerConfigurationResponse)
|
||||
.contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\"");
|
||||
assertThat(providerConfigurationResponse).contains(
|
||||
"\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(providerConfigurationResponse)
|
||||
.contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\"");
|
||||
assertThat(providerConfigurationResponse).contains(
|
||||
"\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"code_challenge_methods_supported\":[\"S256\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"tls_client_certificate_bound_access_tokens\":true");
|
||||
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]");
|
||||
assertThat(providerConfigurationResponse).contains("\"userinfo_endpoint\":\"https://example.com/userinfo\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"end_session_endpoint\":\"https://example.com/connect/logout\"");
|
||||
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(providerConfigurationResponse)
|
||||
.contains("\"end_session_endpoint\":\"https://example.com/connect/logout\"");
|
||||
assertThat(providerConfigurationResponse).contains(
|
||||
"\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -86,13 +86,16 @@ public class AuthorizationServerSettingsTests {
|
||||
public void buildWhenIssuerSetAndMultipleIssuersAllowedTrueThenThrowIllegalArgumentException() {
|
||||
String issuer = "https://example.com:9000";
|
||||
assertThatIllegalArgumentException()
|
||||
.isThrownBy(() -> AuthorizationServerSettings.builder().issuer(issuer).multipleIssuersAllowed(true).build())
|
||||
.withMessage("The issuer identifier (" + issuer + ") cannot be set when isMultipleIssuersAllowed() is true.");
|
||||
.isThrownBy(() -> AuthorizationServerSettings.builder().issuer(issuer).multipleIssuersAllowed(true).build())
|
||||
.withMessage(
|
||||
"The issuer identifier (" + issuer + ") cannot be set when isMultipleIssuersAllowed() is true.");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void buildWhenIssuerNotSetAndMultipleIssuersAllowedTrueThenDefaultsAreSet() {
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder().multipleIssuersAllowed(true).build();
|
||||
AuthorizationServerSettings authorizationServerSettings = AuthorizationServerSettings.builder()
|
||||
.multipleIssuersAllowed(true)
|
||||
.build();
|
||||
|
||||
assertThat(authorizationServerSettings.getIssuer()).isNull();
|
||||
assertThat(authorizationServerSettings.isMultipleIssuersAllowed()).isTrue();
|
||||
|
||||
@@ -65,9 +65,10 @@ public class ClientSettingsTests {
|
||||
@Test
|
||||
public void x509CertificateSubjectDNWhenProvidedThenSet() {
|
||||
ClientSettings clientSettings = ClientSettings.builder()
|
||||
.x509CertificateSubjectDN("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US")
|
||||
.build();
|
||||
assertThat(clientSettings.getX509CertificateSubjectDN()).isEqualTo("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US");
|
||||
.x509CertificateSubjectDN("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US")
|
||||
.build();
|
||||
assertThat(clientSettings.getX509CertificateSubjectDN())
|
||||
.isEqualTo("CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US");
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -153,18 +153,16 @@ public class TokenSettingsTests {
|
||||
|
||||
@Test
|
||||
public void x509CertificateBoundAccessTokensWhenTrueThenSet() {
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.x509CertificateBoundAccessTokens(true)
|
||||
.build();
|
||||
TokenSettings tokenSettings = TokenSettings.builder().x509CertificateBoundAccessTokens(true).build();
|
||||
assertThat(tokenSettings.isX509CertificateBoundAccessTokens()).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void settingWhenCustomThenSet() {
|
||||
TokenSettings tokenSettings = TokenSettings.builder()
|
||||
.setting("name1", "value1")
|
||||
.settings(settings -> settings.put("name2", "value2"))
|
||||
.build();
|
||||
.setting("name1", "value1")
|
||||
.settings(settings -> settings.put("name2", "value2"))
|
||||
.build();
|
||||
assertThat(tokenSettings.getSettings()).hasSize(10);
|
||||
assertThat(tokenSettings.<String>getSetting("name1")).isEqualTo("value1");
|
||||
assertThat(tokenSettings.<String>getSetting("name2")).isEqualTo("value2");
|
||||
|
||||
@@ -29,22 +29,25 @@ public final class TestX509Certificates {
|
||||
// Generate the Root certificate (Trust Anchor or most-trusted CA)
|
||||
KeyPair rootKeyPair = X509CertificateUtils.generateRSAKeyPair();
|
||||
String distinguishedName = "CN=spring-samples-trusted-ca, OU=Spring Samples, O=Spring, C=US";
|
||||
X509Certificate rootCertificate = X509CertificateUtils.createTrustAnchorCertificate(rootKeyPair, distinguishedName);
|
||||
X509Certificate rootCertificate = X509CertificateUtils.createTrustAnchorCertificate(rootKeyPair,
|
||||
distinguishedName);
|
||||
|
||||
// Generate the CA (intermediary) certificate
|
||||
KeyPair caKeyPair = X509CertificateUtils.generateRSAKeyPair();
|
||||
distinguishedName = "CN=spring-samples-ca, OU=Spring Samples, O=Spring, C=US";
|
||||
X509Certificate caCertificate = X509CertificateUtils.createCACertificate(
|
||||
rootCertificate, rootKeyPair.getPrivate(), caKeyPair.getPublic(), distinguishedName);
|
||||
X509Certificate caCertificate = X509CertificateUtils.createCACertificate(rootCertificate,
|
||||
rootKeyPair.getPrivate(), caKeyPair.getPublic(), distinguishedName);
|
||||
|
||||
// Generate certificate for demo-client-sample
|
||||
KeyPair demoClientKeyPair = X509CertificateUtils.generateRSAKeyPair();
|
||||
distinguishedName = "CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US";
|
||||
X509Certificate demoClientCertificate = X509CertificateUtils.createEndEntityCertificate(
|
||||
caCertificate, caKeyPair.getPrivate(), demoClientKeyPair.getPublic(), distinguishedName);
|
||||
X509Certificate demoClientCertificate = X509CertificateUtils.createEndEntityCertificate(caCertificate,
|
||||
caKeyPair.getPrivate(), demoClientKeyPair.getPublic(), distinguishedName);
|
||||
|
||||
DEMO_CLIENT_PKI_CERTIFICATE = new X509Certificate[] { demoClientCertificate, caCertificate, rootCertificate };
|
||||
} catch (Exception ex) {
|
||||
DEMO_CLIENT_PKI_CERTIFICATE = new X509Certificate[] { demoClientCertificate, caCertificate,
|
||||
rootCertificate };
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
@@ -55,10 +58,12 @@ public final class TestX509Certificates {
|
||||
// Generate self-signed certificate for demo-client-sample
|
||||
KeyPair keyPair = X509CertificateUtils.generateRSAKeyPair();
|
||||
String distinguishedName = "CN=demo-client-sample, OU=Spring Samples, O=Spring, C=US";
|
||||
X509Certificate demoClientSelfSignedCertificate = X509CertificateUtils.createTrustAnchorCertificate(keyPair, distinguishedName);
|
||||
X509Certificate demoClientSelfSignedCertificate = X509CertificateUtils.createTrustAnchorCertificate(keyPair,
|
||||
distinguishedName);
|
||||
|
||||
DEMO_CLIENT_SELF_SIGNED_CERTIFICATE = new X509Certificate[] { demoClientSelfSignedCertificate };
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,15 +44,20 @@ import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public final class X509CertificateUtils {
|
||||
|
||||
private static final String BC_PROVIDER = "BC";
|
||||
|
||||
private static final String SHA256_RSA_SIGNATURE_ALGORITHM = "SHA256withRSA";
|
||||
|
||||
private static final Date DEFAULT_START_DATE;
|
||||
|
||||
private static final Date DEFAULT_END_DATE;
|
||||
|
||||
static {
|
||||
Security.addProvider(new BouncyCastleProvider());
|
||||
|
||||
// Setup default certificate start date to yesterday and end date for 1 year validity
|
||||
// Setup default certificate start date to yesterday and end date for 1 year
|
||||
// validity
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
calendar.add(Calendar.DATE, -1);
|
||||
DEFAULT_START_DATE = calendar.getTime();
|
||||
@@ -69,34 +74,31 @@ public final class X509CertificateUtils {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA", BC_PROVIDER);
|
||||
keyPairGenerator.initialize(new RSAKeyGenParameterSpec(2048, RSAKeyGenParameterSpec.F4));
|
||||
keyPair = keyPairGenerator.generateKeyPair();
|
||||
} catch (Exception ex) {
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw new IllegalStateException(ex);
|
||||
}
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public static X509Certificate createTrustAnchorCertificate(KeyPair keyPair, String distinguishedName) throws Exception {
|
||||
public static X509Certificate createTrustAnchorCertificate(KeyPair keyPair, String distinguishedName)
|
||||
throws Exception {
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
subject,
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
keyPair.getPublic());
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(subject, serialNum, DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE, subject, keyPair.getPublic());
|
||||
|
||||
// Add Extensions
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
// A BasicConstraints to mark root certificate as CA certificate
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(true))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||
// A BasicConstraints to mark root certificate as CA certificate
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(true))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(keyPair.getPublic()));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(keyPair.getPrivate());
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER)
|
||||
.build(keyPair.getPrivate());
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
@@ -109,32 +111,26 @@ public final class X509CertificateUtils {
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
signerCert.getSubjectX500Principal(),
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
certKey);
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(signerCert.getSubjectX500Principal(),
|
||||
serialNum, DEFAULT_START_DATE, DEFAULT_END_DATE, subject, certKey);
|
||||
|
||||
// Add Extensions
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
// A BasicConstraints to mark as CA certificate and how many CA certificates can follow it in the chain
|
||||
// (with 0 meaning the chain ends with the next certificate in the chain).
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(0))
|
||||
// KeyUsage specifies what the public key in the certificate can be used for.
|
||||
// In this case, it can be used for signing other certificates and/or
|
||||
// signing Certificate Revocation Lists (CRLs).
|
||||
.addExtension(Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
// A BasicConstraints to mark as CA certificate and how many CA certificates
|
||||
// can follow it in the chain
|
||||
// (with 0 meaning the chain ends with the next certificate in the chain).
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(0))
|
||||
// KeyUsage specifies what the public key in the certificate can be used for.
|
||||
// In this case, it can be used for signing other certificates and/or
|
||||
// signing Certificate Revocation Lists (CRLs).
|
||||
.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.keyCertSign | KeyUsage.cRLSign))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false, extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(signerKey);
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER)
|
||||
.build(signerKey);
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
@@ -147,26 +143,18 @@ public final class X509CertificateUtils {
|
||||
X500Principal subject = new X500Principal(distinguishedName);
|
||||
BigInteger serialNum = new BigInteger(Long.toString(new SecureRandom().nextLong()));
|
||||
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(
|
||||
signerCert.getSubjectX500Principal(),
|
||||
serialNum,
|
||||
DEFAULT_START_DATE,
|
||||
DEFAULT_END_DATE,
|
||||
subject,
|
||||
certKey);
|
||||
X509v3CertificateBuilder certBuilder = new JcaX509v3CertificateBuilder(signerCert.getSubjectX500Principal(),
|
||||
serialNum, DEFAULT_START_DATE, DEFAULT_END_DATE, subject, certKey);
|
||||
|
||||
JcaX509ExtensionUtils extensionUtils = new JcaX509ExtensionUtils();
|
||||
certBuilder
|
||||
.addExtension(Extension.basicConstraints, true, new BasicConstraints(false))
|
||||
.addExtension(Extension.keyUsage, true,
|
||||
new KeyUsage(KeyUsage.digitalSignature))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false,
|
||||
extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
certBuilder.addExtension(Extension.basicConstraints, true, new BasicConstraints(false))
|
||||
.addExtension(Extension.keyUsage, true, new KeyUsage(KeyUsage.digitalSignature))
|
||||
.addExtension(Extension.authorityKeyIdentifier, false,
|
||||
extensionUtils.createAuthorityKeyIdentifier(signerCert))
|
||||
.addExtension(Extension.subjectKeyIdentifier, false, extensionUtils.createSubjectKeyIdentifier(certKey));
|
||||
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM)
|
||||
.setProvider(BC_PROVIDER).build(signerKey);
|
||||
ContentSigner signer = new JcaContentSignerBuilder(SHA256_RSA_SIGNATURE_ALGORITHM).setProvider(BC_PROVIDER)
|
||||
.build(signerKey);
|
||||
|
||||
JcaX509CertificateConverter converter = new JcaX509CertificateConverter().setProvider(BC_PROVIDER);
|
||||
|
||||
|
||||
@@ -63,8 +63,8 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenNotAuthorizationServerMetadataRequestThenNotProcessed() throws Exception {
|
||||
AuthorizationServerContextHolder.setContext(
|
||||
new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
AuthorizationServerContextHolder
|
||||
.setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
|
||||
String requestUri = "/path";
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri);
|
||||
@@ -79,8 +79,8 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
|
||||
|
||||
@Test
|
||||
public void doFilterWhenAuthorizationServerMetadataRequestPostThenNotProcessed() throws Exception {
|
||||
AuthorizationServerContextHolder.setContext(
|
||||
new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
AuthorizationServerContextHolder
|
||||
.setContext(new TestAuthorizationServerContext(AuthorizationServerSettings.builder().build(), null));
|
||||
|
||||
String requestUri = DEFAULT_OAUTH2_AUTHORIZATION_SERVER_METADATA_ENDPOINT_URI;
|
||||
MockHttpServletRequest request = new MockHttpServletRequest("POST", requestUri);
|
||||
@@ -126,16 +126,24 @@ public class OAuth2AuthorizationServerMetadataEndpointFilterTests {
|
||||
assertThat(response.getContentType()).isEqualTo(MediaType.APPLICATION_JSON_VALUE);
|
||||
String authorizationServerMetadataResponse = response.getContentAsString();
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"issuer\":\"https://example.com\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse)
|
||||
.contains("\"authorization_endpoint\":\"https://example.com/oauth2/v1/authorize\"");
|
||||
assertThat(authorizationServerMetadataResponse)
|
||||
.contains("\"token_endpoint\":\"https://example.com/oauth2/v1/token\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains(
|
||||
"\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"jwks_uri\":\"https://example.com/oauth2/v1/jwks\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains(
|
||||
"\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\",\"urn:ietf:params:oauth:grant-type:device_code\",\"urn:ietf:params:oauth:grant-type:token-exchange\"]");
|
||||
assertThat(authorizationServerMetadataResponse)
|
||||
.contains("\"revocation_endpoint\":\"https://example.com/oauth2/v1/revoke\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains(
|
||||
"\"revocation_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse)
|
||||
.contains("\"introspection_endpoint\":\"https://example.com/oauth2/v1/introspect\"");
|
||||
assertThat(authorizationServerMetadataResponse).contains(
|
||||
"\"introspection_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\",\"client_secret_jwt\",\"private_key_jwt\",\"tls_client_auth\",\"self_signed_tls_client_auth\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"code_challenge_methods_supported\":[\"S256\"]");
|
||||
assertThat(authorizationServerMetadataResponse).contains("\"tls_client_certificate_bound_access_tokens\":true");
|
||||
}
|
||||
|
||||
@@ -452,17 +452,16 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
@Test
|
||||
public void doFilterWhenTokenExchangeRequestThenAccessTokenResponse() throws Exception {
|
||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE).build();
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||
OAuth2AccessToken.TokenType.BEARER, "token",
|
||||
.authorizationGrantType(AuthorizationGrantType.TOKEN_EXCHANGE)
|
||||
.build();
|
||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient,
|
||||
ClientAuthenticationMethod.CLIENT_SECRET_BASIC, registeredClient.getClientSecret());
|
||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "token",
|
||||
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
|
||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken("refresh-token", Instant.now());
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||
new OAuth2AccessTokenAuthenticationToken(
|
||||
registeredClient, clientPrincipal, accessToken, refreshToken);
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = new OAuth2AccessTokenAuthenticationToken(
|
||||
registeredClient, clientPrincipal, accessToken, refreshToken);
|
||||
|
||||
when(this.authenticationManager.authenticate(any())).thenReturn(accessTokenAuthentication);
|
||||
|
||||
@@ -478,24 +477,22 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
|
||||
verifyNoInteractions(filterChain);
|
||||
|
||||
ArgumentCaptor<OAuth2TokenExchangeAuthenticationToken> tokenExchangeAuthenticationCaptor =
|
||||
ArgumentCaptor.forClass(OAuth2TokenExchangeAuthenticationToken.class);
|
||||
ArgumentCaptor<OAuth2TokenExchangeAuthenticationToken> tokenExchangeAuthenticationCaptor = ArgumentCaptor
|
||||
.forClass(OAuth2TokenExchangeAuthenticationToken.class);
|
||||
verify(this.authenticationManager).authenticate(tokenExchangeAuthenticationCaptor.capture());
|
||||
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthenticationToken =
|
||||
tokenExchangeAuthenticationCaptor.getValue();
|
||||
OAuth2TokenExchangeAuthenticationToken tokenExchangeAuthenticationToken = tokenExchangeAuthenticationCaptor
|
||||
.getValue();
|
||||
assertThat(tokenExchangeAuthenticationToken.getSubjectToken()).isEqualTo("subject-token");
|
||||
assertThat(tokenExchangeAuthenticationToken.getSubjectTokenType()).isEqualTo(ACCESS_TOKEN_TYPE);
|
||||
assertThat(tokenExchangeAuthenticationToken.getPrincipal()).isEqualTo(clientPrincipal);
|
||||
assertThat(tokenExchangeAuthenticationToken.getScopes()).isEqualTo(registeredClient.getScopes());
|
||||
assertThat(tokenExchangeAuthenticationToken.getAdditionalParameters())
|
||||
.containsExactly(entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }));
|
||||
assertThat(tokenExchangeAuthenticationToken.getDetails())
|
||||
.asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
|
||||
assertThat(tokenExchangeAuthenticationToken.getAdditionalParameters()).containsExactly(
|
||||
entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }));
|
||||
assertThat(tokenExchangeAuthenticationToken.getDetails()).asInstanceOf(type(WebAuthenticationDetails.class))
|
||||
.extracting(WebAuthenticationDetails::getRemoteAddress)
|
||||
.isEqualTo(REMOTE_ADDRESS);
|
||||
|
||||
assertThat(response.getStatus()).isEqualTo(HttpStatus.OK.value());
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
@@ -503,10 +500,10 @@ public class OAuth2TokenEndpointFilterTests {
|
||||
OAuth2AccessToken accessTokenResult = accessTokenResponse.getAccessToken();
|
||||
assertThat(accessTokenResult.getTokenType()).isEqualTo(accessToken.getTokenType());
|
||||
assertThat(accessTokenResult.getTokenValue()).isEqualTo(accessToken.getTokenValue());
|
||||
assertThat(accessTokenResult.getIssuedAt()).isBetween(
|
||||
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResult.getExpiresAt()).isBetween(
|
||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResult.getIssuedAt()).isBetween(accessToken.getIssuedAt().minusSeconds(1),
|
||||
accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResult.getExpiresAt()).isBetween(accessToken.getExpiresAt().minusSeconds(1),
|
||||
accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes());
|
||||
|
||||
OAuth2RefreshToken refreshTokenResult = accessTokenResponse.getRefreshToken();
|
||||
|
||||
@@ -53,13 +53,16 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||
* @author Dmitriy Dubson
|
||||
*/
|
||||
public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests {
|
||||
|
||||
private final RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
|
||||
new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private final HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter = new OAuth2AccessTokenResponseHttpMessageConverter();
|
||||
|
||||
private final OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(
|
||||
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC, this.registeredClient.getClientSecret());
|
||||
private final OAuth2AccessTokenResponseAuthenticationSuccessHandler authenticationSuccessHandler =
|
||||
new OAuth2AccessTokenResponseAuthenticationSuccessHandler();
|
||||
this.registeredClient, ClientAuthenticationMethod.CLIENT_SECRET_BASIC,
|
||||
this.registeredClient.getClientSecret());
|
||||
|
||||
private final OAuth2AccessTokenResponseAuthenticationSuccessHandler authenticationSuccessHandler = new OAuth2AccessTokenResponseAuthenticationSuccessHandler();
|
||||
|
||||
@Test
|
||||
public void setAccessTokenResponseCustomizerWhenNullThenThrowIllegalArgumentException() {
|
||||
@@ -79,23 +82,22 @@ public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||
Authentication authentication = new OAuth2AccessTokenAuthenticationToken(
|
||||
this.registeredClient, this.clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||
Authentication authentication = new OAuth2AccessTokenAuthenticationToken(this.registeredClient,
|
||||
this.clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||
|
||||
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authentication);
|
||||
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo(accessToken.getTokenValue());
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(accessToken.getTokenType());
|
||||
assertThat(accessTokenResponse.getAccessToken().getIssuedAt()).isBetween(
|
||||
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(
|
||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getIssuedAt())
|
||||
.isBetween(accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt())
|
||||
.isBetween(accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getRefreshToken()).isNotNull();
|
||||
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue());
|
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(
|
||||
Map.of("param1", "value1")
|
||||
);
|
||||
assertThat(accessTokenResponse.getAdditionalParameters())
|
||||
.containsExactlyInAnyOrderEntriesOf(Map.of("param1", "value1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -103,16 +105,17 @@ public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
assertThatThrownBy(() ->
|
||||
this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, new TestingAuthenticationToken(this.clientPrincipal, null)))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
assertThatThrownBy(() -> this.authenticationSuccessHandler.onAuthenticationSuccess(request, response,
|
||||
new TestingAuthenticationToken(this.clientPrincipal, null)))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.SERVER_ERROR);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onAuthenticationSuccessWhenAccessTokenResponseCustomizerSetThenAccessTokenResponseCustomized() throws Exception {
|
||||
public void onAuthenticationSuccessWhenAccessTokenResponseCustomizerSetThenAccessTokenResponseCustomized()
|
||||
throws Exception {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||
|
||||
@@ -120,8 +123,8 @@ public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests {
|
||||
OAuth2AccessToken accessToken = authorization.getAccessToken().getToken();
|
||||
OAuth2RefreshToken refreshToken = authorization.getRefreshToken().getToken();
|
||||
Map<String, Object> additionalParameters = Collections.singletonMap("param1", "value1");
|
||||
Authentication authentication = new OAuth2AccessTokenAuthenticationToken(
|
||||
this.registeredClient, this.clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||
Authentication authentication = new OAuth2AccessTokenAuthenticationToken(this.registeredClient,
|
||||
this.clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||
|
||||
Consumer<OAuth2AccessTokenAuthenticationContext> accessTokenResponseCustomizer = (authenticationContext) -> {
|
||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication = authenticationContext.getAuthentication();
|
||||
@@ -136,20 +139,19 @@ public class OAuth2AccessTokenResponseAuthenticationSuccessHandlerTests {
|
||||
OAuth2AccessTokenResponse accessTokenResponse = readAccessTokenResponse(response);
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenValue()).isEqualTo(accessToken.getTokenValue());
|
||||
assertThat(accessTokenResponse.getAccessToken().getTokenType()).isEqualTo(accessToken.getTokenType());
|
||||
assertThat(accessTokenResponse.getAccessToken().getIssuedAt()).isBetween(
|
||||
accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt()).isBetween(
|
||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getIssuedAt())
|
||||
.isBetween(accessToken.getIssuedAt().minusSeconds(1), accessToken.getIssuedAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getAccessToken().getExpiresAt())
|
||||
.isBetween(accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||
assertThat(accessTokenResponse.getRefreshToken()).isNotNull();
|
||||
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue());
|
||||
assertThat(accessTokenResponse.getAdditionalParameters()).containsExactlyInAnyOrderEntriesOf(
|
||||
Map.of("param1", "value1", "authorization_id", "id")
|
||||
);
|
||||
assertThat(accessTokenResponse.getAdditionalParameters())
|
||||
.containsExactlyInAnyOrderEntriesOf(Map.of("param1", "value1", "authorization_id", "id"));
|
||||
}
|
||||
|
||||
private OAuth2AccessTokenResponse readAccessTokenResponse(MockHttpServletResponse response) throws Exception {
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(
|
||||
response.getContentAsByteArray(), HttpStatus.valueOf(response.getStatus()));
|
||||
MockClientHttpResponse httpResponse = new MockClientHttpResponse(response.getContentAsByteArray(),
|
||||
HttpStatus.valueOf(response.getStatus()));
|
||||
return this.accessTokenHttpResponseConverter.read(OAuth2AccessTokenResponse.class, httpResponse);
|
||||
}
|
||||
|
||||
|
||||
@@ -41,11 +41,17 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
|
||||
* @author Steve Riesenberg
|
||||
*/
|
||||
public class OAuth2TokenExchangeAuthenticationConverterTests {
|
||||
|
||||
private static final String CLIENT_ID = "client-1";
|
||||
|
||||
private static final String TOKEN_URI = "/oauth2/token";
|
||||
|
||||
private static final String SUBJECT_TOKEN = "EfYu_0jEL";
|
||||
|
||||
private static final String ACTOR_TOKEN = "JlNE_xR1f";
|
||||
|
||||
private static final String ACCESS_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:access_token";
|
||||
|
||||
private static final String JWT_TOKEN_TYPE_VALUE = "urn:ietf:params:oauth:token-type:jwt";
|
||||
|
||||
private OAuth2TokenExchangeAuthenticationConverter converter;
|
||||
@@ -222,7 +228,6 @@ public class OAuth2TokenExchangeAuthenticationConverterTests {
|
||||
// @formatter:on
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void convertWhenMultipleActorTokenParametersThenInvalidRequestError() {
|
||||
MockHttpServletRequest request = createRequest();
|
||||
@@ -312,8 +317,8 @@ public class OAuth2TokenExchangeAuthenticationConverterTests {
|
||||
securityContext.setAuthentication(new TestingAuthenticationToken(CLIENT_ID, null));
|
||||
SecurityContextHolder.setContext(securityContext);
|
||||
|
||||
OAuth2TokenExchangeAuthenticationToken authentication =
|
||||
(OAuth2TokenExchangeAuthenticationToken) this.converter.convert(request);
|
||||
OAuth2TokenExchangeAuthenticationToken authentication = (OAuth2TokenExchangeAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication).isNotNull();
|
||||
assertThat(authentication.getResources()).containsExactly("https://mydomain.com/resource1",
|
||||
"https://mydomain.com/resource2");
|
||||
|
||||
@@ -39,6 +39,7 @@ import static org.assertj.core.api.Assertions.entry;
|
||||
* @author Joe Grandja
|
||||
*/
|
||||
public class X509ClientCertificateAuthenticationConverterTests {
|
||||
|
||||
private final X509ClientCertificateAuthenticationConverter converter = new X509ClientCertificateAuthenticationConverter();
|
||||
|
||||
@Test
|
||||
@@ -51,8 +52,7 @@ public class X509ClientCertificateAuthenticationConverterTests {
|
||||
@Test
|
||||
public void convertWhenEmptyX509CertificateThenReturnNull() {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setAttribute("jakarta.servlet.request.X509Certificate",
|
||||
new X509Certificate[0]);
|
||||
request.setAttribute("jakarta.servlet.request.X509Certificate", new X509Certificate[0]);
|
||||
Authentication authentication = this.converter.convert(request);
|
||||
assertThat(authentication).isNull();
|
||||
}
|
||||
@@ -62,11 +62,10 @@ public class X509ClientCertificateAuthenticationConverterTests {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
request.setAttribute("jakarta.servlet.request.X509Certificate",
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
assertThatThrownBy(() -> this.converter.convert(request))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -76,11 +75,10 @@ public class X509ClientCertificateAuthenticationConverterTests {
|
||||
TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-1");
|
||||
request.addParameter(OAuth2ParameterNames.CLIENT_ID, "client-2");
|
||||
assertThatThrownBy(() -> this.converter.convert(request))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
assertThatThrownBy(() -> this.converter.convert(request)).isInstanceOf(OAuth2AuthenticationException.class)
|
||||
.extracting(ex -> ((OAuth2AuthenticationException) ex).getError())
|
||||
.extracting("errorCode")
|
||||
.isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -93,16 +91,16 @@ public class X509ClientCertificateAuthenticationConverterTests {
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter("custom-param-1", "custom-value-1");
|
||||
request.addParameter("custom-param-2", "custom-value-1", "custom-value-2");
|
||||
OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
|
||||
OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo("client-1");
|
||||
assertThat(authentication.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_PKI_CERTIFICATE);
|
||||
assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
assertThat(authentication.getAdditionalParameters())
|
||||
.containsOnly(
|
||||
entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
entry(OAuth2ParameterNames.CODE, "code"),
|
||||
entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] {"custom-value-1", "custom-value-2"}));
|
||||
assertThat(authentication.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.TLS_CLIENT_AUTH);
|
||||
assertThat(authentication.getAdditionalParameters()).containsOnly(
|
||||
entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
entry(OAuth2ParameterNames.CODE, "code"), entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -115,16 +113,16 @@ public class X509ClientCertificateAuthenticationConverterTests {
|
||||
request.addParameter(OAuth2ParameterNames.CODE, "code");
|
||||
request.addParameter("custom-param-1", "custom-value-1");
|
||||
request.addParameter("custom-param-2", "custom-value-1", "custom-value-2");
|
||||
OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter.convert(request);
|
||||
OAuth2ClientAuthenticationToken authentication = (OAuth2ClientAuthenticationToken) this.converter
|
||||
.convert(request);
|
||||
assertThat(authentication.getPrincipal()).isEqualTo("client-1");
|
||||
assertThat(authentication.getCredentials()).isEqualTo(TestX509Certificates.DEMO_CLIENT_SELF_SIGNED_CERTIFICATE);
|
||||
assertThat(authentication.getClientAuthenticationMethod()).isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH);
|
||||
assertThat(authentication.getAdditionalParameters())
|
||||
.containsOnly(
|
||||
entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
entry(OAuth2ParameterNames.CODE, "code"),
|
||||
entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] {"custom-value-1", "custom-value-2"}));
|
||||
assertThat(authentication.getClientAuthenticationMethod())
|
||||
.isEqualTo(ClientAuthenticationMethod.SELF_SIGNED_TLS_CLIENT_AUTH);
|
||||
assertThat(authentication.getAdditionalParameters()).containsOnly(
|
||||
entry(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue()),
|
||||
entry(OAuth2ParameterNames.CODE, "code"), entry("custom-param-1", "custom-value-1"),
|
||||
entry("custom-param-2", new String[] { "custom-value-1", "custom-value-2" }));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user