diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimAccessor.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimAccessor.java index a6630476..d32a9180 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimAccessor.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimAccessor.java @@ -146,12 +146,12 @@ public interface OidcClientMetadataClaimAccessor extends ClaimAccessor { } /** - * Returns the {@code URL} of the OAuth 2.0 Client Configuration Endpoint. + * Returns the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used. * - * @return the {@code URL} of the OAuth 2.0 Client Configuration Endpoint + * @return the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used * @since 0.2.1 */ - default URL getRegistrationClientUri() { + default URL getRegistrationClientUrl() { return getClaimAsURL(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI); } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimNames.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimNames.java index 786f4db9..0fc03c2a 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimNames.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientMetadataClaimNames.java @@ -84,13 +84,13 @@ public interface OidcClientMetadataClaimNames { String ID_TOKEN_SIGNED_RESPONSE_ALG = "id_token_signed_response_alg"; /** - * {@code registration_access_token} - Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration + * {@code registration_access_token} - the Registration Access Token that can be used at the Client Configuration Endpoint * @since 0.2.1 */ String REGISTRATION_ACCESS_TOKEN = "registration_access_token"; /** - * {@code registration_client_uri} - the {@code URL} of the OAuth 2.0 Client Configuration Endpoint + * {@code registration_client_uri} - the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used * @since 0.2.1 */ String REGISTRATION_CLIENT_URI = "registration_client_uri"; diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistration.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistration.java index 2361a775..8ed3f95f 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistration.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistration.java @@ -252,23 +252,25 @@ public final class OidcClientRegistration implements OidcClientMetadataClaimAcce } /** - * Sets the Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration, OPTIONAL. + * Sets the Registration Access Token that can be used at the Client Configuration Endpoint, OPTIONAL. * - * @param registrationAccessToken the Registration Access Token that can be used at the Client Configuration Endpoint to perform subsequent operations upon the Client registration + * @param registrationAccessToken the Registration Access Token that can be used at the Client Configuration Endpoint * @return the {@link Builder} for further configuration + * @since 0.2.1 */ public Builder registrationAccessToken(String registrationAccessToken) { return claim(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, registrationAccessToken); } /** - * Sets the {@code URL} of the OAuth 2.0 Client Configuration Endpoint, OPTIONAL. + * Sets the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used, OPTIONAL. * - * @param registrationClientUri the {@code URL} of the OAuth 2.0 Client Configuration Endpoint + * @param registrationClientUrl the {@code URL} of the Client Configuration Endpoint where the Registration Access Token can be used * @return the {@link Builder} for further configuration + * @since 0.2.1 */ - public Builder registrationClientUri(String registrationClientUri) { - return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUri); + public Builder registrationClientUrl(String registrationClientUrl) { + return claim(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, registrationClientUrl); } /** diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/JwtUtils.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/JwtUtils.java index a477d689..ef9d88db 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/JwtUtils.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/JwtUtils.java @@ -19,6 +19,7 @@ import java.time.Instant; import java.util.Collections; import java.util.Set; +import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.security.oauth2.jwt.JoseHeader; @@ -29,14 +30,17 @@ import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; /** + * TODO + * This class is mostly a straight copy from {@code org.springframework.security.oauth2.server.authorization.authentication.JwtUtils}. + * It should be consolidated when we introduce a token generator abstraction. + * + * Utility methods used by the {@link AuthenticationProvider}'s when issuing {@link Jwt}'s. * - * Utility methods used by the {@link OidcClientRegistrationAuthenticationProvider} when issuing {@link Jwt}'s. * @author Ovidiu Popa * @since 0.2.1 */ final class JwtUtils { - //TODO Duplicate of {@code org.springframework.security.oauth2.server.authorization.authentication.JwtUtils}. To be refactored private JwtUtils() { } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java index d4b83a2f..e07cf893 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProvider.java @@ -20,13 +20,12 @@ import java.net.URISyntaxException; import java.time.Instant; import java.util.Base64; import java.util.Collection; -import java.util.HashSet; +import java.util.Collections; import java.util.List; import java.util.Set; import java.util.UUID; import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.lang.Nullable; import org.springframework.security.authentication.AuthenticationProvider; import org.springframework.security.core.Authentication; import org.springframework.security.core.AuthenticationException; @@ -56,18 +55,17 @@ import org.springframework.security.oauth2.server.authorization.config.TokenSett import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; -import org.springframework.util.StringUtils; import org.springframework.web.util.UriComponentsBuilder; /** - * An {@link AuthenticationProvider} implementation for OpenID Connect Dynamic Client Registration 1.0 and - * OpenID Connect Client Configuration 1.0. + * An {@link AuthenticationProvider} implementation for OpenID Connect 1.0 Dynamic Client Registration (and Configuration) Endpoint. * * @author Ovidiu Popa * @author Joe Grandja * @since 0.1.1 * @see RegisteredClientRepository * @see OAuth2AuthorizationService + * @see JwtEncoder * @see 3. Client Registration Endpoint * @see 4. Client Configuration Endpoint */ @@ -80,9 +78,25 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe private static final String DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE = "client.read"; private final RegisteredClientRepository registeredClientRepository; private final OAuth2AuthorizationService authorizationService; - private final JwtEncoder jwtEncoder; + private JwtEncoder jwtEncoder; private ProviderSettings providerSettings; + /** + * Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters. + * + * @param registeredClientRepository the repository of registered clients + * @param authorizationService the authorization service + * @deprecated Use {@link #OidcClientRegistrationAuthenticationProvider(RegisteredClientRepository, OAuth2AuthorizationService, JwtEncoder)} instead + */ + @Deprecated + public OidcClientRegistrationAuthenticationProvider(RegisteredClientRepository registeredClientRepository, + OAuth2AuthorizationService authorizationService) { + Assert.notNull(registeredClientRepository, "registeredClientRepository cannot be null"); + Assert.notNull(authorizationService, "authorizationService cannot be null"); + this.registeredClientRepository = registeredClientRepository; + this.authorizationService = authorizationService; + } + /** * Constructs an {@code OidcClientRegistrationAuthenticationProvider} using the provided parameters. * @@ -100,6 +114,12 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe this.jwtEncoder = jwtEncoder; } + @Deprecated + @Autowired(required = false) + protected void setJwtEncoder(JwtEncoder jwtEncoder) { + this.jwtEncoder = jwtEncoder; + } + @Autowired(required = false) protected void setProviderSettings(ProviderSettings providerSettings) { this.providerSettings = providerSettings; @@ -110,7 +130,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = (OidcClientRegistrationAuthenticationToken) authentication; - // Validate the "initial" and the registration access token + // Validate the "initial" or "registration" access token AbstractOAuth2TokenAuthenticationToken accessTokenAuthentication = null; if (AbstractOAuth2TokenAuthenticationToken.class.isAssignableFrom(clientRegistrationAuthentication.getPrincipal().getClass())) { accessTokenAuthentication = (AbstractOAuth2TokenAuthenticationToken) clientRegistrationAuthentication.getPrincipal(); @@ -132,78 +152,77 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); } - if (clientRegistrationAuthentication.getClientRegistration() != null) { + return clientRegistrationAuthentication.getClientRegistration() != null ? + registerClient(clientRegistrationAuthentication, authorization) : + findRegistration(clientRegistrationAuthentication, authorization); + } - return registerClient(clientRegistrationAuthentication, authorization); - } + @Override + public boolean supports(Class authentication) { + return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication); + } - if (isNotAuthorized(authorizedAccessToken, DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - } + private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, + OAuth2Authorization authorization) { - RegisteredClient registeredClient = this.registeredClientRepository - .findByClientId(clientRegistrationAuthentication.getClientId()); + OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); + checkScopeForConfiguration(authorizedAccessToken); + RegisteredClient registeredClient = this.registeredClientRepository.findByClientId( + clientRegistrationAuthentication.getClientId()); if (registeredClient == null) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); } + if (!registeredClient.getId().equals(authorization.getRegisteredClientId())) { throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); } - String registrationClientUri = registrationClientUri(getIssuer(), this.providerSettings.getOidcClientRegistrationEndpoint(), registeredClient.getClientId()); - return new OidcClientRegistrationAuthenticationToken(accessTokenAuthentication, - convert(registeredClient, registrationClientUri, null)); + OidcClientRegistration clientRegistration = buildRegistration(registeredClient).build(); + return new OidcClientRegistrationAuthenticationToken( + (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration); } private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication, OAuth2Authorization authorization) { OAuth2Authorization.Token authorizedAccessToken = authorization.getAccessToken(); - if (isNotAuthorized(authorizedAccessToken, DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE)) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); - } + checkScopeForRegistration(authorizedAccessToken); if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) { // TODO Add OAuth2ErrorCodes.INVALID_REDIRECT_URI throw new OAuth2AuthenticationException("invalid_redirect_uri"); } - RegisteredClient registeredClient = create(clientRegistrationAuthentication.getClientRegistration()); + RegisteredClient registeredClient = createClient(clientRegistrationAuthentication.getClientRegistration()); this.registeredClientRepository.save(registeredClient); + OAuth2Authorization registeredClientAuthorization = registerAccessToken(registeredClient); + // Invalidate the "initial" access token as it can only be used once authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorizedAccessToken.getToken()); if (authorization.getRefreshToken() != null) { authorization = OidcAuthenticationProviderUtils.invalidate(authorization, authorization.getRefreshToken().getToken()); } this.authorizationService.save(authorization); - String registrationClientUri = registrationClientUri(getIssuer(), this.providerSettings.getOidcClientRegistrationEndpoint(), registeredClient.getClientId()); - String registrationAccessToken = registerAccessToken(registeredClient) - .getAccessToken().getToken().getTokenValue(); - - return new OidcClientRegistrationAuthenticationToken((AbstractOAuth2TokenAuthenticationToken) clientRegistrationAuthentication.getPrincipal(), - convert(registeredClient, registrationClientUri, registrationAccessToken)); - } - @Override - public boolean supports(Class authentication) { - return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication); - } + OidcClientRegistration clientRegistration = buildRegistration(registeredClient) + .registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue()) + .build(); - private String getIssuer() { - return this.providerSettings != null ? this.providerSettings.getIssuer() : null; + return new OidcClientRegistrationAuthenticationToken( + (Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration); } private OAuth2Authorization registerAccessToken(RegisteredClient registeredClient) { - - String issuer = getIssuer(); - Set authorizedScopes = new HashSet<>(); - authorizedScopes.add(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE); JoseHeader headers = JwtUtils.headers().build(); + + Set authorizedScopes = Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE); + JwtClaimsSet claims = JwtUtils.accessTokenClaims( - registeredClient, issuer, registeredClient.getClientId(), authorizedScopes).build(); + registeredClient, this.providerSettings.getIssuer(), registeredClient.getClientId(), authorizedScopes) + .build(); Jwt registrationAccessToken = this.jwtEncoder.encode(headers, claims); @@ -212,7 +231,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe registrationAccessToken.getExpiresAt(), authorizedScopes); // @formatter:off - OAuth2Authorization accessTokenAuthorization = OAuth2Authorization.withRegisteredClient(registeredClient) + OAuth2Authorization registeredClientAuthorization = OAuth2Authorization.withRegisteredClient(registeredClient) .principalName(registeredClient.getClientId()) .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) .token(accessToken, @@ -222,14 +241,69 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe .build(); // @formatter:on - this.authorizationService.save(accessTokenAuthorization); - return accessTokenAuthorization; + this.authorizationService.save(registeredClientAuthorization); + + return registeredClientAuthorization; + } + + private OidcClientRegistration.Builder buildRegistration(RegisteredClient registeredClient) { + // @formatter:off + OidcClientRegistration.Builder builder = OidcClientRegistration.builder() + .clientId(registeredClient.getClientId()) + .clientIdIssuedAt(registeredClient.getClientIdIssuedAt()) + .clientSecret(registeredClient.getClientSecret()) + .clientName(registeredClient.getClientName()); + + builder.redirectUris(redirectUris -> + redirectUris.addAll(registeredClient.getRedirectUris())); + + builder.grantTypes(grantTypes -> + registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType -> + grantTypes.add(authorizationGrantType.getValue()))); + + if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) { + builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue()); + } + + if (!CollectionUtils.isEmpty(registeredClient.getScopes())) { + builder.scopes(scopes -> + scopes.addAll(registeredClient.getScopes())); + } + + String registrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) + .path(this.providerSettings.getOidcClientRegistrationEndpoint()) + .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()) + .toUriString(); + + builder + .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()) + .idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()) + .registrationClientUrl(registrationClientUri); + + return builder; + // @formatter:on + } + + private static void checkScopeForRegistration(OAuth2Authorization.Token authorizedAccessToken) { + checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE)); + } + + private static void checkScopeForConfiguration(OAuth2Authorization.Token authorizedAccessToken) { + checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE)); } @SuppressWarnings("unchecked") - private static boolean isNotAuthorized(OAuth2Authorization.Token authorizedAccessToken, String requiredScope) { - Object scope = authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); - return scope == null || !((Collection) scope).contains(requiredScope); + private static void checkScope(OAuth2Authorization.Token authorizedAccessToken, Set requiredScope) { + Collection authorizedScope = Collections.emptySet(); + if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) { + authorizedScope = (Collection) authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE); + } + if (!authorizedScope.containsAll(requiredScope)) { + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); + } else if (authorizedScope.size() != requiredScope.size()) { + // Restrict the access token to only contain the required scope + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN); + } } private static boolean isValidRedirectUris(List redirectUris) { @@ -251,7 +325,7 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe return true; } - private static RegisteredClient create(OidcClientRegistration clientRegistration) { + private static RegisteredClient createClient(OidcClientRegistration clientRegistration) { // @formatter:off RegisteredClient.Builder builder = RegisteredClient.withId(UUID.randomUUID().toString()) .clientId(CLIENT_ID_GENERATOR.generateKey()) @@ -298,47 +372,4 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe // @formatter:on } - - private static String registrationClientUri(String issuer, String oidcClientRegistrationEndpoint, String clientId){ - return UriComponentsBuilder.fromUriString(issuer) - .path(oidcClientRegistrationEndpoint) - .queryParam(OAuth2ParameterNames.CLIENT_ID, clientId).toUriString(); - } - - private static OidcClientRegistration convert(RegisteredClient registeredClient, String registrationClientUri, - @Nullable String registrationAccessToken) { - // @formatter:off - OidcClientRegistration.Builder builder = OidcClientRegistration.builder() - .clientId(registeredClient.getClientId()) - .clientIdIssuedAt(registeredClient.getClientIdIssuedAt()) - .clientSecret(registeredClient.getClientSecret()) - .clientName(registeredClient.getClientName()); - - builder.redirectUris(redirectUris -> - redirectUris.addAll(registeredClient.getRedirectUris())); - - builder.grantTypes(grantTypes -> - registeredClient.getAuthorizationGrantTypes().forEach(authorizationGrantType -> - grantTypes.add(authorizationGrantType.getValue()))); - - if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.AUTHORIZATION_CODE)) { - builder.responseType(OAuth2AuthorizationResponseType.CODE.getValue()); - } - - if (!CollectionUtils.isEmpty(registeredClient.getScopes())) { - builder.scopes(scopes -> - scopes.addAll(registeredClient.getScopes())); - } - - builder - .tokenEndpointAuthenticationMethod(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()) - .idTokenSignedResponseAlgorithm(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()) - .registrationClientUri(registrationClientUri); - if (StringUtils.hasText(registrationAccessToken)) { - builder.registrationAccessToken(registrationAccessToken); - } - return builder.build(); - // @formatter:on - } - } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java index 19e4753f..a227c21e 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationToken.java @@ -17,6 +17,7 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentica import java.util.Collections; +import org.springframework.lang.Nullable; import org.springframework.security.authentication.AbstractAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.Version; @@ -24,7 +25,7 @@ import org.springframework.security.oauth2.core.oidc.OidcClientRegistration; import org.springframework.util.Assert; /** - * An {@link Authentication} implementation used for OpenID Connect Dynamic Client Registration 1.0. + * An {@link Authentication} implementation used for OpenID Connect 1.0 Dynamic Client Registration (and Configuration) Endpoint. * * @author Joe Grandja * @author Ovidiu Popa @@ -36,8 +37,9 @@ import org.springframework.util.Assert; public class OidcClientRegistrationAuthenticationToken extends AbstractAuthenticationToken { private static final long serialVersionUID = Version.SERIAL_VERSION_UID; private final Authentication principal; - private OidcClientRegistration clientRegistration; - private String clientId; + private final OidcClientRegistration clientRegistration; + private final String clientId; + /** * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided parameters. * @@ -50,6 +52,7 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic Assert.notNull(clientRegistration, "clientRegistration cannot be null"); this.principal = principal; this.clientRegistration = clientRegistration; + this.clientId = null; setAuthenticated(principal.isAuthenticated()); } @@ -57,13 +60,14 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic * Constructs an {@code OidcClientRegistrationAuthenticationToken} using the provided parameters. * * @param principal the authenticated principal - * @param clientId the registered client_id + * @param clientId the client identifier */ public OidcClientRegistrationAuthenticationToken(Authentication principal, String clientId) { super(Collections.emptyList()); Assert.notNull(principal, "principal cannot be null"); - Assert.hasText(clientId, "clientId cannot be null or empty"); + Assert.hasText(clientId, "clientId cannot be empty"); this.principal = principal; + this.clientRegistration = null; this.clientId = clientId; setAuthenticated(principal.isAuthenticated()); } @@ -88,11 +92,12 @@ public class OidcClientRegistrationAuthenticationToken extends AbstractAuthentic } /** - * Returns the registered client_id. + * Returns the client identifier. * - * @return the registered client_id + * @return the client identifier * @since 0.2.1 */ + @Nullable public String getClientId() { return this.clientId; } diff --git a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java index 3eeb2217..7c4911f8 100644 --- a/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java +++ b/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilter.java @@ -47,7 +47,7 @@ import org.springframework.util.StringUtils; import org.springframework.web.filter.OncePerRequestFilter; /** - * A {@code Filter} that processes OpenID Connect Dynamic Client Registration 1.0 Requests and OpenID Connect Client Configuration 1.0 Requests. + * A {@code Filter} that processes OpenID Connect Dynamic Client Registration (and Configuration) 1.0 Requests. * * @author Ovidiu Popa * @author Joe Grandja @@ -64,8 +64,6 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi private final AuthenticationManager authenticationManager; private final RequestMatcher clientRegistrationEndpointMatcher; - private final RequestMatcher registerClientEndpointMatcher; - private final RequestMatcher clientConfigurationEndpointMatcher; private final HttpMessageConverter clientRegistrationHttpMessageConverter = new OidcClientRegistrationHttpMessageConverter(); private final HttpMessageConverter errorHttpResponseConverter = @@ -91,15 +89,14 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi Assert.notNull(authenticationManager, "authenticationManager cannot be null"); Assert.hasText(clientRegistrationEndpointUri, "clientRegistrationEndpointUri cannot be empty"); this.authenticationManager = authenticationManager; - this.registerClientEndpointMatcher = new AntPathRequestMatcher( - clientRegistrationEndpointUri, HttpMethod.POST.name()); - this.clientConfigurationEndpointMatcher = createClientConfigurationEndpointMatcher(clientRegistrationEndpointUri); - this.clientRegistrationEndpointMatcher = new OrRequestMatcher(this.registerClientEndpointMatcher, this.clientConfigurationEndpointMatcher); + this.clientRegistrationEndpointMatcher = new OrRequestMatcher( + new AntPathRequestMatcher( + clientRegistrationEndpointUri, HttpMethod.POST.name()), + createConfigureClientMatcher(clientRegistrationEndpointUri)); } - private static RequestMatcher createClientConfigurationEndpointMatcher(String clientRegistrationEndpointUri) { - - RequestMatcher clientConfigurationRequestGetMatcher = new AntPathRequestMatcher( + private static RequestMatcher createConfigureClientMatcher(String clientRegistrationEndpointUri) { + RequestMatcher configureClientGetMatcher = new AntPathRequestMatcher( clientRegistrationEndpointUri, HttpMethod.GET.name()); RequestMatcher clientIdMatcher = request -> { @@ -107,7 +104,7 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi return StringUtils.hasText(clientId); }; - return new AndRequestMatcher(clientConfigurationRequestGetMatcher, clientIdMatcher); + return new AndRequestMatcher(configureClientGetMatcher, clientIdMatcher); } @Override @@ -120,17 +117,17 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi } try { - OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationToken = convert(request); + OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication = convert(request); OidcClientRegistrationAuthenticationToken clientRegistrationAuthenticationResult = - (OidcClientRegistrationAuthenticationToken) this.authenticationManager.authenticate(clientRegistrationAuthenticationToken); + (OidcClientRegistrationAuthenticationToken) this.authenticationManager.authenticate(clientRegistrationAuthentication); - if (clientRegistrationAuthenticationToken.getClientRegistration() != null) { - sendClientRegistrationResponse(response, HttpStatus.CREATED, clientRegistrationAuthenticationResult.getClientRegistration()); - return; + HttpStatus httpStatus = HttpStatus.OK; + if (clientRegistrationAuthentication.getClientRegistration() != null) { + httpStatus = HttpStatus.CREATED; } - sendClientRegistrationResponse(response, HttpStatus.OK, clientRegistrationAuthenticationResult.getClientRegistration()); + sendClientRegistrationResponse(response, httpStatus, clientRegistrationAuthenticationResult.getClientRegistration()); } catch (OAuth2AuthenticationException ex) { sendErrorResponse(response, ex.getError()); @@ -145,41 +142,23 @@ public final class OidcClientRegistrationEndpointFilter extends OncePerRequestFi } } - private OidcClientRegistrationAuthenticationToken convert(HttpServletRequest request) { - if (this.registerClientEndpointMatcher.matches(request)) { - return convertOidcClientRegistrationRequest(request); - } + private OidcClientRegistrationAuthenticationToken convert(HttpServletRequest request) throws Exception { + Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - if (this.clientConfigurationEndpointMatcher.matches(request)) { - return convertOidcClientConfigurationRequest(request); + if ("POST".equals(request.getMethod())) { + OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read( + OidcClientRegistration.class, new ServletServerHttpRequest(request)); + return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); } - throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST)); - } - - private OidcClientRegistrationAuthenticationToken convertOidcClientConfigurationRequest(HttpServletRequest request) { - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); + // client_id (REQUIRED) String clientId = request.getParameter(OAuth2ParameterNames.CLIENT_ID); String[] clientIdParameters = request.getParameterValues(OAuth2ParameterNames.CLIENT_ID); if (!StringUtils.hasText(clientId) || clientIdParameters.length != 1) { - throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); + throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_REQUEST); } - return new OidcClientRegistrationAuthenticationToken(principal, clientId); - } - private OidcClientRegistrationAuthenticationToken convertOidcClientRegistrationRequest(HttpServletRequest request) { - try { - Authentication principal = SecurityContextHolder.getContext().getAuthentication(); - OidcClientRegistration clientRegistration = this.clientRegistrationHttpMessageConverter.read( - OidcClientRegistration.class, new ServletServerHttpRequest(request)); - return new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); - } catch (IOException ex) { - OAuth2Error error = new OAuth2Error( - OAuth2ErrorCodes.INVALID_REQUEST, - "OpenID Client Registration Error: " + ex.getMessage(), - "https://openid.net/specs/openid-connect-registration-1_0.html#RegistrationError"); - throw new OAuth2AuthenticationException(error); - } + return new OidcClientRegistrationAuthenticationToken(principal, clientId); } private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException { diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java index 447cd534..92d1ade0 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/authorization/OidcClientRegistrationTests.java @@ -18,17 +18,16 @@ package org.springframework.security.config.annotation.web.configurers.oauth2.se import java.net.URLEncoder; import java.nio.charset.StandardCharsets; import java.util.Base64; -import java.util.stream.Collectors; import com.nimbusds.jose.jwk.JWKSet; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; - import org.junit.After; import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.http.HttpHeaders; @@ -138,28 +137,6 @@ public class OidcClientRegistrationTests { public void requestWhenClientRegistrationRequestAuthorizedThenClientRegistrationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); - // ***** (1) Obtain the "initial" access token used for registering the client - - String clientRegistrationScope = "client.create"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() - .scope(clientRegistrationScope) - .build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - // ***** (2) Register the client - // @formatter:off OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .clientName("client-name") @@ -171,20 +148,8 @@ public class OidcClientRegistrationTests { .build(); // @formatter:on - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // Register the client - mvcResult = this.mvc.perform(post(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) - .andExpect(status().isCreated()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); + OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); - OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse()); assertThat(clientRegistrationResponse.getClientId()).isNotNull(); assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull(); assertThat(clientRegistrationResponse.getClientSecret()).isNotNull(); @@ -202,181 +167,75 @@ public class OidcClientRegistrationTests { .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientRegistrationResponse.getRegistrationClientUri()) - .isNotNull(); + assertThat(clientRegistrationResponse.getRegistrationClientUrl()).isNotNull(); assertThat(clientRegistrationResponse.getRegistrationAccessToken()).isNotEmpty(); } - @Test - public void requestWhenClientConfigurationRequestAndRegisteredClientNotEqualToAuthorizationRegisteredClientThenUnauthorized() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // ***** (1) Obtain the registration access token used for fetching the registered client configuration - - String clientConfigurationRequestScope = "client.read"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(clientConfigurationRequestScope) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - this.registeredClientRepository.save(registeredClient); - - RegisteredClient unauthorizedRegisteredClient = TestRegisteredClients.registeredClient() - .id("registration-2") - .clientId("client-2") - .scope(clientConfigurationRequestScope) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - this.registeredClientRepository.save(unauthorizedRegisteredClient); - - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // ***** (2) Get RegisteredClient Configuration - this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .queryParam(OAuth2ParameterNames.CLIENT_ID, unauthorizedRegisteredClient.getClientId())) - .andExpect(status().isUnauthorized()); - } - @Test public void requestWhenClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception { this.spring.register(AuthorizationServerConfiguration.class).autowire(); - // ***** (1) Obtain the registration access token used for fetching the registered client configuration - - String clientConfigurationRequestScope = "client.read"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(clientConfigurationRequestScope) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) + // @formatter:off + OidcClientRegistration clientRegistration = OidcClientRegistration.builder() + .clientName("client-name") + .redirectUri("https://client.example.com") + .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) + .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .scope("scope1") + .scope("scope2") .build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope)) - .andReturn(); + // @formatter:on - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); + OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration); HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); + httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken()); - // ***** (2) Get RegisteredClient Configuration - mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) + MvcResult mvcResult = this.mvc.perform(get(clientRegistrationResponse.getRegistrationClientUrl().toURI()) + .headers(httpHeaders)) .andExpect(status().isOk()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(registeredClient.getClientId()); - assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(registeredClient.getClientSecret()); - assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientConfigurationResponse.getClientName()).isEqualTo(registeredClient.getClientName()); + + assertThat(clientConfigurationResponse.getClientId()).isEqualTo(clientRegistrationResponse.getClientId()); + assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isEqualTo(clientRegistrationResponse.getClientIdIssuedAt()); + assertThat(clientConfigurationResponse.getClientSecret()).isEqualTo(clientRegistrationResponse.getClientSecret()); + assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isEqualTo(clientRegistrationResponse.getClientSecretExpiresAt()); + assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName()); assertThat(clientConfigurationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris()); + .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris()); assertThat(clientConfigurationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getAuthorizationGrantTypes().stream().map(AuthorizationGrantType::getValue).collect(Collectors.toList())); + .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes()); assertThat(clientConfigurationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); + .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getResponseTypes()); assertThat(clientConfigurationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getScopes()); + .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes()); assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); + .isEqualTo(clientRegistrationResponse.getTokenEndpointAuthenticationMethod()); assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientConfigurationResponse.getRegistrationClientUri()) - .isNotNull(); + .isEqualTo(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); + assertThat(clientConfigurationResponse.getRegistrationClientUrl()) + .isEqualTo(clientRegistrationResponse.getRegistrationClientUrl()); assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); } - @Test - public void requestWhenClientConfigurationRequestTwiceSameAccessTokenAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - - // ***** (1) Obtain the registration access token used for fetching the registered client configuration - - String clientConfigurationRequestScope = "client.read"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .scope(clientConfigurationRequestScope) - .authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS) - .build(); - this.registeredClientRepository.save(registeredClient); - - MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientConfigurationRequestScope) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.access_token").isNotEmpty()) - .andExpect(jsonPath("$.scope").value(clientConfigurationRequestScope)) - .andReturn(); - - OAuth2AccessToken accessToken = readAccessTokenResponse(mvcResult.getResponse()).getAccessToken(); - - HttpHeaders httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(accessToken.getTokenValue()); - - // ***** (2) Get RegisteredClient Configuration - mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - assertClientConfigurationResponse(registeredClient, mvcResult); - - // ***** (3) Get RegisteredClient Configuration with the same access token - mvcResult = this.mvc.perform(get(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId())) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - assertClientConfigurationResponse(registeredClient, mvcResult); - } - - @Test - public void requestWhenClientRegistrationRequestAndClientConfigurationRequestAuthorizedThenClientRegistrationResponse() throws Exception { - this.spring.register(AuthorizationServerConfiguration.class).autowire(); - + private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception { // ***** (1) Obtain the "initial" access token used for registering the client String clientRegistrationScope = "client.create"; - RegisteredClient registeredClient = TestRegisteredClients.registeredClient2() + RegisteredClient clientRegistrar = TestRegisteredClients.registeredClient2() .scope(clientRegistrationScope) .build(); - this.registeredClientRepository.save(registeredClient); + this.registeredClientRepository.save(clientRegistrar); MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI) - .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) - .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( - registeredClient.getClientId(), registeredClient.getClientSecret()))) + .param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) + .param(OAuth2ParameterNames.SCOPE, clientRegistrationScope) + .header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth( + clientRegistrar.getClientId(), clientRegistrar.getClientSecret()))) .andExpect(status().isOk()) .andExpect(jsonPath("$.access_token").isNotEmpty()) .andExpect(jsonPath("$.scope").value(clientRegistrationScope)) @@ -386,93 +245,22 @@ public class OidcClientRegistrationTests { // ***** (2) Register the client - // @formatter:off - OidcClientRegistration clientRegistration = OidcClientRegistration.builder() - .clientName("client-name") - .redirectUri("https://client.example.com") - .grantType(AuthorizationGrantType.AUTHORIZATION_CODE.getValue()) - .grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue()) - .scope("scope1") - .scope("scope2") - .build(); - // @formatter:on - HttpHeaders httpHeaders = new HttpHeaders(); httpHeaders.setBearerAuth(accessToken.getTokenValue()); // Register the client mvcResult = this.mvc.perform(post(DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI) - .headers(httpHeaders) - .contentType(MediaType.APPLICATION_JSON) - .content(getClientRegistrationRequestContent(clientRegistration))) + .headers(httpHeaders) + .contentType(MediaType.APPLICATION_JSON) + .content(getClientRegistrationRequestContent(clientRegistration))) .andExpect(status().isCreated()) .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) .andReturn(); - OidcClientRegistration clientRegistrationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - - - httpHeaders = new HttpHeaders(); - httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken()); - - // ***** (3) Get RegisteredClient Configuration - mvcResult = this.mvc.perform(get(clientRegistrationResponse.getRegistrationClientUri().toString()) - .headers(httpHeaders)) - .andExpect(status().isOk()) - .andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store"))) - .andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache"))) - .andReturn(); - - OidcClientRegistration clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(clientRegistrationResponse.getClientId()); - assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(clientRegistrationResponse.getClientSecret()); - assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientConfigurationResponse.getClientName()).isEqualTo(clientRegistrationResponse.getClientName()); - assertThat(clientConfigurationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getRedirectUris()); - assertThat(clientConfigurationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes()); - assertThat(clientConfigurationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientConfigurationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getScopes()); - assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientConfigurationResponse.getRegistrationClientUri()) - .isNotNull(); - assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); - } - - private static void assertClientConfigurationResponse(RegisteredClient registeredClient, MvcResult mvcResult) throws Exception { - OidcClientRegistration clientConfigurationResponse; - clientConfigurationResponse = readClientRegistrationResponse(mvcResult.getResponse()); - assertThat(clientConfigurationResponse.getClientId()).isNotNull().isEqualTo(registeredClient.getClientId()); - assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull(); - assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(registeredClient.getClientSecret()); - assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull(); - assertThat(clientConfigurationResponse.getClientName()).isEqualTo(registeredClient.getClientName()); - assertThat(clientConfigurationResponse.getRedirectUris()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getRedirectUris()); - assertThat(clientConfigurationResponse.getGrantTypes()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getAuthorizationGrantTypes().stream().map(AuthorizationGrantType::getValue).collect(Collectors.toList())); - assertThat(clientConfigurationResponse.getResponseTypes()) - .containsExactly(OAuth2AuthorizationResponseType.CODE.getValue()); - assertThat(clientConfigurationResponse.getScopes()) - .containsExactlyInAnyOrderElementsOf(registeredClient.getScopes()); - assertThat(clientConfigurationResponse.getTokenEndpointAuthenticationMethod()) - .isEqualTo(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()); - assertThat(clientConfigurationResponse.getIdTokenSignedResponseAlgorithm()) - .isEqualTo(SignatureAlgorithm.RS256.getName()); - assertThat(clientConfigurationResponse.getRegistrationClientUri()) - .isNotNull(); - assertThat(clientConfigurationResponse.getRegistrationAccessToken()).isNull(); + return readClientRegistrationResponse(mvcResult.getResponse()); } - private static String encodeBasicAuth(String clientId, String secret) throws Exception { clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name()); secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name()); @@ -552,8 +340,9 @@ public class OidcClientRegistrationTests { @Bean ProviderSettings providerSettings() { - return ProviderSettings.builder().issuer("http://auth-server:9000") - .oidcClientRegistrationEndpoint("/connect/register").build(); + return ProviderSettings.builder() + .issuer("https://auth-server:9000") + .build(); } @Bean diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistrationTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistrationTests.java index 9f956229..02e04579 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistrationTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/OidcClientRegistrationTests.java @@ -63,9 +63,9 @@ public class OidcClientRegistrationTests { .scope("scope1") .scope("scope2") .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .claim("a-claim", "a-value") .registrationAccessToken("registration-access-token") - .registrationClientUri("https://auth-server.com/connect/register?client_id=1") + .registrationClientUrl("https://auth-server.com/connect/register?client_id=1") + .claim("a-claim", "a-value") .build(); // @formatter:on @@ -80,9 +80,9 @@ public class OidcClientRegistrationTests { assertThat(clientRegistration.getResponseTypes()).containsOnly("code"); assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256"); - assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token"); - assertThat(clientRegistration.getRegistrationClientUri().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1"); + assertThat(clientRegistration.getRegistrationClientUrl().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1"); + assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); } @Test @@ -108,9 +108,9 @@ public class OidcClientRegistrationTests { claims.put(OidcClientMetadataClaimNames.RESPONSE_TYPES, Collections.singletonList("code")); claims.put(OidcClientMetadataClaimNames.SCOPE, Arrays.asList("scope1", "scope2")); claims.put(OidcClientMetadataClaimNames.ID_TOKEN_SIGNED_RESPONSE_ALG, SignatureAlgorithm.RS256.getName()); - claims.put("a-claim", "a-value"); claims.put(OidcClientMetadataClaimNames.REGISTRATION_ACCESS_TOKEN, "registration-access-token"); claims.put(OidcClientMetadataClaimNames.REGISTRATION_CLIENT_URI, "https://auth-server.com/connect/register?client_id=1"); + claims.put("a-claim", "a-value"); OidcClientRegistration clientRegistration = OidcClientRegistration.withClaims(claims).build(); @@ -125,9 +125,9 @@ public class OidcClientRegistrationTests { assertThat(clientRegistration.getResponseTypes()).containsOnly("code"); assertThat(clientRegistration.getScopes()).containsExactlyInAnyOrder("scope1", "scope2"); assertThat(clientRegistration.getIdTokenSignedResponseAlgorithm()).isEqualTo("RS256"); - assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); assertThat(clientRegistration.getRegistrationAccessToken()).isEqualTo("registration-access-token"); - assertThat(clientRegistration.getRegistrationClientUri().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1"); + assertThat(clientRegistration.getRegistrationClientUrl().toString()).isEqualTo("https://auth-server.com/connect/register?client_id=1"); + assertThat(clientRegistration.getClaimAsString("a-claim")).isEqualTo("a-value"); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java index dc547e42..f63246b6 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/core/oidc/http/converter/OidcClientRegistrationHttpMessageConverterTests.java @@ -184,9 +184,9 @@ public class OidcClientRegistrationHttpMessageConverterTests { .scope("scope1") .scope("scope2") .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .claim("a-claim", "a-value") - .registrationClientUri("https://auth-server.com/connect/register?client_id=1") .registrationAccessToken("registration-access-token") + .registrationClientUrl("https://auth-server.com/connect/register?client_id=1") + .claim("a-claim", "a-value") .build(); // @formatter:on @@ -205,9 +205,9 @@ public class OidcClientRegistrationHttpMessageConverterTests { assertThat(clientRegistrationResponse).contains("\"response_types\":[\"code\"]"); assertThat(clientRegistrationResponse).contains("\"scope\":\"scope1 scope2\""); assertThat(clientRegistrationResponse).contains("\"id_token_signed_response_alg\":\"RS256\""); - assertThat(clientRegistrationResponse).contains("\"a-claim\":\"a-value\""); assertThat(clientRegistrationResponse).contains("\"registration_access_token\":\"registration-access-token\""); assertThat(clientRegistrationResponse).contains("\"registration_client_uri\":\"https://auth-server.com/connect/register?client_id=1\""); + assertThat(clientRegistrationResponse).contains("\"a-claim\":\"a-value\""); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java index 7c40d8ea..db9368dd 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationProviderTests.java @@ -16,13 +16,16 @@ package org.springframework.security.oauth2.server.authorization.oidc.authentication; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Set; import org.junit.Before; import org.junit.Test; import org.mockito.ArgumentCaptor; + import org.springframework.security.authentication.TestingAuthenticationToken; import org.springframework.security.core.authority.AuthorityUtils; import org.springframework.security.oauth2.core.AuthorizationGrantType; @@ -57,6 +60,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -70,34 +74,32 @@ import static org.mockito.Mockito.when; public class OidcClientRegistrationAuthenticationProviderTests { private RegisteredClientRepository registeredClientRepository; private OAuth2AuthorizationService authorizationService; - private OidcClientRegistrationAuthenticationProvider authenticationProvider; private JwtEncoder jwtEncoder; private ProviderSettings providerSettings; + private OidcClientRegistrationAuthenticationProvider authenticationProvider; @Before public void setUp() { - this.registeredClientRepository = mock(RegisteredClientRepository.class); this.authorizationService = mock(OAuth2AuthorizationService.class); this.jwtEncoder = mock(JwtEncoder.class); - this.providerSettings = ProviderSettings.builder().issuer("http://auth-server:9000").build(); + this.providerSettings = ProviderSettings.builder().issuer("https://auth-server:9000").build(); this.authenticationProvider = new OidcClientRegistrationAuthenticationProvider( - this.registeredClientRepository, this.authorizationService, - this.jwtEncoder); - this.authenticationProvider.setProviderSettings(providerSettings); + this.registeredClientRepository, this.authorizationService, this.jwtEncoder); + this.authenticationProvider.setProviderSettings(this.providerSettings); } @Test public void constructorWhenRegisteredClientRepositoryNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService, jwtEncoder)) + .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(null, this.authorizationService, this.jwtEncoder)) .withMessage("registeredClientRepository cannot be null"); } @Test public void constructorWhenAuthorizationServiceNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() - .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null, jwtEncoder)) + .isThrownBy(() -> new OidcClientRegistrationAuthenticationProvider(this.registeredClientRepository, null, this.jwtEncoder)) .withMessage("authorizationService cannot be null"); } @@ -114,13 +116,14 @@ public class OidcClientRegistrationAuthenticationProviderTests { } @Test - public void authenticateWhenClientRegistrationRequestAndPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() { + public void authenticateWhenPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() { TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .redirectUri("https://client.example.com") .build(); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -129,13 +132,14 @@ public class OidcClientRegistrationAuthenticationProviderTests { } @Test - public void authenticateWhenClientRegistrationRequestAndPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { + public void authenticateWhenPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientRegistration()); OidcClientRegistration clientRegistration = OidcClientRegistration.builder() .redirectUri("https://client.example.com") .build(); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -144,7 +148,7 @@ public class OidcClientRegistrationAuthenticationProviderTests { } @Test - public void authenticateWhenClientRegistrationRequestAndAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { + public void authenticateWhenAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { Jwt jwt = createJwtClientRegistration(); JwtAuthenticationToken principal = new JwtAuthenticationToken( jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create")); @@ -152,7 +156,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .redirectUri("https://client.example.com") .build(); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -163,7 +168,7 @@ public class OidcClientRegistrationAuthenticationProviderTests { } @Test - public void authenticateWhenClientRegistrationRequestAndAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { + public void authenticateWhenAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { Jwt jwt = createJwtClientRegistration(); OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), @@ -182,7 +187,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .redirectUri("https://client.example.com") .build(); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -211,7 +217,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .redirectUri("https://client.example.com") .build(); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -221,6 +228,36 @@ public class OidcClientRegistrationAuthenticationProviderTests { eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); } + @Test + public void authenticateWhenClientRegistrationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() { + Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.create", "scope1"))); + OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, + jwt.getTokenValue(), jwt.getIssuedAt(), + jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); + RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); + OAuth2Authorization authorization = TestOAuth2Authorizations.authorization( + registeredClient, jwtAccessToken, jwt.getClaims()).build(); + when(this.authorizationService.findByToken( + eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) + .thenReturn(authorization); + + JwtAuthenticationToken principal = new JwtAuthenticationToken( + jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create", "SCOPE_scope1")); + OidcClientRegistration clientRegistration = OidcClientRegistration.builder() + .redirectUri("https://client.example.com") + .build(); + + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); + + assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) + .isInstanceOf(OAuth2AuthenticationException.class) + .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") + .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); + verify(this.authorizationService).findByToken( + eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); + } + @Test public void authenticateWhenClientRegistrationRequestAndInvalidRedirectUriThenThrowOAuth2AuthenticationException() { Jwt jwt = createJwtClientRegistration(); @@ -242,7 +279,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .build(); // @formatter:on - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -273,7 +311,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .build(); // @formatter:on - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -295,7 +334,7 @@ public class OidcClientRegistrationAuthenticationProviderTests { when(this.authorizationService.findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) .thenReturn(authorization); - when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(Collections.singleton("client.read"))); + when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwtClientConfiguration()); JwtAuthenticationToken principal = new JwtAuthenticationToken( jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create")); @@ -310,7 +349,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { .build(); // @formatter:on - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, clientRegistration); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, clientRegistration); OidcClientRegistrationAuthenticationToken authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -323,20 +363,20 @@ public class OidcClientRegistrationAuthenticationProviderTests { verify(this.authorizationService, times(2)).save(authorizationCaptor.capture()); verify(this.jwtEncoder).encode(any(), any()); - // assert access token + // assert "registration" access token, which should be used for subsequent calls to client configuration endpoint OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0); + assertThat(authorizationResult.getAccessToken().getToken().getScopes()) + .containsExactly("client.read"); + assertThat(authorizationResult.getAccessToken().isActive()).isTrue(); + assertThat(authorizationResult.getRefreshToken()).isNull(); + + // assert "initial" access token is invalidated + authorizationResult = authorizationCaptor.getAllValues().get(1); assertThat(authorizationResult.getAccessToken().isInvalidated()).isTrue(); if (authorizationResult.getRefreshToken() != null) { assertThat(authorizationResult.getRefreshToken().isInvalidated()).isTrue(); } - // assert registration access token which should be used for subsequent calls to client configuration endpoint - authorizationResult = authorizationCaptor.getAllValues().get(1); - assertThat(authorizationResult.getAccessToken().isInvalidated()).isFalse(); - assertThat(authorizationResult.getRefreshToken()).isNull(); - assertThat(authorizationResult.getAccessToken().getToken().getScopes()) - .containsExactly("client.read"); - RegisteredClient registeredClientResult = registeredClientCaptor.getValue(); assertThat(registeredClientResult.getId()).isNotNull(); assertThat(registeredClientResult.getClientId()).isNotNull(); @@ -375,84 +415,44 @@ public class OidcClientRegistrationAuthenticationProviderTests { assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) .isEqualTo(registeredClientResult.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - String expectedRegistrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) + String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) .path(this.providerSettings.getOidcClientRegistrationEndpoint()) - .queryParam("client_id", registeredClientResult.getClientId()).toUriString(); - - assertThat(clientRegistrationResult.getRegistrationClientUri().toString()).isEqualTo(expectedRegistrationClientUri); - assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNotEmpty().isEqualTo(jwt.getTokenValue()); - } - - @Test - public void authenticateWhenClientConfigurationRequestAndPrincipalNotOAuth2TokenAuthenticationTokenThenThrowOAuth2AuthenticationException() { - TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "credentials"); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } + .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClientResult.getClientId()).toUriString(); - @Test - public void authenticateWhenClientConfigurationRequestAndPrincipalNotAuthenticatedThenThrowOAuth2AuthenticationException() { - JwtAuthenticationToken principal = new JwtAuthenticationToken(createJwtClientConfiguration()); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - } - - @Test - public void authenticateWhenClientConfigurationRequestAndAccessTokenNotFoundThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); - JwtAuthenticationToken principal = new JwtAuthenticationToken( - jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1"); - - assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) - .isInstanceOf(OAuth2AuthenticationException.class) - .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); - verify(this.authorizationService).findByToken( - eq(jwt.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); + assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl); + assertThat(clientRegistrationResult.getRegistrationAccessToken()).isEqualTo(jwt.getTokenValue()); } @Test - public void authenticateWhenClientConfigurationRequestAndAccessTokenNotActiveThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwtClientConfiguration(); + public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() { + Jwt jwt = createJwt(Collections.singleton("unauthorized.scope")); OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization( registeredClient, jwtAccessToken, jwt.getClaims()).build(); - authorization = OidcAuthenticationProviderUtils.invalidate(authorization, jwtAccessToken); when(this.authorizationService.findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) .thenReturn(authorization); JwtAuthenticationToken principal = new JwtAuthenticationToken( - jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); + jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope")); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, "client-1"); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, registeredClient.getClientId()); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); + .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); verify(this.authorizationService).findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); } @Test - public void authenticateWhenClientConfigurationRequestAndAccessTokenNotAuthorizedThenThrowOAuth2AuthenticationException() { - Jwt jwt = createJwt(Collections.singleton("unauthorized.scope")); + public void authenticateWhenClientConfigurationRequestAndAccessTokenContainsRequiredScopeAndAdditionalScopeThenThrowOAuth2AuthenticationException() { + Jwt jwt = createJwt(new HashSet<>(Arrays.asList("client.read", "scope1"))); OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); @@ -464,14 +464,15 @@ public class OidcClientRegistrationAuthenticationProviderTests { .thenReturn(authorization); JwtAuthenticationToken principal = new JwtAuthenticationToken( - jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope")); + jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read", "SCOPE_scope1")); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId()); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, registeredClient.getClientId()); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) .extracting(ex -> ((OAuth2AuthenticationException) ex).getError()).extracting("errorCode") - .isEqualTo(OAuth2ErrorCodes.INSUFFICIENT_SCOPE); + .isEqualTo(OAuth2ErrorCodes.INVALID_TOKEN); verify(this.authorizationService).findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)); } @@ -482,21 +483,18 @@ public class OidcClientRegistrationAuthenticationProviderTests { OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .build(); + RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); OAuth2Authorization authorization = TestOAuth2Authorizations.authorization( registeredClient, jwtAccessToken, jwt.getClaims()).build(); when(this.authorizationService.findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) .thenReturn(authorization); - when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .thenReturn(null); - JwtAuthenticationToken principal = new JwtAuthenticationToken( jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId()); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, registeredClient.getClientId()); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -509,30 +507,27 @@ public class OidcClientRegistrationAuthenticationProviderTests { } @Test - public void authenticateWhenClientConfigurationRequestRegisteredClientNotEqualToAuthorizationRegisteredClientThenThrowOAuth2AuthenticationException() { + public void authenticateWhenClientConfigurationRequestClientIdNotEqualToAuthorizedClientThenThrowOAuth2AuthenticationException() { Jwt jwt = createJwtClientConfiguration(); OAuth2AccessToken jwtAccessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaim(OAuth2ParameterNames.SCOPE)); - - RegisteredClient registeredClient = TestRegisteredClients.registeredClient() - .id("registration-1").clientId("client-1").build(); - RegisteredClient authorizationRegisteredClient = TestRegisteredClients.registeredClient() + RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build(); + RegisteredClient authorizedRegisteredClient = TestRegisteredClients.registeredClient() .id("registration-2").clientId("client-2").build(); - OAuth2Authorization authorization = TestOAuth2Authorizations.authorization( - authorizationRegisteredClient, jwtAccessToken, jwt.getClaims()).build(); + authorizedRegisteredClient, jwtAccessToken, jwt.getClaims()).build(); when(this.authorizationService.findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) .thenReturn(authorization); - when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) .thenReturn(registeredClient); JwtAuthenticationToken principal = new JwtAuthenticationToken( jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); - OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId()); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, registeredClient.getClientId()); assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication)) .isInstanceOf(OAuth2AuthenticationException.class) @@ -558,15 +553,14 @@ public class OidcClientRegistrationAuthenticationProviderTests { when(this.authorizationService.findByToken( eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN))) .thenReturn(authorization); + when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) + .thenReturn(registeredClient); JwtAuthenticationToken principal = new JwtAuthenticationToken( jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); - when(this.registeredClientRepository.findByClientId(eq(registeredClient.getClientId()))) - .thenReturn(registeredClient); - - OidcClientRegistrationAuthenticationToken authentication = - new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId()); + OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken( + principal, registeredClient.getClientId()); OidcClientRegistrationAuthenticationToken authenticationResult = (OidcClientRegistrationAuthenticationToken) this.authenticationProvider.authenticate(authentication); @@ -576,8 +570,8 @@ public class OidcClientRegistrationAuthenticationProviderTests { verify(this.registeredClientRepository).findByClientId( eq(registeredClient.getClientId())); - // verify that the registration access token is not invalidated after its used - verify(this.authorizationService, times(0)).save(eq(authorization)); + // verify that the "registration" access token is not invalidated after it is used + verify(this.authorizationService, never()).save(eq(authorization)); assertThat(authorization.getAccessToken().isInvalidated()).isFalse(); OidcClientRegistration clientRegistrationResult = authenticationResult.getClientRegistration(); @@ -602,10 +596,12 @@ public class OidcClientRegistrationAuthenticationProviderTests { .isEqualTo(registeredClient.getClientAuthenticationMethods().iterator().next().getValue()); assertThat(clientRegistrationResult.getIdTokenSignedResponseAlgorithm()) .isEqualTo(registeredClient.getTokenSettings().getIdTokenSignatureAlgorithm().getName()); - String expectedRegistrationClientUri = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) + + String expectedRegistrationClientUrl = UriComponentsBuilder.fromUriString(this.providerSettings.getIssuer()) .path(this.providerSettings.getOidcClientRegistrationEndpoint()) - .queryParam("client_id", registeredClient.getClientId()).toUriString(); - assertThat(clientRegistrationResult.getRegistrationClientUri().toString()).isEqualTo(expectedRegistrationClientUri); + .queryParam(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId()).toUriString(); + + assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl); assertThat(clientRegistrationResult.getRegistrationAccessToken()).isNull(); } diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java index 948baaa2..7d1f83f6 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/authentication/OidcClientRegistrationAuthenticationTokenTests.java @@ -51,14 +51,14 @@ public class OidcClientRegistrationAuthenticationTokenTests { public void constructorWhenClientIdNullThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() .isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, (String) null)) - .withMessage("clientId cannot be null or empty"); + .withMessage("clientId cannot be empty"); } @Test public void constructorWhenClientIdEmptyThenThrowIllegalArgumentException() { assertThatIllegalArgumentException() .isThrownBy(() -> new OidcClientRegistrationAuthenticationToken(this.principal, "")) - .withMessage("clientId cannot be null or empty"); + .withMessage("clientId cannot be empty"); } @Test diff --git a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java index 3e2ed014..9bb78136 100644 --- a/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java +++ b/oauth2-authorization-server/src/test/java/org/springframework/security/oauth2/server/authorization/oidc/web/OidcClientRegistrationEndpointFilterTests.java @@ -25,6 +25,7 @@ import javax.servlet.http.HttpServletResponse; import org.junit.After; import org.junit.Before; import org.junit.Test; + import org.springframework.http.HttpStatus; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.mock.http.client.MockClientHttpRequest; @@ -219,8 +220,8 @@ public class OidcClientRegistrationEndpointFilterTests { .tokenEndpointAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC.getValue()) .responseType(OAuth2AuthorizationResponseType.CODE.getValue()) .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) - .registrationClientUri("http://auth-server:9000/connect/register?client_id=client-id") .registrationAccessToken("registration-access-token") + .registrationClientUrl("https://auth-server:9000/connect/register?client_id=client-id") .build(); // @formatter:on @@ -270,23 +271,10 @@ public class OidcClientRegistrationEndpointFilterTests { .isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod()); assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) .isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientRegistrationResponse.getRegistrationClientUri()) - .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUri()); assertThat(clientRegistrationResponse.getRegistrationAccessToken()) .isEqualTo(expectedClientRegistrationResponse.getRegistrationAccessToken()); - } - - @Test - public void doFilterWhenNotClientConfigurationRequestThenNotProcessed() throws Exception { - String requestUri = "/path"; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); + assertThat(clientRegistrationResponse.getRegistrationClientUrl()) + .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUrl()); } @Test @@ -330,7 +318,7 @@ public class OidcClientRegistrationEndpointFilterTests { } @Test - public void doFilterWhenClientConfigurationRequestMultipleClientIdParametersThenInvalidClientError() throws Exception { + public void doFilterWhenClientConfigurationRequestMultipleClientIdThenInvalidRequestError() throws Exception { String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); @@ -340,10 +328,11 @@ public class OidcClientRegistrationEndpointFilterTests { FilterChain filterChain = mock(FilterChain.class); this.filter.doFilter(request, response, filterChain); + verifyNoInteractions(filterChain); - assertThat(response.getStatus()).isEqualTo(HttpStatus.UNAUTHORIZED.value()); + assertThat(response.getStatus()).isEqualTo(HttpStatus.BAD_REQUEST.value()); OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_CLIENT); + assertThat(error.getErrorCode()).isEqualTo(OAuth2ErrorCodes.INVALID_REQUEST); } @Test @@ -353,7 +342,7 @@ public class OidcClientRegistrationEndpointFilterTests { } @Test - public void doFilterWhenClientConfigurationRequestInsufficientTokenScopeThenForbiddenError() throws Exception { + public void doFilterWhenClientConfigurationRequestInsufficientScopeThenForbiddenError() throws Exception { doFilterWhenClientConfigurationRequestInvalidThenError( OAuth2ErrorCodes.INSUFFICIENT_SCOPE, HttpStatus.FORBIDDEN); } @@ -364,6 +353,35 @@ public class OidcClientRegistrationEndpointFilterTests { OAuth2ErrorCodes.INVALID_CLIENT, HttpStatus.UNAUTHORIZED); } + private void doFilterWhenClientConfigurationRequestInvalidThenError( + String errorCode, HttpStatus status) throws Exception { + Jwt jwt = createJwt("client.read"); + JwtAuthenticationToken principal = new JwtAuthenticationToken( + jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); + + SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); + securityContext.setAuthentication(principal); + SecurityContextHolder.setContext(securityContext); + + when(this.authenticationManager.authenticate(any())) + .thenThrow(new OAuth2AuthenticationException(errorCode)); + + String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; + MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); + request.setServletPath(requestUri); + request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1"); + MockHttpServletResponse response = new MockHttpServletResponse(); + FilterChain filterChain = mock(FilterChain.class); + + this.filter.doFilter(request, response, filterChain); + + verifyNoInteractions(filterChain); + + assertThat(response.getStatus()).isEqualTo(status.value()); + OAuth2Error error = readError(response); + assertThat(error.getErrorCode()).isEqualTo(errorCode); + } + @Test public void doFilterWhenClientConfigurationRequestValidThenSuccessResponse() throws Exception { // @formatter:off @@ -380,7 +398,7 @@ public class OidcClientRegistrationEndpointFilterTests { .idTokenSignedResponseAlgorithm(SignatureAlgorithm.RS256.getName()) .scope("scope1") .scope("scope2") - .registrationClientUri("http://auth-server:9000/connect/register?client_id=client-id") + .registrationClientUrl("https://auth-server:9000/connect/register?client_id=client-id") .build(); // @formatter:on @@ -400,7 +418,7 @@ public class OidcClientRegistrationEndpointFilterTests { String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client-id"); + request.setParameter(OAuth2ParameterNames.CLIENT_ID, expectedClientRegistrationResponse.getClientId()); MockHttpServletResponse response = new MockHttpServletResponse(); FilterChain filterChain = mock(FilterChain.class); @@ -430,38 +448,8 @@ public class OidcClientRegistrationEndpointFilterTests { .isEqualTo(expectedClientRegistrationResponse.getTokenEndpointAuthenticationMethod()); assertThat(clientRegistrationResponse.getIdTokenSignedResponseAlgorithm()) .isEqualTo(expectedClientRegistrationResponse.getIdTokenSignedResponseAlgorithm()); - assertThat(clientRegistrationResponse.getRegistrationClientUri()) - .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUri()); - } - - - private void doFilterWhenClientConfigurationRequestInvalidThenError( - String errorCode, HttpStatus status) throws Exception { - Jwt jwt = createJwt("client.read"); - JwtAuthenticationToken principal = new JwtAuthenticationToken( - jwt, AuthorityUtils.createAuthorityList("SCOPE_client.read")); - - SecurityContext securityContext = SecurityContextHolder.createEmptyContext(); - securityContext.setAuthentication(principal); - SecurityContextHolder.setContext(securityContext); - - when(this.authenticationManager.authenticate(any())) - .thenThrow(new OAuth2AuthenticationException(errorCode)); - - String requestUri = DEFAULT_OIDC_CLIENT_REGISTRATION_ENDPOINT_URI; - MockHttpServletRequest request = new MockHttpServletRequest("GET", requestUri); - request.setServletPath(requestUri); - request.setParameter(OAuth2ParameterNames.CLIENT_ID, "client1"); - MockHttpServletResponse response = new MockHttpServletResponse(); - FilterChain filterChain = mock(FilterChain.class); - - this.filter.doFilter(request, response, filterChain); - - verifyNoInteractions(filterChain); - - assertThat(response.getStatus()).isEqualTo(status.value()); - OAuth2Error error = readError(response); - assertThat(error.getErrorCode()).isEqualTo(errorCode); + assertThat(clientRegistrationResponse.getRegistrationClientUrl()) + .isEqualTo(expectedClientRegistrationResponse.getRegistrationClientUrl()); } private OAuth2Error readError(MockHttpServletResponse response) throws Exception {