Commit 64a0d2f2 authored by Madhura Bhave's avatar Madhura Bhave

Merge pull request #14199 from ayudovin

* gh-14199:
  Polish "issuer uri in reactive resource-server config"
  Support issuer uri in reactive OAuth2 resource-server config
parents db27d8fd 35d7bb7f
...@@ -17,16 +17,20 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive ...@@ -17,16 +17,20 @@ package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.security.oauth2.resource.IssuerUriCondition;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties; import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoders;
/** /**
* Configures a {@link ReactiveJwtDecoder} when a JWK Set URI is available. * Configures a {@link ReactiveJwtDecoder} when a JWK Set URI is available.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Artsiom Yudovin
*/ */
@Configuration @Configuration
class ReactiveOAuth2ResourceServerJwkConfiguration { class ReactiveOAuth2ResourceServerJwkConfiguration {
...@@ -45,4 +49,12 @@ class ReactiveOAuth2ResourceServerJwkConfiguration { ...@@ -45,4 +49,12 @@ class ReactiveOAuth2ResourceServerJwkConfiguration {
return new NimbusReactiveJwtDecoder(this.properties.getJwt().getJwkSetUri()); return new NimbusReactiveJwtDecoder(this.properties.getJwt().getJwkSetUri());
} }
@Bean
@Conditional(IssuerUriCondition.class)
@ConditionalOnMissingBean
public ReactiveJwtDecoder jwtDecoderByIssuerUri() {
return ReactiveJwtDecoders
.fromOidcIssuerLocation(this.properties.getJwt().getIssuerUri());
}
} }
...@@ -15,9 +15,18 @@ ...@@ -15,9 +15,18 @@
*/ */
package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive; package org.springframework.boot.autoconfigure.security.oauth2.resource.reactive;
import java.io.IOException;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import org.junit.After;
import org.junit.Test; import org.junit.Test;
import org.testcontainers.shaded.com.fasterxml.jackson.core.JsonProcessingException;
import org.testcontainers.shaded.com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.boot.autoconfigure.AutoConfigurations; import org.springframework.boot.autoconfigure.AutoConfigurations;
import org.springframework.boot.test.context.FilteredClassLoader; import org.springframework.boot.test.context.FilteredClassLoader;
...@@ -25,6 +34,9 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplic ...@@ -25,6 +34,9 @@ import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplic
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner; import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.ReactiveAuthenticationManager; import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.config.BeanIds; import org.springframework.security.config.BeanIds;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
...@@ -47,6 +59,7 @@ import static org.mockito.Mockito.mock; ...@@ -47,6 +59,7 @@ import static org.mockito.Mockito.mock;
* Tests for {@link ReactiveOAuth2ResourceServerAutoConfiguration}. * Tests for {@link ReactiveOAuth2ResourceServerAutoConfiguration}.
* *
* @author Madhura Bhave * @author Madhura Bhave
* @author Artsiom Yudovin
*/ */
public class ReactiveOAuth2ResourceServerAutoConfigurationTests { public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
...@@ -55,6 +68,15 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { ...@@ -55,6 +68,15 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.of(ReactiveOAuth2ResourceServerAutoConfiguration.class)) .of(ReactiveOAuth2ResourceServerAutoConfiguration.class))
.withUserConfiguration(TestConfig.class); .withUserConfiguration(TestConfig.class);
private MockWebServer server;
@After
public void cleanup() throws Exception {
if (this.server != null) {
this.server.shutdown();
}
}
@Test @Test
public void autoConfigurationShouldConfigureResourceServer() { public void autoConfigurationShouldConfigureResourceServer() {
this.contextRunner.withPropertyValues( this.contextRunner.withPropertyValues(
...@@ -66,6 +88,39 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { ...@@ -66,6 +88,39 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
}); });
} }
@Test
public void autoConfigurationShouldConfigureResourceServerUsingOidcIssuerUri()
throws IOException {
this.server = new MockWebServer();
this.server.start();
String issuer = this.server.url("").toString();
String cleanIssuerPath = cleanIssuerPath(issuer);
setupMockResponse(cleanIssuerPath);
this.contextRunner
.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.issuer-uri=http://"
+ this.server.getHostName() + ":" + this.server.getPort())
.run((context) -> {
assertThat(context.getBean(ReactiveJwtDecoder.class))
.isInstanceOf(NimbusReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
});
}
@Test
public void autoConfigurationWhenBothSetUriAndIssuerUriPresentShouldUseSetUri() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.jwk-set-uri=http://jwk-set-uri.com",
"spring.security.oauth2.resourceserver.jwt.issuer-uri=http://jwk-oidc-issuer-location.com")
.run((context) -> {
assertThat(context.getBean(ReactiveJwtDecoder.class))
.isInstanceOf(NimbusReactiveJwtDecoder.class);
assertFilterConfiguredWithJwtAuthenticationManager(context);
assertThat(context.containsBean("jwtDecoder")).isTrue();
assertThat(context.containsBean("jwtDecoderByIssuerUri")).isFalse();
});
}
@Test @Test
public void autoConfigurationWhenJwkSetUriNullShouldNotFail() { public void autoConfigurationWhenJwkSetUriNullShouldNotFail() {
this.contextRunner.run((context) -> assertThat(context) this.contextRunner.run((context) -> assertThat(context)
...@@ -80,6 +135,14 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { ...@@ -80,6 +135,14 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
.run((this::assertFilterConfiguredWithJwtAuthenticationManager)); .run((this::assertFilterConfiguredWithJwtAuthenticationManager));
} }
@Test
public void jwtDecoderByIssuerUriBeanIsConditionalOnMissingBean() {
this.contextRunner.withPropertyValues(
"spring.security.oauth2.resourceserver.jwt.issuer-uri=http://jwk-oidc-issuer-location.com")
.withUserConfiguration(JwtDecoderConfig.class)
.run((this::assertFilterConfiguredWithJwtAuthenticationManager));
}
@Test @Test
public void autoConfigurationShouldBeConditionalOnBearerTokenAuthenticationTokenClass() { public void autoConfigurationShouldBeConditionalOnBearerTokenAuthenticationTokenClass() {
this.contextRunner.withPropertyValues( this.contextRunner.withPropertyValues(
...@@ -119,6 +182,42 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests { ...@@ -119,6 +182,42 @@ public class ReactiveOAuth2ResourceServerAutoConfigurationTests {
} }
private String cleanIssuerPath(String issuer) {
if (issuer.endsWith("/")) {
return issuer.substring(0, issuer.length() - 1);
}
return issuer;
}
private void setupMockResponse(String issuer) throws JsonProcessingException {
MockResponse mockResponse = new MockResponse()
.setResponseCode(HttpStatus.OK.value())
.setBody(new ObjectMapper().writeValueAsString(getResponse(issuer)))
.setHeader(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_JSON_VALUE);
this.server.enqueue(mockResponse);
}
private Map<String, Object> getResponse(String issuer) {
Map<String, Object> response = new HashMap<>();
response.put("authorization_endpoint", "https://example.com/o/oauth2/v2/auth");
response.put("claims_supported", Collections.emptyList());
response.put("code_challenge_methods_supported", Collections.emptyList());
response.put("id_token_signing_alg_values_supported", Collections.emptyList());
response.put("issuer", issuer);
response.put("jwks_uri", "https://example.com/oauth2/v3/certs");
response.put("response_types_supported", Collections.emptyList());
response.put("revocation_endpoint", "https://example.com/o/oauth2/revoke");
response.put("scopes_supported", Collections.singletonList("openid"));
response.put("subject_types_supported", Collections.singletonList("public"));
response.put("grant_types_supported",
Collections.singletonList("authorization_code"));
response.put("token_endpoint", "https://example.com/oauth2/v4/token");
response.put("token_endpoint_auth_methods_supported",
Collections.singletonList("client_secret_basic"));
response.put("userinfo_endpoint", "https://example.com/oauth2/v3/userinfo");
return response;
}
@EnableWebFluxSecurity @EnableWebFluxSecurity
static class TestConfig { static class TestConfig {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment