Browse Source

Add AuthenticationReactorContextFilter

Fixes gh-4501
Rob Winch 8 năm trước cách đây
mục cha
commit
b0b9b32c0c

+ 2 - 0
config/src/main/java/org/springframework/security/config/web/server/HttpSecurity.java

@@ -29,6 +29,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.security.web.server.authorization.AuthorizationContext;
 import org.springframework.security.web.server.authorization.AuthorizationWebFilter;
 import org.springframework.security.web.server.authorization.DelegatingReactiveAuthorizationManager;
+import org.springframework.security.web.server.context.AuthenticationReactorContextFilter;
 import org.springframework.security.web.server.context.SecurityContextRepositoryWebFilter;
 import org.springframework.security.web.server.authorization.ExceptionTranslationWebFilter;
 import org.springframework.security.web.server.context.SecurityContextRepository;
@@ -114,6 +115,7 @@ public class HttpSecurity {
 			securityContextRepository.ifPresent( scr -> httpBasic.securityContextRepository(scr)) ;
 			filters.add(httpBasic.build());
 		}
+		filters.add(new AuthenticationReactorContextFilter());
 		if(authorizeExchangeBuilder != null) {
 			filters.add(new ExceptionTranslationWebFilter());
 			filters.add(authorizeExchangeBuilder.build());

+ 61 - 1
config/src/test/java/org/springframework/security/config/annotation/web/reactive/EnableWebFluxSecurityTests.java

@@ -25,7 +25,11 @@ import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Bean;
 import org.springframework.core.Ordered;
 import org.springframework.core.annotation.Order;
+import org.springframework.core.io.buffer.DataBuffer;
+import org.springframework.core.io.buffer.DefaultDataBufferFactory;
+import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.config.web.server.HttpSecurity;
+import org.springframework.security.core.Authentication;
 import org.springframework.security.core.userdetails.MapUserDetailsRepository;
 import org.springframework.security.core.userdetails.User;
 import org.springframework.security.core.userdetails.UserDetailsRepository;
@@ -35,8 +39,14 @@ import org.springframework.security.web.server.WebFilterChainFilter;
 import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
 import org.springframework.test.context.junit4.SpringRunner;
 import org.springframework.test.web.reactive.server.WebTestClient;
+import reactor.core.publisher.Mono;
 
-import static org.mockito.Mockito.mock;
+import java.nio.charset.StandardCharsets;
+import java.security.Principal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.Credentials.basicAuthenticationCredentials;
+import static org.springframework.web.reactive.function.client.ExchangeFilterFunctions.basicAuthentication;
 
 /**
  * @author Rob Winch
@@ -60,6 +70,50 @@ public class EnableWebFluxSecurityTests {
 				.expectBody().isEmpty();
 		}
 
+		@Test
+		public void defaultPopulatesReactorContext() {
+			Principal currentPrincipal = new TestingAuthenticationToken("user", "password", "ROLE_USER");
+			WebTestClient client = WebTestClientBuilder.bindToWebFilters(
+				(exchange, chain) ->
+					chain.filter(exchange.mutate().principal(Mono.just(currentPrincipal)).build()),
+				springSecurityFilterChain,
+				(exchange,chain) ->
+					Mono.currentContext()
+						.flatMap( c -> c.<Mono<Principal>>get(Authentication.class))
+						.flatMap( principal -> exchange.getResponse()
+							.writeWith(Mono.just(toDataBuffer(principal.getName()))))
+			).build();
+
+			client
+				.get()
+				.uri("/")
+				.exchange()
+				.expectStatus().isOk()
+				.expectBody(String.class).consumeWith( result -> assertThat(result.getResponseBody()).isEqualTo(currentPrincipal.getName()));
+		}
+
+		@Test
+		public void defaultPopulatesReactorContextWhenAuthenticating() {
+			WebTestClient client = WebTestClientBuilder.bindToWebFilters(
+				springSecurityFilterChain,
+				(exchange,chain) ->
+					Mono.currentContext()
+						.flatMap( c -> c.<Mono<Principal>>get(Authentication.class))
+						.flatMap( principal -> exchange.getResponse()
+							.writeWith(Mono.just(toDataBuffer(principal.getName()))))
+			)
+			.filter(basicAuthentication())
+			.build();
+
+			client
+				.get()
+				.uri("/")
+				.attributes(basicAuthenticationCredentials("user","password"))
+				.exchange()
+				.expectStatus().isOk()
+				.expectBody(String.class).consumeWith( result -> assertThat(result.getResponseBody()).isEqualTo("user"));
+		}
+
 		@EnableWebFluxSecurity
 		static class Config {
 			@Bean
@@ -121,4 +175,10 @@ public class EnableWebFluxSecurityTests {
 			}
 		}
 	}
+
+	private static DataBuffer toDataBuffer(String body) {
+		DataBuffer buffer = new DefaultDataBufferFactory().allocateBuffer();
+		buffer.write(body.getBytes(StandardCharsets.UTF_8));
+		return buffer;
+	}
 }

+ 45 - 0
webflux/src/main/java/org/springframework/security/web/server/context/AuthenticationReactorContextFilter.java

@@ -0,0 +1,45 @@
+/*
+ *
+ *  * Copyright 2002-2017 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
+ *  *
+ *  *      http://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.web.server.context;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.WebFilter;
+import org.springframework.web.server.WebFilterChain;
+import reactor.core.publisher.Mono;
+import reactor.util.context.Context;
+
+import java.security.Principal;
+
+/**
+ * Populate the {@link Principal} from {@link ServerWebExchange#getPrincipal()} into the
+ * Reactor {@link Context}.
+ *
+ * @author Rob Winch
+ * @since 5.0
+ */
+public class AuthenticationReactorContextFilter implements WebFilter {
+
+	@Override
+	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
+
+		return chain.filter(exchange)
+				.contextStart((Context context) -> context.put(Authentication.class, exchange.getPrincipal()));
+	}
+}

+ 94 - 0
webflux/src/test/java/org/springframework/security/web/server/context/AuthenticationReactorContextFilterTests.java

@@ -0,0 +1,94 @@
+/*
+ *
+ *  * Copyright 2002-2017 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
+ *  *
+ *  *      http://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.web.server.context;
+
+import org.junit.Test;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.security.authentication.TestingAuthenticationToken;
+import org.springframework.security.core.Authentication;
+import org.springframework.web.server.ServerWebExchange;
+import org.springframework.web.server.handler.DefaultWebFilterChain;
+import reactor.core.publisher.Mono;
+import reactor.test.StepVerifier;
+import reactor.util.context.Context;
+
+import java.security.Principal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * @author Rob Winch
+ * @since 5.0
+ */
+public class AuthenticationReactorContextFilterTests {
+	AuthenticationReactorContextFilter filter = new AuthenticationReactorContextFilter();
+
+	Principal principal = new TestingAuthenticationToken("user","password", "ROLE_USER");
+
+	ServerWebExchange exchange = MockServerHttpRequest.get("/").toExchange();
+
+	@Test
+	public void filterWhenExistingContextAndPrincipalNotNullThenContextPopulated() {
+		exchange = exchange.mutate().principal(Mono.just(principal)).build();
+		StepVerifier.create(filter.filter(exchange,
+			new DefaultWebFilterChain( e ->
+				Mono.currentContext().doOnSuccess( context -> {
+					Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
+					assertThat(contextPrincipal).isEqualTo(principal);
+					assertThat(context.<String>get("foo")).isEqualTo("bar");
+				})
+				.then()
+			)
+		)
+		.contextStart( context -> context.put("foo", "bar")))
+		.verifyComplete();
+	}
+
+	@Test
+	public void filterWhenPrincipalNotNullThenContextPopulated() {
+		exchange = exchange.mutate().principal(Mono.just(principal)).build();
+		StepVerifier.create(filter.filter(exchange,
+			new DefaultWebFilterChain( e ->
+				Mono.currentContext().doOnSuccess( context -> {
+					Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
+					assertThat(contextPrincipal).isEqualTo(principal);
+				})
+				.then()
+			)
+		))
+		.verifyComplete();
+	}
+
+	@Test
+	public void filterWhenPrincipalNullThenContextEmpty() {
+		Context defaultContext = Context.empty();
+		StepVerifier.create(filter.filter(exchange,
+			new DefaultWebFilterChain( e ->
+				Mono.currentContext()
+					.defaultIfEmpty(defaultContext)
+					.doOnSuccess( context -> {
+					Principal contextPrincipal = context.<Mono<Principal>>get(Authentication.class).block();
+					assertThat(contextPrincipal).isNull();
+				})
+				.then()
+			)
+		))
+		.verifyComplete();
+	}
+}