Add OpenID Connect 1.0 Authorization Code Flow
Closes gh-53
This commit is contained in:
parent
8c71e56350
commit
f2bb523105
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.core.oidc;
|
package org.springframework.security.oauth2.core.oidc;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||||
import org.springframework.security.oauth2.server.authorization.Version;
|
import org.springframework.security.oauth2.server.authorization.Version;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
@ -241,6 +242,30 @@ public final class OidcProviderConfiguration implements OidcProviderMetadataClai
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add this {@link JwsAlgorithm JWS} signing algorithm to the collection of {@code id_token_signing_alg_values_supported}
|
||||||
|
* in the resulting {@link OidcProviderConfiguration}, REQUIRED.
|
||||||
|
*
|
||||||
|
* @param signingAlgorithm the {@link JwsAlgorithm JWS} signing algorithm supported for the {@link OidcIdToken ID Token}
|
||||||
|
* @return the {@link Builder} for further configuration
|
||||||
|
*/
|
||||||
|
public Builder idTokenSigningAlgorithm(String signingAlgorithm) {
|
||||||
|
addClaimToClaimList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithm);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
|
||||||
|
* allowing the ability to add, replace, or remove.
|
||||||
|
*
|
||||||
|
* @param signingAlgorithmsConsumer a {@code Consumer} of the {@link JwsAlgorithm JWS} signing algorithms for the {@link OidcIdToken ID Token}
|
||||||
|
* @return the {@link Builder} for further configuration
|
||||||
|
*/
|
||||||
|
public Builder idTokenSigningAlgorithms(Consumer<List<String>> signingAlgorithmsConsumer) {
|
||||||
|
acceptClaimValues(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, signingAlgorithmsConsumer);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use this claim in the resulting {@link OidcProviderConfiguration}.
|
* Use this claim in the resulting {@link OidcProviderConfiguration}.
|
||||||
*
|
*
|
||||||
@ -296,6 +321,9 @@ public final class OidcProviderConfiguration implements OidcProviderMetadataClai
|
|||||||
Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
|
Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be null");
|
||||||
Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
|
Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes must be of type List");
|
||||||
Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
|
Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED), "subjectTypes cannot be empty");
|
||||||
|
Assert.notNull(this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be null");
|
||||||
|
Assert.isInstanceOf(List.class, this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms must be of type List");
|
||||||
|
Assert.notEmpty((List<?>) this.claims.get(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED), "idTokenSigningAlgorithms cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void validateURL(Object url, String errorMessage) {
|
private static void validateURL(Object url, String errorMessage) {
|
||||||
|
@ -17,6 +17,8 @@ package org.springframework.security.oauth2.core.oidc;
|
|||||||
|
|
||||||
|
|
||||||
import org.springframework.security.oauth2.core.ClaimAccessor;
|
import org.springframework.security.oauth2.core.ClaimAccessor;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -115,4 +117,14 @@ public interface OidcProviderMetadataClaimAccessor extends ClaimAccessor {
|
|||||||
return getClaimAsStringList(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED);
|
return getClaimAsStringList(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||||
|
* to encode the claims in a {@link Jwt} {@code (id_token_signing_alg_values_supported)}.
|
||||||
|
*
|
||||||
|
* @return the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||||
|
*/
|
||||||
|
default List<String> getIdTokenSigningAlgorithms() {
|
||||||
|
return getClaimAsStringList(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.oauth2.core.oidc;
|
package org.springframework.security.oauth2.core.oidc;
|
||||||
|
|
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned
|
* The names of the "claims" defined by OpenID Connect Discovery 1.0 that can be returned
|
||||||
* in the OpenID Provider Configuration Response.
|
* in the OpenID Provider Configuration Response.
|
||||||
@ -70,4 +72,9 @@ public interface OidcProviderMetadataClaimNames {
|
|||||||
*/
|
*/
|
||||||
String SCOPES_SUPPORTED = "scopes_supported";
|
String SCOPES_SUPPORTED = "scopes_supported";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@code id_token_signing_alg_values_supported} - the {@link JwsAlgorithm JWS} signing algorithms supported for the {@link OidcIdToken ID Token}
|
||||||
|
*/
|
||||||
|
String ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED = "id_token_signing_alg_values_supported";
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -143,6 +143,7 @@ public class OidcProviderConfigurationHttpMessageConverter
|
|||||||
claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
claimConverters.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, collectionStringConverter);
|
||||||
claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
claimConverters.put(OidcProviderMetadataClaimNames.GRANT_TYPES_SUPPORTED, collectionStringConverter);
|
||||||
claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter);
|
claimConverters.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, collectionStringConverter);
|
||||||
|
claimConverters.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, collectionStringConverter);
|
||||||
claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
claimConverters.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, collectionStringConverter);
|
||||||
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
this.claimTypeConverter = new ClaimTypeConverter(claimConverters);
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,7 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Authentication} implementation used when issuing an
|
* An {@link Authentication} implementation used when issuing an
|
||||||
@ -45,6 +46,7 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
private final Authentication clientPrincipal;
|
private final Authentication clientPrincipal;
|
||||||
private final OAuth2AccessToken accessToken;
|
private final OAuth2AccessToken accessToken;
|
||||||
private final OAuth2RefreshToken refreshToken;
|
private final OAuth2RefreshToken refreshToken;
|
||||||
|
private final Map<String, Object> additionalParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
||||||
@ -68,14 +70,30 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
*/
|
*/
|
||||||
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
|
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
|
||||||
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
|
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken) {
|
||||||
|
this(registeredClient, clientPrincipal, accessToken, refreshToken, Collections.emptyMap());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an {@code OAuth2AccessTokenAuthenticationToken} using the provided parameters.
|
||||||
|
*
|
||||||
|
* @param registeredClient the registered client
|
||||||
|
* @param clientPrincipal the authenticated client principal
|
||||||
|
* @param accessToken the access token
|
||||||
|
* @param refreshToken the refresh token
|
||||||
|
* @param additionalParameters the additional parameters
|
||||||
|
*/
|
||||||
|
public OAuth2AccessTokenAuthenticationToken(RegisteredClient registeredClient, Authentication clientPrincipal,
|
||||||
|
OAuth2AccessToken accessToken, @Nullable OAuth2RefreshToken refreshToken, Map<String, Object> additionalParameters) {
|
||||||
super(Collections.emptyList());
|
super(Collections.emptyList());
|
||||||
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
Assert.notNull(registeredClient, "registeredClient cannot be null");
|
||||||
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null");
|
||||||
Assert.notNull(accessToken, "accessToken cannot be null");
|
Assert.notNull(accessToken, "accessToken cannot be null");
|
||||||
|
Assert.notNull(additionalParameters, "additionalParameters cannot be null");
|
||||||
this.registeredClient = registeredClient;
|
this.registeredClient = registeredClient;
|
||||||
this.clientPrincipal = clientPrincipal;
|
this.clientPrincipal = clientPrincipal;
|
||||||
this.accessToken = accessToken;
|
this.accessToken = accessToken;
|
||||||
this.refreshToken = refreshToken;
|
this.refreshToken = refreshToken;
|
||||||
|
this.additionalParameters = additionalParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -115,4 +133,13 @@ public class OAuth2AccessTokenAuthenticationToken extends AbstractAuthentication
|
|||||||
public OAuth2RefreshToken getRefreshToken() {
|
public OAuth2RefreshToken getRefreshToken() {
|
||||||
return this.refreshToken;
|
return this.refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the additional parameters.
|
||||||
|
*
|
||||||
|
* @return a {@code Map} of the additional parameters, may be empty
|
||||||
|
*/
|
||||||
|
public Map<String, Object> getAdditionalParameters() {
|
||||||
|
return this.additionalParameters;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ import org.springframework.security.oauth2.core.OAuth2Error;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
@ -39,6 +42,9 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Toke
|
|||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
import static org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthenticationProviderUtils.getAuthenticatedClientElseThrowInvalidClient;
|
||||||
@ -118,8 +124,9 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
}
|
}
|
||||||
|
|
||||||
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
Set<String> authorizedScopes = authorization.getAttribute(OAuth2AuthorizationAttributeNames.AUTHORIZED_SCOPES);
|
||||||
Jwt jwt = OAuth2TokenIssuerUtil
|
Jwt jwt = OAuth2TokenIssuerUtil.issueJwtAccessToken(
|
||||||
.issueJwtAccessToken(this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(), authorizedScopes, registeredClient.getTokenSettings().accessTokenTimeToLive());
|
this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(),
|
||||||
|
authorizedScopes, registeredClient.getTokenSettings().accessTokenTimeToLive());
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), authorizedScopes);
|
jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), authorizedScopes);
|
||||||
|
|
||||||
@ -132,6 +139,16 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
tokensBuilder.refreshToken(refreshToken);
|
tokensBuilder.refreshToken(refreshToken);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OidcIdToken idToken = null;
|
||||||
|
if (authorizationRequest.getScopes().contains(OidcScopes.OPENID)) {
|
||||||
|
Jwt jwtIdToken = OAuth2TokenIssuerUtil.issueIdToken(
|
||||||
|
this.jwtEncoder, authorization.getPrincipalName(), registeredClient.getClientId(),
|
||||||
|
(String) authorizationRequest.getAdditionalParameters().get(OidcParameterNames.NONCE));
|
||||||
|
idToken = new OidcIdToken(jwtIdToken.getTokenValue(), jwtIdToken.getIssuedAt(),
|
||||||
|
jwtIdToken.getExpiresAt(), jwtIdToken.getClaims());
|
||||||
|
tokensBuilder.token(idToken);
|
||||||
|
}
|
||||||
|
|
||||||
OAuth2Tokens tokens = tokensBuilder.build();
|
OAuth2Tokens tokens = tokensBuilder.build();
|
||||||
authorization = OAuth2Authorization.from(authorization)
|
authorization = OAuth2Authorization.from(authorization)
|
||||||
.tokens(tokens)
|
.tokens(tokens)
|
||||||
@ -143,7 +160,14 @@ public class OAuth2AuthorizationCodeAuthenticationProvider implements Authentica
|
|||||||
|
|
||||||
this.authorizationService.save(authorization);
|
this.authorizationService.save(authorization);
|
||||||
|
|
||||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken);
|
Map<String, Object> additionalParameters = Collections.emptyMap();
|
||||||
|
if (idToken != null) {
|
||||||
|
additionalParameters = new HashMap<>();
|
||||||
|
additionalParameters.put(OidcParameterNames.ID_TOKEN, idToken.getTokenValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new OAuth2AccessTokenAuthenticationToken(
|
||||||
|
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -20,14 +20,17 @@ import org.springframework.security.crypto.keygen.StringKeyGenerator;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeader;
|
import org.springframework.security.oauth2.jose.JoseHeader;
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
|
||||||
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
import org.springframework.security.oauth2.jwt.JwtEncoder;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
@ -43,7 +46,7 @@ class OAuth2TokenIssuerUtil {
|
|||||||
static Jwt issueJwtAccessToken(JwtEncoder jwtEncoder, String subject, String audience, Set<String> scopes, Duration tokenTimeToLive) {
|
static Jwt issueJwtAccessToken(JwtEncoder jwtEncoder, String subject, String audience, Set<String> scopes, Duration tokenTimeToLive) {
|
||||||
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
||||||
|
|
||||||
String issuer = "https://oauth2.provider.com"; // TODO Allow configuration for issuer claim
|
String issuer = "http://auth-server:9000"; // TODO Allow configuration for issuer claim
|
||||||
Instant issuedAt = Instant.now();
|
Instant issuedAt = Instant.now();
|
||||||
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
|
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
|
||||||
|
|
||||||
@ -60,6 +63,31 @@ class OAuth2TokenIssuerUtil {
|
|||||||
return jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
return jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static Jwt issueIdToken(JwtEncoder jwtEncoder, String subject, String audience, String nonce) {
|
||||||
|
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
|
||||||
|
|
||||||
|
String issuer = "http://auth-server:9000"; // TODO Allow configuration for issuer claim
|
||||||
|
Instant issuedAt = Instant.now();
|
||||||
|
Instant expiresAt = issuedAt.plus(30, ChronoUnit.MINUTES); // TODO Allow configuration for id token time-to-live
|
||||||
|
|
||||||
|
JwtClaimsSet.Builder builder = JwtClaimsSet.builder()
|
||||||
|
.issuer(issuer)
|
||||||
|
.subject(subject)
|
||||||
|
.audience(Collections.singletonList(audience))
|
||||||
|
.issuedAt(issuedAt)
|
||||||
|
.expiresAt(expiresAt)
|
||||||
|
.claim(IdTokenClaimNames.AZP, audience);
|
||||||
|
if (StringUtils.hasText(nonce)) {
|
||||||
|
builder.claim(IdTokenClaimNames.NONCE, nonce);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO Add 'auth_time' claim
|
||||||
|
|
||||||
|
JwtClaimsSet jwtClaimsSet = builder.build();
|
||||||
|
|
||||||
|
return jwtEncoder.encode(joseHeader, jwtClaimsSet);
|
||||||
|
}
|
||||||
|
|
||||||
static OAuth2RefreshToken issueRefreshToken(Duration tokenTimeToLive) {
|
static OAuth2RefreshToken issueRefreshToken(Duration tokenTimeToLive) {
|
||||||
Instant issuedAt = Instant.now();
|
Instant issuedAt = Instant.now();
|
||||||
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
|
Instant expiresAt = issuedAt.plus(tokenTimeToLive);
|
||||||
|
@ -30,6 +30,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
|||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
@ -40,7 +41,10 @@ import org.springframework.security.oauth2.server.authorization.token.OAuth2Auth
|
|||||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2Tokens;
|
||||||
import org.springframework.security.web.DefaultRedirectStrategy;
|
import org.springframework.security.web.DefaultRedirectStrategy;
|
||||||
import org.springframework.security.web.RedirectStrategy;
|
import org.springframework.security.web.RedirectStrategy;
|
||||||
|
import org.springframework.security.web.util.matcher.AndRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
|
||||||
|
import org.springframework.security.web.util.matcher.OrRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
@ -120,10 +124,24 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
Assert.hasText(authorizationEndpointUri, "authorizationEndpointUri cannot be empty");
|
||||||
this.registeredClientRepository = registeredClientRepository;
|
this.registeredClientRepository = registeredClientRepository;
|
||||||
this.authorizationService = authorizationService;
|
this.authorizationService = authorizationService;
|
||||||
this.authorizationRequestMatcher = new AntPathRequestMatcher(
|
|
||||||
|
RequestMatcher authorizationRequestGetMatcher = new AntPathRequestMatcher(
|
||||||
authorizationEndpointUri, HttpMethod.GET.name());
|
authorizationEndpointUri, HttpMethod.GET.name());
|
||||||
this.userConsentMatcher = new AntPathRequestMatcher(
|
RequestMatcher authorizationRequestPostMatcher = new AntPathRequestMatcher(
|
||||||
authorizationEndpointUri, HttpMethod.POST.name());
|
authorizationEndpointUri, HttpMethod.POST.name());
|
||||||
|
RequestMatcher openidScopeMatcher = request -> {
|
||||||
|
String scope = request.getParameter(OAuth2ParameterNames.SCOPE);
|
||||||
|
return StringUtils.hasText(scope) && scope.contains(OidcScopes.OPENID);
|
||||||
|
};
|
||||||
|
RequestMatcher consentActionMatcher = request ->
|
||||||
|
request.getParameter(UserConsentPage.CONSENT_ACTION_PARAMETER_NAME) != null;
|
||||||
|
this.authorizationRequestMatcher = new OrRequestMatcher(
|
||||||
|
authorizationRequestGetMatcher,
|
||||||
|
new AndRequestMatcher(
|
||||||
|
authorizationRequestPostMatcher, openidScopeMatcher,
|
||||||
|
new NegatedRequestMatcher(consentActionMatcher)));
|
||||||
|
this.userConsentMatcher = new AndRequestMatcher(
|
||||||
|
authorizationRequestPostMatcher, consentActionMatcher);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -289,7 +307,8 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} else if (registeredClient.getRedirectUris().size() != 1) {
|
} else if (authorizationRequestContext.isAuthenticationRequest() || // redirect_uri is REQUIRED for OpenID Connect
|
||||||
|
registeredClient.getRedirectUris().size() != 1) {
|
||||||
authorizationRequestContext.setError(
|
authorizationRequestContext.setError(
|
||||||
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
createError(OAuth2ErrorCodes.INVALID_REQUEST, OAuth2ParameterNames.REDIRECT_URI));
|
||||||
return;
|
return;
|
||||||
@ -476,6 +495,10 @@ public class OAuth2AuthorizationEndpointFilter extends OncePerRequestFilter {
|
|||||||
return this.redirectUri;
|
return this.redirectUri;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isAuthenticationRequest() {
|
||||||
|
return getScopes().contains(OidcScopes.OPENID);
|
||||||
|
}
|
||||||
|
|
||||||
protected String resolveRedirectUri() {
|
protected String resolveRedirectUri() {
|
||||||
return StringUtils.hasText(getRedirectUri()) ?
|
return StringUtils.hasText(getRedirectUri()) ?
|
||||||
getRedirectUri() :
|
getRedirectUri() :
|
||||||
|
@ -44,6 +44,7 @@ import org.springframework.security.oauth2.server.authorization.authentication.O
|
|||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
@ -161,7 +162,7 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
|
|
||||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationManager.authenticate(authorizationGrantAuthentication);
|
||||||
sendAccessTokenResponse(response, accessTokenAuthentication.getAccessToken(), accessTokenAuthentication.getRefreshToken());
|
sendAccessTokenResponse(response, accessTokenAuthentication);
|
||||||
|
|
||||||
} catch (OAuth2AuthenticationException ex) {
|
} catch (OAuth2AuthenticationException ex) {
|
||||||
SecurityContextHolder.clearContext();
|
SecurityContextHolder.clearContext();
|
||||||
@ -169,8 +170,12 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendAccessTokenResponse(HttpServletResponse response, OAuth2AccessToken accessToken,
|
private void sendAccessTokenResponse(HttpServletResponse response,
|
||||||
OAuth2RefreshToken refreshToken) throws IOException {
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication) throws IOException {
|
||||||
|
|
||||||
|
OAuth2AccessToken accessToken = accessTokenAuthentication.getAccessToken();
|
||||||
|
OAuth2RefreshToken refreshToken = accessTokenAuthentication.getRefreshToken();
|
||||||
|
Map<String, Object> additionalParameters = accessTokenAuthentication.getAdditionalParameters();
|
||||||
|
|
||||||
OAuth2AccessTokenResponse.Builder builder =
|
OAuth2AccessTokenResponse.Builder builder =
|
||||||
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
OAuth2AccessTokenResponse.withToken(accessToken.getTokenValue())
|
||||||
@ -182,6 +187,9 @@ public class OAuth2TokenEndpointFilter extends OncePerRequestFilter {
|
|||||||
if (refreshToken != null) {
|
if (refreshToken != null) {
|
||||||
builder.refreshToken(refreshToken.getTokenValue());
|
builder.refreshToken(refreshToken.getTokenValue());
|
||||||
}
|
}
|
||||||
|
if (!CollectionUtils.isEmpty(additionalParameters)) {
|
||||||
|
builder.additionalParameters(additionalParameters);
|
||||||
|
}
|
||||||
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
|
OAuth2AccessTokenResponse accessTokenResponse = builder.build();
|
||||||
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
ServletServerHttpResponse httpResponse = new ServletServerHttpResponse(response);
|
||||||
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
|
this.accessTokenHttpResponseConverter.write(accessTokenResponse, null, httpResponse);
|
||||||
|
@ -23,6 +23,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResp
|
|||||||
import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
|
import org.springframework.security.oauth2.core.oidc.OidcProviderConfiguration;
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
import org.springframework.security.oauth2.core.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter;
|
import org.springframework.security.oauth2.core.oidc.http.converter.OidcProviderConfigurationHttpMessageConverter;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
|
||||||
import org.springframework.security.web.util.matcher.RequestMatcher;
|
import org.springframework.security.web.util.matcher.RequestMatcher;
|
||||||
@ -86,6 +87,7 @@ public class OidcProviderConfigurationEndpointFilter extends OncePerRequestFilte
|
|||||||
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
.grantType(AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())
|
||||||
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
.grantType(AuthorizationGrantType.REFRESH_TOKEN.getValue())
|
||||||
.subjectType("public")
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm(SignatureAlgorithm.RS256.getName())
|
||||||
.scope(OidcScopes.OPENID)
|
.scope(OidcScopes.OPENID)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
@ -15,25 +15,59 @@
|
|||||||
*/
|
*/
|
||||||
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
package org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
|
||||||
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration;
|
||||||
import org.springframework.security.config.test.SpringTestRule;
|
import org.springframework.security.config.test.SpringTestRule;
|
||||||
import org.springframework.security.crypto.key.CryptoKeySource;
|
import org.springframework.security.crypto.key.CryptoKeySource;
|
||||||
|
import org.springframework.security.crypto.key.StaticKeyGeneratingCryptoKeySource;
|
||||||
|
import org.springframework.security.oauth2.core.AuthorizationGrantType;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.TokenType;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.token.OAuth2AuthorizationCode;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2AuthorizationEndpointFilter;
|
||||||
|
import org.springframework.security.oauth2.server.authorization.web.OAuth2TokenEndpointFilter;
|
||||||
import org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter;
|
import org.springframework.security.oauth2.server.authorization.web.OidcProviderConfigurationEndpointFilter;
|
||||||
import org.springframework.test.web.servlet.MockMvc;
|
import org.springframework.test.web.servlet.MockMvc;
|
||||||
import org.springframework.test.web.servlet.MvcResult;
|
import org.springframework.test.web.servlet.MvcResult;
|
||||||
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.net.URLEncoder;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Base64;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.reset;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
|
||||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
|
||||||
|
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
|
||||||
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
|
||||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
|
||||||
|
|
||||||
@ -44,6 +78,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
|
|||||||
*/
|
*/
|
||||||
public class OidcTests {
|
public class OidcTests {
|
||||||
private static final String issuerUrl = "https://example.com/issuer1";
|
private static final String issuerUrl = "https://example.com/issuer1";
|
||||||
|
private static RegisteredClientRepository registeredClientRepository;
|
||||||
|
private static OAuth2AuthorizationService authorizationService;
|
||||||
|
private static CryptoKeySource keySource;
|
||||||
|
|
||||||
@Rule
|
@Rule
|
||||||
public final SpringTestRule spring = new SpringTestRule();
|
public final SpringTestRule spring = new SpringTestRule();
|
||||||
@ -51,14 +88,26 @@ public class OidcTests {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private MockMvc mvc;
|
private MockMvc mvc;
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void init() {
|
||||||
|
registeredClientRepository = mock(RegisteredClientRepository.class);
|
||||||
|
authorizationService = mock(OAuth2AuthorizationService.class);
|
||||||
|
keySource = new StaticKeyGeneratingCryptoKeySource();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
reset(registeredClientRepository);
|
||||||
|
reset(authorizationService);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void requestWhenConfigurationRequestAndIssuerSetThenReturnConfigurationResponse() throws Exception {
|
public void requestWhenConfigurationRequestAndIssuerSetThenReturnConfigurationResponse() throws Exception {
|
||||||
this.spring.register(AuthorizationServerConfigurationWithIssuer.class).autowire();
|
this.spring.register(AuthorizationServerConfigurationWithIssuer.class).autowire();
|
||||||
|
|
||||||
this.mvc.perform(get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
|
this.mvc.perform(get(OidcProviderConfigurationEndpointFilter.DEFAULT_OIDC_PROVIDER_CONFIGURATION_ENDPOINT_URI))
|
||||||
.andExpect(status().is2xxSuccessful())
|
.andExpect(status().is2xxSuccessful())
|
||||||
.andExpect(jsonPath("issuer").value(issuerUrl))
|
.andExpect(jsonPath("issuer").value(issuerUrl));
|
||||||
.andReturn();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -85,18 +134,98 @@ public class OidcTests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void requestWhenAuthenticationRequestThenTokenResponseIncludesIdToken() throws Exception {
|
||||||
|
this.spring.register(AuthorizationServerConfiguration.class).autowire();
|
||||||
|
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||||
|
when(registeredClientRepository.findByClientId(eq(registeredClient.getClientId())))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
MvcResult mvcResult = this.mvc.perform(get(OAuth2AuthorizationEndpointFilter.DEFAULT_AUTHORIZATION_ENDPOINT_URI)
|
||||||
|
.params(getAuthorizationRequestParameters(registeredClient))
|
||||||
|
.with(user("user")))
|
||||||
|
.andExpect(status().is3xxRedirection())
|
||||||
|
.andReturn();
|
||||||
|
assertThat(mvcResult.getResponse().getRedirectedUrl()).matches("https://example.com\\?code=.{15,}&state=state");
|
||||||
|
|
||||||
|
verify(registeredClientRepository).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization authorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
when(authorizationService.findByToken(
|
||||||
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
|
eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
this.mvc.perform(post(OAuth2TokenEndpointFilter.DEFAULT_TOKEN_ENDPOINT_URI)
|
||||||
|
.params(getTokenRequestParameters(registeredClient, authorization))
|
||||||
|
.header(HttpHeaders.AUTHORIZATION, "Basic " + encodeBasicAuth(
|
||||||
|
registeredClient.getClientId(), registeredClient.getClientSecret())))
|
||||||
|
.andExpect(status().isOk())
|
||||||
|
.andExpect(header().string(HttpHeaders.CACHE_CONTROL, containsString("no-store")))
|
||||||
|
.andExpect(header().string(HttpHeaders.PRAGMA, containsString("no-cache")))
|
||||||
|
.andExpect(jsonPath("$.access_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.token_type").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.expires_in").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.refresh_token").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.scope").isNotEmpty())
|
||||||
|
.andExpect(jsonPath("$.id_token").isNotEmpty());
|
||||||
|
|
||||||
|
verify(registeredClientRepository, times(2)).findByClientId(eq(registeredClient.getClientId()));
|
||||||
|
verify(authorizationService).findByToken(
|
||||||
|
eq(authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue()),
|
||||||
|
eq(TokenType.AUTHORIZATION_CODE));
|
||||||
|
verify(authorizationService, times(2)).save(any());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiValueMap<String, String> getAuthorizationRequestParameters(RegisteredClient registeredClient) {
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
|
parameters.set(OAuth2ParameterNames.RESPONSE_TYPE, OAuth2AuthorizationResponseType.CODE.getValue());
|
||||||
|
parameters.set(OAuth2ParameterNames.CLIENT_ID, registeredClient.getClientId());
|
||||||
|
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||||
|
parameters.set(OAuth2ParameterNames.SCOPE,
|
||||||
|
StringUtils.collectionToDelimitedString(registeredClient.getScopes(), " "));
|
||||||
|
parameters.set(OAuth2ParameterNames.STATE, "state");
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MultiValueMap<String, String> getTokenRequestParameters(RegisteredClient registeredClient,
|
||||||
|
OAuth2Authorization authorization) {
|
||||||
|
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>();
|
||||||
|
parameters.set(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.AUTHORIZATION_CODE.getValue());
|
||||||
|
parameters.set(OAuth2ParameterNames.CODE, authorization.getTokens().getToken(OAuth2AuthorizationCode.class).getTokenValue());
|
||||||
|
parameters.set(OAuth2ParameterNames.REDIRECT_URI, registeredClient.getRedirectUris().iterator().next());
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String encodeBasicAuth(String clientId, String secret) throws Exception {
|
||||||
|
clientId = URLEncoder.encode(clientId, StandardCharsets.UTF_8.name());
|
||||||
|
secret = URLEncoder.encode(secret, StandardCharsets.UTF_8.name());
|
||||||
|
String credentialsString = clientId + ":" + secret;
|
||||||
|
byte[] encodedBytes = Base64.getEncoder().encode(credentialsString.getBytes(StandardCharsets.UTF_8));
|
||||||
|
return new String(encodedBytes, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
@EnableWebSecurity
|
@EnableWebSecurity
|
||||||
@Import(OAuth2AuthorizationServerConfiguration.class)
|
@Import(OAuth2AuthorizationServerConfiguration.class)
|
||||||
static class AuthorizationServerConfiguration {
|
static class AuthorizationServerConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
RegisteredClientRepository registeredClientRepository() {
|
RegisteredClientRepository registeredClientRepository() {
|
||||||
return mock(RegisteredClientRepository.class);
|
return registeredClientRepository;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
OAuth2AuthorizationService authorizationService() {
|
||||||
|
return authorizationService;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
CryptoKeySource keySource() {
|
CryptoKeySource keySource() {
|
||||||
return mock(CryptoKeySource.class);
|
return keySource;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,7 +41,8 @@ public class OidcProviderConfigurationTests {
|
|||||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||||
.scope("openid")
|
.scope("openid")
|
||||||
.responseType("code")
|
.responseType("code")
|
||||||
.subjectType("public");
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm("RS256");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void buildWhenAllRequiredClaimsAndAdditionalClaimsThenCreated() {
|
public void buildWhenAllRequiredClaimsAndAdditionalClaimsThenCreated() {
|
||||||
@ -55,6 +56,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
.grantType("authorization_code")
|
.grantType("authorization_code")
|
||||||
.grantType("client_credentials")
|
.grantType("client_credentials")
|
||||||
.subjectType("public")
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm("RS256")
|
||||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||||
.claim("a-claim", "a-value")
|
.claim("a-claim", "a-value")
|
||||||
.build();
|
.build();
|
||||||
@ -67,6 +69,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||||
assertThat(providerConfiguration.<String>getClaim("a-claim")).isEqualTo("a-value");
|
assertThat(providerConfiguration.<String>getClaim("a-claim")).isEqualTo("a-value");
|
||||||
}
|
}
|
||||||
@ -81,6 +84,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
.scope("openid")
|
.scope("openid")
|
||||||
.responseType("code")
|
.responseType("code")
|
||||||
.subjectType("public")
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm("RS256")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
assertThat(providerConfiguration.getIssuer()).isEqualTo(url("https://example.com/issuer1"));
|
||||||
@ -91,6 +95,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,6 +109,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
||||||
claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||||
claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
|
claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
|
||||||
|
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.singletonList("RS256"));
|
||||||
claims.put("some-claim", "some-value");
|
claims.put("some-claim", "some-value");
|
||||||
|
|
||||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
||||||
@ -116,6 +122,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||||
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
||||||
}
|
}
|
||||||
@ -130,6 +137,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
claims.put(OidcProviderMetadataClaimNames.SCOPES_SUPPORTED, Collections.singletonList("openid"));
|
||||||
claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
claims.put(OidcProviderMetadataClaimNames.RESPONSE_TYPES_SUPPORTED, Collections.singletonList("code"));
|
||||||
claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
|
claims.put(OidcProviderMetadataClaimNames.SUBJECT_TYPES_SUPPORTED, Collections.singletonList("public"));
|
||||||
|
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.singletonList("RS256"));
|
||||||
claims.put("some-claim", "some-value");
|
claims.put("some-claim", "some-value");
|
||||||
|
|
||||||
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
OidcProviderConfiguration providerConfiguration = OidcProviderConfiguration.withClaims(claims).build();
|
||||||
@ -142,6 +150,7 @@ public class OidcProviderConfigurationTests {
|
|||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||||
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
assertThat(providerConfiguration.<String>getClaim("some-claim")).isEqualTo("some-value");
|
||||||
}
|
}
|
||||||
@ -332,6 +341,42 @@ public class OidcProviderConfigurationTests {
|
|||||||
.hasMessageContaining("subjectTypes cannot be empty");
|
.hasMessageContaining("subjectTypes cannot be empty");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenMissingIdTokenSigningAlgorithmsThenThrowIllegalArgumentException() {
|
||||||
|
OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
|
||||||
|
.claims((claims) -> claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED));
|
||||||
|
|
||||||
|
assertThatThrownBy(builder::build)
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("idTokenSigningAlgorithms cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenIdTokenSigningAlgorithmsNotListThenThrowIllegalArgumentException() {
|
||||||
|
OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
|
||||||
|
.claims((claims) -> {
|
||||||
|
claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||||
|
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, "RS256");
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThatThrownBy(builder::build)
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("idTokenSigningAlgorithms must be of type List");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void buildWhenIdTokenSigningAlgorithmsEmptyListThenThrowIllegalArgumentException() {
|
||||||
|
OidcProviderConfiguration.Builder builder = this.minimalConfigurationBuilder
|
||||||
|
.claims((claims) -> {
|
||||||
|
claims.remove(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED);
|
||||||
|
claims.put(OidcProviderMetadataClaimNames.ID_TOKEN_SIGNING_ALG_VALUES_SUPPORTED, Collections.emptyList());
|
||||||
|
});
|
||||||
|
|
||||||
|
assertThatThrownBy(builder::build)
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessageContaining("idTokenSigningAlgorithms cannot be empty");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void responseTypesWhenAddingOrRemovingThenCorrectValues() {
|
public void responseTypesWhenAddingOrRemovingThenCorrectValues() {
|
||||||
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
||||||
@ -368,6 +413,19 @@ public class OidcProviderConfigurationTests {
|
|||||||
assertThat(configuration.getSubjectTypes()).containsExactly("some-subject-type");
|
assertThat(configuration.getSubjectTypes()).containsExactly("some-subject-type");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void idTokenSigningAlgorithmsWhenAddingOrRemovingThenCorrectValues() {
|
||||||
|
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
||||||
|
.idTokenSigningAlgorithm("should-be-removed")
|
||||||
|
.idTokenSigningAlgorithms(signingAlgorithms -> {
|
||||||
|
signingAlgorithms.clear();
|
||||||
|
signingAlgorithms.add("ES256");
|
||||||
|
})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(configuration.getIdTokenSigningAlgorithms()).containsExactly("ES256");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void scopesWhenAddingOrRemovingThenCorrectValues() {
|
public void scopesWhenAddingOrRemovingThenCorrectValues() {
|
||||||
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
OidcProviderConfiguration configuration = this.minimalConfigurationBuilder
|
||||||
|
@ -65,7 +65,8 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
+ " \"token_endpoint\": \"https://example.com/issuer1/oauth2/token\",\n"
|
||||||
+ " \"jwks_uri\": \"https://example.com/issuer1/oauth2/jwks\",\n"
|
+ " \"jwks_uri\": \"https://example.com/issuer1/oauth2/jwks\",\n"
|
||||||
+ " \"response_types_supported\": [\"code\"],\n"
|
+ " \"response_types_supported\": [\"code\"],\n"
|
||||||
+ " \"subject_types_supported\": [\"public\"]\n"
|
+ " \"subject_types_supported\": [\"public\"],\n"
|
||||||
|
+ " \"id_token_signing_alg_values_supported\": [\"RS256\"]\n"
|
||||||
+ "}\n";
|
+ "}\n";
|
||||||
// @formatter:on
|
// @formatter:on
|
||||||
MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), HttpStatus.OK);
|
MockClientHttpResponse response = new MockClientHttpResponse(providerConfigurationResponse.getBytes(), HttpStatus.OK);
|
||||||
@ -78,6 +79,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
assertThat(providerConfiguration.getJwkSetUri()).isEqualTo(new URL("https://example.com/issuer1/oauth2/jwks"));
|
||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getScopes()).isNull();
|
assertThat(providerConfiguration.getScopes()).isNull();
|
||||||
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
assertThat(providerConfiguration.getGrantTypes()).isNull();
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).isNull();
|
||||||
@ -95,6 +97,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
+ " \"response_types_supported\": [\"code\"],\n"
|
+ " \"response_types_supported\": [\"code\"],\n"
|
||||||
+ " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n"
|
+ " \"grant_types_supported\": [\"authorization_code\", \"client_credentials\"],\n"
|
||||||
+ " \"subject_types_supported\": [\"public\"],\n"
|
+ " \"subject_types_supported\": [\"public\"],\n"
|
||||||
|
+ " \"id_token_signing_alg_values_supported\": [\"RS256\"],\n"
|
||||||
+ " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
+ " \"token_endpoint_auth_methods_supported\": [\"client_secret_basic\"],\n"
|
||||||
+ " \"custom_claim\": \"value\",\n"
|
+ " \"custom_claim\": \"value\",\n"
|
||||||
+ " \"custom_collection_claim\": [\"value1\", \"value2\"]\n"
|
+ " \"custom_collection_claim\": [\"value1\", \"value2\"]\n"
|
||||||
@ -112,6 +115,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
assertThat(providerConfiguration.getResponseTypes()).containsExactly("code");
|
||||||
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
assertThat(providerConfiguration.getGrantTypes()).containsExactlyInAnyOrder("authorization_code", "client_credentials");
|
||||||
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
assertThat(providerConfiguration.getSubjectTypes()).containsExactly("public");
|
||||||
|
assertThat(providerConfiguration.getIdTokenSigningAlgorithms()).containsExactly("RS256");
|
||||||
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
assertThat(providerConfiguration.getTokenEndpointAuthenticationMethods()).containsExactly("client_secret_basic");
|
||||||
assertThat(providerConfiguration.<String>getClaim("custom_claim")).isEqualTo("value");
|
assertThat(providerConfiguration.<String>getClaim("custom_claim")).isEqualTo("value");
|
||||||
assertThat(providerConfiguration.getClaimAsStringList("custom_collection_claim")).containsExactlyInAnyOrder("value1", "value2");
|
assertThat(providerConfiguration.getClaimAsStringList("custom_collection_claim")).containsExactlyInAnyOrder("value1", "value2");
|
||||||
@ -155,6 +159,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
.grantType("authorization_code")
|
.grantType("authorization_code")
|
||||||
.grantType("client_credentials")
|
.grantType("client_credentials")
|
||||||
.subjectType("public")
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm("RS256")
|
||||||
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
.tokenEndpointAuthenticationMethod("client_secret_basic")
|
||||||
.claim("custom_claim", "value")
|
.claim("custom_claim", "value")
|
||||||
.claim("custom_collection_claim", Arrays.asList("value1", "value2"))
|
.claim("custom_collection_claim", Arrays.asList("value1", "value2"))
|
||||||
@ -172,6 +177,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
|
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
||||||
|
assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"custom_claim\":\"value\"");
|
assertThat(providerConfigurationResponse).contains("\"custom_claim\":\"value\"");
|
||||||
assertThat(providerConfigurationResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
|
assertThat(providerConfigurationResponse).contains("\"custom_collection_claim\":[\"value1\",\"value2\"]");
|
||||||
@ -194,6 +200,7 @@ public class OidcProviderConfigurationHttpMessageConverterTests {
|
|||||||
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
.jwkSetUri("https://example.com/issuer1/oauth2/jwks")
|
||||||
.responseType("code")
|
.responseType("code")
|
||||||
.subjectType("public")
|
.subjectType("public")
|
||||||
|
.idTokenSigningAlgorithm("RS256")
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||||
|
@ -17,10 +17,15 @@ package org.springframework.security.oauth2.server.authorization.authentication;
|
|||||||
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
|
||||||
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
|
import java.time.temporal.ChronoUnit;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
@ -36,6 +41,9 @@ public class OAuth2AccessTokenAuthenticationTokenTests {
|
|||||||
new OAuth2ClientAuthenticationToken(this.registeredClient);
|
new OAuth2ClientAuthenticationToken(this.registeredClient);
|
||||||
private OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
private OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER,
|
||||||
"access-token", Instant.now(), Instant.now().plusSeconds(300));
|
"access-token", Instant.now(), Instant.now().plusSeconds(300));
|
||||||
|
private OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2(
|
||||||
|
"refresh-token", Instant.now(), Instant.now().plus(1, ChronoUnit.DAYS));
|
||||||
|
private Map<String, Object> additionalParameters = Collections.singletonMap("custom-param", "custom-value");
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
public void constructorWhenRegisteredClientNullThenThrowIllegalArgumentException() {
|
||||||
@ -58,13 +66,23 @@ public class OAuth2AccessTokenAuthenticationTokenTests {
|
|||||||
.hasMessage("accessToken cannot be null");
|
.hasMessage("accessToken cannot be null");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void constructorWhenAdditionalParametersNullThenThrowIllegalArgumentException() {
|
||||||
|
assertThatThrownBy(() -> new OAuth2AccessTokenAuthenticationToken(
|
||||||
|
this.registeredClient, this.clientPrincipal, this.accessToken, this.refreshToken, null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class)
|
||||||
|
.hasMessage("additionalParameters cannot be null");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void constructorWhenAllValuesProvidedThenCreated() {
|
public void constructorWhenAllValuesProvidedThenCreated() {
|
||||||
OAuth2AccessTokenAuthenticationToken authentication = new OAuth2AccessTokenAuthenticationToken(
|
OAuth2AccessTokenAuthenticationToken authentication = new OAuth2AccessTokenAuthenticationToken(
|
||||||
this.registeredClient, this.clientPrincipal, this.accessToken);
|
this.registeredClient, this.clientPrincipal, this.accessToken, this.refreshToken, this.additionalParameters);
|
||||||
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
assertThat(authentication.getPrincipal()).isEqualTo(this.clientPrincipal);
|
||||||
assertThat(authentication.getCredentials().toString()).isEmpty();
|
assertThat(authentication.getCredentials().toString()).isEmpty();
|
||||||
assertThat(authentication.getRegisteredClient()).isEqualTo(this.registeredClient);
|
assertThat(authentication.getRegisteredClient()).isEqualTo(this.registeredClient);
|
||||||
assertThat(authentication.getAccessToken()).isEqualTo(this.accessToken);
|
assertThat(authentication.getAccessToken()).isEqualTo(this.accessToken);
|
||||||
|
assertThat(authentication.getRefreshToken()).isEqualTo(this.refreshToken);
|
||||||
|
assertThat(authentication.getAdditionalParameters()).isEqualTo(this.additionalParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,6 +25,9 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||||
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
import org.springframework.security.oauth2.jose.JoseHeaderNames;
|
||||||
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
@ -49,9 +52,11 @@ import java.util.Set;
|
|||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.ArgumentMatchers.eq;
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@ -251,6 +256,43 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
|
|||||||
assertThat(updatedAuthorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()).isTrue();
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenValidCodeAndAuthenticationRequestThenReturnIdToken() {
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||||
|
OAuth2Authorization authorization = TestOAuth2Authorizations.authorization(registeredClient).build();
|
||||||
|
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(TokenType.AUTHORIZATION_CODE)))
|
||||||
|
.thenReturn(authorization);
|
||||||
|
|
||||||
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
|
OAuth2AuthorizationRequest authorizationRequest = authorization.getAttribute(
|
||||||
|
OAuth2AuthorizationAttributeNames.AUTHORIZATION_REQUEST);
|
||||||
|
OAuth2AuthorizationCodeAuthenticationToken authentication =
|
||||||
|
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
|
||||||
|
|
||||||
|
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
|
||||||
|
|
||||||
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
|
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
|
||||||
|
|
||||||
|
verify(this.jwtEncoder, times(2)).encode(any(), any()); // Access token and ID Token
|
||||||
|
|
||||||
|
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
|
||||||
|
verify(this.authorizationService).save(authorizationCaptor.capture());
|
||||||
|
OAuth2Authorization updatedAuthorization = authorizationCaptor.getValue();
|
||||||
|
|
||||||
|
assertThat(accessTokenAuthentication.getRegisteredClient().getId()).isEqualTo(updatedAuthorization.getRegisteredClientId());
|
||||||
|
assertThat(accessTokenAuthentication.getPrincipal()).isEqualTo(clientPrincipal);
|
||||||
|
assertThat(accessTokenAuthentication.getAccessToken()).isEqualTo(updatedAuthorization.getTokens().getAccessToken());
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isNotNull();
|
||||||
|
assertThat(accessTokenAuthentication.getRefreshToken()).isEqualTo(updatedAuthorization.getTokens().getRefreshToken());
|
||||||
|
OAuth2AuthorizationCode authorizationCode = updatedAuthorization.getTokens().getToken(OAuth2AuthorizationCode.class);
|
||||||
|
assertThat(updatedAuthorization.getTokens().getTokenMetadata(authorizationCode).isInvalidated()).isTrue();
|
||||||
|
OidcIdToken idToken = updatedAuthorization.getTokens().getToken(OidcIdToken.class);
|
||||||
|
assertThat(idToken).isNotNull();
|
||||||
|
assertThat(accessTokenAuthentication.getAdditionalParameters())
|
||||||
|
.containsExactly(entry(OidcParameterNames.ID_TOKEN, idToken.getTokenValue()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenTokenTimeToLiveConfiguredThenTokenExpirySet() {
|
public void authenticateWhenTokenTimeToLiveConfiguredThenTokenExpirySet() {
|
||||||
Duration accessTokenTTL = Duration.ofHours(2);
|
Duration accessTokenTTL = Duration.ofHours(2);
|
||||||
|
@ -148,7 +148,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
|
|||||||
public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() {
|
public void authenticateWhenScopeRequestedThenAccessTokenContainsScope() {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
OAuth2ClientAuthenticationToken clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
Set<String> requestedScope = Collections.singleton("openid");
|
Set<String> requestedScope = Collections.singleton("scope1");
|
||||||
OAuth2ClientCredentialsAuthenticationToken authentication =
|
OAuth2ClientCredentialsAuthenticationToken authentication =
|
||||||
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope);
|
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope);
|
||||||
|
|
||||||
|
@ -31,9 +31,7 @@ public class TestRegisteredClients {
|
|||||||
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
.authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN)
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.redirectUri("https://example.com")
|
.redirectUri("https://example.com")
|
||||||
.scope("openid")
|
.scope("scope1");
|
||||||
.scope("profile")
|
|
||||||
.scope("email");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static RegisteredClient.Builder registeredClient2() {
|
public static RegisteredClient.Builder registeredClient2() {
|
||||||
@ -46,9 +44,6 @@ public class TestRegisteredClients {
|
|||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.BASIC)
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.POST)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.POST)
|
||||||
.redirectUri("https://example.com")
|
.redirectUri("https://example.com")
|
||||||
.scope("openid")
|
|
||||||
.scope("profile")
|
|
||||||
.scope("email")
|
|
||||||
.scope("scope1")
|
.scope("scope1")
|
||||||
.scope("scope2");
|
.scope("scope2");
|
||||||
}
|
}
|
||||||
@ -59,9 +54,7 @@ public class TestRegisteredClients {
|
|||||||
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
|
||||||
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
|
.clientAuthenticationMethod(ClientAuthenticationMethod.NONE)
|
||||||
.redirectUri("https://example.com")
|
.redirectUri("https://example.com")
|
||||||
.scope("openid")
|
.scope("scope1")
|
||||||
.scope("profile")
|
|
||||||
.scope("email")
|
|
||||||
.clientSettings(clientSettings -> clientSettings.requireProofKey(true));
|
.clientSettings(clientSettings -> clientSettings.requireProofKey(true));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,6 +32,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequ
|
|||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponseType;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.OidcScopes;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationAttributeNames;
|
||||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
|
||||||
@ -168,6 +169,20 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
OAuth2ErrorCodes.UNAUTHORIZED_CLIENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthenticationRequestMissingRedirectUriThenInvalidRequestError() throws Exception {
|
||||||
|
// redirect_uri is REQUIRED for OpenID Connect requests
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||||
|
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||||
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
|
doFilterWhenAuthorizationRequestInvalidParameterThenError(
|
||||||
|
registeredClient,
|
||||||
|
OAuth2ParameterNames.REDIRECT_URI,
|
||||||
|
OAuth2ErrorCodes.INVALID_REQUEST,
|
||||||
|
request -> request.removeParameter(OAuth2ParameterNames.REDIRECT_URI));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestInvalidRedirectUriThenInvalidRequestError() throws Exception {
|
public void doFilterWhenAuthorizationRequestInvalidRedirectUriThenInvalidRequestError() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
@ -394,7 +409,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestValidNotAuthenticatedThenContinueChainToCommenceAuthentication() throws Exception {
|
public void doFilterWhenAuthorizationRequestNotAuthenticatedThenContinueChainToCommenceAuthentication() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||||
.thenReturn(registeredClient);
|
.thenReturn(registeredClient);
|
||||||
@ -411,12 +426,27 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationRequestValidThenAuthorizationResponse() throws Exception {
|
public void doFilterWhenAuthorizationRequestGetThenAuthorizationResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
|
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||||
|
doFilterWhenAuthorizationRequestThenAuthorizationResponse(registeredClient, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void doFilterWhenAuthorizationRequestPostThenAuthorizationResponse() throws Exception {
|
||||||
|
// OpenID Connect requests support POST method
|
||||||
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().scope(OidcScopes.OPENID).build();
|
||||||
|
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
||||||
|
request.setMethod("POST");
|
||||||
|
doFilterWhenAuthorizationRequestThenAuthorizationResponse(registeredClient, request);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doFilterWhenAuthorizationRequestThenAuthorizationResponse(
|
||||||
|
RegisteredClient registeredClient, MockHttpServletRequest request) throws Exception {
|
||||||
|
|
||||||
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
when(this.registeredClientRepository.findByClientId((eq(registeredClient.getClientId()))))
|
||||||
.thenReturn(registeredClient);
|
.thenReturn(registeredClient);
|
||||||
|
|
||||||
MockHttpServletRequest request = createAuthorizationRequest(registeredClient);
|
|
||||||
MockHttpServletResponse response = new MockHttpServletResponse();
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
FilterChain filterChain = mock(FilterChain.class);
|
FilterChain filterChain = mock(FilterChain.class);
|
||||||
|
|
||||||
@ -455,7 +485,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenPkceRequiredAndAuthorizationRequestValidThenAuthorizationResponse() throws Exception {
|
public void doFilterWhenPkceRequiredAndAuthorizationRequestThenAuthorizationResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
.clientSettings(clientSettings -> clientSettings.requireProofKey(true))
|
.clientSettings(clientSettings -> clientSettings.requireProofKey(true))
|
||||||
.build();
|
.build();
|
||||||
@ -501,7 +531,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenUserConsentRequiredAndAuthorizationRequestValidThenUserConsentResponse() throws Exception {
|
public void doFilterWhenUserConsentRequiredAndAuthorizationRequestThenUserConsentResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient()
|
||||||
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
.clientSettings(clientSettings -> clientSettings.requireUserConsent(true))
|
||||||
.build();
|
.build();
|
||||||
@ -722,7 +752,7 @@ public class OAuth2AuthorizationEndpointFilterTests {
|
|||||||
OAuth2ParameterNames.CLIENT_ID,
|
OAuth2ParameterNames.CLIENT_ID,
|
||||||
OAuth2ErrorCodes.ACCESS_DENIED,
|
OAuth2ErrorCodes.ACCESS_DENIED,
|
||||||
DEFAULT_ERROR_URI,
|
DEFAULT_ERROR_URI,
|
||||||
request -> request.removeParameter("consent_action"));
|
request -> request.setParameter("consent_action", "cancel"));
|
||||||
|
|
||||||
verify(this.authorizationService).remove(eq(authorization));
|
verify(this.authorizationService).remove(eq(authorization));
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
|||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
import org.springframework.security.oauth2.core.OAuth2ErrorCodes;
|
||||||
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2RefreshToken2;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
|
||||||
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
import org.springframework.security.oauth2.core.http.converter.OAuth2AccessTokenResponseHttpMessageConverter;
|
||||||
@ -53,10 +54,13 @@ import javax.servlet.http.HttpServletResponse;
|
|||||||
import java.time.Duration;
|
import java.time.Duration;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThat;
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.assertj.core.api.Assertions.entry;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
@ -199,16 +203,19 @@ public class OAuth2TokenEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenAuthorizationCodeTokenRequestValidThenAccessTokenResponse() throws Exception {
|
public void doFilterWhenAuthorizationCodeTokenRequestThenAccessTokenResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||||
OAuth2AccessToken.TokenType.BEARER, "token",
|
OAuth2AccessToken.TokenType.BEARER, "token",
|
||||||
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
|
Instant.now(), Instant.now().plus(Duration.ofHours(1)),
|
||||||
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
new HashSet<>(Arrays.asList("scope1", "scope2")));
|
||||||
|
OAuth2RefreshToken refreshToken = new OAuth2RefreshToken2(
|
||||||
|
"refresh-token", Instant.now(), Instant.now().plus(Duration.ofDays(1)));
|
||||||
|
Map<String, Object> additionalParameters = Collections.singletonMap("custom-param", "custom-value");
|
||||||
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
|
||||||
new OAuth2AccessTokenAuthenticationToken(
|
new OAuth2AccessTokenAuthenticationToken(
|
||||||
registeredClient, clientPrincipal, accessToken);
|
registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters);
|
||||||
|
|
||||||
when(this.authenticationManager.authenticate(any())).thenReturn(accessTokenAuthentication);
|
when(this.authenticationManager.authenticate(any())).thenReturn(accessTokenAuthentication);
|
||||||
|
|
||||||
@ -247,6 +254,8 @@ public class OAuth2TokenEndpointFilterTests {
|
|||||||
assertThat(accessTokenResult.getExpiresAt()).isBetween(
|
assertThat(accessTokenResult.getExpiresAt()).isBetween(
|
||||||
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
accessToken.getExpiresAt().minusSeconds(1), accessToken.getExpiresAt().plusSeconds(1));
|
||||||
assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes());
|
assertThat(accessTokenResult.getScopes()).isEqualTo(accessToken.getScopes());
|
||||||
|
assertThat(accessTokenResponse.getRefreshToken().getTokenValue()).isEqualTo(refreshToken.getTokenValue());
|
||||||
|
assertThat(accessTokenResponse.getAdditionalParameters()).containsExactly(entry("custom-param", "custom-value"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -260,7 +269,7 @@ public class OAuth2TokenEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenClientCredentialsTokenRequestValidThenAccessTokenResponse() throws Exception {
|
public void doFilterWhenClientCredentialsTokenRequestThenAccessTokenResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient2().build();
|
||||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||||
@ -338,7 +347,7 @@ public class OAuth2TokenEndpointFilterTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void doFilterWhenRefreshTokenRequestValidThenAccessTokenResponse() throws Exception {
|
public void doFilterWhenRefreshTokenRequestThenAccessTokenResponse() throws Exception {
|
||||||
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
|
||||||
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
Authentication clientPrincipal = new OAuth2ClientAuthenticationToken(registeredClient);
|
||||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
OAuth2AccessToken accessToken = new OAuth2AccessToken(
|
||||||
|
@ -112,6 +112,7 @@ public class OidcProviderConfigurationEndpointFilterTests {
|
|||||||
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
assertThat(providerConfigurationResponse).contains("\"response_types_supported\":[\"code\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
|
assertThat(providerConfigurationResponse).contains("\"grant_types_supported\":[\"authorization_code\",\"client_credentials\",\"refresh_token\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
assertThat(providerConfigurationResponse).contains("\"subject_types_supported\":[\"public\"]");
|
||||||
|
assertThat(providerConfigurationResponse).contains("\"id_token_signing_alg_values_supported\":[\"RS256\"]");
|
||||||
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
assertThat(providerConfigurationResponse).contains("\"token_endpoint_auth_methods_supported\":[\"client_secret_basic\",\"client_secret_post\"]");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user