Browse Source
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-6379pull/6441/head
6 changed files with 362 additions and 73 deletions
@ -0,0 +1,81 @@
@@ -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 @@
@@ -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 @@
@@ -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 @@
@@ -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