Polish gh-427
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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() {
|
||||
}
|
||||
|
||||
|
||||
@@ -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 <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientRegistration">3. Client Registration Endpoint</a>
|
||||
* @see <a href="https://openid.net/specs/openid-connect-registration-1_0.html#ClientConfigurationEndpoint">4. Client Configuration Endpoint</a>
|
||||
*/
|
||||
@@ -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,59 +152,9 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_TOKEN);
|
||||
}
|
||||
|
||||
if (clientRegistrationAuthentication.getClientRegistration() != null) {
|
||||
|
||||
return registerClient(clientRegistrationAuthentication, authorization);
|
||||
}
|
||||
|
||||
if (isNotAuthorized(authorizedAccessToken, DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE)) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
}
|
||||
|
||||
private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
|
||||
OAuth2Authorization authorization) {
|
||||
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken = authorization.getAccessToken();
|
||||
if (isNotAuthorized(authorizedAccessToken, DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE)) {
|
||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INSUFFICIENT_SCOPE);
|
||||
}
|
||||
|
||||
if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) {
|
||||
// TODO Add OAuth2ErrorCodes.INVALID_REDIRECT_URI
|
||||
throw new OAuth2AuthenticationException("invalid_redirect_uri");
|
||||
}
|
||||
|
||||
RegisteredClient registeredClient = create(clientRegistrationAuthentication.getClientRegistration());
|
||||
this.registeredClientRepository.save(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));
|
||||
return clientRegistrationAuthentication.getClientRegistration() != null ?
|
||||
registerClient(clientRegistrationAuthentication, authorization) :
|
||||
findRegistration(clientRegistrationAuthentication, authorization);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -192,18 +162,67 @@ public final class OidcClientRegistrationAuthenticationProvider implements Authe
|
||||
return OidcClientRegistrationAuthenticationToken.class.isAssignableFrom(authentication);
|
||||
}
|
||||
|
||||
private String getIssuer() {
|
||||
return this.providerSettings != null ? this.providerSettings.getIssuer() : null;
|
||||
private OidcClientRegistrationAuthenticationToken findRegistration(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
|
||||
OAuth2Authorization authorization) {
|
||||
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> 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);
|
||||
}
|
||||
|
||||
OidcClientRegistration clientRegistration = buildRegistration(registeredClient).build();
|
||||
|
||||
return new OidcClientRegistrationAuthenticationToken(
|
||||
(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
|
||||
}
|
||||
|
||||
private OidcClientRegistrationAuthenticationToken registerClient(OidcClientRegistrationAuthenticationToken clientRegistrationAuthentication,
|
||||
OAuth2Authorization authorization) {
|
||||
|
||||
OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken = authorization.getAccessToken();
|
||||
checkScopeForRegistration(authorizedAccessToken);
|
||||
|
||||
if (!isValidRedirectUris(clientRegistrationAuthentication.getClientRegistration().getRedirectUris())) {
|
||||
// TODO Add OAuth2ErrorCodes.INVALID_REDIRECT_URI
|
||||
throw new OAuth2AuthenticationException("invalid_redirect_uri");
|
||||
}
|
||||
|
||||
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);
|
||||
|
||||
OidcClientRegistration clientRegistration = buildRegistration(registeredClient)
|
||||
.registrationAccessToken(registeredClientAuthorization.getAccessToken().getToken().getTokenValue())
|
||||
.build();
|
||||
|
||||
return new OidcClientRegistrationAuthenticationToken(
|
||||
(Authentication) clientRegistrationAuthentication.getPrincipal(), clientRegistration);
|
||||
}
|
||||
|
||||
private OAuth2Authorization registerAccessToken(RegisteredClient registeredClient) {
|
||||
|
||||
String issuer = getIssuer();
|
||||
Set<String> authorizedScopes = new HashSet<>();
|
||||
authorizedScopes.add(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE);
|
||||
JoseHeader headers = JwtUtils.headers().build();
|
||||
|
||||
Set<String> 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<OAuth2AccessToken> authorizedAccessToken) {
|
||||
checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_REGISTRATION_AUTHORIZED_SCOPE));
|
||||
}
|
||||
|
||||
private static void checkScopeForConfiguration(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken) {
|
||||
checkScope(authorizedAccessToken, Collections.singleton(DEFAULT_CLIENT_CONFIGURATION_AUTHORIZED_SCOPE));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static boolean isNotAuthorized(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken, String requiredScope) {
|
||||
Object scope = authorizedAccessToken.getClaims().get(OAuth2ParameterNames.SCOPE);
|
||||
return scope == null || !((Collection<String>) scope).contains(requiredScope);
|
||||
private static void checkScope(OAuth2Authorization.Token<OAuth2AccessToken> authorizedAccessToken, Set<String> requiredScope) {
|
||||
Collection<String> authorizedScope = Collections.emptySet();
|
||||
if (authorizedAccessToken.getClaims().containsKey(OAuth2ParameterNames.SCOPE)) {
|
||||
authorizedScope = (Collection<String>) 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<String> 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
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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<OidcClientRegistration> clientRegistrationHttpMessageConverter =
|
||||
new OidcClientRegistrationHttpMessageConverter();
|
||||
private final HttpMessageConverter<OAuth2Error> 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);
|
||||
}
|
||||
|
||||
if (this.clientConfigurationEndpointMatcher.matches(request)) {
|
||||
return convertOidcClientConfigurationRequest(request);
|
||||
}
|
||||
|
||||
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.INVALID_REQUEST));
|
||||
}
|
||||
|
||||
private OidcClientRegistrationAuthenticationToken convertOidcClientConfigurationRequest(HttpServletRequest request) {
|
||||
private OidcClientRegistrationAuthenticationToken convert(HttpServletRequest request) throws Exception {
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||
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);
|
||||
}
|
||||
return new OidcClientRegistrationAuthenticationToken(principal, clientId);
|
||||
}
|
||||
|
||||
private OidcClientRegistrationAuthenticationToken convertOidcClientRegistrationRequest(HttpServletRequest request) {
|
||||
try {
|
||||
Authentication principal = SecurityContextHolder.getContext().getAuthentication();
|
||||
if ("POST".equals(request.getMethod())) {
|
||||
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);
|
||||
}
|
||||
|
||||
// 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_REQUEST);
|
||||
}
|
||||
|
||||
return new OidcClientRegistrationAuthenticationToken(principal, clientId);
|
||||
}
|
||||
|
||||
private void sendClientRegistrationResponse(HttpServletResponse response, HttpStatus httpStatus, OidcClientRegistration clientRegistration) throws IOException {
|
||||
|
||||
@@ -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());
|
||||
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
|
||||
|
||||
// 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 = readClientRegistrationResponse(mvcResult.getResponse());
|
||||
assertThat(clientRegistrationResponse.getClientId()).isNotNull();
|
||||
assertThat(clientRegistrationResponse.getClientIdIssuedAt()).isNotNull();
|
||||
assertThat(clientRegistrationResponse.getClientSecret()).isNotNull();
|
||||
@@ -202,190 +167,14 @@ 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)
|
||||
.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();
|
||||
|
||||
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.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();
|
||||
}
|
||||
|
||||
@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();
|
||||
|
||||
// ***** (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")
|
||||
@@ -397,81 +186,80 @@ public class OidcClientRegistrationTests {
|
||||
.build();
|
||||
// @formatter:on
|
||||
|
||||
OidcClientRegistration clientRegistrationResponse = registerClient(clientRegistration);
|
||||
|
||||
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 = readClientRegistrationResponse(mvcResult.getResponse());
|
||||
|
||||
|
||||
httpHeaders = new HttpHeaders();
|
||||
httpHeaders.setBearerAuth(clientRegistrationResponse.getRegistrationAccessToken());
|
||||
|
||||
// ***** (3) Get RegisteredClient Configuration
|
||||
mvcResult = this.mvc.perform(get(clientRegistrationResponse.getRegistrationClientUri().toString())
|
||||
.headers(httpHeaders))
|
||||
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(clientRegistrationResponse.getClientId());
|
||||
assertThat(clientConfigurationResponse.getClientIdIssuedAt()).isNotNull();
|
||||
assertThat(clientConfigurationResponse.getClientSecret()).isNotNull().isEqualTo(clientRegistrationResponse.getClientSecret());
|
||||
assertThat(clientConfigurationResponse.getClientSecretExpiresAt()).isNull();
|
||||
|
||||
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(clientRegistrationResponse.getRedirectUris());
|
||||
assertThat(clientConfigurationResponse.getGrantTypes())
|
||||
.containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getGrantTypes());
|
||||
assertThat(clientConfigurationResponse.getResponseTypes())
|
||||
.containsExactly(OAuth2AuthorizationResponseType.CODE.getValue());
|
||||
.containsExactlyInAnyOrderElementsOf(clientRegistrationResponse.getResponseTypes());
|
||||
assertThat(clientConfigurationResponse.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();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
private OidcClientRegistration registerClient(OidcClientRegistration clientRegistration) throws Exception {
|
||||
// ***** (1) Obtain the "initial" access token used for registering the client
|
||||
|
||||
String clientRegistrationScope = "client.create";
|
||||
RegisteredClient clientRegistrar = TestRegisteredClients.registeredClient2()
|
||||
.scope(clientRegistrationScope)
|
||||
.build();
|
||||
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(
|
||||
clientRegistrar.getClientId(), clientRegistrar.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
|
||||
|
||||
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();
|
||||
|
||||
return readClientRegistrationResponse(mvcResult.getResponse());
|
||||
}
|
||||
|
||||
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||
clientId = URLEncoder.encode(clientId, 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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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,79 +415,12 @@ 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();
|
||||
.queryParam(OAuth2ParameterNames.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);
|
||||
}
|
||||
|
||||
@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));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientConfigurationRequestAndAccessTokenNotActiveThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientConfiguration();
|
||||
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"));
|
||||
|
||||
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(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
|
||||
assertThat(clientRegistrationResult.getRegistrationClientUrl().toString()).isEqualTo(expectedRegistrationClientUrl);
|
||||
assertThat(clientRegistrationResult.getRegistrationAccessToken()).isEqualTo(jwt.getTokenValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -466,7 +439,8 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
JwtAuthenticationToken principal = new JwtAuthenticationToken(
|
||||
jwt, AuthorityUtils.createAuthorityList("SCOPE_unauthorized.scope"));
|
||||
|
||||
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(principal, registeredClient.getClientId());
|
||||
OidcClientRegistrationAuthenticationToken authentication = new OidcClientRegistrationAuthenticationToken(
|
||||
principal, registeredClient.getClientId());
|
||||
|
||||
assertThatThrownBy(() -> this.authenticationProvider.authenticate(authentication))
|
||||
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||
@@ -477,26 +451,50 @@ public class OidcClientRegistrationAuthenticationProviderTests {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientConfiguration();
|
||||
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));
|
||||
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", "SCOPE_scope1"));
|
||||
|
||||
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);
|
||||
verify(this.authorizationService).findByToken(
|
||||
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void authenticateWhenClientConfigurationRequestAndRegisteredClientNotFoundThenThrowOAuth2AuthenticationException() {
|
||||
Jwt jwt = createJwtClientConfiguration();
|
||||
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.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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user