Extract the ID Token JwtDecoderFactory to enable user customization
This commit ensures that the JwtDecoderFactory is not a private field inside the Oidc authentication provider by extracting this class and giving the possibility to customize the way different providers are validated. Fixes: gh-6379
This commit is contained in:
parent
dd45a49f02
commit
fe5f10e9a2
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -27,11 +27,9 @@ 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;
|
||||||
|
@ -43,16 +41,10 @@ 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.JwtException;
|
||||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
|
||||||
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of an {@link AuthenticationProvider}
|
* An implementation of an {@link AuthenticationProvider}
|
||||||
|
@ -82,10 +74,9 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||||
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
|
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
|
||||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
||||||
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
|
||||||
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
private final OAuth2UserService<OidcUserRequest, OidcUser> userService;
|
||||||
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new DefaultJwtDecoderFactory();
|
private JwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -219,30 +210,4 @@ public class OidcAuthorizationCodeAuthenticationProvider implements Authenticati
|
||||||
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
OidcIdToken idToken = new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims());
|
||||||
return idToken;
|
return idToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultJwtDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
|
|
||||||
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
|
||||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
|
||||||
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(
|
|
||||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
|
||||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
|
||||||
clientRegistration.getRegistrationId() +
|
|
||||||
"'. Check to ensure you have configured the JwkSet URI.",
|
|
||||||
null
|
|
||||||
);
|
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
|
||||||
}
|
|
||||||
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
|
||||||
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
|
||||||
OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
|
|
||||||
new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration));
|
|
||||||
jwtDecoder.setJwtValidator(jwtValidator);
|
|
||||||
return jwtDecoder;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright 2002-2018 the original author or authors.
|
* Copyright 2002-2019 the original author or authors.
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
@ -26,12 +26,10 @@ 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;
|
||||||
|
@ -39,19 +37,14 @@ 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.JwtException;
|
||||||
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
|
|
||||||
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;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.StringUtils;
|
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
|
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
* An implementation of an {@link org.springframework.security.authentication.AuthenticationProvider} for OAuth 2.0 Login,
|
||||||
|
@ -83,7 +76,6 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
private static final String INVALID_STATE_PARAMETER_ERROR_CODE = "invalid_state_parameter";
|
||||||
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
|
private static final String INVALID_REDIRECT_URI_PARAMETER_ERROR_CODE = "invalid_redirect_uri_parameter";
|
||||||
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
private static final String INVALID_ID_TOKEN_ERROR_CODE = "invalid_id_token";
|
||||||
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
|
||||||
|
|
||||||
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
private final ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient;
|
||||||
|
|
||||||
|
@ -91,7 +83,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
|
|
||||||
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities);
|
||||||
|
|
||||||
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new DefaultJwtDecoderFactory();
|
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||||
|
|
||||||
public OidcAuthorizationCodeReactiveAuthenticationManager(
|
public OidcAuthorizationCodeReactiveAuthenticationManager(
|
||||||
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
ReactiveOAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> accessTokenResponseClient,
|
||||||
|
@ -199,30 +191,4 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements
|
||||||
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()));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class DefaultJwtDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
|
||||||
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
|
||||||
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
|
||||||
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
|
||||||
OAuth2Error oauth2Error = new OAuth2Error(
|
|
||||||
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
|
||||||
"Failed to find a Signature Verifier for Client Registration: '" +
|
|
||||||
clientRegistration.getRegistrationId() +
|
|
||||||
"'. Check to ensure you have configured the JwkSet URI.",
|
|
||||||
null
|
|
||||||
);
|
|
||||||
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
|
||||||
}
|
|
||||||
NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(
|
|
||||||
clientRegistration.getProviderDetails().getJwkSetUri());
|
|
||||||
OAuth2TokenValidator<Jwt> jwtValidator = new DelegatingOAuth2TokenValidator<>(
|
|
||||||
new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration));
|
|
||||||
jwtDecoder.setJwtValidator(jwtValidator);
|
|
||||||
return jwtDecoder;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,81 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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.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.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.JwtDecoderFactory;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a default or custom implementation for {@link OAuth2TokenValidator}
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @author Rafael Dominguez
|
||||||
|
* @since 5.2
|
||||||
|
*
|
||||||
|
* @see OAuth2TokenValidator
|
||||||
|
* @see Jwt
|
||||||
|
*/
|
||||||
|
public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
|
||||||
|
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
||||||
|
private final Map<String, JwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||||
|
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = OidcIdTokenValidator::new;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||||
|
Assert.notNull(clientRegistration, "clientRegistration cannot be null.");
|
||||||
|
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||||
|
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||||
|
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||||
|
clientRegistration.getRegistrationId() +
|
||||||
|
"'. Check to ensure you have configured the JwkSet URI.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
|
}
|
||||||
|
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
|
||||||
|
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = jwtValidatorFactory.apply(clientRegistration);
|
||||||
|
jwtDecoder.setJwtValidator(jwtValidator);
|
||||||
|
return jwtDecoder;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user customization for the {@link OAuth2TokenValidator}
|
||||||
|
*
|
||||||
|
* @param jwtValidatorFactory
|
||||||
|
*/
|
||||||
|
public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
|
||||||
|
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null.");
|
||||||
|
this.jwtValidatorFactory = jwtValidatorFactory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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.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.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||||
|
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
|
||||||
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides a default or custom reactive implementation for {@link OAuth2TokenValidator}
|
||||||
|
*
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @author Rafael Dominguez
|
||||||
|
* @since 5.2
|
||||||
|
*
|
||||||
|
* @see OAuth2TokenValidator
|
||||||
|
* @see Jwt
|
||||||
|
*/
|
||||||
|
public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
|
||||||
|
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
|
||||||
|
private final Map<String, ReactiveJwtDecoder> jwtDecoders = new ConcurrentHashMap<>();
|
||||||
|
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory = OidcIdTokenValidator::new;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
|
||||||
|
Assert.notNull(clientRegistration, "clientRegistration cannot be null.");
|
||||||
|
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
|
||||||
|
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
|
||||||
|
OAuth2Error oauth2Error = new OAuth2Error(
|
||||||
|
MISSING_SIGNATURE_VERIFIER_ERROR_CODE,
|
||||||
|
"Failed to find a Signature Verifier for Client Registration: '" +
|
||||||
|
clientRegistration.getRegistrationId() +
|
||||||
|
"'. Check to ensure you have configured the JwkSet URI.",
|
||||||
|
null
|
||||||
|
);
|
||||||
|
throw new OAuth2AuthenticationException(oauth2Error, oauth2Error.toString());
|
||||||
|
}
|
||||||
|
NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(
|
||||||
|
clientRegistration.getProviderDetails().getJwkSetUri());
|
||||||
|
OAuth2TokenValidator<Jwt> jwtValidator = jwtValidatorFactory.apply(clientRegistration);
|
||||||
|
jwtDecoder.setJwtValidator(jwtValidator);
|
||||||
|
return jwtDecoder;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Allows user customization for the {@link OAuth2TokenValidator}
|
||||||
|
*
|
||||||
|
* @param jwtValidatorFactory
|
||||||
|
*/
|
||||||
|
public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
|
||||||
|
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null.");
|
||||||
|
this.jwtValidatorFactory = jwtValidatorFactory;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @author Rafael Dominguez
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public class OidcIdTokenDecoderFactoryTests {
|
||||||
|
|
||||||
|
private ClientRegistration.Builder registration = TestClientRegistrations.clientRegistration()
|
||||||
|
.scope("openid");
|
||||||
|
|
||||||
|
private OidcIdTokenDecoderFactory idTokenDecoderFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException(){
|
||||||
|
assertThatThrownBy(()-> idTokenDecoderFactory.setJwtValidatorFactory(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException(){
|
||||||
|
assertThatThrownBy(() -> idTokenDecoderFactory.createDecoder(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException(){
|
||||||
|
assertThatThrownBy(()-> idTokenDecoderFactory.createDecoder(registration.jwkSetUri(null).build()))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenClientRegistrationValidThenReturnDecoder(){
|
||||||
|
assertThat(idTokenDecoderFactory.createDecoder(registration.build()))
|
||||||
|
.isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied(){
|
||||||
|
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customValidator = mock(Function.class);
|
||||||
|
idTokenDecoderFactory.setJwtValidatorFactory(customValidator);
|
||||||
|
|
||||||
|
when(customValidator.apply(any(ClientRegistration.class)))
|
||||||
|
.thenReturn(customJwtValidatorFactory.apply(registration.build()));
|
||||||
|
|
||||||
|
idTokenDecoderFactory.createDecoder(registration.build());
|
||||||
|
|
||||||
|
verify(customValidator).apply(any(ClientRegistration.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customJwtValidatorFactory = (c) -> {
|
||||||
|
OidcIdTokenValidator idTokenValidator = new OidcIdTokenValidator(c);
|
||||||
|
if (c.getRegistrationId().equals("registration-id1")) {
|
||||||
|
idTokenValidator.setClockSkew(Duration.ofSeconds(30));
|
||||||
|
} else if (c.getRegistrationId().equals("registration-id2")) {
|
||||||
|
idTokenValidator.setClockSkew(Duration.ofSeconds(70));
|
||||||
|
}
|
||||||
|
return idTokenValidator;
|
||||||
|
};
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2002-2019 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.OAuth2TokenValidator;
|
||||||
|
import org.springframework.security.oauth2.jwt.Jwt;
|
||||||
|
|
||||||
|
import java.time.Duration;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
||||||
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.Mockito.mock;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author Joe Grandja
|
||||||
|
* @author Rafael Dominguez
|
||||||
|
* @since 5.2
|
||||||
|
*/
|
||||||
|
public class ReactiveOidcIdTokenDecoderFactoryTests {
|
||||||
|
|
||||||
|
private ClientRegistration.Builder registration = TestClientRegistrations.clientRegistration()
|
||||||
|
.scope("openid");
|
||||||
|
|
||||||
|
private ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() {
|
||||||
|
idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException(){
|
||||||
|
assertThatThrownBy(()-> idTokenDecoderFactory.setJwtValidatorFactory(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException(){
|
||||||
|
assertThatThrownBy(() -> idTokenDecoderFactory.createDecoder(null))
|
||||||
|
.isInstanceOf(IllegalArgumentException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException(){
|
||||||
|
assertThatThrownBy(()-> idTokenDecoderFactory.createDecoder(registration.jwkSetUri(null).build()))
|
||||||
|
.isInstanceOf(OAuth2AuthenticationException.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenClientRegistrationValidThenReturnDecoder(){
|
||||||
|
assertThat(idTokenDecoderFactory.createDecoder(registration.build()))
|
||||||
|
.isNotNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied(){
|
||||||
|
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customValidator = mock(Function.class);
|
||||||
|
idTokenDecoderFactory.setJwtValidatorFactory(customValidator);
|
||||||
|
|
||||||
|
when(customValidator.apply(any(ClientRegistration.class)))
|
||||||
|
.thenReturn(customJwtValidatorFactory.apply(registration.build()));
|
||||||
|
|
||||||
|
idTokenDecoderFactory.createDecoder(registration.build());
|
||||||
|
|
||||||
|
verify(customValidator).apply(any(ClientRegistration.class));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customJwtValidatorFactory = (c) -> {
|
||||||
|
OidcIdTokenValidator idTokenValidator = new OidcIdTokenValidator(c);
|
||||||
|
if (c.getRegistrationId().equals("registration-id1")) {
|
||||||
|
idTokenValidator.setClockSkew(Duration.ofSeconds(30));
|
||||||
|
} else if (c.getRegistrationId().equals("registration-id2")) {
|
||||||
|
idTokenValidator.setClockSkew(Duration.ofSeconds(70));
|
||||||
|
}
|
||||||
|
return idTokenValidator;
|
||||||
|
};
|
||||||
|
}
|
Loading…
Reference in New Issue