Polish gh-6415

This commit is contained in:
Joe Grandja 2019-01-14 13:11:52 -05:00
parent fe5f10e9a2
commit 2a867997e2
6 changed files with 67 additions and 79 deletions

View File

@ -66,6 +66,7 @@ import java.util.Map;
* @see OAuth2AccessTokenResponseClient * @see OAuth2AccessTokenResponseClient
* @see OidcUserService * @see OidcUserService
* @see OidcUser * @see OidcUser
* @see OidcIdTokenDecoderFactory
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 Authorization Code Grant Flow</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#CodeFlowAuth">Section 3.1 Authorization Code Grant Flow</a>
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1 Token Request</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenRequest">Section 3.1.3.1 Token Request</a>
* @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 Token Response</a> * @see <a target="_blank" href="http://openid.net/specs/openid-connect-core-1_0.html#TokenResponse">Section 3.1.3.3 Token Response</a>

View File

@ -66,6 +66,7 @@ import java.util.Map;
* @see ReactiveOAuth2AccessTokenResponseClient * @see ReactiveOAuth2AccessTokenResponseClient
* @see ReactiveOAuth2UserService * @see ReactiveOAuth2UserService
* @see OAuth2User * @see OAuth2User
* @see ReactiveOidcIdTokenDecoderFactory
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1">Section 4.1 Authorization Code Grant Flow</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.3">Section 4.1.3 Access Token Request</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a> * @see <a target="_blank" href="https://tools.ietf.org/html/rfc6749#section-4.1.4">Section 4.1.4 Access Token Response</a>

View File

@ -19,6 +19,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 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.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
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;
@ -33,14 +34,16 @@ import java.util.function.Function;
import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri;
/** /**
* Provides a default or custom implementation for {@link OAuth2TokenValidator} * A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder}
* used for {@link OidcIdToken} signature verification.
* The provided {@link JwtDecoder} is associated to a specific {@link ClientRegistration}.
* *
* @author Joe Grandja * @author Joe Grandja
* @author Rafael Dominguez * @author Rafael Dominguez
* @since 5.2 * @since 5.2
* * @see JwtDecoderFactory
* @see OAuth2TokenValidator * @see ClientRegistration
* @see Jwt * @see OidcIdToken
*/ */
public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> { public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<ClientRegistration> {
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
@ -49,7 +52,7 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
@Override @Override
public JwtDecoder createDecoder(ClientRegistration clientRegistration) { public JwtDecoder createDecoder(ClientRegistration clientRegistration) {
Assert.notNull(clientRegistration, "clientRegistration cannot be null."); Assert.notNull(clientRegistration, "clientRegistration cannot be null");
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> { return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) { if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
OAuth2Error oauth2Error = new OAuth2Error( OAuth2Error oauth2Error = new OAuth2Error(
@ -63,19 +66,20 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory<Client
} }
String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri(); String jwkSetUri = clientRegistration.getProviderDetails().getJwkSetUri();
NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build()); NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withJwkSetUri(jwkSetUri).build());
OAuth2TokenValidator<Jwt> jwtValidator = jwtValidatorFactory.apply(clientRegistration); OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration);
jwtDecoder.setJwtValidator(jwtValidator); jwtDecoder.setJwtValidator(jwtValidator);
return jwtDecoder; return jwtDecoder;
}); });
} }
/** /**
* Allows user customization for the {@link OAuth2TokenValidator} * Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by the {@link JwtDecoder}.
* The default is {@link OidcIdTokenValidator}.
* *
* @param jwtValidatorFactory * @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator}
*/ */
public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) { public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null."); Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
this.jwtValidatorFactory = jwtValidatorFactory; this.jwtValidatorFactory = jwtValidatorFactory;
} }
} }

View File

@ -19,6 +19,7 @@ import org.springframework.security.oauth2.client.registration.ClientRegistratio
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; 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.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
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;
@ -31,14 +32,16 @@ import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function; import java.util.function.Function;
/** /**
* Provides a default or custom reactive implementation for {@link OAuth2TokenValidator} * A {@link ReactiveJwtDecoderFactory factory} that provides a {@link ReactiveJwtDecoder}
* used for {@link OidcIdToken} signature verification.
* The provided {@link ReactiveJwtDecoder} is associated to a specific {@link ClientRegistration}.
* *
* @author Joe Grandja * @author Joe Grandja
* @author Rafael Dominguez * @author Rafael Dominguez
* @since 5.2 * @since 5.2
* * @see ReactiveJwtDecoderFactory
* @see OAuth2TokenValidator * @see ClientRegistration
* @see Jwt * @see OidcIdToken
*/ */
public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> { public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecoderFactory<ClientRegistration> {
private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier"; private static final String MISSING_SIGNATURE_VERIFIER_ERROR_CODE = "missing_signature_verifier";
@ -47,7 +50,7 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
@Override @Override
public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) { public ReactiveJwtDecoder createDecoder(ClientRegistration clientRegistration) {
Assert.notNull(clientRegistration, "clientRegistration cannot be null."); Assert.notNull(clientRegistration, "clientRegistration cannot be null");
return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> { return this.jwtDecoders.computeIfAbsent(clientRegistration.getRegistrationId(), key -> {
if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) { if (!StringUtils.hasText(clientRegistration.getProviderDetails().getJwkSetUri())) {
OAuth2Error oauth2Error = new OAuth2Error( OAuth2Error oauth2Error = new OAuth2Error(
@ -61,19 +64,20 @@ public final class ReactiveOidcIdTokenDecoderFactory implements ReactiveJwtDecod
} }
NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder( NimbusReactiveJwtDecoder jwtDecoder = new NimbusReactiveJwtDecoder(
clientRegistration.getProviderDetails().getJwkSetUri()); clientRegistration.getProviderDetails().getJwkSetUri());
OAuth2TokenValidator<Jwt> jwtValidator = jwtValidatorFactory.apply(clientRegistration); OAuth2TokenValidator<Jwt> jwtValidator = this.jwtValidatorFactory.apply(clientRegistration);
jwtDecoder.setJwtValidator(jwtValidator); jwtDecoder.setJwtValidator(jwtValidator);
return jwtDecoder; return jwtDecoder;
}); });
} }
/** /**
* Allows user customization for the {@link OAuth2TokenValidator} * Sets the factory that provides an {@link OAuth2TokenValidator}, which is used by the {@link ReactiveJwtDecoder}.
* The default is {@link OidcIdTokenValidator}.
* *
* @param jwtValidatorFactory * @param jwtValidatorFactory the factory that provides an {@link OAuth2TokenValidator}
*/ */
public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) { public final void setJwtValidatorFactory(Function<ClientRegistration, OAuth2TokenValidator<Jwt>> jwtValidatorFactory) {
Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null."); Assert.notNull(jwtValidatorFactory, "jwtValidatorFactory cannot be null");
this.jwtValidatorFactory = jwtValidatorFactory; this.jwtValidatorFactory = jwtValidatorFactory;
} }
} }

View File

@ -23,15 +23,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import java.time.Duration;
import java.util.function.Function; import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/** /**
* @author Joe Grandja * @author Joe Grandja
@ -45,55 +42,47 @@ public class OidcIdTokenDecoderFactoryTests {
private OidcIdTokenDecoderFactory idTokenDecoderFactory; private OidcIdTokenDecoderFactory idTokenDecoderFactory;
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> defaultJwtValidatorFactory = OidcIdTokenValidator::new;
@Before @Before
public void setUp() { public void setUp() {
idTokenDecoderFactory = new OidcIdTokenDecoderFactory(); this.idTokenDecoderFactory = new OidcIdTokenDecoderFactory();
} }
@Test @Test
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException(){ public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(()-> idTokenDecoderFactory.setJwtValidatorFactory(null)) assertThatThrownBy(() -> this.idTokenDecoderFactory.setJwtValidatorFactory(null))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }
@Test @Test
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException(){ public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> idTokenDecoderFactory.createDecoder(null)) assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(null))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }
@Test @Test
public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException(){ public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException() {
assertThatThrownBy(()-> idTokenDecoderFactory.createDecoder(registration.jwkSetUri(null).build())) assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(this.registration.jwkSetUri(null).build()))
.isInstanceOf(OAuth2AuthenticationException.class); .isInstanceOf(OAuth2AuthenticationException.class);
} }
@Test @Test
public void createDecoderWhenClientRegistrationValidThenReturnDecoder(){ public void createDecoderWhenClientRegistrationValidThenReturnDecoder() {
assertThat(idTokenDecoderFactory.createDecoder(registration.build())) assertThat(this.idTokenDecoderFactory.createDecoder(this.registration.build()))
.isNotNull(); .isNotNull();
} }
@Test @Test
public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied(){ public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied() {
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customValidator = mock(Function.class); Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customJwtValidatorFactory = mock(Function.class);
idTokenDecoderFactory.setJwtValidatorFactory(customValidator); this.idTokenDecoderFactory.setJwtValidatorFactory(customJwtValidatorFactory);
when(customValidator.apply(any(ClientRegistration.class))) when(customJwtValidatorFactory.apply(any(ClientRegistration.class)))
.thenReturn(customJwtValidatorFactory.apply(registration.build())); .thenReturn(this.defaultJwtValidatorFactory.apply(this.registration.build()));
idTokenDecoderFactory.createDecoder(registration.build()); this.idTokenDecoderFactory.createDecoder(this.registration.build());
verify(customValidator).apply(any(ClientRegistration.class)); verify(customJwtValidatorFactory).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;
};
} }

View File

@ -23,15 +23,12 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.Jwt;
import java.time.Duration;
import java.util.function.Function; import java.util.function.Function;
import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock; import static org.mockito.Mockito.*;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
/** /**
* @author Joe Grandja * @author Joe Grandja
@ -45,55 +42,47 @@ public class ReactiveOidcIdTokenDecoderFactoryTests {
private ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory; private ReactiveOidcIdTokenDecoderFactory idTokenDecoderFactory;
private Function<ClientRegistration, OAuth2TokenValidator<Jwt>> defaultJwtValidatorFactory = OidcIdTokenValidator::new;
@Before @Before
public void setUp() { public void setUp() {
idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory(); this.idTokenDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
} }
@Test @Test
public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException(){ public void setJwtValidatorFactoryWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(()-> idTokenDecoderFactory.setJwtValidatorFactory(null)) assertThatThrownBy(() -> this.idTokenDecoderFactory.setJwtValidatorFactory(null))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }
@Test @Test
public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException(){ public void createDecoderWhenClientRegistrationNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> idTokenDecoderFactory.createDecoder(null)) assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(null))
.isInstanceOf(IllegalArgumentException.class); .isInstanceOf(IllegalArgumentException.class);
} }
@Test @Test
public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException(){ public void createDecoderWhenJwkSetUriEmptyThenThrowOAuth2AuthenticationException() {
assertThatThrownBy(()-> idTokenDecoderFactory.createDecoder(registration.jwkSetUri(null).build())) assertThatThrownBy(() -> this.idTokenDecoderFactory.createDecoder(this.registration.jwkSetUri(null).build()))
.isInstanceOf(OAuth2AuthenticationException.class); .isInstanceOf(OAuth2AuthenticationException.class);
} }
@Test @Test
public void createDecoderWhenClientRegistrationValidThenReturnDecoder(){ public void createDecoderWhenClientRegistrationValidThenReturnDecoder() {
assertThat(idTokenDecoderFactory.createDecoder(registration.build())) assertThat(this.idTokenDecoderFactory.createDecoder(this.registration.build()))
.isNotNull(); .isNotNull();
} }
@Test @Test
public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied(){ public void createDecoderWhenCustomJwtValidatorFactorySetThenApplied() {
Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customValidator = mock(Function.class); Function<ClientRegistration, OAuth2TokenValidator<Jwt>> customJwtValidatorFactory = mock(Function.class);
idTokenDecoderFactory.setJwtValidatorFactory(customValidator); this.idTokenDecoderFactory.setJwtValidatorFactory(customJwtValidatorFactory);
when(customValidator.apply(any(ClientRegistration.class))) when(customJwtValidatorFactory.apply(any(ClientRegistration.class)))
.thenReturn(customJwtValidatorFactory.apply(registration.build())); .thenReturn(this.defaultJwtValidatorFactory.apply(this.registration.build()));
idTokenDecoderFactory.createDecoder(registration.build()); this.idTokenDecoderFactory.createDecoder(this.registration.build());
verify(customValidator).apply(any(ClientRegistration.class)); verify(customJwtValidatorFactory).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;
};
} }