Merge branch '2.7.x'

Closes gh-31404
pull/31442/head
Andy Wilkinson 2 years ago
commit 80ae3f36a9

@ -20,6 +20,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays;
import java.util.List; import java.util.List;
import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.ConfigurationProperties;
@ -59,9 +60,9 @@ public class OAuth2ResourceServerProperties {
private String jwkSetUri; private String jwkSetUri;
/** /**
* JSON Web Algorithm used for verifying the digital signatures. * JSON Web Algorithms used for verifying the digital signatures.
*/ */
private String jwsAlgorithm = "RS256"; private List<String> jwsAlgorithms = Arrays.asList("RS256");
/** /**
* URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0 * URI that can either be an OpenID Connect discovery endpoint or an OAuth 2.0
@ -87,12 +88,12 @@ public class OAuth2ResourceServerProperties {
this.jwkSetUri = jwkSetUri; this.jwkSetUri = jwkSetUri;
} }
public String getJwsAlgorithm() { public List<String> getJwsAlgorithms() {
return this.jwsAlgorithm; return this.jwsAlgorithms;
} }
public void setJwsAlgorithm(String jwsAlgorithm) { public void setJwsAlgorithms(List<String> jwsAlgortithms) {
this.jwsAlgorithm = jwsAlgorithm; this.jwsAlgorithms = jwsAlgortithms;
} }
public String getIssuerUri() { public String getIssuerUri() {

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -78,8 +79,7 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
ReactiveJwtDecoder jwtDecoder() { ReactiveJwtDecoder jwtDecoder() {
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = NimbusReactiveJwtDecoder NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = NimbusReactiveJwtDecoder
.withJwkSetUri(this.properties.getJwkSetUri()) .withJwkSetUri(this.properties.getJwkSetUri()).jwsAlgorithms(this::jwsAlgorithms).build();
.jwsAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build();
String issuerUri = this.properties.getIssuerUri(); String issuerUri = this.properties.getIssuerUri();
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null) Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault; ? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
@ -87,6 +87,12 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
return nimbusReactiveJwtDecoder; return nimbusReactiveJwtDecoder;
} }
private void jwsAlgorithms(Set<SignatureAlgorithm> signatureAlgorithms) {
for (String algorithm : this.properties.getJwsAlgorithms()) {
signatureAlgorithms.add(SignatureAlgorithm.from(algorithm));
}
}
private OAuth2TokenValidator<Jwt> getValidators(Supplier<OAuth2TokenValidator<Jwt>> defaultValidator) { private OAuth2TokenValidator<Jwt> getValidators(Supplier<OAuth2TokenValidator<Jwt>> defaultValidator) {
OAuth2TokenValidator<Jwt> defaultValidators = defaultValidator.get(); OAuth2TokenValidator<Jwt> defaultValidators = defaultValidator.get();
List<String> audiences = this.properties.getAudiences(); List<String> audiences = this.properties.getAudiences();
@ -106,7 +112,7 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey()))); .generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey())));
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(publicKey) NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder.withPublicKey(publicKey)
.signatureAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build(); .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())).build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault)); jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault));
return jwtDecoder; return jwtDecoder;
} }
@ -116,6 +122,17 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
return Base64.getMimeDecoder().decode(keyValue); return Base64.getMimeDecoder().decode(keyValue);
} }
private String exactlyOneAlgorithm() {
List<String> algorithms = this.properties.getJwsAlgorithms();
int count = (algorithms != null) ? algorithms.size() : 0;
if (count != 1) {
throw new IllegalStateException(
"Creating a JWT decoder using a public key requires exactly one JWS algorithm but " + count
+ " were configured");
}
return algorithms.get(0);
}
@Bean @Bean
@Conditional(IssuerUriCondition.class) @Conditional(IssuerUriCondition.class)
SupplierReactiveJwtDecoder jwtDecoderByIssuerUri() { SupplierReactiveJwtDecoder jwtDecoderByIssuerUri() {

@ -23,6 +23,7 @@ import java.util.ArrayList;
import java.util.Base64; import java.util.Base64;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.Set;
import java.util.function.Supplier; import java.util.function.Supplier;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
@ -78,7 +79,7 @@ class OAuth2ResourceServerJwtConfiguration {
@ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri") @ConditionalOnProperty(name = "spring.security.oauth2.resourceserver.jwt.jwk-set-uri")
JwtDecoder jwtDecoderByJwkKeySetUri() { JwtDecoder jwtDecoderByJwkKeySetUri() {
NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri()) NimbusJwtDecoder nimbusJwtDecoder = NimbusJwtDecoder.withJwkSetUri(this.properties.getJwkSetUri())
.jwsAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build(); .jwsAlgorithms(this::jwsAlgorithms).build();
String issuerUri = this.properties.getIssuerUri(); String issuerUri = this.properties.getIssuerUri();
Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null) Supplier<OAuth2TokenValidator<Jwt>> defaultValidator = (issuerUri != null)
? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault; ? () -> JwtValidators.createDefaultWithIssuer(issuerUri) : JwtValidators::createDefault;
@ -86,6 +87,12 @@ class OAuth2ResourceServerJwtConfiguration {
return nimbusJwtDecoder; return nimbusJwtDecoder;
} }
private void jwsAlgorithms(Set<SignatureAlgorithm> signatureAlgorithms) {
for (String algorithm : this.properties.getJwsAlgorithms()) {
signatureAlgorithms.add(SignatureAlgorithm.from(algorithm));
}
}
private OAuth2TokenValidator<Jwt> getValidators(Supplier<OAuth2TokenValidator<Jwt>> defaultValidator) { private OAuth2TokenValidator<Jwt> getValidators(Supplier<OAuth2TokenValidator<Jwt>> defaultValidator) {
OAuth2TokenValidator<Jwt> defaultValidators = defaultValidator.get(); OAuth2TokenValidator<Jwt> defaultValidators = defaultValidator.get();
List<String> audiences = this.properties.getAudiences(); List<String> audiences = this.properties.getAudiences();
@ -105,7 +112,7 @@ class OAuth2ResourceServerJwtConfiguration {
RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA") RSAPublicKey publicKey = (RSAPublicKey) KeyFactory.getInstance("RSA")
.generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey()))); .generatePublic(new X509EncodedKeySpec(getKeySpec(this.properties.readPublicKey())));
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey) NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder.withPublicKey(publicKey)
.signatureAlgorithm(SignatureAlgorithm.from(this.properties.getJwsAlgorithm())).build(); .signatureAlgorithm(SignatureAlgorithm.from(exactlyOneAlgorithm())).build();
jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault)); jwtDecoder.setJwtValidator(getValidators(JwtValidators::createDefault));
return jwtDecoder; return jwtDecoder;
} }
@ -115,6 +122,17 @@ class OAuth2ResourceServerJwtConfiguration {
return Base64.getMimeDecoder().decode(keyValue); return Base64.getMimeDecoder().decode(keyValue);
} }
private String exactlyOneAlgorithm() {
List<String> algorithms = this.properties.getJwsAlgorithms();
int count = (algorithms != null) ? algorithms.size() : 0;
if (count != 1) {
throw new IllegalStateException(
"Creating a JWT decoder using a public key requires exactly one JWS algorithm but " + count
+ " were configured");
}
return algorithms.get(0);
}
@Bean @Bean
@Conditional(IssuerUriCondition.class) @Conditional(IssuerUriCondition.class)
SupplierJwtDecoder jwtDecoderByIssuerUri() { SupplierJwtDecoder jwtDecoderByIssuerUri() {

@ -2049,6 +2049,14 @@
"name": "spring.security.filter.order", "name": "spring.security.filter.order",
"defaultValue": -100 "defaultValue": -100
}, },
{
"name": "spring.security.oauth2.resourceserver.jwt.jws-algorithm",
"type": "java.lang.String",
"deprecation": {
"replacement": "spring.security.oauth2.resourceserver.jwt.jws-algorithms",
"level": "error"
}
},
{ {
"name": "spring.session.hazelcast.flush-mode", "name": "spring.session.hazelcast.flush-mode",
"defaultValue": "on-save" "defaultValue": "on-save"

@ -25,7 +25,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Set;
import java.util.stream.Stream; import java.util.stream.Stream;
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.JsonProcessingException;
@ -33,6 +32,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.nimbusds.jose.JWSAlgorithm; import com.nimbusds.jose.JWSAlgorithm;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono; import reactor.core.publisher.Mono;
@ -115,30 +115,55 @@ class ReactiveOAuth2ResourceServerAutoConfigurationTests {
}); });
} }
@SuppressWarnings("unchecked")
@Test @Test
void autoConfigurationUsingJwkSetUriShouldConfigureResourceServerUsingJwsAlgorithm() { void autoConfigurationUsingJwkSetUriShouldConfigureResourceServerUsingSingleJwsAlgorithm() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS512")
.run((context) -> {
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = context.getBean(NimbusReactiveJwtDecoder.class);
assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$2.arg$1.jwsAlgs")
.asInstanceOf(InstanceOfAssertFactories.collection(JWSAlgorithm.class))
.containsExactlyInAnyOrder(JWSAlgorithm.RS512);
});
}
@Test
void autoConfigurationUsingJwkSetUriShouldConfigureResourceServerUsingMultipleJwsAlgorithms() {
this.contextRunner this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.jwt.jws-algorithm=RS512") "spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS256, RS384, RS512")
.run((context) -> { .run((context) -> {
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = context.getBean(NimbusReactiveJwtDecoder.class); NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = context.getBean(NimbusReactiveJwtDecoder.class);
assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$2.arg$1.jwsAlgs") assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$2.arg$1.jwsAlgs")
.matches((algorithms) -> ((Set<JWSAlgorithm>) algorithms).contains(JWSAlgorithm.RS512)); .asInstanceOf(InstanceOfAssertFactories.collection(JWSAlgorithm.class))
.containsExactlyInAnyOrder(JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512);
}); });
} }
@Test @Test
void autoConfigurationUsingPublicKeyValueShouldConfigureResourceServerUsingJwsAlgorithm() { void autoConfigurationUsingPublicKeyValueShouldConfigureResourceServerUsingSingleJwsAlgorithm() {
this.contextRunner.withPropertyValues( this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location", "spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
"spring.security.oauth2.resourceserver.jwt.jws-algorithm=RS384").run((context) -> { "spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS384").run((context) -> {
NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = context.getBean(NimbusReactiveJwtDecoder.class); NimbusReactiveJwtDecoder nimbusReactiveJwtDecoder = context.getBean(NimbusReactiveJwtDecoder.class);
assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$1.jwsKeySelector.expectedJWSAlg") assertThat(nimbusReactiveJwtDecoder).extracting("jwtProcessor.arg$1.jwsKeySelector.expectedJWSAlg")
.isEqualTo(JWSAlgorithm.RS384); .isEqualTo(JWSAlgorithm.RS384);
}); });
} }
@Test
void autoConfigurationUsingPublicKeyValueWithMultipleJwsAlgorithmsShouldFail() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
"spring.security.oauth2.resourceserver.jwt.jws-algorithms=RSA256,RS384").run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasRootCauseMessage(
"Creating a JWT decoder using a public key requires exactly one JWS algorithm but 2 were "
+ "configured");
});
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException { void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws IOException {

@ -32,6 +32,7 @@ import com.nimbusds.jose.JWSAlgorithm;
import jakarta.servlet.Filter; import jakarta.servlet.Filter;
import okhttp3.mockwebserver.MockResponse; import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer; import okhttp3.mockwebserver.MockWebServer;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
@ -54,6 +55,7 @@ import org.springframework.security.oauth2.jwt.JwtClaimValidator;
import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator; import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator; import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.SupplierJwtDecoder; import org.springframework.security.oauth2.jwt.SupplierJwtDecoder;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
@ -119,20 +121,60 @@ class OAuth2ResourceServerAutoConfigurationTests {
} }
@Test @Test
void autoConfigurationShouldConfigureResourceServerWithJwsAlgorithm() { void autoConfigurationShouldConfigureResourceServerWithSingleJwsAlgorithm() {
this.contextRunner this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com", .withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.jwt.jws-algorithm=RS384") "spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS384")
.run((context) -> { .run((context) -> {
JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class); JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
Object processor = ReflectionTestUtils.getField(jwtDecoder, "jwtProcessor"); Object processor = ReflectionTestUtils.getField(jwtDecoder, "jwtProcessor");
Object keySelector = ReflectionTestUtils.getField(processor, "jwsKeySelector"); Object keySelector = ReflectionTestUtils.getField(processor, "jwsKeySelector");
assertThat(keySelector).hasFieldOrPropertyWithValue("jwsAlgs", assertThat(keySelector).extracting("jwsAlgs")
Collections.singleton(JWSAlgorithm.RS384)); .asInstanceOf(InstanceOfAssertFactories.collection(JWSAlgorithm.class))
.containsExactlyInAnyOrder(JWSAlgorithm.RS384);
assertThat(getBearerTokenFilter(context)).isNotNull();
});
}
@Test
void autoConfigurationShouldConfigureResourceServerWithMultipleJwsAlgorithms() {
this.contextRunner
.withPropertyValues("spring.security.oauth2.resourceserver.jwt.jwk-set-uri=https://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS256, RS384, RS512")
.run((context) -> {
JwtDecoder jwtDecoder = context.getBean(JwtDecoder.class);
Object processor = ReflectionTestUtils.getField(jwtDecoder, "jwtProcessor");
Object keySelector = ReflectionTestUtils.getField(processor, "jwsKeySelector");
assertThat(keySelector).extracting("jwsAlgs")
.asInstanceOf(InstanceOfAssertFactories.collection(JWSAlgorithm.class))
.containsExactlyInAnyOrder(JWSAlgorithm.RS256, JWSAlgorithm.RS384, JWSAlgorithm.RS512);
assertThat(getBearerTokenFilter(context)).isNotNull(); assertThat(getBearerTokenFilter(context)).isNotNull();
}); });
} }
@Test
void autoConfigurationUsingPublicKeyValueShouldConfigureResourceServerUsingSingleJwsAlgorithm() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
"spring.security.oauth2.resourceserver.jwt.jws-algorithms=RS384").run((context) -> {
NimbusJwtDecoder nimbusJwtDecoder = context.getBean(NimbusJwtDecoder.class);
assertThat(nimbusJwtDecoder).extracting("jwtProcessor.jwsKeySelector.expectedJWSAlg")
.isEqualTo(JWSAlgorithm.RS384);
});
}
@Test
void autoConfigurationUsingPublicKeyValueWithMultipleJwsAlgorithmsShouldFail() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.public-key-location=classpath:public-key-location",
"spring.security.oauth2.resourceserver.jwt.jws-algorithms=RSA256,RS384").run((context) -> {
assertThat(context).hasFailed();
assertThat(context.getStartupFailure()).hasRootCauseMessage(
"Creating a JWT decoder using a public key requires exactly one JWS algorithm but 2 were "
+ "configured");
});
}
@Test @Test
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception { void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri() throws Exception {

Loading…
Cancel
Save