2 changed files with 207 additions and 0 deletions
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
/* |
||||
* 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 |
||||
* |
||||
* 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.jwt; |
||||
|
||||
import com.nimbusds.jose.JOSEException; |
||||
import com.nimbusds.jose.JWSAlgorithm; |
||||
import com.nimbusds.jose.jwk.JWK; |
||||
import com.nimbusds.jose.jwk.JWKSelector; |
||||
import com.nimbusds.jose.jwk.source.JWKSource; |
||||
import com.nimbusds.jose.proc.BadJOSEException; |
||||
import com.nimbusds.jose.proc.JWSKeySelector; |
||||
import com.nimbusds.jose.proc.JWSVerificationKeySelector; |
||||
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.DefaultJWTProcessor; |
||||
import com.nimbusds.jwt.proc.JWTProcessor; |
||||
import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; |
||||
import org.springframework.util.Assert; |
||||
import reactor.core.publisher.Mono; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* An implementation of a {@link JwtDecoder} 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. |
||||
* |
||||
* <p> |
||||
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK internally. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 5.1 |
||||
* @see JwtDecoder |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7517">JSON Web Key (JWK)</a> |
||||
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a> |
||||
*/ |
||||
public final class NimbusJwkReactiveJwtDecoder implements ReactiveJwtDecoder { |
||||
private final JWTProcessor<JWKContext> jwtProcessor; |
||||
|
||||
private final ReactiveRemoteJWKSource reactiveJwkSource; |
||||
|
||||
private final JWKSelectorFactory jwkSelectorFactory; |
||||
|
||||
/** |
||||
* Constructs a {@code NimbusJwtDecoderJwkSupport} using the provided parameters. |
||||
* |
||||
* @param jwkSetUrl the JSON Web Key (JWK) Set {@code URL} |
||||
*/ |
||||
public NimbusJwkReactiveJwtDecoder(String jwkSetUrl) { |
||||
this(jwkSetUrl, JwsAlgorithms.RS256); |
||||
} |
||||
|
||||
/** |
||||
* Constructs a {@code NimbusJwtDecoderJwkSupport} using the provided parameters. |
||||
* |
||||
* @param jwkSetUrl the JSON Web Key (JWK) Set {@code URL} |
||||
* @param jwsAlgorithm the JSON Web Algorithm (JWA) used for verifying the digital signatures |
||||
*/ |
||||
public NimbusJwkReactiveJwtDecoder(String jwkSetUrl, String jwsAlgorithm) { |
||||
Assert.hasText(jwkSetUrl, "jwkSetUrl cannot be empty"); |
||||
Assert.hasText(jwsAlgorithm, "jwsAlgorithm cannot be empty"); |
||||
|
||||
JWSAlgorithm algorithm = JWSAlgorithm.parse(jwsAlgorithm); |
||||
JWKSource jwkSource = new JWKContextJWKSource(); |
||||
JWSKeySelector<JWKContext> jwsKeySelector = |
||||
new JWSVerificationKeySelector<>(algorithm, jwkSource); |
||||
|
||||
DefaultJWTProcessor<JWKContext> jwtProcessor = new DefaultJWTProcessor<>(); |
||||
jwtProcessor.setJWSKeySelector(jwsKeySelector); |
||||
this.jwtProcessor = jwtProcessor; |
||||
|
||||
this.reactiveJwkSource = new ReactiveRemoteJWKSource(jwkSetUrl); |
||||
|
||||
this.jwkSelectorFactory = new JWKSelectorFactory(algorithm); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public Mono<Jwt> decode(String token) throws JwtException { |
||||
JWT jwt = parse(token); |
||||
if (jwt instanceof SignedJWT) { |
||||
return this.decode((SignedJWT) jwt); |
||||
} |
||||
return Mono.empty(); |
||||
} |
||||
|
||||
private JWT parse(String token) { |
||||
try { |
||||
return JWTParser.parse(token); |
||||
} catch (Exception ex) { |
||||
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
private Mono<Jwt> decode(SignedJWT parsedToken) { |
||||
try { |
||||
JWKSelector selector = this.jwkSelectorFactory |
||||
.createSelector(parsedToken.getHeader()); |
||||
return this.reactiveJwkSource.get(selector) |
||||
.map(jwkList -> createJwkSet(parsedToken, jwkList)) |
||||
.map(set -> createJwt(parsedToken, set)); |
||||
} catch (Exception ex) { |
||||
throw new JwtException("An error occurred while attempting to decode the Jwt: " + ex.getMessage(), ex); |
||||
} |
||||
} |
||||
|
||||
private JWTClaimsSet createJwkSet(JWT parsedToken, List<JWK> jwkList) { |
||||
try { |
||||
return this.jwtProcessor.process(parsedToken, new JWKContext(jwkList)); |
||||
} |
||||
catch (BadJOSEException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
catch (JOSEException e) { |
||||
throw new RuntimeException(e); |
||||
} |
||||
} |
||||
|
||||
private Jwt createJwt(JWT parsedJwt, JWTClaimsSet jwtClaimsSet) { |
||||
Instant expiresAt = null; |
||||
if (jwtClaimsSet.getExpirationTime() != null) { |
||||
expiresAt = jwtClaimsSet.getExpirationTime().toInstant(); |
||||
} |
||||
Instant issuedAt = null; |
||||
if (jwtClaimsSet.getIssueTime() != null) { |
||||
issuedAt = jwtClaimsSet.getIssueTime().toInstant(); |
||||
} else if (expiresAt != null) { |
||||
// Default to expiresAt - 1 second
|
||||
issuedAt = Instant.from(expiresAt).minusSeconds(1); |
||||
} |
||||
|
||||
Map<String, Object> headers = new LinkedHashMap<>(parsedJwt.getHeader().toJSONObject()); |
||||
|
||||
return new Jwt(parsedJwt.getParsedString(), issuedAt, expiresAt, headers, jwtClaimsSet.getClaims()); |
||||
} |
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* 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 |
||||
* |
||||
* 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.jwt; |
||||
|
||||
import reactor.core.publisher.Mono; |
||||
|
||||
/** |
||||
* Implementations of this interface are responsible for "decoding" |
||||
* a JSON Web Token (JWT) from it's compact claims representation format to a {@link Jwt}. |
||||
* |
||||
* <p> |
||||
* JWTs may be represented using the JWS Compact Serialization format for a |
||||
* JSON Web Signature (JWS) structure or JWE Compact Serialization format for a |
||||
* JSON Web Encryption (JWE) structure. Therefore, implementors are responsible |
||||
* for verifying a JWS and/or decrypting a JWE. |
||||
* |
||||
* @author Rob Winch |
||||
* @since 5.1 |
||||
* @see Jwt |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519">JSON Web Token (JWT)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515">JSON Web Signature (JWS)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516">JSON Web Encryption (JWE)</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-3.1">JWS Compact Serialization</a> |
||||
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-3.1">JWE Compact Serialization</a> |
||||
*/ |
||||
public interface ReactiveJwtDecoder { |
||||
|
||||
/** |
||||
* Decodes the JWT from it's compact claims representation format and returns a {@link Jwt}. |
||||
* |
||||
* @param token the JWT value |
||||
* @return a {@link Jwt} |
||||
* @throws JwtException if an error occurs while attempting to decode the JWT |
||||
*/ |
||||
Mono<Jwt> decode(String token) throws JwtException; |
||||
|
||||
} |
||||
Loading…
Reference in new issue