diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 7657787dad..6bd8f8e301 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -45,7 +45,7 @@ import org.springframework.security.web.access.AccessDeniedHandler; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; /** * @@ -246,7 +246,7 @@ public final class OAuth2ResourceServerConfigurer jwtProcessor = - JwtProcessors.withJwkSetUri("https://example.org/.well-known/jwks.json") - .restOperations(this.rest).build(); - return new NimbusJwtDecoder(jwtProcessor); + return withJwkSetUri("https://example.org/.well-known/jwks.json") + .restOperations(this.rest).build(); } } 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 index f50dd5b9de..d327f006c4 100644 --- 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 @@ -15,6 +15,10 @@ */ package org.springframework.security.oauth2.client.oidc.authentication; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; + import org.springframework.security.oauth2.client.registration.ClientRegistration; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.core.OAuth2Error; @@ -27,11 +31,7 @@ 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; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; /** * A {@link JwtDecoderFactory factory} that provides a {@link JwtDecoder} @@ -65,7 +65,7 @@ public final class OidcIdTokenDecoderFactory implements JwtDecoderFactory jwtValidator = this.jwtValidatorFactory.apply(clientRegistration); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java index fb27e0976e..00a910bdf6 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtDecoders.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. @@ -24,7 +24,7 @@ import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.web.client.RestTemplate; import org.springframework.web.util.UriComponentsBuilder; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; /** * Allows creating a {@link JwtDecoder} from an @@ -60,8 +60,7 @@ public final class JwtDecoders { OAuth2TokenValidator jwtValidator = JwtValidators.createDefaultWithIssuer(oidcIssuerLocation); - NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder( - withJwkSetUri(openidConfiguration.get("jwks_uri").toString()).build()); + NimbusJwtDecoder jwtDecoder = withJwkSetUri(openidConfiguration.get("jwks_uri").toString()).build(); jwtDecoder.setJwtValidator(jwtValidator); return jwtDecoder; diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtProcessors.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtProcessors.java deleted file mode 100644 index 04b0967fe5..0000000000 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/JwtProcessors.java +++ /dev/null @@ -1,238 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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.jwt; - -import java.io.IOException; -import java.net.MalformedURLException; -import java.net.URL; -import java.security.interfaces.RSAPublicKey; -import java.util.Collections; - -import com.nimbusds.jose.JWSAlgorithm; -import com.nimbusds.jose.jwk.JWKSet; -import com.nimbusds.jose.jwk.RSAKey; -import com.nimbusds.jose.jwk.source.ImmutableJWKSet; -import com.nimbusds.jose.jwk.source.JWKSource; -import com.nimbusds.jose.jwk.source.RemoteJWKSet; -import com.nimbusds.jose.proc.JWSKeySelector; -import com.nimbusds.jose.proc.JWSVerificationKeySelector; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.jose.util.Resource; -import com.nimbusds.jose.util.ResourceRetriever; -import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; -import com.nimbusds.jwt.proc.DefaultJWTProcessor; -import com.nimbusds.jwt.proc.JWTProcessor; - -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.util.Assert; -import org.springframework.web.client.RestOperations; -import org.springframework.web.client.RestTemplate; - -/** - * A collection of builders for creating Nimbus {@link JWTProcessor} instances. - * - * @author Josh Cummings - * @since 5.2 - * @see NimbusJwtDecoder - */ -public final class JwtProcessors { - - /** - * Use the given - * JWK Set uri. - * - * @param jwkSetUri the JWK Set uri to use - * @return a {@link JwtProcessors} for further configurations - */ - public static JwkSetUriJwtProcessorBuilder withJwkSetUri(String jwkSetUri) { - return new JwkSetUriJwtProcessorBuilder(jwkSetUri); - } - - /** - * Use the given public key to validate JWTs - * - * @param key the public key to use - * @return a {@link PublicKeyJwtProcessorBuilder} for further configurations - */ - public static PublicKeyJwtProcessorBuilder withPublicKey(RSAPublicKey key) { - return new PublicKeyJwtProcessorBuilder(key); - } - - /** - * A builder for creating Nimbus {@link JWTProcessor} instances based on a - * JWK Set uri. - */ - public static final class JwkSetUriJwtProcessorBuilder { - private String jwkSetUri; - private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; - private RestOperations restOperations = new RestTemplate(); - - private JwkSetUriJwtProcessorBuilder(String jwkSetUri) { - Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); - this.jwkSetUri = jwkSetUri; - } - - /** - * Use the given signing - * algorithm. - * - * @param jwsAlgorithm the algorithm to use - * @return a {@link JwtProcessors} for further configurations - */ - public JwkSetUriJwtProcessorBuilder jwsAlgorithm(String jwsAlgorithm) { - Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); - this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm); - return this; - } - - /** - * Use the given {@link RestOperations} to coordinate with the authorization servers indicated in the - * JWK Set uri - * as well as the - * Issuer. - * - * @param restOperations - * @return - */ - public JwkSetUriJwtProcessorBuilder restOperations(RestOperations restOperations) { - Assert.notNull(restOperations, "restOperations cannot be null"); - this.restOperations = restOperations; - return this; - } - - /** - * Build the configured {@link JwtDecoder}. - * - * @return the configured {@link JwtDecoder} - */ - public JWTProcessor build() { - ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations); - JWKSource jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever); - JWSKeySelector jwsKeySelector = - new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource); - ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); - jwtProcessor.setJWSKeySelector(jwsKeySelector); - - // Spring Security validates the claim set independent from Nimbus - jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); - - return jwtProcessor; - } - - private static URL toURL(String url) { - try { - return new URL(url); - } catch (MalformedURLException ex) { - throw new IllegalArgumentException("Invalid JWK Set URL \"" + url + "\" : " + ex.getMessage(), ex); - } - } - - private static class RestOperationsResourceRetriever implements ResourceRetriever { - private final RestOperations restOperations; - - RestOperationsResourceRetriever(RestOperations restOperations) { - Assert.notNull(restOperations, "restOperations cannot be null"); - this.restOperations = restOperations; - } - - @Override - public Resource retrieveResource(URL url) throws IOException { - HttpHeaders headers = new HttpHeaders(); - headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); - - ResponseEntity response; - try { - RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI()); - response = this.restOperations.exchange(request, String.class); - } catch (Exception ex) { - throw new IOException(ex); - } - - if (response.getStatusCodeValue() != 200) { - throw new IOException(response.toString()); - } - - return new Resource(response.getBody(), "UTF-8"); - } - } - } - - /** - * A builder for creating Nimbus {@link JWTProcessor} instances based on a - * public key. - */ - public static final class PublicKeyJwtProcessorBuilder { - private JWSAlgorithm jwsAlgorithm; - private RSAKey key; - - private PublicKeyJwtProcessorBuilder(RSAPublicKey key) { - Assert.notNull(key, "key cannot be null"); - this.jwsAlgorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256); - this.key = rsaKey(key); - } - - private static RSAKey rsaKey(RSAPublicKey publicKey) { - return new RSAKey.Builder(publicKey) - .build(); - } - - /** - * Use the given signing - * algorithm. - * - * The value should be one of - * RS256, RS384, or RS512. - * - * @param jwsAlgorithm the algorithm to use - * @return a {@link JwtProcessors} for further configurations - */ - public PublicKeyJwtProcessorBuilder jwsAlgorithm(String jwsAlgorithm) { - Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); - this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm); - return this; - } - - /** - * Build the configured {@link JWTProcessor}. - *mzRC - * @return the configured {@link JWTProcessor} - */ - public JWTProcessor build() { - if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) { - throw new IllegalStateException("The provided key is of type RSA; " + - "however the signature algorithm is of some other type: " + - this.jwsAlgorithm + ". Please indicate one of RS256, RS384, or RS512."); - } - - JWKSet jwkSet = new JWKSet(this.key); - JWKSource jwkSource = new ImmutableJWKSet<>(jwkSet); - JWSKeySelector jwsKeySelector = - new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource); - DefaultJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); - jwtProcessor.setJWSKeySelector(jwsKeySelector); - - // Spring Security validates the claim set independent from Nimbus - jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); - - return jwtProcessor; - } - } -} diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java index 161d32e5d5..1254636210 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoder.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. @@ -16,37 +16,54 @@ package org.springframework.security.oauth2.jwt; +import java.io.IOException; +import java.net.MalformedURLException; +import java.net.URL; +import java.security.interfaces.RSAPublicKey; import java.text.ParseException; import java.time.Instant; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; +import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.RemoteKeySourceException; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.jwk.source.RemoteJWKSet; +import com.nimbusds.jose.proc.JWSKeySelector; +import com.nimbusds.jose.proc.JWSVerificationKeySelector; import com.nimbusds.jose.proc.SecurityContext; +import com.nimbusds.jose.util.Resource; +import com.nimbusds.jose.util.ResourceRetriever; import com.nimbusds.jwt.JWT; import com.nimbusds.jwt.JWTClaimsSet; import com.nimbusds.jwt.JWTParser; import com.nimbusds.jwt.SignedJWT; +import com.nimbusds.jwt.proc.ConfigurableJWTProcessor; +import com.nimbusds.jwt.proc.DefaultJWTProcessor; import com.nimbusds.jwt.proc.JWTProcessor; import org.springframework.core.convert.converter.Converter; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; +import org.springframework.http.MediaType; +import org.springframework.http.RequestEntity; +import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.util.Assert; +import org.springframework.web.client.RestOperations; +import org.springframework.web.client.RestTemplate; /** * A low-level Nimbus implementation of {@link JwtDecoder} which takes a raw Nimbus configuration. * - * It's simple to produce an instance of {@code JWTProcessor} using {@link JwtProcessors}: - *
- * 	JWTProcessor<SecurityContext> jwtProcessor = JwtProcessors.fromJwkSetUri(uri).build();
- * 	NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(jwtProcessor);
- * 
- * * @author Josh Cummings * @since 5.2 - * @see JwtProcessors */ public final class NimbusJwtDecoder implements JwtDecoder { private static final String DECODING_ERROR_MESSAGE_TEMPLATE = @@ -154,4 +171,201 @@ public final class NimbusJwtDecoder implements JwtDecoder { return jwt; } + + /** + * Use the given + * JWK Set uri. + * + * @param jwkSetUri the JWK Set uri to use + * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations + * + * @since 5.2 + */ + public static JwkSetUriJwtDecoderBuilder withJwkSetUri(String jwkSetUri) { + return new JwkSetUriJwtDecoderBuilder(jwkSetUri); + } + + /** + * Use the given public key to validate JWTs + * + * @param key the public key to use + * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations + * + * @since 5.2 + */ + public static PublicKeyJwtDecoderBuilder withPublicKey(RSAPublicKey key) { + return new PublicKeyJwtDecoderBuilder(key); + } + + /** + * A builder for creating {@link NimbusJwtDecoder} instances based on a + * JWK Set uri. + * + * @since 5.2 + */ + public static final class JwkSetUriJwtDecoderBuilder { + private String jwkSetUri; + private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256; + private RestOperations restOperations = new RestTemplate(); + + private JwkSetUriJwtDecoderBuilder(String jwkSetUri) { + Assert.hasText(jwkSetUri, "jwkSetUri cannot be empty"); + this.jwkSetUri = jwkSetUri; + } + + /** + * Use the given signing + * algorithm. + * + * @param jwsAlgorithm the algorithm to use + * @return a {@link JwkSetUriJwtDecoderBuilder} for further configurations + */ + public JwkSetUriJwtDecoderBuilder jwsAlgorithm(String jwsAlgorithm) { + Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); + this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm); + return this; + } + + /** + * Use the given {@link RestOperations} to coordinate with the authorization servers indicated in the + * JWK Set uri + * as well as the + * Issuer. + * + * @param restOperations + * @return + */ + public JwkSetUriJwtDecoderBuilder restOperations(RestOperations restOperations) { + Assert.notNull(restOperations, "restOperations cannot be null"); + this.restOperations = restOperations; + return this; + } + + JWTProcessor processor() { + ResourceRetriever jwkSetRetriever = new RestOperationsResourceRetriever(this.restOperations); + JWKSource jwkSource = new RemoteJWKSet<>(toURL(this.jwkSetUri), jwkSetRetriever); + JWSKeySelector jwsKeySelector = + new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource); + ConfigurableJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); + jwtProcessor.setJWSKeySelector(jwsKeySelector); + + // Spring Security validates the claim set independent from Nimbus + jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + + return jwtProcessor; + } + + /** + * Build the configured {@link NimbusJwtDecoder}. + * + * @return the configured {@link NimbusJwtDecoder} + */ + public NimbusJwtDecoder build() { + return new NimbusJwtDecoder(processor()); + } + + private static URL toURL(String url) { + try { + return new URL(url); + } catch (MalformedURLException ex) { + throw new IllegalArgumentException("Invalid JWK Set URL \"" + url + "\" : " + ex.getMessage(), ex); + } + } + + private static class RestOperationsResourceRetriever implements ResourceRetriever { + private final RestOperations restOperations; + + RestOperationsResourceRetriever(RestOperations restOperations) { + Assert.notNull(restOperations, "restOperations cannot be null"); + this.restOperations = restOperations; + } + + @Override + public Resource retrieveResource(URL url) throws IOException { + HttpHeaders headers = new HttpHeaders(); + headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON_UTF8)); + + ResponseEntity response; + try { + RequestEntity request = new RequestEntity<>(headers, HttpMethod.GET, url.toURI()); + response = this.restOperations.exchange(request, String.class); + } catch (Exception ex) { + throw new IOException(ex); + } + + if (response.getStatusCodeValue() != 200) { + throw new IOException(response.toString()); + } + + return new Resource(response.getBody(), "UTF-8"); + } + } + } + + /** + * A builder for creating {@link NimbusJwtDecoder} instances based on a + * public key. + * + * @since 5.2 + */ + public static final class PublicKeyJwtDecoderBuilder { + private JWSAlgorithm jwsAlgorithm; + private RSAKey key; + + private PublicKeyJwtDecoderBuilder(RSAPublicKey key) { + Assert.notNull(key, "key cannot be null"); + this.jwsAlgorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256); + this.key = rsaKey(key); + } + + private static RSAKey rsaKey(RSAPublicKey publicKey) { + return new RSAKey.Builder(publicKey) + .build(); + } + + /** + * Use the given signing + * algorithm. + * + * The value should be one of + * RS256, RS384, or RS512. + * + * @param jwsAlgorithm the algorithm to use + * @return a {@link PublicKeyJwtDecoderBuilder} for further configurations + */ + public PublicKeyJwtDecoderBuilder jwsAlgorithm(String jwsAlgorithm) { + Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); + this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm); + return this; + } + + JWTProcessor processor() { + if (!JWSAlgorithm.Family.RSA.contains(this.jwsAlgorithm)) { + throw new IllegalStateException("The provided key is of type RSA; " + + "however the signature algorithm is of some other type: " + + this.jwsAlgorithm + ". Please indicate one of RS256, RS384, or RS512."); + } + + JWKSet jwkSet = new JWKSet(this.key); + JWKSource jwkSource = new ImmutableJWKSet<>(jwkSet); + JWSKeySelector jwsKeySelector = + new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource); + DefaultJWTProcessor jwtProcessor = new DefaultJWTProcessor<>(); + jwtProcessor.setJWSKeySelector(jwsKeySelector); + + // Spring Security validates the claim set independent from Nimbus + jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { }); + + return jwtProcessor; + } + + /** + * Build the configured {@link NimbusJwtDecoder}. + * + * @return the configured {@link NimbusJwtDecoder} + */ + public NimbusJwtDecoder build() { + return new NimbusJwtDecoder(processor()); + } + } } diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderJwkSupport.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderJwkSupport.java index b1c6bcb4a9..0df754124a 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderJwkSupport.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderJwkSupport.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. @@ -24,7 +24,7 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.util.Assert; import org.springframework.web.client.RestOperations; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; /** * An implementation of a {@link JwtDecoder} that "decodes" a @@ -49,7 +49,7 @@ import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUr */ @Deprecated public final class NimbusJwtDecoderJwkSupport implements JwtDecoder { - private JwtProcessors.JwkSetUriJwtProcessorBuilder jwtProcessorBuilder; + private NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder jwtDecoderBuilder; private OAuth2TokenValidator jwtValidator = JwtValidators.createDefault(); private Converter, Map> claimSetConverter = MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap()); @@ -75,12 +75,12 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder { Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); - this.jwtProcessorBuilder = withJwkSetUri(jwkSetUrl).jwsAlgorithm(jwsAlgorithm); + this.jwtDecoderBuilder = withJwkSetUri(jwkSetUrl).jwsAlgorithm(jwsAlgorithm); this.delegate = makeDelegate(); } private NimbusJwtDecoder makeDelegate() { - NimbusJwtDecoder delegate = new NimbusJwtDecoder(this.jwtProcessorBuilder.build()); + NimbusJwtDecoder delegate = this.jwtDecoderBuilder.build(); delegate.setClaimSetConverter(this.claimSetConverter); delegate.setJwtValidator(this.jwtValidator); return delegate; @@ -121,7 +121,7 @@ public final class NimbusJwtDecoderJwkSupport implements JwtDecoder { */ public final void setRestOperations(RestOperations restOperations) { Assert.notNull(restOperations, "restOperations cannot be null"); - this.jwtProcessorBuilder = this.jwtProcessorBuilder.restOperations(restOperations); + this.jwtDecoderBuilder = this.jwtDecoderBuilder.restOperations(restOperations); this.delegate = makeDelegate(); } } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtProcessorsTest.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtProcessorsTest.java deleted file mode 100644 index 51c340009f..0000000000 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/JwtProcessorsTest.java +++ /dev/null @@ -1,149 +0,0 @@ -/* - * Copyright 2002-2018 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://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.jwt; - -import java.security.KeyFactory; -import java.security.NoSuchAlgorithmException; -import java.security.interfaces.RSAPublicKey; -import java.security.spec.EncodedKeySpec; -import java.security.spec.InvalidKeySpecException; -import java.security.spec.X509EncodedKeySpec; -import java.util.Base64; - -import com.nimbusds.jose.proc.BadJOSEException; -import com.nimbusds.jose.proc.SecurityContext; -import com.nimbusds.jwt.JWTClaimsSet; -import com.nimbusds.jwt.proc.JWTProcessor; -import org.junit.BeforeClass; -import org.junit.Test; - -import org.springframework.http.HttpStatus; -import org.springframework.http.RequestEntity; -import org.springframework.http.ResponseEntity; -import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; -import org.springframework.web.client.RestOperations; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; - -/** - * Tests for {@link JwtProcessors} - */ -public class JwtProcessorsTest { - private static final String JWK_SET = "{\"keys\":[{\"p\":\"5VhDhCcOp9D3krJi2W2uF-LPovIKqtU59Jdt_gF6iwPw_0Bgo8UlbFRv3HdnVYUExLdkvHOoA9bmbkV0w_6TUwLP65PKPu6P8JfeWsIuGi3wY91_CsfahxGmVZxlZookBUIMMnylvM9fGR6-daNgkl31CKqDpkJt9XF35yjVpbM\",\"kty\":\"RSA\",\"q\":\"v3yrgOhimojmLVsLBdynmw_pfAlZPw2eXVzoJ514xP94UZJQRY_NOUjYV0O9Vqict_Qv42sUa-uurY8n_0Btslt--iJsMyTHYMIKjbyeFAqAGFuXYbQPnorEOkuZhT1NIBZhlLLuKSD8DVCtsEv2EVgBTwyzFJ6QbXLqVpNUvZs\",\"d\":\"ZyjCopsw7TszSV4qMIyunb0PaGfHbQ_0LJcAxhNwQsf3MYR6j0J9k1GVxq2SjpRylgKJg8CKjySaU4frewH7MEaNLNdfR2_XMFSKW3KFggdNRtW1TFwjcHfpBLTvB3MEaTx56Sohn0eXqd_Wa2EAfRiLjllwOeqwXqgdSXdvKfkkmV2DBZ2h100wLJB87Y5kvlGvbNDs0KgTFaPWRZkCQz3CGhGPDyTJVwzgJvIxbHgzl9DnW6FlgrP_DZmyfGbJ833FZSiBczTQGDWT7euR3h491fKPCHTXjdULtU1578NldRAo8SOXH4ThXXA_kwKafIGlKx5LZPNwMWgNuVvE6Q\",\"e\":\"AQAB\",\"use\":\"sig\",\"kid\":\"one\",\"qi\":\"eaGTNLhJf1K82YqB6VKrYWz1hxsKnjRBg-V-kuWJXvW7HQsLFKx56kXy_ximz_IQDZOO3F-rW_7Saz3RvWuFt_Yq7sRcLCMtpiDRbZ-nGDgHxQHedtLoalLLPmJkMMsZwZzXf9l6LO6a8r30lrC_C-kPY5K7lz97ZToKeper7c8\",\"dp\":\"QQJ4-O_dTqKEWvfn3zwg2jJ3qvezIGOarwNxsUuYAenXGXOVMTcD-aYhozvRdcNj66MUkfqyyIvU-7MCe0AhYKluaJeW_6m98XQLGmzqho85EgXKKjMmdZ0CKkhP0fYcacUkEfeVP2UEzukREeWCzVqGx7MV6D3yT12foE3J6dM\",\"dq\":\"PsH2V5ZSEsHBZqYLE83ApMJvTHan6FFnUMQNVkZ2-WGdJmbkphe-NAMa3GbYHBnA201NkKRcmg4xPrLHchHEogr4r7QucAiiy6Rs3w0tZfYXC2ShVaU05Uoni8-RLijsKRMMwjZudc5YrWh-tGQA7qhALY9E9gIN5cEe6mb5A_c\",\"n\":\"q4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D-J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl__tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ_J97qTC-K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQ\"}]}"; - private static final String JWK_SET_URI = "http://issuer/.well-known/jwks.json"; - private static final String RS512_SIGNED_JWT = "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYxMTl9.LKAx-60EBfD7jC1jb1eKcjO4uLvf3ssISV-8tN-qp7gAjSvKvj4YA9-V2mIb6jcS1X_xGmNy6EIimZXpWaBR3nJmeu-jpe85u4WaW2Ztr8ecAi-dTO7ZozwdtljKuBKKvj4u1nF70zyCNl15AozSG0W1ASrjUuWrJtfyDG6WoZ8VfNMuhtU-xUYUFvscmeZKUYQcJ1KS-oV5tHeF8aNiwQoiPC_9KXCOZtNEJFdq6-uzFdHxvOP2yex5Gbmg5hXonauIFXG2ZPPGdXzm-5xkhBpgM8U7A_6wb3So8wBvLYYm2245QUump63AJRAy8tQpwt4n9MvQxQgS3z9R-NK92A"; - private static final String RS256_SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w"; - private static final String VERIFY_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB"; - - private static KeyFactory kf; - - @BeforeClass - public static void keyFactory() throws NoSuchAlgorithmException { - kf = KeyFactory.getInstance("RSA"); - } - - @Test - public void withJwkSetUriWhenNullOrEmptyThenThrowsException() { - assertThatCode(() -> withJwkSetUri(null)).isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void jwsAlgorithmWhenNullOrEmptyThenThrowsException() { - JwtProcessors.JwkSetUriJwtProcessorBuilder builder = withJwkSetUri(JWK_SET_URI); - assertThatCode(() -> builder.jwsAlgorithm(null)).isInstanceOf(IllegalArgumentException.class); - assertThatCode(() -> builder.jwsAlgorithm("")).isInstanceOf(IllegalArgumentException.class); - assertThatCode(() -> builder.jwsAlgorithm("RS4096")).doesNotThrowAnyException(); - } - - @Test - public void restOperationsWhenNullThenThrowsException() { - JwtProcessors.JwkSetUriJwtProcessorBuilder builder = withJwkSetUri(JWK_SET_URI); - assertThatCode(() -> builder.restOperations(null)).isInstanceOf(IllegalArgumentException.class); - } - - // gh-5603 - @Test - public void processWhenSignedThenOk() throws Exception { - RestOperations restOperations = mockJwkSetResponse(JWK_SET); - JWTProcessor processor = - withJwkSetUri(JWK_SET_URI).restOperations(restOperations).build(); - assertThat(processor.process(RS256_SIGNED_JWT, null)) - .extracting(JWTClaimsSet::getExpirationTime) - .isNotNull(); - verify(restOperations).exchange(any(RequestEntity.class), eq(String.class)); - } - - @Test - public void withPublicKeyWhenNullThenThrowsException() { - assertThatThrownBy(() -> JwtProcessors.withPublicKey(null)) - .isInstanceOf(IllegalArgumentException.class); - } - - @Test - public void buildWhenSignatureAlgorithmMismatchesKeyTypeThenThrowsException() { - assertThatCode(() -> JwtProcessors.withPublicKey(key()) - .jwsAlgorithm(JwsAlgorithms.ES256) - .build()) - .isInstanceOf(IllegalStateException.class); - } - - @Test - public void processWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception { - JWTProcessor processor = JwtProcessors.withPublicKey(key()).build(); - assertThat(processor.process(RS256_SIGNED_JWT, null)) - .extracting(JWTClaimsSet::getSubject) - .isEqualTo("test-subject"); - } - - @Test - public void processWhenUsingPublicKeyWithRs512ThenSuccessfullyDecodes() throws Exception { - JWTProcessor processor = JwtProcessors - .withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build(); - assertThat(processor.process(RS512_SIGNED_JWT, null)) - .extracting(JWTClaimsSet::getSubject) - .isEqualTo("test-subject"); - } - - @Test - public void processWhenSignatureMismatchesAlgorithmThenThrowsException() throws Exception { - JWTProcessor processor = JwtProcessors - .withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build(); - assertThatCode(() -> processor.process(RS256_SIGNED_JWT, null)) - .isInstanceOf(BadJOSEException.class); - } - - private RSAPublicKey key() throws InvalidKeySpecException { - byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes()); - EncodedKeySpec spec = new X509EncodedKeySpec(decoded); - return (RSAPublicKey) kf.generatePublic(spec); - } - - private static RestOperations mockJwkSetResponse(String response) { - RestOperations restOperations = mock(RestOperations.class); - when(restOperations.exchange(any(RequestEntity.class), eq(String.class))) - .thenReturn(new ResponseEntity<>(response, HttpStatus.OK)); - return restOperations; - } -} diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java index acf63fbf35..4d93df3da2 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtDecoderTests.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. @@ -16,8 +16,15 @@ package org.springframework.security.oauth2.jwt; +import java.security.KeyFactory; +import java.security.NoSuchAlgorithmException; +import java.security.interfaces.RSAPublicKey; +import java.security.spec.EncodedKeySpec; +import java.security.spec.InvalidKeySpecException; +import java.security.spec.X509EncodedKeySpec; import java.text.ParseException; import java.util.Arrays; +import java.util.Base64; import java.util.Collections; import java.util.Map; @@ -31,6 +38,7 @@ import com.nimbusds.jwt.proc.DefaultJWTProcessor; import com.nimbusds.jwt.proc.JWTProcessor; import okhttp3.mockwebserver.MockWebServer; import org.assertj.core.api.Assertions; +import org.junit.BeforeClass; import org.junit.Test; import org.springframework.core.convert.converter.Converter; @@ -40,6 +48,7 @@ import org.springframework.http.ResponseEntity; import org.springframework.security.oauth2.core.OAuth2Error; import org.springframework.security.oauth2.core.OAuth2TokenValidator; import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult; +import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.web.client.RestOperations; import static org.assertj.core.api.Assertions.assertThat; @@ -49,7 +58,8 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; -import static org.springframework.security.oauth2.jwt.JwtProcessors.withJwkSetUri; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withJwkSetUri; +import static org.springframework.security.oauth2.jwt.NimbusJwtDecoder.withPublicKey; /** * Tests for {@link NimbusJwtDecoder} @@ -65,8 +75,20 @@ public class NimbusJwtDecoderTests { private static final String UNSIGNED_JWT = "eyJhbGciOiJub25lIiwidHlwIjoiSldUIn0.eyJleHAiOi0yMDMzMjI0OTcsImp0aSI6IjEyMyIsInR5cCI6IkpXVCJ9."; private static final String EMPTY_EXP_CLAIM_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJhdWQiOiJhdWRpZW5jZSJ9.D1eT0jpBEpuh74p-YT-uF81Z7rkVqIpUtJ5hWWFiVShZ9s8NIntK4Q1GlvlziiySSaVYaXtpTmDB3c8r-Z5Mj4ibihiueCSq7jaPD3sA8IMQKL-L6Uol8MSD_lSFE2n3fVBTxFeaejBKfZsDxnhzgpy8g7PncR47w8NHs-7tKO4qw7G_SV3hkNpDNoqZTfMImxyWEebgKM2pJAhN4das2CO1KAjYMfEByLcgYncE8fzdYPJhMFo2XRRSQABoeUBuKSAwIntBaOGvcb-qII_Hefc5U0cmpNItG75F2XfX803plKI4FFpAxJsbPKWSQmhs6bZOrhx0x74pY5LS3ghmJw"; + private static final String JWK_SET_URI = "http://issuer/.well-known/jwks.json"; + private static final String RS512_SIGNED_JWT = "eyJhbGciOiJSUzUxMiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYxMTl9.LKAx-60EBfD7jC1jb1eKcjO4uLvf3ssISV-8tN-qp7gAjSvKvj4YA9-V2mIb6jcS1X_xGmNy6EIimZXpWaBR3nJmeu-jpe85u4WaW2Ztr8ecAi-dTO7ZozwdtljKuBKKvj4u1nF70zyCNl15AozSG0W1ASrjUuWrJtfyDG6WoZ8VfNMuhtU-xUYUFvscmeZKUYQcJ1KS-oV5tHeF8aNiwQoiPC_9KXCOZtNEJFdq6-uzFdHxvOP2yex5Gbmg5hXonauIFXG2ZPPGdXzm-5xkhBpgM8U7A_6wb3So8wBvLYYm2245QUump63AJRAy8tQpwt4n9MvQxQgS3z9R-NK92A"; + private static final String RS256_SIGNED_JWT = "eyJhbGciOiJSUzI1NiJ9.eyJzdWIiOiJ0ZXN0LXN1YmplY3QiLCJleHAiOjE5NzQzMjYzMzl9.CT-H2OWEqmSs1NWmnta5ealLFvM8OlbQTjGhfRcKLNxrTrzsOkqBJl-AN3k16BQU7mS32o744TiiZ29NcDlxPsr1MqTlN86-dobPiuNIDLp3A1bOVdXMcVFuMYkrNv0yW0tGS9OjEqsCCuZDkZ1by6AhsHLbGwRY-6AQdcRouZygGpOQu1hNun5j8q5DpSTY4AXKARIFlF-O3OpVbPJ0ebr3Ki-i3U9p_55H0e4-wx2bqcApWlqgofl1I8NKWacbhZgn81iibup2W7E0CzCzh71u1Mcy3xk1sYePx-dwcxJnHmxJReBBWjJZEAeCrkbnn_OCuo2fA-EQyNJtlN5F2w"; + private static final String VERIFY_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAq4yKxb6SNePdDmQi9xFCrP6QvHosErQzryknQTTTffs0t3cy3Er3lIceuhZ7yQNSCDfPFqG8GoyoKhuChRiA5D+J2ab7bqTa1QJKfnCyERoscftgN2fXPHjHoiKbpGV2tMVw8mXl//tePOAiKbMJaBUnlAvJgkk1rVm08dSwpLC1sr2M19euf9jwnRGkMRZuhp9iCPgECRke5T8Ixpv0uQjSmGHnWUKTFlbj8sM83suROR1Ue64JSGScANc5vk3huJ/J97qTC+K2oKj6L8d9O8dpc4obijEOJwpydNvTYDgbiivYeSB00KS9jlBkQ5B2QqLvLVEygDl3dp59nGx6YQIDAQAB"; + + private static KeyFactory kf; + NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder(withoutSigning()); + @BeforeClass + public static void keyFactory() throws NoSuchAlgorithmException { + kf = KeyFactory.getInstance("RSA"); + } + @Test public void constructorWhenJwtProcessorIsNullThenThrowIllegalArgumentException() { assertThatThrownBy(() -> new NimbusJwtDecoder(null)) @@ -180,8 +202,7 @@ public class NimbusJwtDecoderTests { public void decodeWhenJwkEndpointIsUnresponsiveThenReturnsJwtException() throws Exception { try ( MockWebServer server = new MockWebServer() ) { String jwkSetUri = server.url("/.well-known/jwks.json").toString(); - NimbusJwtDecoder jwtDecoder = new NimbusJwtDecoder( - withJwkSetUri(jwkSetUri).build()); + NimbusJwtDecoder jwtDecoder = withJwkSetUri(jwkSetUri).build(); server.shutdown(); assertThatCode(() -> jwtDecoder.decode(SIGNED_JWT)) @@ -190,13 +211,75 @@ public class NimbusJwtDecoderTests { } } + @Test + public void withJwkSetUriWhenNullOrEmptyThenThrowsException() { + Assertions.assertThatCode(() -> withJwkSetUri(null)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void jwsAlgorithmWhenNullOrEmptyThenThrowsException() { + NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = withJwkSetUri(JWK_SET_URI); + Assertions.assertThatCode(() -> builder.jwsAlgorithm(null)).isInstanceOf(IllegalArgumentException.class); + Assertions.assertThatCode(() -> builder.jwsAlgorithm("")).isInstanceOf(IllegalArgumentException.class); + Assertions.assertThatCode(() -> builder.jwsAlgorithm("RS4096")).doesNotThrowAnyException(); + } + + @Test + public void restOperationsWhenNullThenThrowsException() { + NimbusJwtDecoder.JwkSetUriJwtDecoderBuilder builder = withJwkSetUri(JWK_SET_URI); + Assertions.assertThatCode(() -> builder.restOperations(null)).isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void withPublicKeyWhenNullThenThrowsException() { + assertThatThrownBy(() -> withPublicKey(null)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + public void buildWhenSignatureAlgorithmMismatchesKeyTypeThenThrowsException() { + Assertions.assertThatCode(() -> withPublicKey(key()) + .jwsAlgorithm(JwsAlgorithms.ES256) + .build()) + .isInstanceOf(IllegalStateException.class); + } + + @Test + public void decodeWhenUsingPublicKeyThenSuccessfullyDecodes() throws Exception { + NimbusJwtDecoder decoder = withPublicKey(key()).build(); + assertThat(decoder.decode(RS256_SIGNED_JWT)) + .extracting(Jwt::getSubject) + .isEqualTo("test-subject"); + } + + @Test + public void decodeWhenUsingPublicKeyWithRs512ThenSuccessfullyDecodes() throws Exception { + NimbusJwtDecoder decoder = withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build(); + assertThat(decoder.decode(RS512_SIGNED_JWT)) + .extracting(Jwt::getSubject) + .isEqualTo("test-subject"); + } + + @Test + public void decodeWhenSignatureMismatchesAlgorithmThenThrowsException() throws Exception { + NimbusJwtDecoder decoder = withPublicKey(key()).jwsAlgorithm(JwsAlgorithms.RS512).build(); + Assertions.assertThatCode(() -> decoder.decode(RS256_SIGNED_JWT)) + .isInstanceOf(JwtException.class); + } + + private RSAPublicKey key() throws InvalidKeySpecException { + byte[] decoded = Base64.getDecoder().decode(VERIFY_KEY.getBytes()); + EncodedKeySpec spec = new X509EncodedKeySpec(decoded); + return (RSAPublicKey) kf.generatePublic(spec); + } + private static JWTProcessor withSigning(String jwkResponse) { RestOperations restOperations = mock(RestOperations.class); when(restOperations.exchange(any(RequestEntity.class), eq(String.class))) .thenReturn(new ResponseEntity<>(jwkResponse, HttpStatus.OK)); return withJwkSetUri("http://issuer/.well-known/jwks.json") .restOperations(restOperations) - .build(); + .processor(); } private static JWTProcessor withoutSigning() { diff --git a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java index b12cdef913..a9c6ef889a 100644 --- a/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.java +++ b/samples/boot/oauth2resourceserver-static/src/main/java/sample/OAuth2ResourceServerSecurityConfiguration.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. @@ -25,7 +25,6 @@ import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; import org.springframework.security.oauth2.jwt.JwtDecoder; -import org.springframework.security.oauth2.jwt.JwtProcessors; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; /** @@ -50,7 +49,7 @@ public class OAuth2ResourceServerSecurityConfiguration extends WebSecurityConfig @Bean JwtDecoder jwtDecoder() throws Exception { - return new NimbusJwtDecoder(JwtProcessors.withPublicKey(key()).build()); + return NimbusJwtDecoder.withPublicKey(key()).build(); } private RSAPublicKey key() throws Exception {