From f784306b6e59cf32c6d116039756d9192261b379 Mon Sep 17 00:00:00 2001 From: douxf Date: Wed, 22 May 2024 14:15:16 +0800 Subject: [PATCH 1/2] Address --- .../ROOT/pages/servlet/authorization/method-security.adoc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc index 390b376ea99..af6fad39da6 100644 --- a/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc +++ b/docs/modules/ROOT/pages/servlet/authorization/method-security.adoc @@ -1074,7 +1074,7 @@ class MethodSecurityConfig { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) Advisor postAuthorize() { - return AuthorizationManagerBeforeMethodInterceptor.postAuthorize(); + return AuthorizationManagerAfterMethodInterceptor.postAuthorize(); } } ---- @@ -1089,7 +1089,7 @@ class MethodSecurityConfig { @Bean @Role(BeanDefinition.ROLE_INFRASTRUCTURE) fun postAuthorize() : Advisor { - return AuthorizationManagerBeforeMethodInterceptor.postAuthorize() + return AuthorizationManagerAfterMethodInterceptor.postAuthorize() } } ---- From 28928852696fd14692dcb9e77fec8929dd46af92 Mon Sep 17 00:00:00 2001 From: douxf Date: Tue, 31 Dec 2024 20:19:07 +0800 Subject: [PATCH 2/2] enhancement for NimbusJwtEncoder to supporting key rotation close 16170 --- .../security/oauth2/jwt/NimbusJwtEncoder.java | 70 ++++++++----------- .../oauth2/jwt/NimbusJwtEncoderTests.java | 21 ++++++ 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java index 2de3e64a815..c7c539c5aca 100644 --- a/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java +++ b/oauth2/oauth2-jose/src/main/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoder.java @@ -16,28 +16,9 @@ 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.*; 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.*; import com.nimbusds.jose.jwk.source.JWKSource; import com.nimbusds.jose.proc.SecurityContext; import com.nimbusds.jose.produce.JWSSignerFactory; @@ -45,12 +26,18 @@ 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.security.oauth2.jose.jws.SignatureAlgorithm; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; +import java.net.URI; +import java.net.URL; +import java.time.Instant; +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; + /** * 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 @@ -61,7 +48,6 @@ * NOTE: This implementation uses the Nimbus JOSE + JWT SDK. * * @author Joe Grandja - * @since 5.6 * @see JwtEncoder * @see com.nimbusds.jose.jwk.source.JWKSource * @see com.nimbusds.jose.jwk.JWK @@ -73,6 +59,7 @@ * Compact Serialization * @see Nimbus * JOSE + JWT SDK + * @since 5.6 */ public final class NimbusJwtEncoder implements JwtEncoder { @@ -84,6 +71,8 @@ public final class NimbusJwtEncoder implements JwtEncoder { private final Map jwsSigners = new ConcurrentHashMap<>(); + private Converter, JWK> jwkSelector; + private final JWKSource jwkSource; /** @@ -95,6 +84,10 @@ public NimbusJwtEncoder(JWKSource jwkSource) { this.jwkSource = jwkSource; } + public void setJwkSelector(Converter, JWK> jwkSelector) { + this.jwkSelector = jwkSelector; + } + @Override public Jwt encode(JwtEncoderParameters parameters) throws JwtEncodingException { Assert.notNull(parameters, "parameters cannot be null"); @@ -118,21 +111,21 @@ private JWK selectJwk(JwsHeader headers) { try { JWKSelector jwkSelector = new JWKSelector(createJwkMatcher(headers)); jwks = this.jwkSource.get(jwkSelector, null); - } - catch (Exception ex) { + } 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")); } + if (null != this.jwkSelector) { + return this.jwkSelector.convert(jwks); + } + if (jwks.size() > 1) { + throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, + "Found multiple JWK signing keys for algorithm '" + headers.getAlgorithm().getName() + "'")); + } return jwks.get(0); } @@ -146,8 +139,7 @@ private String serialize(JwsHeader headers, JwtClaimsSet claims, JWK jwk) { SignedJWT signedJwt = new SignedJWT(jwsHeader, jwtClaimsSet); try { signedJwt.sign(jwsSigner); - } - catch (JOSEException ex) { + } catch (JOSEException ex) { throw new JwtEncodingException( String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to sign the JWT -> " + ex.getMessage()), ex); } @@ -167,8 +159,7 @@ private static JWKMatcher createJwkMatcher(JwsHeader headers) { .x509CertSHA256Thumbprint(Base64URL.from(headers.getX509SHA256Thumbprint())) .build(); // @formatter:on - } - else if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) { + } else if (JWSAlgorithm.Family.HMAC_SHA.contains(jwsAlgorithm)) { // @formatter:off return new JWKMatcher.Builder() .keyType(KeyType.forAlgorithm(jwsAlgorithm)) @@ -206,8 +197,7 @@ private static JwsHeader addKeyIdentifierHeadersIfNecessary(JwsHeader headers, J private static JWSSigner createSigner(JWK jwk) { try { return JWS_SIGNER_FACTORY.createJWSSigner(jwk); - } - catch (JOSEException ex) { + } catch (JOSEException ex) { throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Failed to create a JWS Signer -> " + ex.getMessage()), ex); } @@ -224,8 +214,7 @@ private static JWSHeader convert(JwsHeader headers) { if (!CollectionUtils.isEmpty(jwk)) { try { builder.jwk(JWK.parse(jwk)); - } - catch (Exception ex) { + } catch (Exception ex) { throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Unable to convert '" + JoseHeaderNames.JWK + "' JOSE header"), ex); } @@ -342,8 +331,7 @@ private static JWTClaimsSet convert(JwtClaimsSet claims) { private static URI convertAsURI(String header, URL url) { try { return url.toURI(); - } - catch (Exception ex) { + } catch (Exception ex) { throw new JwtEncodingException(String.format(ENCODING_ERROR_MESSAGE_TEMPLATE, "Unable to convert '" + header + "' JOSE header to a URI"), ex); } diff --git a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java index e9825f0a359..ee70b121255 100644 --- a/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java +++ b/oauth2/oauth2-jose/src/test/java/org/springframework/security/oauth2/jwt/NimbusJwtEncoderTests.java @@ -121,6 +121,27 @@ public void encodeWhenJwkMultipleSelectedThenThrowJwtEncodingException() throws .withMessageContaining("Found multiple JWK signing keys for algorithm 'RS256'"); } + @Test + public void encodeWhenJwkMultipleSelectedWithJwkSelector() throws Exception { + RSAKey rsaJwk = TestJwks.DEFAULT_RSA_JWK; + this.jwkList.add(rsaJwk); + this.jwkList.add(rsaJwk); + this.jwtEncoder.setJwkSelector(jwkSelector -> jwkSelector.get(0)); + + JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256).build(); + JwtClaimsSet jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build(); + + Jwt encodedJws = this.jwtEncoder.encode(JwtEncoderParameters.from(jwtClaimsSet)); + assertThat(encodedJws.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.RS256); + + this.jwtEncoder.setJwkSelector(jwkSelector -> jwkSelector.get(jwkSelector.size()-1)); + jwtClaimsSet = TestJwtClaimsSets.jwtClaimsSet().build(); + encodedJws = this.jwtEncoder.encode(JwtEncoderParameters.from(jwtClaimsSet)); + assertThat(encodedJws.getHeaders()).containsEntry(JoseHeaderNames.ALG, SignatureAlgorithm.RS256); + this.jwtEncoder.setJwkSelector(null); + } + + @Test public void encodeWhenJwkSelectEmptyThenThrowJwtEncodingException() { JwsHeader jwsHeader = JwsHeader.with(SignatureAlgorithm.RS256).build();