Remove JwtEncoder and associated classes

Issue gh-596

Closes gh-724
This commit is contained in:
Joe Grandja
2022-05-03 18:34:55 -04:00
parent 47717a8163
commit a1fefd937b
25 changed files with 124 additions and 1741 deletions

View File

@@ -28,7 +28,7 @@ import org.springframework.core.ResolvableType;
import org.springframework.security.config.annotation.web.HttpSecurityBuilder;
import org.springframework.security.oauth2.core.OAuth2Token;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
@@ -138,7 +138,7 @@ final class OAuth2ConfigurerUtils {
if (jwtEncoder == null) {
JWKSource<SecurityContext> jwkSource = getJwkSource(builder);
if (jwkSource != null) {
jwtEncoder = new NimbusJwsEncoder(jwkSource);
jwtEncoder = new NimbusJwtEncoder(jwkSource);
}
}
if (jwtEncoder != null) {

View File

@@ -1,396 +0,0 @@
/*
* Copyright 2020-2022 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.net.URL;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.security.oauth2.jose.JwaAlgorithm;
import org.springframework.util.Assert;
/**
* The JOSE header is a JSON object representing the header parameters of a JSON Web Token,
* whether the JWT is a JWS or JWE, that describe the cryptographic operations applied to the JWT
* and optionally, additional properties of the JWT.
*
* @author Anoop Garlapati
* @author Joe Grandja
* @since 0.0.1
* @see Jwt
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519#section-5">JWT JOSE Header</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-4">JWS JOSE Header</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516#section-4">JWE JOSE Header</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public final class JoseHeader {
private final Map<String, Object> headers;
private JoseHeader(Map<String, Object> headers) {
this.headers = Collections.unmodifiableMap(new HashMap<>(headers));
}
/**
* Returns the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
*
* @return the {@link JwaAlgorithm}
*/
@SuppressWarnings("unchecked")
public <T extends JwaAlgorithm> T getAlgorithm() {
return (T) getHeader(JoseHeaderNames.ALG);
}
/**
* Returns the JWK Set URL that refers to the resource of a set of JSON-encoded public keys,
* one of which corresponds to the key used to digitally sign the JWS or encrypt the JWE.
*
* @return the JWK Set URL
*/
public URL getJwkSetUrl() {
return getHeader(JoseHeaderNames.JKU);
}
/**
* Returns the JSON Web Key which is the public key that corresponds to the key
* used to digitally sign the JWS or encrypt the JWE.
*
* @return the JSON Web Key
*/
public Map<String, Object> getJwk() {
return getHeader(JoseHeaderNames.JWK);
}
/**
* Returns the key ID that is a hint indicating which key was used to secure the JWS or JWE.
*
* @return the key ID
*/
public String getKeyId() {
return getHeader(JoseHeaderNames.KID);
}
/**
* Returns the X.509 URL that refers to the resource for the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @return the X.509 URL
*/
public URL getX509Url() {
return getHeader(JoseHeaderNames.X5U);
}
/**
* Returns the X.509 certificate chain that contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or
* encrypt the JWE. The certificate or certificate chain is represented as a
* {@code List} of certificate value {@code String}s. Each {@code String} in the
* {@code List} is a Base64-encoded DER PKIX certificate value.
*
* @return the X.509 certificate chain
*/
public List<String> getX509CertificateChain() {
return getHeader(JoseHeaderNames.X5C);
}
/**
* Returns the X.509 certificate SHA-1 thumbprint that is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @return the X.509 certificate SHA-1 thumbprint
*/
public String getX509SHA1Thumbprint() {
return getHeader(JoseHeaderNames.X5T);
}
/**
* Returns the X.509 certificate SHA-256 thumbprint that is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @return the X.509 certificate SHA-256 thumbprint
*/
public String getX509SHA256Thumbprint() {
return getHeader(JoseHeaderNames.X5T_S256);
}
/**
* Returns the type header that declares the media type of the JWS/JWE.
*
* @return the type header
*/
public String getType() {
return getHeader(JoseHeaderNames.TYP);
}
/**
* Returns the content type header that declares the media type of the secured content (the payload).
*
* @return the content type header
*/
public String getContentType() {
return getHeader(JoseHeaderNames.CTY);
}
/**
* Returns the critical headers that indicates which extensions to the JWS/JWE/JWA specifications
* are being used that MUST be understood and processed.
*
* @return the critical headers
*/
public Set<String> getCritical() {
return getHeader(JoseHeaderNames.CRIT);
}
/**
* Returns the headers.
*
* @return the headers
*/
public Map<String, Object> getHeaders() {
return this.headers;
}
/**
* Returns the header value.
*
* @param name the header name
* @param <T> the type of the header value
* @return the header value
*/
@SuppressWarnings("unchecked")
public <T> T getHeader(String name) {
Assert.hasText(name, "name cannot be empty");
return (T) getHeaders().get(name);
}
/**
* Returns a new {@link Builder}.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Returns a new {@link Builder}, initialized with the provided {@link JwaAlgorithm}.
*
* @param jwaAlgorithm the {@link JwaAlgorithm}
* @return the {@link Builder}
*/
public static Builder withAlgorithm(JwaAlgorithm jwaAlgorithm) {
return new Builder(jwaAlgorithm);
}
/**
* Returns a new {@link Builder}, initialized with the provided {@code headers}.
*
* @param headers the headers
* @return the {@link Builder}
*/
public static Builder from(JoseHeader headers) {
return new Builder(headers);
}
/**
* A builder for {@link JoseHeader}.
*/
public static final class Builder {
private final Map<String, Object> headers = new HashMap<>();
private Builder() {
}
private Builder(JwaAlgorithm jwaAlgorithm) {
algorithm(jwaAlgorithm);
}
private Builder(JoseHeader headers) {
Assert.notNull(headers, "headers cannot be null");
this.headers.putAll(headers.getHeaders());
}
/**
* Sets the {@link JwaAlgorithm JWA algorithm} used to digitally sign the JWS or encrypt the JWE.
*
* @param jwaAlgorithm the {@link JwaAlgorithm}
* @return the {@link Builder}
*/
public Builder algorithm(JwaAlgorithm jwaAlgorithm) {
Assert.notNull(jwaAlgorithm, "jwaAlgorithm cannot be null");
return header(JoseHeaderNames.ALG, jwaAlgorithm);
}
/**
* Sets the JWK Set URL that refers to the resource of a set of JSON-encoded public keys,
* one of which corresponds to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param jwkSetUrl the JWK Set URL
* @return the {@link Builder}
*/
public Builder jwkSetUrl(String jwkSetUrl) {
return header(JoseHeaderNames.JKU, convertAsURL(JoseHeaderNames.JKU, jwkSetUrl));
}
/**
* Sets the JSON Web Key which is the public key that corresponds to the key
* used to digitally sign the JWS or encrypt the JWE.
*
* @param jwk the JSON Web Key
* @return the {@link Builder}
*/
public Builder jwk(Map<String, Object> jwk) {
return header(JoseHeaderNames.JWK, jwk);
}
/**
* Sets the key ID that is a hint indicating which key was used to secure the JWS or JWE.
*
* @param keyId the key ID
* @return the {@link Builder}
*/
public Builder keyId(String keyId) {
return header(JoseHeaderNames.KID, keyId);
}
/**
* Sets the X.509 URL that refers to the resource for the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param x509Url the X.509 URL
* @return the {@link Builder}
*/
public Builder x509Url(String x509Url) {
return header(JoseHeaderNames.X5U, convertAsURL(JoseHeaderNames.X5U, x509Url));
}
/**
* Sets the X.509 certificate chain that contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign the JWS or
* encrypt the JWE. The certificate or certificate chain is represented as a
* {@code List} of certificate value {@code String}s. Each {@code String} in the
* {@code List} is a Base64-encoded DER PKIX certificate value.
*
* @param x509CertificateChain the X.509 certificate chain
* @return the {@link Builder}
*/
public Builder x509CertificateChain(List<String> x509CertificateChain) {
return header(JoseHeaderNames.X5C, x509CertificateChain);
}
/**
* Sets the X.509 certificate SHA-1 thumbprint that is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param x509SHA1Thumbprint the X.509 certificate SHA-1 thumbprint
* @return the {@link Builder}
*/
public Builder x509SHA1Thumbprint(String x509SHA1Thumbprint) {
return header(JoseHeaderNames.X5T, x509SHA1Thumbprint);
}
/**
* Sets the X.509 certificate SHA-256 thumbprint that is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign the JWS or encrypt the JWE.
*
* @param x509SHA256Thumbprint the X.509 certificate SHA-256 thumbprint
* @return the {@link Builder}
*/
public Builder x509SHA256Thumbprint(String x509SHA256Thumbprint) {
return header(JoseHeaderNames.X5T_S256, x509SHA256Thumbprint);
}
/**
* Sets the type header that declares the media type of the JWS/JWE.
*
* @param type the type header
* @return the {@link Builder}
*/
public Builder type(String type) {
return header(JoseHeaderNames.TYP, type);
}
/**
* Sets the content type header that declares the media type of the secured content (the payload).
*
* @param contentType the content type header
* @return the {@link Builder}
*/
public Builder contentType(String contentType) {
return header(JoseHeaderNames.CTY, contentType);
}
/**
* Sets the critical headers that indicates which extensions to the JWS/JWE/JWA specifications
* are being used that MUST be understood and processed.
*
* @param headerNames the critical header names
* @return the {@link Builder}
*/
public Builder critical(Set<String> headerNames) {
return header(JoseHeaderNames.CRIT, headerNames);
}
/**
* Sets the header.
*
* @param name the header name
* @param value the header value
* @return the {@link Builder}
*/
public Builder header(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.headers.put(name, value);
return this;
}
/**
* A {@code Consumer} to be provided access to the headers
* allowing the ability to add, replace, or remove.
*
* @param headersConsumer a {@code Consumer} of the headers
* @return the {@link Builder}
*/
public Builder headers(Consumer<Map<String, Object>> headersConsumer) {
headersConsumer.accept(this.headers);
return this;
}
/**
* Builds a new {@link JoseHeader}.
*
* @return a {@link JoseHeader}
*/
public JoseHeader build() {
Assert.notEmpty(this.headers, "headers cannot be empty");
return new JoseHeader(this.headers);
}
private static URL convertAsURL(String header, String value) {
URL convertedValue = ClaimConversionService.getSharedInstance().convert(value, URL.class);
Assert.isTrue(convertedValue != null,
() -> "Unable to convert header '" + header + "' of type '" + value.getClass() + "' to URL.");
return convertedValue;
}
}
}

View File

@@ -1,101 +0,0 @@
/*
* Copyright 2020-2022 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;
/**
* The Registered Header Parameter Names defined by the JSON Web Token (JWT),
* JSON Web Signature (JWS) and JSON Web Encryption (JWE) specifications
* that may be contained in the JOSE Header of a JWT.
*
* @author Anoop Garlapati
* @author Joe Grandja
* @since 0.0.1
* @see JoseHeader
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7519#section-5">JWT JOSE Header</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7515#section-4">JWS JOSE Header</a>
* @see <a target="_blank" href="https://tools.ietf.org/html/rfc7516#section-4">JWE JOSE Header</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public final class JoseHeaderNames {
/**
* {@code alg} - the algorithm header identifies the cryptographic algorithm used to secure a JWS or JWE
*/
public static final String ALG = "alg";
/**
* {@code jku} - the JWK Set URL header is a URI that refers to a resource for a set of JSON-encoded public keys,
* one of which corresponds to the key used to digitally sign a JWS or encrypt a JWE
*/
public static final String JKU = "jku";
/**
* {@code jwk} - the JSON Web Key header is the public key that corresponds to the key
* used to digitally sign a JWS or encrypt a JWE
*/
public static final String JWK = "jwk";
/**
* {@code kid} - the key ID header is a hint indicating which key was used to secure a JWS or JWE
*/
public static final String KID = "kid";
/**
* {@code x5u} - the X.509 URL header is a URI that refers to a resource for the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
public static final String X5U = "x5u";
/**
* {@code x5c} - the X.509 certificate chain header contains the X.509 public key certificate
* or certificate chain corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
public static final String X5C = "x5c";
/**
* {@code x5t} - the X.509 certificate SHA-1 thumbprint header is a base64url-encoded SHA-1 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
public static final String X5T = "x5t";
/**
* {@code x5t#S256} - the X.509 certificate SHA-256 thumbprint header is a base64url-encoded SHA-256 thumbprint (a.k.a. digest)
* of the DER encoding of the X.509 certificate corresponding to the key used to digitally sign a JWS or encrypt a JWE
*/
public static final String X5T_S256 = "x5t#S256";
/**
* {@code typ} - the type header is used by JWS/JWE applications to declare the media type of a JWS/JWE
*/
public static final String TYP = "typ";
/**
* {@code cty} - the content type header is used by JWS/JWE applications to declare the media type
* of the secured content (the payload)
*/
public static final String CTY = "cty";
/**
* {@code crit} - the critical header indicates that extensions to the JWS/JWE/JWA specifications
* are being used that MUST be understood and processed
*/
public static final String CRIT = "crit";
private JoseHeaderNames() {
}
}

View File

@@ -1,204 +0,0 @@
/*
* Copyright 2020-2022 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.net.URL;
import java.time.Instant;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import org.springframework.security.oauth2.core.converter.ClaimConversionService;
import org.springframework.util.Assert;
/**
* The {@link Jwt JWT} Claims Set is a JSON object representing the claims conveyed by a JSON Web Token.
*
* @author Anoop Garlapati
* @author Joe Grandja
* @since 0.0.1
* @see Jwt
* @see JwtClaimAccessor
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519#section-4">JWT Claims Set</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public final class JwtClaimsSet implements JwtClaimAccessor {
private final Map<String, Object> claims;
private JwtClaimsSet(Map<String, Object> claims) {
this.claims = Collections.unmodifiableMap(new HashMap<>(claims));
}
@Override
public Map<String, Object> getClaims() {
return this.claims;
}
/**
* Returns a new {@link Builder}.
*
* @return the {@link Builder}
*/
public static Builder builder() {
return new Builder();
}
/**
* Returns a new {@link Builder}, initialized with the provided {@code claims}.
*
* @param claims a JWT claims set
* @return the {@link Builder}
*/
public static Builder from(JwtClaimsSet claims) {
return new Builder(claims);
}
/**
* A builder for {@link JwtClaimsSet}.
*/
public static final class Builder {
private final Map<String, Object> claims = new HashMap<>();
private Builder() {
}
private Builder(JwtClaimsSet claims) {
Assert.notNull(claims, "claims cannot be null");
this.claims.putAll(claims.getClaims());
}
/**
* Sets the issuer {@code (iss)} claim, which identifies the principal that issued the JWT.
*
* @param issuer the issuer identifier
* @return the {@link Builder}
*/
public Builder issuer(String issuer) {
return claim(JwtClaimNames.ISS, issuer);
}
/**
* Sets the subject {@code (sub)} claim, which identifies the principal that is the subject of the JWT.
*
* @param subject the subject identifier
* @return the {@link Builder}
*/
public Builder subject(String subject) {
return claim(JwtClaimNames.SUB, subject);
}
/**
* Sets the audience {@code (aud)} claim, which identifies the recipient(s) that the JWT is intended for.
*
* @param audience the audience that this JWT is intended for
* @return the {@link Builder}
*/
public Builder audience(List<String> audience) {
return claim(JwtClaimNames.AUD, audience);
}
/**
* Sets the expiration time {@code (exp)} claim, which identifies the time
* on or after which the JWT MUST NOT be accepted for processing.
*
* @param expiresAt the time on or after which the JWT MUST NOT be accepted for processing
* @return the {@link Builder}
*/
public Builder expiresAt(Instant expiresAt) {
return claim(JwtClaimNames.EXP, expiresAt);
}
/**
* Sets the not before {@code (nbf)} claim, which identifies the time
* before which the JWT MUST NOT be accepted for processing.
*
* @param notBefore the time before which the JWT MUST NOT be accepted for processing
* @return the {@link Builder}
*/
public Builder notBefore(Instant notBefore) {
return claim(JwtClaimNames.NBF, notBefore);
}
/**
* Sets the issued at {@code (iat)} claim, which identifies the time at which the JWT was issued.
*
* @param issuedAt the time at which the JWT was issued
* @return the {@link Builder}
*/
public Builder issuedAt(Instant issuedAt) {
return claim(JwtClaimNames.IAT, issuedAt);
}
/**
* Sets the JWT ID {@code (jti)} claim, which provides a unique identifier for the JWT.
*
* @param jti the unique identifier for the JWT
* @return the {@link Builder}
*/
public Builder id(String jti) {
return claim(JwtClaimNames.JTI, jti);
}
/**
* Sets the claim.
*
* @param name the claim name
* @param value the claim value
* @return the {@link Builder}
*/
public Builder claim(String name, Object value) {
Assert.hasText(name, "name cannot be empty");
Assert.notNull(value, "value cannot be null");
this.claims.put(name, value);
return this;
}
/**
* A {@code Consumer} to be provided access to the claims
* allowing the ability to add, replace, or remove.
*
* @param claimsConsumer a {@code Consumer} of the claims
*/
public Builder claims(Consumer<Map<String, Object>> claimsConsumer) {
claimsConsumer.accept(this.claims);
return this;
}
/**
* Builds a new {@link JwtClaimsSet}.
*
* @return a {@link JwtClaimsSet}
*/
public JwtClaimsSet build() {
Assert.notEmpty(this.claims, "claims cannot be empty");
// The value of the 'iss' claim is a String or URL (StringOrURI).
// Attempt to convert to URL.
Object issuer = this.claims.get(JwtClaimNames.ISS);
if (issuer != null) {
URL convertedValue = ClaimConversionService.getSharedInstance().convert(issuer, URL.class);
if (convertedValue != null) {
this.claims.put(JwtClaimNames.ISS, convertedValue);
}
}
return new JwtClaimsSet(this.claims);
}
}
}

View File

@@ -1,56 +0,0 @@
/*
* Copyright 2020-2022 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;
/**
* Implementations of this interface are responsible for encoding
* a JSON Web Token (JWT) to it's compact claims representation format.
*
* <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 signing a JWS and/or encrypting a JWE.
*
* @author Anoop Garlapati
* @author Joe Grandja
* @since 0.0.1
* @see Jwt
* @see JoseHeader
* @see JwtClaimsSet
* @see JwtDecoder
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519">JSON Web Token (JWT)</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515">JSON Web Signature (JWS)</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516">JSON Web Encryption (JWE)</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">JWS Compact Serialization</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7516#section-3.1">JWE Compact Serialization</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
@FunctionalInterface
public interface JwtEncoder {
/**
* Encode the JWT to it's compact claims representation format.
*
* @param headers the JOSE header
* @param claims the JWT Claims Set
* @return a {@link Jwt}
* @throws JwtEncodingException if an error occurs while attempting to encode the JWT
*/
Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException;
}

View File

@@ -1,48 +0,0 @@
/*
* Copyright 2020-2022 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;
/**
* This exception is thrown when an error occurs
* while attempting to encode a JSON Web Token (JWT).
*
* @author Joe Grandja
* @since 0.0.1
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public class JwtEncodingException extends JwtException {
/**
* Constructs a {@code JwtEncodingException} using the provided parameters.
*
* @param message the detail message
*/
public JwtEncodingException(String message) {
super(message);
}
/**
* Constructs a {@code JwtEncodingException} using the provided parameters.
*
* @param message the detail message
* @param cause the root cause
*/
public JwtEncodingException(String message, Throwable cause) {
super(message, cause);
}
}

View File

@@ -1,344 +0,0 @@
/*
* Copyright 2020-2022 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.net.URI;
import java.net.URL;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import com.nimbusds.jose.JOSEException;
import com.nimbusds.jose.JOSEObjectType;
import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
import com.nimbusds.jose.JWSSigner;
import com.nimbusds.jose.crypto.factories.DefaultJWSSignerFactory;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKMatcher;
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.KeyType;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.jose.produce.JWSSignerFactory;
import com.nimbusds.jose.util.Base64;
import com.nimbusds.jose.util.Base64URL;
import com.nimbusds.jwt.JWTClaimsSet;
import com.nimbusds.jwt.SignedJWT;
import org.springframework.core.convert.converter.Converter;
import org.springframework.util.Assert;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
/**
* An implementation of a {@link JwtEncoder} that encodes a JSON Web Token (JWT) using the
* JSON Web Signature (JWS) Compact Serialization format. The private/secret key used for
* signing the JWS is supplied by the {@code com.nimbusds.jose.jwk.source.JWKSource}
* provided via the constructor.
*
* <p>
* <b>NOTE:</b> This implementation uses the Nimbus JOSE + JWT SDK.
*
* @author Joe Grandja
* @since 0.0.1
* @see JwtEncoder
* @see com.nimbusds.jose.jwk.source.JWKSource
* @see com.nimbusds.jose.jwk.JWK
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7519">JSON Web Token (JWT)</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515">JSON Web Signature (JWS)</a>
* @see <a target="_blank" href="https://datatracker.ietf.org/doc/html/rfc7515#section-3.1">JWS Compact Serialization</a>
* @see <a target="_blank" href="https://connect2id.com/products/nimbus-jose-jwt">Nimbus JOSE + JWT SDK</a>
* @deprecated See <a target="_blank" href="https://github.com/spring-projects/spring-authorization-server/issues/596">gh-596</a>
*/
@Deprecated
public final class NimbusJwsEncoder implements JwtEncoder {
private static final String ENCODING_ERROR_MESSAGE_TEMPLATE = "An error occurred while attempting to encode the Jwt: %s";
private static final Converter<JoseHeader, JWSHeader> JWS_HEADER_CONVERTER = new JwsHeaderConverter();
private static final Converter<JwtClaimsSet, JWTClaimsSet> JWT_CLAIMS_SET_CONVERTER = new JwtClaimsSetConverter();
private static final JWSSignerFactory JWS_SIGNER_FACTORY = new DefaultJWSSignerFactory();
private final Map<JWK, JWSSigner> jwsSigners = new ConcurrentHashMap<>();
private final JWKSource<SecurityContext> jwkSource;
/**
* Constructs a {@code NimbusJwsEncoder} using the provided parameters.
* @param jwkSource the {@code com.nimbusds.jose.jwk.source.JWKSource}
*/
public NimbusJwsEncoder(JWKSource<SecurityContext> jwkSource) {
Assert.notNull(jwkSource, "jwkSource cannot be null");
this.jwkSource = jwkSource;
}
@Override
public Jwt encode(JoseHeader headers, JwtClaimsSet claims) throws JwtEncodingException {
Assert.notNull(headers, "headers cannot be null");
Assert.notNull(claims, "claims cannot be null");
JWK jwk = selectJwk(headers);
headers = addKeyIdentifierHeadersIfNecessary(headers, jwk);
String jws = serialize(headers, claims, jwk);
return new Jwt(jws, claims.getIssuedAt(), claims.getExpiresAt(), headers.getHeaders(), claims.getClaims());
}
private JWK selectJwk(JoseHeader headers) {
List<JWK> jwks;
try {
JWKSelector jwkSelector = new JWKSelector(createJwkMatcher(headers));
jwks = this.jwkSource.get(jwkSelector, null);
} catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to select a JWK signing key -> " + ex.getMessage()), ex);
}
if (jwks.size() > 1) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Found multiple JWK signing keys for algorithm '" + headers.getAlgorithm().getName() + "'"));
}
if (jwks.isEmpty()) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to select a JWK signing key"));
}
return jwks.get(0);
}
private String serialize(JoseHeader headers, JwtClaimsSet claims, JWK jwk) {
JWSHeader jwsHeader = JWS_HEADER_CONVERTER.convert(headers);
JWTClaimsSet jwtClaimsSet = JWT_CLAIMS_SET_CONVERTER.convert(claims);
JWSSigner jwsSigner = this.jwsSigners.computeIfAbsent(jwk, NimbusJwsEncoder::createSigner);
SignedJWT signedJwt = new SignedJWT(jwsHeader, jwtClaimsSet);
try {
signedJwt.sign(jwsSigner);
} catch (JOSEException ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to sign the JWT -> " + ex.getMessage()), ex);
}
return signedJwt.serialize();
}
private static JWKMatcher createJwkMatcher(JoseHeader headers) {
JWSAlgorithm jwsAlgorithm = JWSAlgorithm.parse(headers.getAlgorithm().getName());
if (JWSAlgorithm.Family.RSA.contains(jwsAlgorithm) || JWSAlgorithm.Family.EC.contains(jwsAlgorithm)) {
// @formatter:off
return new JWKMatcher.Builder()
.keyType(KeyType.forAlgorithm(jwsAlgorithm))
.keyID(headers.getKeyId())
.keyUses(KeyUse.SIGNATURE, null)
.algorithms(jwsAlgorithm, null)
.x509CertSHA256Thumbprint(Base64URL.from(headers.getX509SHA256Thumbprint()))
.build();
// @formatter:on
} else if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) {
// @formatter:off
return new JWKMatcher.Builder()
.keyType(KeyType.forAlgorithm(jwsAlgorithm))
.keyID(headers.getKeyId())
.privateOnly(true)
.algorithms(jwsAlgorithm, null)
.build();
// @formatter:on
}
return null;
}
private static JoseHeader addKeyIdentifierHeadersIfNecessary(JoseHeader headers, JWK jwk) {
// Check if headers have already been added
if (StringUtils.hasText(headers.getKeyId()) && StringUtils.hasText(headers.getX509SHA256Thumbprint())) {
return headers;
}
// Check if headers can be added from JWK
if (!StringUtils.hasText(jwk.getKeyID()) && jwk.getX509CertSHA256Thumbprint() == null) {
return headers;
}
JoseHeader.Builder headersBuilder = JoseHeader.from(headers);
if (!StringUtils.hasText(headers.getKeyId()) && StringUtils.hasText(jwk.getKeyID())) {
headersBuilder.keyId(jwk.getKeyID());
}
if (!StringUtils.hasText(headers.getX509SHA256Thumbprint()) && jwk.getX509CertSHA256Thumbprint() != null) {
headersBuilder.x509SHA256Thumbprint(jwk.getX509CertSHA256Thumbprint().toString());
}
return headersBuilder.build();
}
private static JWSSigner createSigner(JWK jwk) {
try {
return JWS_SIGNER_FACTORY.createJWSSigner(jwk);
} catch (JOSEException ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Failed to create a JWS Signer -> " + ex.getMessage()), ex);
}
}
private static class JwsHeaderConverter implements Converter<JoseHeader, JWSHeader> {
@Override
public JWSHeader convert(JoseHeader headers) {
JWSHeader.Builder builder = new JWSHeader.Builder(JWSAlgorithm.parse(headers.getAlgorithm().getName()));
if (headers.getJwkSetUrl() != null) {
builder.jwkURL(convertAsURI(JoseHeaderNames.JKU, headers.getJwkSetUrl()));
}
Map<String, Object> jwk = headers.getJwk();
if (!CollectionUtils.isEmpty(jwk)) {
try {
builder.jwk(JWK.parse(jwk));
} catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Unable to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex);
}
}
String keyId = headers.getKeyId();
if (StringUtils.hasText(keyId)) {
builder.keyID(keyId);
}
if (headers.getX509Url() != null) {
builder.x509CertURL(convertAsURI(JoseHeaderNames.X5U, headers.getX509Url()));
}
List<String> x509CertificateChain = headers.getX509CertificateChain();
if (!CollectionUtils.isEmpty(x509CertificateChain)) {
List<Base64> x5cList = new ArrayList<>();
x509CertificateChain.forEach((x5c) -> x5cList.add(new Base64(x5c)));
if (!x5cList.isEmpty()) {
builder.x509CertChain(x5cList);
}
}
String x509SHA1Thumbprint = headers.getX509SHA1Thumbprint();
if (StringUtils.hasText(x509SHA1Thumbprint)) {
builder.x509CertThumbprint(new Base64URL(x509SHA1Thumbprint));
}
String x509SHA256Thumbprint = headers.getX509SHA256Thumbprint();
if (StringUtils.hasText(x509SHA256Thumbprint)) {
builder.x509CertSHA256Thumbprint(new Base64URL(x509SHA256Thumbprint));
}
String type = headers.getType();
if (StringUtils.hasText(type)) {
builder.type(new JOSEObjectType(type));
}
String contentType = headers.getContentType();
if (StringUtils.hasText(contentType)) {
builder.contentType(contentType);
}
Set<String> critical = headers.getCritical();
if (!CollectionUtils.isEmpty(critical)) {
builder.criticalParams(critical);
}
Map<String, Object> customHeaders = new HashMap<>();
headers.getHeaders().forEach((name, value) -> {
if (!JWSHeader.getRegisteredParameterNames().contains(name)) {
customHeaders.put(name, value);
}
});
if (!customHeaders.isEmpty()) {
builder.customParams(customHeaders);
}
return builder.build();
}
private static URI convertAsURI(String header, URL url) {
try {
return url.toURI();
} catch (Exception ex) {
throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE,
"Unable to convert '" + header + "' JOSE header to a URI"), ex);
}
}
}
private static class JwtClaimsSetConverter implements Converter<JwtClaimsSet, JWTClaimsSet> {
@Override
public JWTClaimsSet convert(JwtClaimsSet claims) {
JWTClaimsSet.Builder builder = new JWTClaimsSet.Builder();
// NOTE: The value of the 'iss' claim is a String or URL (StringOrURI).
Object issuer = claims.getClaim(JwtClaimNames.ISS);
if (issuer != null) {
builder.issuer(issuer.toString());
}
String subject = claims.getSubject();
if (StringUtils.hasText(subject)) {
builder.subject(subject);
}
List<String> audience = claims.getAudience();
if (!CollectionUtils.isEmpty(audience)) {
builder.audience(audience);
}
Instant expiresAt = claims.getExpiresAt();
if (expiresAt != null) {
builder.expirationTime(Date.from(expiresAt));
}
Instant notBefore = claims.getNotBefore();
if (notBefore != null) {
builder.notBeforeTime(Date.from(notBefore));
}
Instant issuedAt = claims.getIssuedAt();
if (issuedAt != null) {
builder.issueTime(Date.from(issuedAt));
}
String jwtId = claims.getId();
if (StringUtils.hasText(jwtId)) {
builder.jwtID(jwtId);
}
Map<String, Object> customClaims = new HashMap<>();
claims.getClaims().forEach((name, value) -> {
if (!JWTClaimsSet.getRegisteredNames().contains(name)) {
customClaims.put(name, value);
}
});
if (!customClaims.isEmpty()) {
customClaims.forEach(builder::claim);
}
return builder.build();
}
}
}

View File

@@ -21,10 +21,11 @@ import java.util.Map;
import java.util.function.Consumer;
import org.springframework.lang.Nullable;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.util.Assert;
/**
@@ -33,9 +34,9 @@ import org.springframework.util.Assert;
* @author Joe Grandja
* @since 0.1.0
* @see OAuth2TokenContext
* @see JoseHeader.Builder
* @see JwsHeader.Builder
* @see JwtClaimsSet.Builder
* @see JwtEncoder#encode(JoseHeader, JwtClaimsSet)
* @see JwtEncoder#encode(JwtEncoderParameters)
*/
public final class JwtEncodingContext implements OAuth2TokenContext {
private final Map<Object, Object> context;
@@ -58,13 +59,13 @@ public final class JwtEncodingContext implements OAuth2TokenContext {
}
/**
* Returns the {@link JoseHeader.Builder headers}
* Returns the {@link JwsHeader.Builder headers}
* allowing the ability to add, replace, or remove.
*
* @return the {@link JoseHeader.Builder}
* @return the {@link JwsHeader.Builder}
*/
public JoseHeader.Builder getHeaders() {
return get(JoseHeader.Builder.class);
public JwsHeader.Builder getHeaders() {
return get(JwsHeader.Builder.class);
}
/**
@@ -84,7 +85,7 @@ public final class JwtEncodingContext implements OAuth2TokenContext {
* @param claimsBuilder the claims to initialize the builder
* @return the {@link Builder}
*/
public static Builder with(JoseHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
public static Builder with(JwsHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
return new Builder(headersBuilder, claimsBuilder);
}
@@ -93,24 +94,24 @@ public final class JwtEncodingContext implements OAuth2TokenContext {
*/
public static final class Builder extends AbstractBuilder<JwtEncodingContext, Builder> {
private Builder(JoseHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
private Builder(JwsHeader.Builder headersBuilder, JwtClaimsSet.Builder claimsBuilder) {
Assert.notNull(headersBuilder, "headersBuilder cannot be null");
Assert.notNull(claimsBuilder, "claimsBuilder cannot be null");
put(JoseHeader.Builder.class, headersBuilder);
put(JwsHeader.Builder.class, headersBuilder);
put(JwtClaimsSet.Builder.class, claimsBuilder);
}
/**
* A {@code Consumer} of the {@link JoseHeader.Builder headers}
* A {@code Consumer} of the {@link JwsHeader.Builder headers}
* allowing the ability to add, replace, or remove.
*
* @deprecated Use {@link #getHeaders()} instead
* @param headersConsumer a {@code Consumer} of the {@link JoseHeader.Builder headers}
* @param headersConsumer a {@code Consumer} of the {@link JwsHeader.Builder headers}
* @return the {@link Builder} for further configuration
*/
@Deprecated
public Builder headers(Consumer<JoseHeader.Builder> headersConsumer) {
headersConsumer.accept(get(JoseHeader.Builder.class));
public Builder headers(Consumer<JwsHeader.Builder> headersConsumer) {
headersConsumer.accept(get(JwsHeader.Builder.class));
return this;
}

View File

@@ -30,10 +30,11 @@ import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
@@ -127,7 +128,7 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
}
// @formatter:on
JoseHeader.Builder headersBuilder = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256);
JwsHeader.Builder headersBuilder = JwsHeader.with(SignatureAlgorithm.RS256);
if (this.jwtCustomizer != null) {
// @formatter:off
@@ -150,10 +151,10 @@ public final class JwtGenerator implements OAuth2TokenGenerator<Jwt> {
this.jwtCustomizer.customize(jwtContext);
}
JoseHeader headers = headersBuilder.build();
JwsHeader headers = headersBuilder.build();
JwtClaimsSet claims = claimsBuilder.build();
Jwt jwt = this.jwtEncoder.encode(headers, claims);
Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claims));
return jwt;
}

View File

@@ -81,7 +81,7 @@ import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationConsentService;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
@@ -161,7 +161,7 @@ public class OAuth2AuthorizationCodeGrantTests {
private static EmbeddedDatabase db;
private static JWKSource<SecurityContext> jwkSource;
private static NimbusJwsEncoder jwtEncoder;
private static NimbusJwtEncoder jwtEncoder;
private static ProviderSettings providerSettings;
private static HttpMessageConverter<OAuth2AccessTokenResponse> accessTokenHttpResponseConverter =
new OAuth2AccessTokenResponseHttpMessageConverter();
@@ -197,7 +197,7 @@ public class OAuth2AuthorizationCodeGrantTests {
public static void init() {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
jwtEncoder = new NimbusJwsEncoder(jwkSource);
jwtEncoder = new NimbusJwtEncoder(jwkSource);
providerSettings = ProviderSettings.builder()
.authorizationEndpoint("/test/authorize")
.tokenEndpoint("/test/token")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 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.
@@ -64,12 +64,13 @@ import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
@@ -135,7 +136,7 @@ public class OidcClientRegistrationTests {
JWKSet jwkSet = new JWKSet(TestJwks.DEFAULT_RSA_JWK);
jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
clientJwkSet = new JWKSet(TestJwks.generateRsaJwk().build());
jwtClientAssertionEncoder = new NimbusJwsEncoder((jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet));
jwtClientAssertionEncoder = new NimbusJwtEncoder((jwkSelector, securityContext) -> jwkSelector.select(clientJwkSet));
db = new EmbeddedDatabaseBuilder()
.generateUniqueName(true)
.setType(EmbeddedDatabaseType.HSQL)
@@ -280,12 +281,12 @@ public class OidcClientRegistrationTests {
this.registeredClientRepository.save(clientRegistrar);
// @formatter:off
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256)
JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256)
.build();
JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(clientRegistrar)
.build();
// @formatter:on
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(joseHeader, jwtClaimsSet);
Jwt jwtAssertion = jwtClientAssertionEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
MvcResult mvcResult = this.mvc.perform(post(DEFAULT_TOKEN_ENDPOINT_URI)
.param(OAuth2ParameterNames.GRANT_TYPE, AuthorizationGrantType.CLIENT_CREDENTIALS.getValue())

View File

@@ -69,17 +69,13 @@ import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.JdbcOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository;
import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository.RegisteredClientParametersMapper;
@@ -88,6 +84,10 @@ import org.springframework.security.oauth2.server.authorization.client.Registere
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.jackson2.TestingAuthenticationTokenMixin;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.web.servlet.MockMvc;
@@ -415,7 +415,7 @@ public class OidcTests {
@Bean
OAuth2TokenGenerator<?> tokenGenerator() {
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwsEncoder(jwkSource()));
JwtGenerator jwtGenerator = new JwtGenerator(new NimbusJwtEncoder(jwkSource()));
jwtGenerator.setJwtCustomizer(jwtCustomizer());
OAuth2RefreshTokenGenerator refreshTokenGenerator = new OAuth2RefreshTokenGenerator();
OAuth2TokenGenerator<OAuth2Token> delegatingTokenGenerator =

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 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.
@@ -45,12 +45,13 @@ import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.InMemoryOAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -207,13 +208,13 @@ public class OidcUserInfoTests {
}
private OAuth2Authorization createAuthorization() {
JoseHeader headers = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
JwsHeader headers = JwsHeader.with(SignatureAlgorithm.RS256).build();
// @formatter:off
JwtClaimsSet claimSet = JwtClaimsSet.builder()
.claims(claims -> claims.putAll(createUserInfo().getClaims()))
.build();
// @formatter:on
Jwt jwt = this.jwtEncoder.encode(headers, claimSet);
Jwt jwt = this.jwtEncoder.encode(JwtEncoderParameters.from(headers, claimSet));
Instant now = Instant.now();
Set<String> scopes = new HashSet<>(Arrays.asList(
@@ -371,7 +372,7 @@ public class OidcUserInfoTests {
@Bean
JwtEncoder jwtEncoder(JWKSource<SecurityContext> jwkSource) {
return new NimbusJwsEncoder(jwkSource);
return new NimbusJwtEncoder(jwkSource);
}
@Bean

View File

@@ -1,108 +0,0 @@
/*
* Copyright 2020-2021 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 org.junit.Test;
import org.springframework.security.oauth2.jose.JwaAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link JoseHeader}.
*
* @author Joe Grandja
*/
public class JoseHeaderTests {
@Test
public void withAlgorithmWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JoseHeader.withAlgorithm(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("jwaAlgorithm cannot be null");
}
@Test
public void buildWhenAllHeadersProvidedThenAllHeadersAreSet() {
JoseHeader expectedJoseHeader = TestJoseHeaders.joseHeader().build();
JoseHeader joseHeader = JoseHeader.withAlgorithm(expectedJoseHeader.getAlgorithm())
.jwkSetUrl(expectedJoseHeader.getJwkSetUrl().toExternalForm())
.jwk(expectedJoseHeader.getJwk())
.keyId(expectedJoseHeader.getKeyId())
.x509Url(expectedJoseHeader.getX509Url().toExternalForm())
.x509CertificateChain(expectedJoseHeader.getX509CertificateChain())
.x509SHA1Thumbprint(expectedJoseHeader.getX509SHA1Thumbprint())
.x509SHA256Thumbprint(expectedJoseHeader.getX509SHA256Thumbprint())
.type(expectedJoseHeader.getType())
.contentType(expectedJoseHeader.getContentType())
.headers(headers -> headers.put("custom-header-name", "custom-header-value"))
.build();
assertThat(joseHeader.<JwaAlgorithm>getAlgorithm()).isEqualTo(expectedJoseHeader.getAlgorithm());
assertThat(joseHeader.getJwkSetUrl()).isEqualTo(expectedJoseHeader.getJwkSetUrl());
assertThat(joseHeader.getJwk()).isEqualTo(expectedJoseHeader.getJwk());
assertThat(joseHeader.getKeyId()).isEqualTo(expectedJoseHeader.getKeyId());
assertThat(joseHeader.getX509Url()).isEqualTo(expectedJoseHeader.getX509Url());
assertThat(joseHeader.getX509CertificateChain()).isEqualTo(expectedJoseHeader.getX509CertificateChain());
assertThat(joseHeader.getX509SHA1Thumbprint()).isEqualTo(expectedJoseHeader.getX509SHA1Thumbprint());
assertThat(joseHeader.getX509SHA256Thumbprint()).isEqualTo(expectedJoseHeader.getX509SHA256Thumbprint());
assertThat(joseHeader.getCritical()).isEqualTo(expectedJoseHeader.getCritical());
assertThat(joseHeader.getType()).isEqualTo(expectedJoseHeader.getType());
assertThat(joseHeader.getContentType()).isEqualTo(expectedJoseHeader.getContentType());
assertThat(joseHeader.<String>getHeader("custom-header-name")).isEqualTo("custom-header-value");
assertThat(joseHeader.getHeaders()).isEqualTo(expectedJoseHeader.getHeaders());
}
@Test
public void fromWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JoseHeader.from(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("headers cannot be null");
}
@Test
public void fromWhenHeadersProvidedThenCopied() {
JoseHeader expectedJoseHeader = TestJoseHeaders.joseHeader().build();
JoseHeader joseHeader = JoseHeader.from(expectedJoseHeader).build();
assertThat(joseHeader.getHeaders()).isEqualTo(expectedJoseHeader.getHeaders());
}
@Test
public void headerWhenNameNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).header(null, "value"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be empty");
}
@Test
public void headerWhenValueNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).header("name", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("value cannot be null");
}
@Test
public void getHeaderWhenNullThenThrowIllegalArgumentException() {
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
assertThatThrownBy(() -> joseHeader.getHeader(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be empty");
}
}

View File

@@ -1,90 +0,0 @@
/*
* Copyright 2020 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 org.junit.Test;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
/**
* Tests for {@link JwtClaimsSet}.
*
* @author Joe Grandja
*/
public class JwtClaimsSetTests {
@Test
public void buildWhenClaimsEmptyThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtClaimsSet.builder().build())
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("claims cannot be empty");
}
@Test
public void buildWhenAllClaimsProvidedThenAllClaimsAreSet() {
JwtClaimsSet expectedJwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
.issuer(expectedJwtClaimsSet.getIssuer().toExternalForm())
.subject(expectedJwtClaimsSet.getSubject())
.audience(expectedJwtClaimsSet.getAudience())
.issuedAt(expectedJwtClaimsSet.getIssuedAt())
.notBefore(expectedJwtClaimsSet.getNotBefore())
.expiresAt(expectedJwtClaimsSet.getExpiresAt())
.id(expectedJwtClaimsSet.getId())
.claims(claims -> claims.put("custom-claim-name", "custom-claim-value"))
.build();
assertThat(jwtClaimsSet.getIssuer()).isEqualTo(expectedJwtClaimsSet.getIssuer());
assertThat(jwtClaimsSet.getSubject()).isEqualTo(expectedJwtClaimsSet.getSubject());
assertThat(jwtClaimsSet.getAudience()).isEqualTo(expectedJwtClaimsSet.getAudience());
assertThat(jwtClaimsSet.getIssuedAt()).isEqualTo(expectedJwtClaimsSet.getIssuedAt());
assertThat(jwtClaimsSet.getNotBefore()).isEqualTo(expectedJwtClaimsSet.getNotBefore());
assertThat(jwtClaimsSet.getExpiresAt()).isEqualTo(expectedJwtClaimsSet.getExpiresAt());
assertThat(jwtClaimsSet.getId()).isEqualTo(expectedJwtClaimsSet.getId());
assertThat(jwtClaimsSet.<String>getClaim("custom-claim-name")).isEqualTo("custom-claim-value");
assertThat(jwtClaimsSet.getClaims()).isEqualTo(expectedJwtClaimsSet.getClaims());
}
@Test
public void fromWhenNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtClaimsSet.from(null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("claims cannot be null");
}
@Test
public void fromWhenClaimsProvidedThenCopied() {
JwtClaimsSet expectedJwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.from(expectedJwtClaimsSet).build();
assertThat(jwtClaimsSet.getClaims()).isEqualTo(expectedJwtClaimsSet.getClaims());
}
@Test
public void claimWhenNameNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtClaimsSet.builder().claim(null, "value"))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("name cannot be empty");
}
@Test
public void claimWhenValueNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtClaimsSet.builder().claim("name", null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("value cannot be null");
}
}

View File

@@ -1,275 +0,0 @@
/*
* Copyright 2020-2021 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.interfaces.ECPrivateKey;
import java.security.interfaces.ECPublicKey;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import com.nimbusds.jose.KeySourceException;
import com.nimbusds.jose.jwk.ECKey;
import com.nimbusds.jose.jwk.JWK;
import com.nimbusds.jose.jwk.JWKSelector;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.KeyUse;
import com.nimbusds.jose.jwk.OctetSequenceKey;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import org.junit.Before;
import org.junit.Test;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import org.springframework.security.oauth2.jose.TestJwks;
import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.BDDMockito.willAnswer;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
/**
* Tests for {@link NimbusJwsEncoder}.
*
* @author Joe Grandja
*/
public class NimbusJwsEncoderTests {
private List<JWK> jwkList;
private JWKSource<SecurityContext> jwkSource;
private NimbusJwsEncoder jwsEncoder;
@Before
public void setUp() {
this.jwkList = new ArrayList<>();
this.jwkSource = (jwkSelector, securityContext) -> jwkSelector.select(new JWKSet(this.jwkList));
this.jwsEncoder = new NimbusJwsEncoder(this.jwkSource);
}
@Test
public void constructorWhenJwkSourceNullThenThrowIllegalArgumentException() {
assertThatIllegalArgumentException().isThrownBy(() -> new NimbusJwsEncoder(null))
.withMessage("jwkSource cannot be null");
}
@Test
public void encodeWhenHeadersNullThenThrowIllegalArgumentException() {
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.encode(null, jwtClaimsSet))
.withMessage("headers cannot be null");
}
@Test
public void encodeWhenClaimsNullThenThrowIllegalArgumentException() {
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
assertThatIllegalArgumentException().isThrownBy(() -> this.jwsEncoder.encode(joseHeader, null))
.withMessage("claims cannot be null");
}
@Test
public void encodeWhenJwkSelectFailedThenThrowJwtEncodingException() throws Exception {
this.jwkSource = mock(JWKSource.class);
this.jwsEncoder = new NimbusJwsEncoder(this.jwkSource);
given(this.jwkSource.get(any(), any())).willThrow(new KeySourceException("key source error"));
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
assertThatExceptionOfType(JwtEncodingException.class)
.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
.withMessageContaining("Failed to select a JWK signing key -> key source error");
}
@Test
public void encodeWhenJwkMultipleSelectedThenThrowJwtEncodingException() throws Exception {
RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
this.jwkList.add(rsaJwk);
this.jwkList.add(rsaJwk);
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
assertThatExceptionOfType(JwtEncodingException.class)
.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
.withMessageContaining("Found multiple JWK signing keys for algorithm 'RS256'");
}
@Test
public void encodeWhenJwkSelectEmptyThenThrowJwtEncodingException() {
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
assertThatExceptionOfType(JwtEncodingException.class)
.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet))
.withMessageContaining("Failed to select a JWK signing key");
}
@Test
public void encodeWhenJwkUseEncryptionThenThrowJwtEncodingException() throws Exception {
// @formatter:off
RSAKey rsaJwk = TestJwks.jwk(TestKeys.DEFAULT_PUBLIC_KEY, TestKeys.DEFAULT_PRIVATE_KEY)
.keyUse(KeyUse.ENCRYPTION)
.build();
// @formatter:on
this.jwkSource = mock(JWKSource.class);
this.jwsEncoder = new NimbusJwsEncoder(this.jwkSource);
given(this.jwkSource.get(any(), any())).willReturn(Collections.singletonList(rsaJwk));
JoseHeader joseHeader = TestJoseHeaders.joseHeader().build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
assertThatExceptionOfType(JwtEncodingException.class)
.isThrownBy(() -> this.jwsEncoder.encode(joseHeader, jwtClaimsSet)).withMessageContaining(
"Failed to create a JWS Signer -> The JWK use must be sig (signature) or unspecified");
}
@Test
public void encodeWhenSuccessThenDecodes() throws Exception {
RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK;
this.jwkList.add(rsaJwk);
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
Jwt encodedJws = this.jwsEncoder.encode(joseHeader, jwtClaimsSet);
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.ALG)).isEqualTo(joseHeader.getAlgorithm());
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.JKU)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.JWK)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.KID)).isEqualTo(rsaJwk.getKeyID());
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.X5U)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.X5C)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.X5T)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.X5T_S256)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.TYP)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.CTY)).isNull();
assertThat(encodedJws.getHeaders().get(JoseHeaderNames.CRIT)).isNull();
assertThat(encodedJws.getIssuer()).isEqualTo(jwtClaimsSet.getIssuer());
assertThat(encodedJws.getSubject()).isEqualTo(jwtClaimsSet.getSubject());
assertThat(encodedJws.getAudience()).isEqualTo(jwtClaimsSet.getAudience());
assertThat(encodedJws.getExpiresAt()).isEqualTo(jwtClaimsSet.getExpiresAt());
assertThat(encodedJws.getNotBefore()).isEqualTo(jwtClaimsSet.getNotBefore());
assertThat(encodedJws.getIssuedAt()).isEqualTo(jwtClaimsSet.getIssuedAt());
assertThat(encodedJws.getId()).isEqualTo(jwtClaimsSet.getId());
assertThat(encodedJws.<String>getClaim("custom-claim-name")).isEqualTo("custom-claim-value");
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(rsaJwk.toRSAPublicKey()).build();
jwtDecoder.decode(encodedJws.getTokenValue());
}
@Test
public void encodeWhenKeysRotatedThenNewKeyUsed() throws Exception {
TestJWKSource jwkSource = new TestJWKSource();
JWKSource<SecurityContext> jwkSourceDelegate = spy(new JWKSource<SecurityContext>() {
@Override
public List<JWK> get(JWKSelector jwkSelector, SecurityContext context) {
return jwkSource.get(jwkSelector, context);
}
});
NimbusJwsEncoder jwsEncoder = new NimbusJwsEncoder(jwkSourceDelegate);
JwkListResultCaptor jwkListResultCaptor = new JwkListResultCaptor();
willAnswer(jwkListResultCaptor).given(jwkSourceDelegate).get(any(), any());
JoseHeader joseHeader = JoseHeader.withAlgorithm(SignatureAlgorithm.RS256).build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build();
Jwt encodedJws = jwsEncoder.encode(joseHeader, jwtClaimsSet);
JWK jwk1 = jwkListResultCaptor.getResult().get(0);
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(((RSAKey) jwk1).toRSAPublicKey()).build();
jwtDecoder.decode(encodedJws.getTokenValue());
jwkSource.rotate(); // Trigger key rotation
encodedJws = jwsEncoder.encode(joseHeader, jwtClaimsSet);
JWK jwk2 = jwkListResultCaptor.getResult().get(0);
jwtDecoder = NimbusJwtDecoder.withPublicKey(((RSAKey) jwk2).toRSAPublicKey()).build();
jwtDecoder.decode(encodedJws.getTokenValue());
assertThat(jwk1.getKeyID()).isNotEqualTo(jwk2.getKeyID());
}
private static final class JwkListResultCaptor implements Answer<List<JWK>> {
private List<JWK> result;
private List<JWK> getResult() {
return this.result;
}
@SuppressWarnings("unchecked")
@Override
public List<JWK> answer(InvocationOnMock invocationOnMock) throws Throwable {
this.result = (List<JWK>) invocationOnMock.callRealMethod();
return this.result;
}
}
private static final class TestJWKSource implements JWKSource<SecurityContext> {
private int keyId = 1000;
private JWKSet jwkSet;
private TestJWKSource() {
init();
}
@Override
public List<JWK> get(JWKSelector jwkSelector, SecurityContext context) {
return jwkSelector.select(this.jwkSet);
}
private void init() {
// @formatter:off
RSAKey rsaJwk = TestJwks.jwk(TestKeys.DEFAULT_PUBLIC_KEY, TestKeys.DEFAULT_PRIVATE_KEY)
.keyID("rsa-jwk-" + this.keyId++)
.build();
ECKey ecJwk = TestJwks.jwk((ECPublicKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPublic(), (ECPrivateKey) TestKeys.DEFAULT_EC_KEY_PAIR.getPrivate())
.keyID("ec-jwk-" + this.keyId++)
.build();
OctetSequenceKey secretJwk = TestJwks.jwk(TestKeys.DEFAULT_SECRET_KEY)
.keyID("secret-jwk-" + this.keyId++)
.build();
// @formatter:on
this.jwkSet = new JWKSet(Arrays.asList(rsaJwk, ecJwk, secretJwk));
}
private void rotate() {
init();
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 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,18 +24,18 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
/**
* @author Joe Grandja
*/
public final class TestJoseHeaders {
public final class TestJwsHeaders {
private TestJoseHeaders() {
private TestJwsHeaders() {
}
public static JoseHeader.Builder joseHeader() {
return joseHeader(SignatureAlgorithm.RS256);
public static JwsHeader.Builder jwsHeader() {
return jwsHeader(SignatureAlgorithm.RS256);
}
public static JoseHeader.Builder joseHeader(SignatureAlgorithm signatureAlgorithm) {
public static JwsHeader.Builder jwsHeader(SignatureAlgorithm signatureAlgorithm) {
// @formatter:off
return JoseHeader.withAlgorithm(signatureAlgorithm)
return JwsHeader.with(signatureAlgorithm)
.jwkSetUrl("https://provider.com/oauth2/jwks")
.jwk(rsaJwk())
.keyId("keyId")

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 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.
@@ -23,9 +23,9 @@ import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.ClientAuthenticationMethod;
import org.springframework.security.oauth2.core.OAuth2TokenType;
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwsHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationCodeAuthenticationToken;
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken;
@@ -52,7 +52,7 @@ public class JwtEncodingContextTests {
@Test
public void withWhenClaimsNullThenThrowIllegalArgumentException() {
assertThatThrownBy(() -> JwtEncodingContext.with(TestJoseHeaders.joseHeader(), null))
assertThatThrownBy(() -> JwtEncodingContext.with(TestJwsHeaders.jwsHeader(), null))
.isInstanceOf(IllegalArgumentException.class)
.hasMessage("claimsBuilder cannot be null");
}
@@ -60,7 +60,7 @@ public class JwtEncodingContextTests {
@Test
public void setWhenValueNullThenThrowIllegalArgumentException() {
JwtEncodingContext.Builder builder = JwtEncodingContext
.with(TestJoseHeaders.joseHeader(), TestJwtClaimsSets.jwtClaimsSet());
.with(TestJwsHeaders.jwsHeader(), TestJwtClaimsSets.jwtClaimsSet());
assertThatThrownBy(() -> builder.registeredClient(null))
.isInstanceOf(IllegalArgumentException.class);
assertThatThrownBy(() -> builder.principal(null))
@@ -79,7 +79,7 @@ public class JwtEncodingContextTests {
@Test
public void buildWhenAllValuesProvidedThenAllValuesAreSet() {
JoseHeader.Builder headers = TestJoseHeaders.joseHeader();
JwsHeader.Builder headers = TestJwsHeaders.jwsHeader();
JwtClaimsSet.Builder claims = TestJwtClaimsSets.jwtClaimsSet();
RegisteredClient registeredClient = TestRegisteredClients.registeredClient().build();
TestingAuthenticationToken principal = new TestingAuthenticationToken("principal", "password");

View File

@@ -44,12 +44,13 @@ import org.springframework.security.oauth2.jose.TestKeys;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.BadJwtException;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.JwtValidationException;
import org.springframework.security.oauth2.jwt.NimbusJwsEncoder;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
@@ -303,7 +304,7 @@ public class JwtClientAssertionAuthenticationProviderTests {
.thenReturn(registeredClient);
// @formatter:off
JoseHeader joseHeader = JoseHeader.withAlgorithm(MacAlgorithm.HS256)
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256)
.build();
JwtClaimsSet jwtClaimsSet = JwtClaimsSet.builder()
.issuer("invalid-iss")
@@ -313,7 +314,7 @@ public class JwtClientAssertionAuthenticationProviderTests {
// @formatter:on
JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256");
Jwt jwtAssertion = jwsEncoder.encode(joseHeader, jwtClaimsSet);
Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion.getTokenValue(), null);
@@ -346,14 +347,14 @@ public class JwtClientAssertionAuthenticationProviderTests {
.thenReturn(registeredClient);
// @formatter:off
JoseHeader joseHeader = JoseHeader.withAlgorithm(MacAlgorithm.HS256)
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256)
.build();
JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(registeredClient)
.build();
// @formatter:on
JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256");
Jwt jwtAssertion = jwsEncoder.encode(joseHeader, jwtClaimsSet);
Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion.getTokenValue(), null);
@@ -392,14 +393,14 @@ public class JwtClientAssertionAuthenticationProviderTests {
Map<String, Object> parameters = createPkceTokenParameters(S256_CODE_VERIFIER);
// @formatter:off
JoseHeader joseHeader = JoseHeader.withAlgorithm(MacAlgorithm.HS256)
JwsHeader jwsHeader = JwsHeader.with(MacAlgorithm.HS256)
.build();
JwtClaimsSet jwtClaimsSet = jwtClientAssertionClaims(registeredClient)
.build();
// @formatter:on
JwtEncoder jwsEncoder = createEncoder(TestKeys.DEFAULT_ENCODED_SECRET_KEY, "HmacSHA256");
Jwt jwtAssertion = jwsEncoder.encode(joseHeader, jwtClaimsSet);
Jwt jwtAssertion = jwsEncoder.encode(JwtEncoderParameters.from(jwsHeader, jwtClaimsSet));
OAuth2ClientAuthenticationToken authentication = new OAuth2ClientAuthenticationToken(
registeredClient.getClientId(), JWT_CLIENT_ASSERTION_AUTHENTICATION_METHOD, jwtAssertion.getTokenValue(), parameters);
@@ -430,7 +431,7 @@ public class JwtClientAssertionAuthenticationProviderTests {
OctetSequenceKey secretKeyJwk = TestJwks.jwk(secretKey).build();
JWKSource<SecurityContext> jwkSource = (jwkSelector, securityContext) ->
jwkSelector.select(new JWKSet(secretKeyJwk));
return new NimbusJwsEncoder(jwkSource);
return new NimbusJwtEncoder(jwkSource);
}
private static String asUrl(String uri, String path) {

View File

@@ -50,17 +50,12 @@ import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
@@ -68,6 +63,12 @@ import org.springframework.security.oauth2.server.authorization.config.ProviderS
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -351,7 +352,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
doAnswer(answer -> {
OAuth2TokenContext context = answer.getArgument(0);
@@ -385,7 +386,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
doAnswer(answer -> {
OAuth2TokenContext context = answer.getArgument(0);
@@ -419,7 +420,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -438,9 +439,9 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
assertThat(jwtEncodingContext.getHeaders()).isNotNull();
assertThat(jwtEncodingContext.getClaims()).isNotNull();
ArgumentCaptor<JwtClaimsSet> jwtClaimsSetCaptor = ArgumentCaptor.forClass(JwtClaimsSet.class);
verify(this.jwtEncoder).encode(any(), jwtClaimsSetCaptor.capture());
JwtClaimsSet jwtClaimsSet = jwtClaimsSetCaptor.getValue();
ArgumentCaptor<JwtEncoderParameters> jwtEncoderParametersCaptor = ArgumentCaptor.forClass(JwtEncoderParameters.class);
verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture());
JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims();
Set<String> scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE);
assertThat(scopes).isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
@@ -475,7 +476,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -511,7 +512,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
assertThat(idTokenContext.getHeaders()).isNotNull();
assertThat(idTokenContext.getClaims()).isNotNull();
verify(this.jwtEncoder, times(2)).encode(any(), any()); // Access token and ID Token
verify(this.jwtEncoder, times(2)).encode(any()); // Access token and ID Token
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());
@@ -549,7 +550,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -568,9 +569,9 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
assertThat(jwtEncodingContext.getHeaders()).isNotNull();
assertThat(jwtEncodingContext.getClaims()).isNotNull();
ArgumentCaptor<JwtClaimsSet> jwtClaimsSetCaptor = ArgumentCaptor.forClass(JwtClaimsSet.class);
verify(this.jwtEncoder).encode(any(), jwtClaimsSetCaptor.capture());
JwtClaimsSet jwtClaimsSet = jwtClaimsSetCaptor.getValue();
ArgumentCaptor<JwtEncoderParameters> jwtEncoderParametersCaptor = ArgumentCaptor.forClass(JwtEncoderParameters.class);
verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture());
JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims();
Set<String> scopes = jwtClaimsSet.getClaim(OAuth2ParameterNames.SCOPE);
assertThat(scopes).isEqualTo(authorization.getAttribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME));
@@ -614,7 +615,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
Instant accessTokenIssuedAt = Instant.now();
Instant accessTokenExpiresAt = accessTokenIssuedAt.plus(accessTokenTTL);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(accessTokenIssuedAt, accessTokenExpiresAt));
when(this.jwtEncoder.encode(any())).thenReturn(createJwt(accessTokenIssuedAt, accessTokenExpiresAt));
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -651,7 +652,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
OAuth2AuthorizationCodeAuthenticationToken authentication =
new OAuth2AuthorizationCodeAuthenticationToken(AUTHORIZATION_CODE, clientPrincipal, authorizationRequest.getRedirectUri(), null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);
@@ -666,7 +667,7 @@ public class OAuth2AuthorizationCodeAuthenticationProviderTests {
when(this.authorizationService.findByToken(eq(AUTHORIZATION_CODE), eq(AUTHORIZATION_CODE_TOKEN_TYPE)))
.thenReturn(authorization);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt());
when(this.jwtEncoder.encode(any())).thenReturn(createJwt());
@SuppressWarnings("unchecked")
Supplier<String> refreshTokenGenerator = spy(new Supplier<String>() {

View File

@@ -39,22 +39,22 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings;
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -217,7 +217,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
OAuth2ClientCredentialsAuthenticationToken authentication =
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, requestedScope, null);
when(this.jwtEncoder.encode(any(), any()))
when(this.jwtEncoder.encode(any()))
.thenReturn(createJwt(Collections.singleton("mapped-scoped")));
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
@@ -252,7 +252,7 @@ public class OAuth2ClientCredentialsAuthenticationProviderTests {
OAuth2ClientCredentialsAuthenticationToken authentication =
new OAuth2ClientCredentialsAuthenticationToken(clientPrincipal, null, null);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(registeredClient.getScopes()));
when(this.jwtEncoder.encode(any())).thenReturn(createJwt(registeredClient.getScopes()));
OAuth2AccessTokenAuthenticationToken accessTokenAuthentication =
(OAuth2AccessTokenAuthenticationToken) this.authenticationProvider.authenticate(authentication);

View File

@@ -48,17 +48,11 @@ import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenCustomizer;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.TestOAuth2Authorizations;
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient;
import org.springframework.security.oauth2.server.authorization.client.TestRegisteredClients;
@@ -66,6 +60,12 @@ import org.springframework.security.oauth2.server.authorization.config.ProviderS
import org.springframework.security.oauth2.server.authorization.config.TokenSettings;
import org.springframework.security.oauth2.server.authorization.context.ProviderContext;
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder;
import org.springframework.security.oauth2.server.authorization.token.DelegatingOAuth2TokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.JwtGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2AccessTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2RefreshTokenGenerator;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenClaimsContext;
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
@@ -99,7 +99,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
public void setUp() {
this.authorizationService = mock(OAuth2AuthorizationService.class);
this.jwtEncoder = mock(JwtEncoder.class);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwt(Collections.singleton("scope1")));
when(this.jwtEncoder.encode(any())).thenReturn(createJwt(Collections.singleton("scope1")));
this.jwtCustomizer = mock(OAuth2TokenCustomizer.class);
JwtGenerator jwtGenerator = new JwtGenerator(this.jwtEncoder);
jwtGenerator.setJwtCustomizer(this.jwtCustomizer);
@@ -265,7 +265,7 @@ public class OAuth2RefreshTokenAuthenticationProviderTests {
assertThat(idTokenContext.getHeaders()).isNotNull();
assertThat(idTokenContext.getClaims()).isNotNull();
verify(this.jwtEncoder, times(2)).encode(any(), any()); // Access token and ID Token
verify(this.jwtEncoder, times(2)).encode(any()); // Access token and ID Token
ArgumentCaptor<OAuth2Authorization> authorizationCaptor = ArgumentCaptor.forClass(OAuth2Authorization.class);
verify(this.authorizationService).save(authorizationCaptor.capture());

View File

@@ -42,11 +42,11 @@ import org.springframework.security.oauth2.core.oidc.OidcClientMetadataClaimName
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.jose.jws.MacAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwsHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService;
@@ -451,7 +451,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwtClientConfiguration());
when(this.jwtEncoder.encode(any())).thenReturn(createJwtClientConfiguration());
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -538,7 +538,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
when(this.authorizationService.findByToken(
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN)))
.thenReturn(authorization);
when(this.jwtEncoder.encode(any(), any())).thenReturn(createJwtClientConfiguration());
when(this.jwtEncoder.encode(any())).thenReturn(createJwtClientConfiguration());
JwtAuthenticationToken principal = new JwtAuthenticationToken(
jwt, AuthorityUtils.createAuthorityList("SCOPE_client.create"));
@@ -565,7 +565,7 @@ public class OidcClientRegistrationAuthenticationProviderTests {
eq(jwtAccessToken.getTokenValue()), eq(OAuth2TokenType.ACCESS_TOKEN));
verify(this.registeredClientRepository).save(registeredClientCaptor.capture());
verify(this.authorizationService, times(2)).save(authorizationCaptor.capture());
verify(this.jwtEncoder).encode(any(), any());
verify(this.jwtEncoder).encode(any());
// assert "registration" access token, which should be used for subsequent calls to client configuration endpoint
OAuth2Authorization authorizationResult = authorizationCaptor.getAllValues().get(0);
@@ -834,13 +834,13 @@ public class OidcClientRegistrationAuthenticationProviderTests {
private static Jwt createJwt(Set<String> scopes) {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
JwsHeader jwsHeader = TestJwsHeaders.jwsHeader()
.build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
.claim(OAuth2ParameterNames.SCOPE, scopes)
.build();
Jwt jwt = Jwt.withTokenValue("jwt-access-token")
.headers(headers -> headers.putAll(joseHeader.getHeaders()))
.headers(headers -> headers.putAll(jwsHeader.getHeaders()))
.claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
.build();
// @formatter:on

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2020-2021 the original author or authors.
* Copyright 2020-2022 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.
@@ -47,10 +47,10 @@ import org.springframework.security.oauth2.core.http.converter.OAuth2ErrorHttpMe
import org.springframework.security.oauth2.core.oidc.OidcClientRegistration;
import org.springframework.security.oauth2.core.oidc.http.converter.OidcClientRegistrationHttpMessageConverter;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.TestJoseHeaders;
import org.springframework.security.oauth2.jwt.TestJwsHeaders;
import org.springframework.security.oauth2.jwt.TestJwtClaimsSets;
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcClientRegistrationAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
@@ -473,13 +473,13 @@ public class OidcClientRegistrationEndpointFilterTests {
private static Jwt createJwt(String scope) {
// @formatter:off
JoseHeader joseHeader = TestJoseHeaders.joseHeader()
JwsHeader jwsHeader = TestJwsHeaders.jwsHeader()
.build();
JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet()
.claim(OAuth2ParameterNames.SCOPE, Collections.singleton(scope))
.build();
Jwt jwt = Jwt.withTokenValue("jwt-access-token")
.headers(headers -> headers.putAll(joseHeader.getHeaders()))
.headers(headers -> headers.putAll(jwsHeader.getHeaders()))
.claims(claims -> claims.putAll(jwtClaimsSet.getClaims()))
.build();
// @formatter:on

View File

@@ -36,11 +36,11 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.oidc.IdTokenClaimNames;
import org.springframework.security.oauth2.core.oidc.OidcScopes;
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames;
import org.springframework.security.oauth2.jose.jws.JwsAlgorithm;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.JoseHeader;
import org.springframework.security.oauth2.jwt.JwsHeader;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.server.authorization.JwtEncodingContext;
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization;
import org.springframework.security.oauth2.server.authorization.OAuth2TokenContext;
@@ -200,14 +200,13 @@ public class JwtGeneratorTests {
assertThat(jwtEncodingContext.getAuthorizationGrantType()).isEqualTo(tokenContext.getAuthorizationGrantType());
assertThat(jwtEncodingContext.<Authentication>getAuthorizationGrant()).isEqualTo(tokenContext.getAuthorizationGrant());
ArgumentCaptor<JoseHeader> joseHeaderCaptor = ArgumentCaptor.forClass(JoseHeader.class);
ArgumentCaptor<JwtClaimsSet> jwtClaimsSetCaptor = ArgumentCaptor.forClass(JwtClaimsSet.class);
verify(this.jwtEncoder).encode(joseHeaderCaptor.capture(), jwtClaimsSetCaptor.capture());
ArgumentCaptor<JwtEncoderParameters> jwtEncoderParametersCaptor = ArgumentCaptor.forClass(JwtEncoderParameters.class);
verify(this.jwtEncoder).encode(jwtEncoderParametersCaptor.capture());
JoseHeader joseHeader = joseHeaderCaptor.getValue();
assertThat(joseHeader.<JwsAlgorithm>getAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
JwsHeader jwsHeader = jwtEncoderParametersCaptor.getValue().getJwsHeader();
assertThat(jwsHeader.getAlgorithm()).isEqualTo(SignatureAlgorithm.RS256);
JwtClaimsSet jwtClaimsSet = jwtClaimsSetCaptor.getValue();
JwtClaimsSet jwtClaimsSet = jwtEncoderParametersCaptor.getValue().getClaims();
assertThat(jwtClaimsSet.getIssuer().toExternalForm()).isEqualTo(tokenContext.getProviderContext().getIssuer());
assertThat(jwtClaimsSet.getSubject()).isEqualTo(tokenContext.getAuthorization().getPrincipalName());
assertThat(jwtClaimsSet.getAudience()).containsExactly(tokenContext.getRegisteredClient().getClientId());