From fe5f10e9a221e307c37f787717cd66dccbe068bf Mon Sep 17 00:00:00 2001 From: Rafael Dominguez <5624449+raphaelDL@users.noreply.github.com> Date: Thu, 10 Jan 2019 09:02:47 -0600 Subject: [PATCH] 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 --- ...thorizationCodeAuthenticationProvider.java | 39 +------- ...tionCodeReactiveAuthenticationManager.java | 38 +------ .../OidcIdTokenDecoderFactory.java | 81 +++++++++++++++ .../ReactiveOidcIdTokenDecoderFactory.java | 79 +++++++++++++++ .../OidcIdTokenDecoderFactoryTests.java | 99 +++++++++++++++++++ ...eactiveOidcIdTokenDecoderFactoryTests.java | 99 +++++++++++++++++++ 6 files changed, 362 insertions(+), 73 deletions(-) create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java create mode 100644 oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactoryTests.java create mode 100644 oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java index 30f5f8a652..8ed7f8a31f 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeAuthenticationProvider.java @@ -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"); * 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.registration.ClientRegistration; 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.OAuth2AuthorizationException; 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.OAuth2AuthorizationRequest; 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.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.util.Assert; -import org.springframework.util.StringUtils; import java.util.Collection; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; /** * 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_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 MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; private final OAuth2AccessTokenResponseClient accessTokenResponseClient; private final OAuth2UserService userService; - private JwtDecoderFactory jwtDecoderFactory = new DefaultJwtDecoderFactory(); + private JwtDecoderFactory jwtDecoderFactory = new OidcIdTokenDecoderFactory(); 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()); return idToken; } - - private static class DefaultJwtDecoderFactory implements JwtDecoderFactory { - private final Map 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 jwtValidator = new DelegatingOAuth2TokenValidator<>( - new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration)); - jwtDecoder.setJwtValidator(jwtValidator); - return jwtDecoder; - }); - } - } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java index dd811f7e47..a5669f28e1 100644 --- a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcAuthorizationCodeReactiveAuthenticationManager.java @@ -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"); * 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.registration.ClientRegistration; 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.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2AuthorizationException; 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.OAuth2AuthorizationRequest; 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.user.OidcUser; 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.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import reactor.core.publisher.Mono; import java.util.Collection; import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; /** * 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_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 MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; private final ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient; @@ -91,7 +83,7 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements private GrantedAuthoritiesMapper authoritiesMapper = (authorities -> authorities); - private ReactiveJwtDecoderFactory jwtDecoderFactory = new DefaultJwtDecoderFactory(); + private ReactiveJwtDecoderFactory jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory(); public OidcAuthorizationCodeReactiveAuthenticationManager( ReactiveOAuth2AccessTokenResponseClient accessTokenResponseClient, @@ -199,30 +191,4 @@ public class OidcAuthorizationCodeReactiveAuthenticationManager implements return jwtDecoder.decode(rawIdToken) .map(jwt -> new OidcIdToken(jwt.getTokenValue(), jwt.getIssuedAt(), jwt.getExpiresAt(), jwt.getClaims())); } - - private static class DefaultJwtDecoderFactory implements ReactiveJwtDecoderFactory { - private final Map 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 jwtValidator = new DelegatingOAuth2TokenValidator<>( - new JwtTimestampValidator(), new OidcIdTokenValidator(clientRegistration)); - jwtDecoder.setJwtValidator(jwtValidator); - return jwtDecoder; - }); - } - } } diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java new file mode 100644 index 0000000000..4463121970 --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactory.java @@ -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 { + private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; + private final Map jwtDecoders = new ConcurrentHashMap<>(); + private Function> 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 jwtValidator = jwtValidatorFactory.apply(clientRegistration); + jwtDecoder.setJwtValidator(jwtValidator); + return jwtDecoder; + }); + } + + /** + * Allows user customization for the {@link OAuth2TokenValidator} + * + * @param jwtValidatorFactory + */ + public final void setJwtValidatorFactory(Function> jwtValidatorFactory) { + Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null."); + this.jwtValidatorFactory = jwtValidatorFactory; + } +} diff --git a/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java new file mode 100644 index 0000000000..b3da55282e --- /dev/null +++ b/oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactory.java @@ -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 { + private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; + private final Map jwtDecoders = new ConcurrentHashMap<>(); + private Function> 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 jwtValidator = jwtValidatorFactory.apply(clientRegistration); + jwtDecoder.setJwtValidator(jwtValidator); + return jwtDecoder; + }); + } + + /** + * Allows user customization for the {@link OAuth2TokenValidator} + * + * @param jwtValidatorFactory + */ + public final void setJwtValidatorFactory(Function> jwtValidatorFactory) { + Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null."); + this.jwtValidatorFactory = jwtValidatorFactory; + } +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactoryTests.java new file mode 100644 index 0000000000..707230b2e1 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/OidcIdTokenDecoderFactoryTests.java @@ -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> 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> 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; + }; +} diff --git a/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java new file mode 100644 index 0000000000..1c80773b63 --- /dev/null +++ b/oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/oidc/authentication/ReactiveOidcIdTokenDecoderFactoryTests.java @@ -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> 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> 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; + }; +}