|
@@ -15,13 +15,6 @@
|
|
|
*/
|
|
|
package org.springframework.security.oauth2.jwt;
|
|
|
|
|
|
-import java.security.interfaces.RSAPublicKey;
|
|
|
-import java.time.Instant;
|
|
|
-import java.util.Collections;
|
|
|
-import java.util.LinkedHashMap;
|
|
|
-import java.util.Map;
|
|
|
-import java.util.function.Function;
|
|
|
-
|
|
|
import com.nimbusds.jose.JOSEException;
|
|
|
import com.nimbusds.jose.JWSAlgorithm;
|
|
|
import com.nimbusds.jose.JWSHeader;
|
|
@@ -31,6 +24,7 @@ import com.nimbusds.jose.jwk.JWKSelector;
|
|
|
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.ImmutableSecret;
|
|
|
import com.nimbusds.jose.jwk.source.JWKSecurityContextJWKSet;
|
|
|
import com.nimbusds.jose.jwk.source.JWKSource;
|
|
|
import com.nimbusds.jose.proc.BadJOSEException;
|
|
@@ -44,26 +38,35 @@ import com.nimbusds.jwt.JWTParser;
|
|
|
import com.nimbusds.jwt.SignedJWT;
|
|
|
import com.nimbusds.jwt.proc.DefaultJWTProcessor;
|
|
|
import com.nimbusds.jwt.proc.JWTProcessor;
|
|
|
-import reactor.core.publisher.Flux;
|
|
|
-import reactor.core.publisher.Mono;
|
|
|
-
|
|
|
import org.springframework.core.convert.converter.Converter;
|
|
|
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.security.oauth2.jose.jws.JwsAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
|
|
|
+import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
|
|
|
import org.springframework.util.Assert;
|
|
|
import org.springframework.web.reactive.function.client.WebClient;
|
|
|
+import reactor.core.publisher.Flux;
|
|
|
+import reactor.core.publisher.Mono;
|
|
|
+
|
|
|
+import javax.crypto.SecretKey;
|
|
|
+import java.security.interfaces.RSAPublicKey;
|
|
|
+import java.time.Instant;
|
|
|
+import java.util.Collections;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.Map;
|
|
|
+import java.util.function.Function;
|
|
|
|
|
|
/**
|
|
|
* An implementation of a {@link ReactiveJwtDecoder} that "decodes" a
|
|
|
* JSON Web Token (JWT) and additionally verifies it's digital signature if the JWT is a
|
|
|
- * JSON Web Signature (JWS). The public key used for verification is obtained from the
|
|
|
- * JSON Web Key (JWK) Set {@code URL} supplied via the constructor.
|
|
|
+ * JSON Web Signature (JWS).
|
|
|
*
|
|
|
* <p>
|
|
|
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK internally.
|
|
|
*
|
|
|
* @author Rob Winch
|
|
|
+ * @author Joe Grandja
|
|
|
* @since 5.1
|
|
|
* @see ReactiveJwtDecoder
|
|
|
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a>
|
|
@@ -75,22 +78,34 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
private final Converter<SignedJWT, Mono<JWTClaimsSet>> jwtProcessor;
|
|
|
|
|
|
private OAuth2TokenValidator<Jwt> jwtValidator = JwtValidators.createDefault();
|
|
|
- private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter = MappedJwtClaimSetConverter
|
|
|
- .withDefaults(Collections.emptyMap());
|
|
|
-
|
|
|
- public NimbusReactiveJwtDecoder(RSAPublicKey publicKey) {
|
|
|
- this.jwtProcessor = withPublicKey(publicKey).processor();
|
|
|
- }
|
|
|
+ private Converter<Map<String, Object>, Map<String, Object>> claimSetConverter =
|
|
|
+ MappedJwtClaimSetConverter.withDefaults(Collections.emptyMap());
|
|
|
|
|
|
/**
|
|
|
- * Constructs a {@code NimbusJwtDecoderJwkSupport} using the provided parameters.
|
|
|
+ * Constructs a {@code NimbusReactiveJwtDecoder} using the provided parameters.
|
|
|
*
|
|
|
* @param jwkSetUrl the JSON Web Key (JWK) Set {@code URL}
|
|
|
*/
|
|
|
public NimbusReactiveJwtDecoder(String jwkSetUrl) {
|
|
|
- this.jwtProcessor = withJwkSetUri(jwkSetUrl).processor();
|
|
|
+ this(withJwkSetUri(jwkSetUrl).processor());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Constructs a {@code NimbusReactiveJwtDecoder} using the provided parameters.
|
|
|
+ *
|
|
|
+ * @param publicKey the {@code RSAPublicKey} used to verify the signature
|
|
|
+ * @since 5.2
|
|
|
+ */
|
|
|
+ public NimbusReactiveJwtDecoder(RSAPublicKey publicKey) {
|
|
|
+ this(withPublicKey(publicKey).processor());
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Constructs a {@code NimbusReactiveJwtDecoder} using the provided parameters.
|
|
|
+ *
|
|
|
+ * @param jwtProcessor the {@link Converter} used to process and verify the signed Jwt and return the Jwt Claim Set
|
|
|
+ * @since 5.2
|
|
|
+ */
|
|
|
public NimbusReactiveJwtDecoder(Converter<SignedJWT, Mono<JWTClaimsSet>> jwtProcessor) {
|
|
|
this.jwtProcessor = jwtProcessor;
|
|
|
}
|
|
@@ -188,6 +203,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
return new PublicKeyReactiveJwtDecoderBuilder(key);
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * Use the given {@code SecretKey} to validate the MAC on a JSON Web Signature (JWS).
|
|
|
+ *
|
|
|
+ * @param secretKey the {@code SecretKey} used to validate the MAC
|
|
|
+ * @return a {@link SecretKeyReactiveJwtDecoderBuilder} for further configurations
|
|
|
+ *
|
|
|
+ * @since 5.2
|
|
|
+ */
|
|
|
+ public static SecretKeyReactiveJwtDecoderBuilder withSecretKey(SecretKey secretKey) {
|
|
|
+ return new SecretKeyReactiveJwtDecoderBuilder(secretKey);
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* Use the given {@link Function} to validate JWTs
|
|
|
*
|
|
@@ -207,8 +234,7 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
* @since 5.2
|
|
|
*/
|
|
|
public static final class JwkSetUriReactiveJwtDecoderBuilder {
|
|
|
-
|
|
|
- private String jwkSetUri;
|
|
|
+ private final String jwkSetUri;
|
|
|
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
|
|
|
private WebClient webClient = WebClient.create();
|
|
|
|
|
@@ -224,9 +250,9 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
* @param jwsAlgorithm the algorithm to use
|
|
|
* @return a {@link JwkSetUriReactiveJwtDecoderBuilder} for further configurations
|
|
|
*/
|
|
|
- public JwkSetUriReactiveJwtDecoderBuilder jwsAlgorithm(String jwsAlgorithm) {
|
|
|
- Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
|
|
|
- this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
|
|
|
+ public JwkSetUriReactiveJwtDecoderBuilder jwsAlgorithm(JwsAlgorithm jwsAlgorithm) {
|
|
|
+ Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
|
|
|
+ this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm.getName());
|
|
|
return this;
|
|
|
}
|
|
|
|
|
@@ -284,19 +310,18 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * A builder for creating Nimbus {@link JWTProcessor} instances based on a
|
|
|
- * public key.
|
|
|
+ * A builder for creating {@link NimbusReactiveJwtDecoder} instances based on a public key.
|
|
|
*
|
|
|
* @since 5.2
|
|
|
*/
|
|
|
public static final class PublicKeyReactiveJwtDecoderBuilder {
|
|
|
+ private final RSAKey key;
|
|
|
private JWSAlgorithm jwsAlgorithm;
|
|
|
- private RSAKey key;
|
|
|
|
|
|
private PublicKeyReactiveJwtDecoderBuilder(RSAPublicKey key) {
|
|
|
Assert.notNull(key, "key cannot be null");
|
|
|
- this.jwsAlgorithm = JWSAlgorithm.parse(JwsAlgorithms.RS256);
|
|
|
this.key = rsaKey(key);
|
|
|
+ this.jwsAlgorithm = JWSAlgorithm.RS256;
|
|
|
}
|
|
|
|
|
|
private static RSAKey rsaKey(RSAPublicKey publicKey) {
|
|
@@ -310,12 +335,12 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
* The value should be one of
|
|
|
* <a href="https://tools.ietf.org/html/rfc7518#section-3.3" target="_blank">RS256, RS384, or RS512</a>.
|
|
|
*
|
|
|
- * @param jwsAlgorithm the algorithm to use
|
|
|
+ * @param signatureAlgorithm the algorithm to use
|
|
|
* @return a {@link PublicKeyReactiveJwtDecoderBuilder} for further configurations
|
|
|
*/
|
|
|
- public PublicKeyReactiveJwtDecoderBuilder jwsAlgorithm(String jwsAlgorithm) {
|
|
|
- Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
|
|
|
- this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
|
|
|
+ public PublicKeyReactiveJwtDecoderBuilder signatureAlgorithm(SignatureAlgorithm signatureAlgorithm) {
|
|
|
+ Assert.notNull(signatureAlgorithm, "signatureAlgorithm cannot be null");
|
|
|
+ this.jwsAlgorithm = JWSAlgorithm.parse(signatureAlgorithm.getName());
|
|
|
return this;
|
|
|
}
|
|
|
|
|
@@ -349,17 +374,71 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ /**
|
|
|
+ * A builder for creating {@link NimbusReactiveJwtDecoder} instances based on a {@code SecretKey}.
|
|
|
+ *
|
|
|
+ * @since 5.2
|
|
|
+ */
|
|
|
+ public static final class SecretKeyReactiveJwtDecoderBuilder {
|
|
|
+ private final SecretKey secretKey;
|
|
|
+ private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.HS256;
|
|
|
+
|
|
|
+ private SecretKeyReactiveJwtDecoderBuilder(SecretKey secretKey) {
|
|
|
+ Assert.notNull(secretKey, "secretKey cannot be null");
|
|
|
+ this.secretKey = secretKey;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Use the given
|
|
|
+ * <a href="https://tools.ietf.org/html/rfc7515#section-4.1.1" target="_blank">algorithm</a>
|
|
|
+ * when generating the MAC.
|
|
|
+ *
|
|
|
+ * The value should be one of
|
|
|
+ * <a href="https://tools.ietf.org/html/rfc7518#section-3.2" target="_blank">HS256, HS384 or HS512</a>.
|
|
|
+ *
|
|
|
+ * @param macAlgorithm the MAC algorithm to use
|
|
|
+ * @return a {@link SecretKeyReactiveJwtDecoderBuilder} for further configurations
|
|
|
+ */
|
|
|
+ public SecretKeyReactiveJwtDecoderBuilder macAlgorithm(MacAlgorithm macAlgorithm) {
|
|
|
+ Assert.notNull(macAlgorithm, "macAlgorithm cannot be null");
|
|
|
+ this.jwsAlgorithm = JWSAlgorithm.parse(macAlgorithm.getName());
|
|
|
+ return this;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * Build the configured {@link NimbusReactiveJwtDecoder}.
|
|
|
+ *
|
|
|
+ * @return the configured {@link NimbusReactiveJwtDecoder}
|
|
|
+ */
|
|
|
+ public NimbusReactiveJwtDecoder build() {
|
|
|
+ return new NimbusReactiveJwtDecoder(processor());
|
|
|
+ }
|
|
|
+
|
|
|
+ Converter<SignedJWT, Mono<JWTClaimsSet>> processor() {
|
|
|
+ JWKSource<SecurityContext> jwkSource = new ImmutableSecret<>(this.secretKey);
|
|
|
+ JWSKeySelector<SecurityContext> jwsKeySelector =
|
|
|
+ new JWSVerificationKeySelector<>(this.jwsAlgorithm, jwkSource);
|
|
|
+ DefaultJWTProcessor<SecurityContext> jwtProcessor = new DefaultJWTProcessor<>();
|
|
|
+ jwtProcessor.setJWSKeySelector(jwsKeySelector);
|
|
|
+
|
|
|
+ // Spring Security validates the claim set independent from Nimbus
|
|
|
+ jwtProcessor.setJWTClaimsSetVerifier((claims, context) -> { });
|
|
|
+
|
|
|
+ return signedJWT -> Mono.just(signedJWT).map(jwt -> createClaimsSet(jwtProcessor, jwt, null));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* A builder for creating {@link NimbusReactiveJwtDecoder} instances.
|
|
|
*
|
|
|
* @since 5.2
|
|
|
*/
|
|
|
public static final class JwkSourceReactiveJwtDecoderBuilder {
|
|
|
- private Function<JWT, Flux<JWK>> jwkSource;
|
|
|
+ private final Function<JWT, Flux<JWK>> jwkSource;
|
|
|
private JWSAlgorithm jwsAlgorithm = JWSAlgorithm.RS256;
|
|
|
|
|
|
private JwkSourceReactiveJwtDecoderBuilder(Function<JWT, Flux<JWK>> jwkSource) {
|
|
|
- Assert.notNull(jwkSource, "jwkSource cannot be empty");
|
|
|
+ Assert.notNull(jwkSource, "jwkSource cannot be null");
|
|
|
this.jwkSource = jwkSource;
|
|
|
}
|
|
|
|
|
@@ -370,9 +449,9 @@ public final class NimbusReactiveJwtDecoder implements ReactiveJwtDecoder {
|
|
|
* @param jwsAlgorithm the algorithm to use
|
|
|
* @return a {@link JwkSourceReactiveJwtDecoderBuilder} for further configurations
|
|
|
*/
|
|
|
- public JwkSourceReactiveJwtDecoderBuilder jwsAlgorithm(String jwsAlgorithm) {
|
|
|
- Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty");
|
|
|
- this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm);
|
|
|
+ public JwkSourceReactiveJwtDecoderBuilder jwsAlgorithm(JwsAlgorithm jwsAlgorithm) {
|
|
|
+ Assert.notNull(jwsAlgorithm, "jwsAlgorithm cannot be null");
|
|
|
+ this.jwsAlgorithm = JWSAlgorithm.parse(jwsAlgorithm.getName());
|
|
|
return this;
|
|
|
}
|
|
|
|