From 1da13df07b6dbfd73bc72cf0e9e76af0bd53af7c Mon Sep 17 00:00:00 2001 From: Eric Deandrea Date: Thu, 29 Nov 2018 12:57:58 -0500 Subject: [PATCH] ServerAuthenticationConverter should be configurable Fixes gh-6186 --- .../config/web/server/ServerHttpSecurity.java | 29 ++++++--- .../server/OAuth2ResourceServerSpecTests.java | 61 ++++++++++++++++--- 2 files changed, 72 insertions(+), 18 deletions(-) diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index 848f6e53cd0..7021e05de30 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -16,6 +16,10 @@ package org.springframework.security.config.web.server; +import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry; +import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match; +import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch; + import java.io.IOException; import java.io.PrintWriter; import java.io.StringWriter; @@ -155,10 +159,6 @@ import org.springframework.web.server.WebFilter; import org.springframework.web.server.WebFilterChain; -import static org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint.DelegateEntry; -import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.match; -import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher.MatchResult.notMatch; - /** * A {@link ServerHttpSecurity} is similar to Spring Security's {@code HttpSecurity} but for WebFlux. * It allows configuring web based security for specific http requests. By default it will be applied @@ -883,6 +883,7 @@ public OAuth2ResourceServerSpec oauth2ResourceServer() { public class OAuth2ResourceServerSpec { private ServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint(); private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler(); + private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter(); private JwtSpec jwt; @@ -915,6 +916,20 @@ public OAuth2ResourceServerSpec authenticationEntryPoint(ServerAuthenticationEnt return this; } + /** + * Configures the {@link ServerAuthenticationConverter} to use for requests authenticating with + * Bearer Tokens. + * + * @param bearerTokenConverter The {@link ServerAuthenticationConverter} to use + * @return The {@link OAuth2ResourceServerSpec} for additional configuration + * @since 5.2 + */ + public OAuth2ResourceServerSpec bearerTokenConverter(ServerAuthenticationConverter bearerTokenConverter) { + Assert.notNull(bearerTokenConverter, "bearerTokenConverter cannot be null"); + this.bearerTokenConverter = bearerTokenConverter; + return this; + } + public JwtSpec jwt() { if (this.jwt == null) { this.jwt = new JwtSpec(); @@ -1003,8 +1018,6 @@ public OAuth2ResourceServerSpec and() { } protected void configure(ServerHttpSecurity http) { - ServerBearerTokenAuthenticationConverter bearerTokenConverter = - new ServerBearerTokenAuthenticationConverter(); this.bearerTokenServerWebExchangeMatcher.setBearerTokenConverter(bearerTokenConverter); registerDefaultAccessDeniedHandler(http); @@ -1083,7 +1096,7 @@ private void registerDefaultCsrfOverride(ServerHttpSecurity http) { } private class BearerTokenServerWebExchangeMatcher implements ServerWebExchangeMatcher { - ServerBearerTokenAuthenticationConverter bearerTokenConverter; + ServerAuthenticationConverter bearerTokenConverter; @Override public Mono matches(ServerWebExchange exchange) { @@ -1092,7 +1105,7 @@ public Mono matches(ServerWebExchange exchange) { .onErrorResume(e -> notMatch()); } - public void setBearerTokenConverter(ServerBearerTokenAuthenticationConverter bearerTokenConverter) { + public void setBearerTokenConverter(ServerAuthenticationConverter bearerTokenConverter) { Assert.notNull(bearerTokenConverter, "bearerTokenConverter cannot be null"); this.bearerTokenConverter = bearerTokenConverter; } diff --git a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java index f301b4a5a01..2409b1b8895 100644 --- a/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java +++ b/config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java @@ -15,6 +15,15 @@ */ package org.springframework.security.config.web.server; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatCode; +import static org.hamcrest.core.StringStartsWith.startsWith; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + import java.io.IOException; import java.math.BigInteger; import java.security.KeyFactory; @@ -27,14 +36,14 @@ import java.util.Collections; import java.util.stream.Collectors; import java.util.stream.Stream; + import javax.annotation.PreDestroy; -import okhttp3.mockwebserver.MockResponse; -import okhttp3.mockwebserver.MockWebServer; import org.apache.http.HttpHeaders; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; + import reactor.core.publisher.Mono; import org.springframework.beans.factory.NoSuchBeanDefinitionException; @@ -56,10 +65,12 @@ import org.springframework.security.oauth2.jose.jws.JwsAlgorithms; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder; +import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter; import org.springframework.security.web.server.SecurityWebFilterChain; import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint; +import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.reactive.server.WebTestClient; @@ -70,14 +81,8 @@ import org.springframework.web.reactive.DispatcherHandler; import org.springframework.web.reactive.config.EnableWebFlux; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatCode; -import static org.hamcrest.core.StringStartsWith.startsWith; -import static org.mockito.ArgumentMatchers.any; -import static org.mockito.ArgumentMatchers.anyString; -import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; +import okhttp3.mockwebserver.MockResponse; +import okhttp3.mockwebserver.MockWebServer; /** * Tests for {@link org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2ResourceServerSpec} @@ -225,6 +230,16 @@ public void postWhenMissingTokenThenReturnsForbidden() { .expectStatus().isForbidden(); } + @Test + public void getWhenCustomBearerTokenServerAuthenticationConverterThenResponds() { + this.spring.register(CustomBearerTokenServerAuthenticationConverter.class, RootController.class).autowire(); + + this.client.get() + .cookie("TOKEN", this.messageReadToken) + .exchange() + .expectStatus().isOk(); + } + @Test public void getWhenSignedAndCustomConverterThenConverts() { this.spring.register(CustomJwtAuthenticationConverterConfig.class, RootController.class).autowire(); @@ -429,6 +444,32 @@ ReactiveAuthenticationManager authenticationManager() { } } + @EnableWebFlux + @EnableWebFluxSecurity + static class CustomBearerTokenServerAuthenticationConverter { + @Bean + SecurityWebFilterChain springSecurity(ServerHttpSecurity http) throws Exception { + // @formatter:off + http + .authorizeExchange() + .anyExchange().hasAuthority("SCOPE_message:read") + .and() + .oauth2ResourceServer() + .bearerTokenConverter(bearerTokenAuthenticationConverter()) + .jwt() + .publicKey(publicKey()); + // @formatter:on + + return http.build(); + } + + @Bean + ServerAuthenticationConverter bearerTokenAuthenticationConverter() { + return exchange -> Mono.justOrEmpty(exchange.getRequest().getCookies().getFirst("TOKEN").getValue()) + .map(BearerTokenAuthenticationToken::new); + } + } + @EnableWebFlux @EnableWebFluxSecurity static class CustomJwtAuthenticationConverterConfig {