mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-07-30 06:03:32 +00:00
Extract OidcTokenValidator to an OAuth2TokenValidator
Fixes gh-5930
This commit is contained in:
parent
7a55af246e
commit
9c0d78da71
@ -630,15 +630,16 @@ public class OAuth2LoginConfigurerTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static JwtDecoder getJwtDecoder() {
|
private static JwtDecoder getJwtDecoder() {
|
||||||
return token -> {
|
Map<String, Object> claims = new HashMap<>();
|
||||||
Map<String, Object> claims = new HashMap<>();
|
claims.put(IdTokenClaimNames.SUB, "sub123");
|
||||||
claims.put(IdTokenClaimNames.SUB, "sub123");
|
claims.put(IdTokenClaimNames.ISS, "http://localhost/iss");
|
||||||
claims.put(IdTokenClaimNames.ISS, "http://localhost/iss");
|
claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d"));
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("clientId", "a", "u", "d"));
|
claims.put(IdTokenClaimNames.AZP, "clientId");
|
||||||
claims.put(IdTokenClaimNames.AZP, "clientId");
|
Jwt jwt = new Jwt("token123", Instant.now(), Instant.now().plusSeconds(3600),
|
||||||
return new Jwt("token123", Instant.now(), Instant.now().plusSeconds(3600),
|
Collections.singletonMap("header1", "value1"), claims);
|
||||||
Collections.singletonMap("header1", "value1"), claims);
|
JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
||||||
};
|
when(jwtDecoder.decode(any())).thenReturn(jwt);
|
||||||
|
return jwtDecoder;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -27,9 +27,11 @@ import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
|||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
@ -40,6 +42,8 @@ import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
|||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtException;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
@ -205,9 +209,14 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
|||||||
|
|
||||||
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
private OidcIdToken createOidcToken(ClientRegistration clientRegistration, OAuth2AccessTokenResponse accessTokenResponse) {
|
||||||
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
JwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||||
Jwt jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
|
Jwt jwt;
|
||||||
|
try {
|
||||||
|
jwt = jwtDecoder.decode((String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN));
|
||||||
|
} catch (JwtException ex) {
|
||||||
|
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, ex.getMessage(), null);
|
||||||
|
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), ex);
|
||||||
|
}
|
||||||
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
||||||
OidcTokenValidator.validateIdToken(idToken, clientRegistration);
|
|
||||||
return idToken;
|
return idToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -228,7 +237,11 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
|||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
||||||
return new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
|
||||||
|
new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration));
|
||||||
|
jwtDecoder.setJwtValidator(jwtValidator);
|
||||||
|
return jwtDecoder;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -26,10 +26,12 @@ import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessT
|
|||||||
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
|
||||||
|
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
import org.springframework.security.oauth2.core.OAuth2AccessToken;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
|
||||||
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
|
||||||
@ -37,6 +39,9 @@ import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
|||||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.core.user.OAuth2User;
|
import org.springframework.security.oauth2.core.user.OAuth2User;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtException;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
||||||
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
@ -138,7 +143,11 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
|
|
||||||
return this.accessTokenResponseClient.getTokenResponse(authzRequest)
|
return this.accessTokenResponseClient.getTokenResponse(authzRequest)
|
||||||
.flatMap(accessTokenResponse -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse))
|
.flatMap(accessTokenResponse -> authenticationResult(authorizationCodeAuthentication, accessTokenResponse))
|
||||||
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()));
|
.onErrorMap(OAuth2AuthorizationException.class, e -> new OAuth2AuthenticationException(e.getError(), e.getError().toString()))
|
||||||
|
.onErrorMap(JwtException.class, e -> {
|
||||||
|
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE, e.getMessage(), null);
|
||||||
|
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString(), e);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -188,8 +197,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
ReactiveJwtDecoder jwtDecoder = this.jwtDecoderFactory.createDecoder(clientRegistration);
|
||||||
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
String rawIdToken = (String) accessTokenResponse.getAdditionalParameters().get(OidcParameterNames.ID_TOKEN);
|
||||||
return jwtDecoder.decode(rawIdToken)
|
return jwtDecoder.decode(rawIdToken)
|
||||||
.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()))
|
.map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims()));
|
||||||
.doOnNext(idToken -> OidcTokenValidator.validateIdToken(idToken, clientRegistration));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultJwtDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
private static class DefaultJwtDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||||
@ -208,7 +216,12 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
|||||||
);
|
);
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
}
|
}
|
||||||
return new NimbusReactiveJwtDecoder(clientRegistration.getProviderDetails().getJwkSetUri());
|
NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(
|
||||||
|
clientRegistration.getProviderDetails().getJwkSetUri());
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
|
||||||
|
new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration));
|
||||||
|
jwtDecoder.setJwtValidator(jwtValidator);
|
||||||
|
return jwtDecoder;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,13 +13,16 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||||
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2Error;
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
@ -27,36 +30,50 @@ import java.time.Instant;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* An {@link OAuth2TokenValidator} responsible for
|
||||||
|
* validating the claims in an {@link OidcIdToken ID Token}.
|
||||||
|
*
|
||||||
* @author Rob Winch
|
* @author Rob Winch
|
||||||
|
* @author Joe Grandja
|
||||||
* @since 5.1
|
* @since 5.1
|
||||||
|
* @see OAuth2TokenValidator
|
||||||
|
* @see Jwt
|
||||||
|
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation">ID Token Validation</a>
|
||||||
*/
|
*/
|
||||||
final class OidcTokenValidator {
|
public final class OidcIdTokenValidator implements OAuth2TokenValidator<Jwt> {
|
||||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
private static final OAuth2Error INVALID_ID_TOKEN_ERROR = new OAuth2Error("invalid_id_token");
|
||||||
|
private final ClientRegistration clientRegistration;
|
||||||
|
|
||||||
static void validateIdToken(OidcIdToken idToken, ClientRegistration clientRegistration) {
|
public OidcIdTokenValidator(ClientRegistration clientRegistration) {
|
||||||
|
Assert.notNull(clientRegistration, "clientRegistration cannot be null");
|
||||||
|
this.clientRegistration = clientRegistration;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OAuth2TokenValidatorResult validate(Jwt idToken) {
|
||||||
// 3.1.3.7 ID Token Validation
|
// 3.1.3.7 ID Token Validation
|
||||||
// http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
// http://openid.net/specs/openid-connect-core-1_0.html#IDTokenValidation
|
||||||
|
|
||||||
// Validate REQUIRED Claims
|
// Validate REQUIRED Claims
|
||||||
URL issuer = idToken.getIssuer();
|
URL issuer = idToken.getIssuer();
|
||||||
if (issuer == null) {
|
if (issuer == null) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
String subject = idToken.getSubject();
|
String subject = idToken.getSubject();
|
||||||
if (subject == null) {
|
if (subject == null) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
List<String> audience = idToken.getAudience();
|
List<String> audience = idToken.getAudience();
|
||||||
if (CollectionUtils.isEmpty(audience)) {
|
if (CollectionUtils.isEmpty(audience)) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
Instant expiresAt = idToken.getExpiresAt();
|
Instant expiresAt = idToken.getExpiresAt();
|
||||||
if (expiresAt == null) {
|
if (expiresAt == null) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
Instant issuedAt = idToken.getIssuedAt();
|
Instant issuedAt = idToken.getIssuedAt();
|
||||||
if (issuedAt == null) {
|
if (issuedAt == null) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
|
// 2. The Issuer Identifier for the OpenID Provider (which is typically obtained during Discovery)
|
||||||
@ -68,21 +85,21 @@ final class OidcTokenValidator {
|
|||||||
// The aud (audience) Claim MAY contain an array with more than one element.
|
// The aud (audience) Claim MAY contain an array with more than one element.
|
||||||
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
|
// The ID Token MUST be rejected if the ID Token does not list the Client as a valid audience,
|
||||||
// or if it contains additional audiences not trusted by the Client.
|
// or if it contains additional audiences not trusted by the Client.
|
||||||
if (!audience.contains(clientRegistration.getClientId())) {
|
if (!audience.contains(this.clientRegistration.getClientId())) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4. If the ID Token contains multiple audiences,
|
// 4. If the ID Token contains multiple audiences,
|
||||||
// the Client SHOULD verify that an azp Claim is present.
|
// the Client SHOULD verify that an azp Claim is present.
|
||||||
String authorizedParty = idToken.getAuthorizedParty();
|
String authorizedParty = idToken.getClaimAsString(IdTokenClaimNames.AZP);
|
||||||
if (audience.size() > 1 && authorizedParty == null) {
|
if (audience.size() > 1 && authorizedParty == null) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. If an azp (authorized party) Claim is present,
|
// 5. If an azp (authorized party) Claim is present,
|
||||||
// the Client SHOULD verify that its client_id is the Claim Value.
|
// the Client SHOULD verify that its client_id is the Claim Value.
|
||||||
if (authorizedParty != null && !authorizedParty.equals(clientRegistration.getClientId())) {
|
if (authorizedParty != null && !authorizedParty.equals(this.clientRegistration.getClientId())) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
|
// 7. The alg value SHOULD be the default of RS256 or the algorithm sent by the Client
|
||||||
@ -92,7 +109,7 @@ final class OidcTokenValidator {
|
|||||||
// 9. The current time MUST be before the time represented by the exp Claim.
|
// 9. The current time MUST be before the time represented by the exp Claim.
|
||||||
Instant now = Instant.now();
|
Instant now = Instant.now();
|
||||||
if (!now.isBefore(expiresAt)) {
|
if (!now.isBefore(expiresAt)) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 10. The iat Claim can be used to reject tokens that were issued too far away from the current time,
|
// 10. The iat Claim can be used to reject tokens that were issued too far away from the current time,
|
||||||
@ -100,7 +117,7 @@ final class OidcTokenValidator {
|
|||||||
// The acceptable range is Client specific.
|
// The acceptable range is Client specific.
|
||||||
Instant maxIssuedAt = now.plusSeconds(30);
|
Instant maxIssuedAt = now.plusSeconds(30);
|
||||||
if (issuedAt.isAfter(maxIssuedAt)) {
|
if (issuedAt.isAfter(maxIssuedAt)) {
|
||||||
throwInvalidIdTokenException();
|
return invalidIdToken();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 11. If a nonce value was sent in the Authentication Request,
|
// 11. If a nonce value was sent in the Authentication Request,
|
||||||
@ -110,12 +127,10 @@ final class OidcTokenValidator {
|
|||||||
// The precise method for detecting replay attacks is Client specific.
|
// The precise method for detecting replay attacks is Client specific.
|
||||||
// TODO Depends on gh-4442
|
// TODO Depends on gh-4442
|
||||||
|
|
||||||
|
return OAuth2TokenValidatorResult.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void throwInvalidIdTokenException() {
|
private static OAuth2TokenValidatorResult invalidIdToken() {
|
||||||
OAuth2Error invalidIdTokenError = new OAuth2Error(INVALID_ID_TOKEN_ERROR_CODE);
|
return OAuth2TokenValidatorResult.failure(INVALID_ID_TOKEN_ERROR);
|
||||||
throw new OAuth2AuthenticationException(invalidIdTokenError, invalidIdTokenError.toString());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private OidcTokenValidator() {}
|
|
||||||
}
|
}
|
@ -42,6 +42,7 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
|
|||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtException;
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -82,7 +83,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
@Before
|
@Before
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public void setUp() throws Exception {
|
public void setUp() {
|
||||||
this.clientRegistration = clientRegistration().clientId("client1").build();
|
this.clientRegistration = clientRegistration().clientId("client1").build();
|
||||||
this.authorizationRequest = request().scope("openid", "profile", "email").build();
|
this.authorizationRequest = request().scope("openid", "profile", "email").build();
|
||||||
this.authorizationResponse = success().build();
|
this.authorizationResponse = success().build();
|
||||||
@ -204,139 +205,20 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenIdTokenIssuerClaimIsNullThenThrowOAuth2AuthenticationException() throws Exception {
|
public void authenticateWhenIdTokenValidationErrorThenThrowOAuth2AuthenticationException() {
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
this.exception.expect(OAuth2AuthenticationException.class);
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
this.exception.expectMessage(containsString("[invalid_id_token] ID Token Validation Error"));
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
JwtDecoder jwtDecoder = mock(JwtDecoder.class);
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
when(jwtDecoder.decode(anyString())).thenThrow(new JwtException("ID Token Validation Error"));
|
||||||
|
this.authenticationProvider.setJwtDecoderFactory(registration -> jwtDecoder);
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
this.authenticationProvider.authenticate(
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenIdTokenSubjectClaimIsNullThenThrowOAuth2AuthenticationException() throws Exception {
|
public void authenticateWhenLoginSuccessThenReturnAuthentication() {
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
|
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenAudienceClaimIsNullThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
|
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenAudienceClaimDoesNotContainClientIdThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Collections.singletonList("other-client"));
|
|
||||||
|
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenMultipleAudienceClaimAndAuthorizedPartyClaimIsNullThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
|
||||||
|
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenAuthorizedPartyClaimNotEqualToClientIdThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
|
||||||
claims.put(IdTokenClaimNames.AZP, "other-client");
|
|
||||||
|
|
||||||
this.setUpIdToken(claims);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenExpiresAtIsBeforeNowThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
|
||||||
claims.put(IdTokenClaimNames.AZP, "client1");
|
|
||||||
|
|
||||||
Instant issuedAt = Instant.now().minusSeconds(10);
|
|
||||||
Instant expiresAt = Instant.from(issuedAt).plusSeconds(5);
|
|
||||||
|
|
||||||
this.setUpIdToken(claims, issuedAt, expiresAt);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenIdTokenIssuedAtIsAfterMaxIssuedAtThenThrowOAuth2AuthenticationException() throws Exception {
|
|
||||||
this.exception.expect(OAuth2AuthenticationException.class);
|
|
||||||
this.exception.expectMessage(containsString("invalid_id_token"));
|
|
||||||
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
|
||||||
claims.put(IdTokenClaimNames.AUD, Arrays.asList("client1", "client2"));
|
|
||||||
claims.put(IdTokenClaimNames.AZP, "client1");
|
|
||||||
|
|
||||||
Instant issuedAt = Instant.now().plusSeconds(35);
|
|
||||||
Instant expiresAt = Instant.from(issuedAt).plusSeconds(60);
|
|
||||||
|
|
||||||
this.setUpIdToken(claims, issuedAt, expiresAt);
|
|
||||||
|
|
||||||
this.authenticationProvider.authenticate(
|
|
||||||
new OAuth2LoginAuthenticationToken(this.clientRegistration, this.authorizationExchange));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void authenticateWhenLoginSuccessThenReturnAuthentication() throws Exception {
|
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||||
@ -365,7 +247,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenAuthoritiesMapperSetThenReturnMappedAuthorities() throws Exception {
|
public void authenticateWhenAuthoritiesMapperSetThenReturnMappedAuthorities() {
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||||
@ -394,7 +276,7 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||||||
|
|
||||||
// gh-5368
|
// gh-5368
|
||||||
@Test
|
@Test
|
||||||
public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToUserRequest() throws Exception {
|
public void authenticateWhenTokenSuccessResponseThenAdditionalParametersAddedToUserRequest() {
|
||||||
Map<String, Object> claims = new HashMap<>();
|
Map<String, Object> claims = new HashMap<>();
|
||||||
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
claims.put(IdTokenClaimNames.ISS, "https://provider.com");
|
||||||
claims.put(IdTokenClaimNames.SUB, "subject1");
|
claims.put(IdTokenClaimNames.SUB, "subject1");
|
||||||
@ -416,13 +298,13 @@ public class OidcAuthorizationCodeAuthenticationProviderTests {
|
|||||||
this.accessTokenResponse.getAdditionalParameters());
|
this.accessTokenResponse.getAdditionalParameters());
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpIdToken(Map<String, Object> claims) throws Exception {
|
private void setUpIdToken(Map<String, Object> claims) {
|
||||||
Instant issuedAt = Instant.now();
|
Instant issuedAt = Instant.now();
|
||||||
Instant expiresAt = Instant.from(issuedAt).plusSeconds(3600);
|
Instant expiresAt = Instant.from(issuedAt).plusSeconds(3600);
|
||||||
this.setUpIdToken(claims, issuedAt, expiresAt);
|
this.setUpIdToken(claims, issuedAt, expiresAt);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setUpIdToken(Map<String, Object> claims, Instant issuedAt, Instant expiresAt) throws Exception {
|
private void setUpIdToken(Map<String, Object> claims, Instant issuedAt, Instant expiresAt) {
|
||||||
Map<String, Object> headers = new HashMap<>();
|
Map<String, Object> headers = new HashMap<>();
|
||||||
headers.put("alg", "RS256");
|
headers.put("alg", "RS256");
|
||||||
|
|
||||||
|
@ -44,6 +44,7 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
|
|||||||
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.DefaultOidcUser;
|
||||||
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
|
||||||
import org.springframework.security.oauth2.jwt.Jwt;
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtException;
|
||||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
@ -143,6 +144,22 @@ public class OidcAuthorizationCodeReactiveAuthenticationManagerTests {
|
|||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
.isInstanceOf(OAuth2AuthenticationException.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void authenticateWhenIdTokenValidationErrorThenOAuth2AuthenticationException() {
|
||||||
|
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo")
|
||||||
|
.tokenType(OAuth2AccessToken.TokenType.BEARER)
|
||||||
|
.additionalParameters(Collections.singletonMap(OidcParameterNames.ID_TOKEN, this.idToken.getTokenValue()))
|
||||||
|
.build();
|
||||||
|
when(this.accessTokenResponseClient.getTokenResponse(any())).thenReturn(Mono.just(accessTokenResponse));
|
||||||
|
|
||||||
|
when(this.jwtDecoder.decode(any())).thenThrow(new JwtException("ID Token Validation Error"));
|
||||||
|
this.manager.setJwtDecoderFactory(c -> this.jwtDecoder);
|
||||||
|
|
||||||
|
assertThatThrownBy(() -> this.manager.authenticate(loginToken()).block())
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class)
|
||||||
|
.hasMessageContaining("[invalid_id_token] ID Token Validation Error");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void authenticationWhenOAuth2UserNotFoundThenEmpty() {
|
public void authenticationWhenOAuth2UserNotFoundThenEmpty() {
|
||||||
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo")
|
OAuth2AccessTokenResponse accessTokenResponse = OAuth2AccessTokenResponse.withToken("foo")
|
||||||
|
@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2002-2018 the original author or authors.
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package org.springframework.security.oauth2.client.oidc.authentication;
|
||||||
|
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
||||||
|
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
||||||
|
import org.springframework.security.oauth2.core.OAuth2Error;
|
||||||
|
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
||||||
|
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.time.Instant;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Rob Winch
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @since 5.1
|
||||||
|
*/
|
||||||
|
public class OidcIdTokenValidatorTests {
|
||||||
|
private ClientRegistration.Builder registration = TestClientRegistrations.clientRegistration();
|
||||||
|
private Map<String, Object> headers = new HashMap<>();
|
||||||
|
private Map<String, Object> claims = new HashMap<>();
|
||||||
|
private Instant issuedAt = Instant.now();
|
||||||
|
private Instant expiresAt = this.issuedAt.plusSeconds(3600);
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
this.headers.put("alg", JwsAlgorithms.RS256);
|
||||||
|
this.claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com");
|
||||||
|
this.claims.put(IdTokenClaimNames.SUB, "rob");
|
||||||
|
this.claims.put(IdTokenClaimNames.AUD, Collections.singletonList("client-id"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenValidThenNoErrors() {
|
||||||
|
assertThat(this.validateIdToken()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenIssuerNullThenHasErrors() {
|
||||||
|
this.claims.remove(IdTokenClaimNames.ISS);
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenSubNullThenHasErrors() {
|
||||||
|
this.claims.remove(IdTokenClaimNames.SUB);
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenAudNullThenHasErrors() {
|
||||||
|
this.claims.remove(IdTokenClaimNames.AUD);
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenIssuedAtNullThenHasErrors() {
|
||||||
|
this.issuedAt = null;
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenExpiresAtNullThenHasErrors() {
|
||||||
|
this.expiresAt = null;
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenAudMultipleAndAzpNullThenHasErrors() {
|
||||||
|
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id", "other"));
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenAzpNotClientIdThenHasErrors() {
|
||||||
|
this.claims.put(IdTokenClaimNames.AZP, "other");
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenMultipleAudAzpClientIdThenNoErrors() {
|
||||||
|
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id", "other"));
|
||||||
|
this.claims.put(IdTokenClaimNames.AZP, "client-id");
|
||||||
|
assertThat(this.validateIdToken()).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenMultipleAudAzpNotClientIdThenHasErrors() {
|
||||||
|
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id-1", "client-id-2"));
|
||||||
|
this.claims.put(IdTokenClaimNames.AZP, "other-client");
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenAudNotClientIdThenHasErrors() {
|
||||||
|
this.claims.put(IdTokenClaimNames.AUD, Collections.singletonList("other-client"));
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenExpiredThenHasErrors() {
|
||||||
|
this.issuedAt = Instant.now().minus(Duration.ofMinutes(1));
|
||||||
|
this.expiresAt = this.issuedAt.plus(Duration.ofSeconds(1));
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenIssuedAtWayInFutureThenHasErrors() {
|
||||||
|
this.issuedAt = Instant.now().plus(Duration.ofMinutes(5));
|
||||||
|
this.expiresAt = this.issuedAt.plus(Duration.ofSeconds(1));
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void validateIdTokenWhenExpiresAtBeforeNowThenHasErrors() {
|
||||||
|
this.issuedAt = Instant.now().minusSeconds(10);
|
||||||
|
this.expiresAt = Instant.from(this.issuedAt).plusSeconds(5);
|
||||||
|
assertThat(this.validateIdToken())
|
||||||
|
.hasSize(1)
|
||||||
|
.extracting(OAuth2Error::getErrorCode)
|
||||||
|
.contains("invalid_id_token");
|
||||||
|
}
|
||||||
|
|
||||||
|
private Collection<OAuth2Error> validateIdToken() {
|
||||||
|
Jwt idToken = new Jwt("token123", this.issuedAt, this.expiresAt, this.headers, this.claims);
|
||||||
|
OidcIdTokenValidator validator = new OidcIdTokenValidator(this.registration.build());
|
||||||
|
return validator.validate(idToken).getErrors();
|
||||||
|
}
|
||||||
|
}
|
@ -1,137 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2002-2018 the original author or authors.
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package org.springframework.security.oauth2.client.oidc.authentication;
|
|
||||||
|
|
||||||
import org.junit.Before;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.springframework.security.oauth2.client.registration.ClientRegistration;
|
|
||||||
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
|
|
||||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
|
|
||||||
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
|
|
||||||
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
|
|
||||||
|
|
||||||
import java.time.Duration;
|
|
||||||
import java.time.Instant;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import static org.assertj.core.api.Assertions.assertThatCode;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @author Rob Winch
|
|
||||||
* @since 5.1
|
|
||||||
*/
|
|
||||||
public class OidcTokenValidatorTests {
|
|
||||||
private ClientRegistration.Builder registration = TestClientRegistrations.clientRegistration();
|
|
||||||
|
|
||||||
private Map<String, Object> claims = new HashMap<>();
|
|
||||||
private Instant issuedAt = Instant.now();
|
|
||||||
private Instant expiresAt = Instant.now().plusSeconds(3600);
|
|
||||||
|
|
||||||
@Before
|
|
||||||
public void setup() {
|
|
||||||
this.claims.put(IdTokenClaimNames.ISS, "https://issuer.example.com");
|
|
||||||
this.claims.put(IdTokenClaimNames.SUB, "rob");
|
|
||||||
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenValidThenNoException() {
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.doesNotThrowAnyException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenIssuerNullThenException() {
|
|
||||||
this.claims.remove(IdTokenClaimNames.ISS);
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenSubNullThenException() {
|
|
||||||
this.claims.remove(IdTokenClaimNames.SUB);
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenAudNullThenException() {
|
|
||||||
this.claims.remove(IdTokenClaimNames.AUD);
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenIssuedAtNullThenException() {
|
|
||||||
this.issuedAt = null;
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenExpiresAtNullThenException() {
|
|
||||||
this.expiresAt = null;
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenAudMultipleAndAzpNullThenException() {
|
|
||||||
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id", "other"));
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenAzpNotClientIdThenException() {
|
|
||||||
this.claims.put(IdTokenClaimNames.AZP, "other");
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenMulitpleAudAzpClientIdThenNoException() {
|
|
||||||
this.claims.put(IdTokenClaimNames.AUD, Arrays.asList("client-id", "other"));
|
|
||||||
this.claims.put(IdTokenClaimNames.AZP, "client-id");
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.doesNotThrowAnyException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenExpiredThenException() {
|
|
||||||
this.issuedAt = Instant.now().minus(Duration.ofMinutes(1));
|
|
||||||
this.expiresAt = this.issuedAt.plus(Duration.ofSeconds(1));
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void validateIdTokenWhenIssuedAtWayInFutureThenException() {
|
|
||||||
this.issuedAt = Instant.now().plus(Duration.ofMinutes(5));
|
|
||||||
this.expiresAt = this.issuedAt.plus(Duration.ofSeconds(1));
|
|
||||||
assertThatCode(() -> validateIdToken())
|
|
||||||
.isInstanceOf(OAuth2AuthenticationException.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void validateIdToken() {
|
|
||||||
OidcIdToken token = new OidcIdToken("token123", this.issuedAt, this.expiresAt, this.claims);
|
|
||||||
OidcTokenValidator.validateIdToken(token, this.registration.build());
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user