浏览代码

Introduce AuthenticationConverterServerWebExchangeMatcher

AuthenticationConverterServerWebExchangeMatcher is ServerWebExchangeMatcher implementation based on AuthenticationConverter which matches if ServerWebExchange can be converted to Authentication.
It can be used as a matcher where SecurityFilterChain should be matched based on used authentication method.
BearerTokenServerWebExchangeMatcher was replaced by this matcher.

Closes gh-8824
Dávid Kováč 5 年之前
父节点
当前提交
37aa5f9b7c

+ 8 - 29
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -108,6 +108,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain;
 import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
 import org.springframework.security.web.server.WebFilterExchange;
 import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilter;
+import org.springframework.security.web.server.authentication.AuthenticationConverterServerWebExchangeMatcher;
 import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
 import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
 import org.springframework.security.web.server.authentication.ReactivePreAuthenticatedAuthenticationManager;
@@ -179,8 +180,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.
@@ -1629,8 +1628,7 @@ public class ServerHttpSecurity {
 		private ServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint();
 		private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
 		private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
-		private BearerTokenServerWebExchangeMatcher bearerTokenServerWebExchangeMatcher =
-				new BearerTokenServerWebExchangeMatcher();
+		private AuthenticationConverterServerWebExchangeMatcher authenticationConverterServerWebExchangeMatcher;
 
 		private JwtSpec jwt;
 		private OpaqueTokenSpec opaqueToken;
@@ -1748,8 +1746,8 @@ public class ServerHttpSecurity {
 		}
 
 		protected void configure(ServerHttpSecurity http) {
-			this.bearerTokenServerWebExchangeMatcher
-					.setBearerTokenConverter(this.bearerTokenConverter);
+			this.authenticationConverterServerWebExchangeMatcher =
+					new AuthenticationConverterServerWebExchangeMatcher(this.bearerTokenConverter);
 
 			registerDefaultAccessDeniedHandler(http);
 			registerDefaultAuthenticationEntryPoint(http);
@@ -1794,7 +1792,7 @@ public class ServerHttpSecurity {
 			if ( http.exceptionHandling != null ) {
 				http.defaultAccessDeniedHandlers.add(
 						new ServerWebExchangeDelegatingServerAccessDeniedHandler.DelegateEntry(
-								this.bearerTokenServerWebExchangeMatcher,
+								this.authenticationConverterServerWebExchangeMatcher,
 								OAuth2ResourceServerSpec.this.accessDeniedHandler
 						)
 				);
@@ -1805,7 +1803,7 @@ public class ServerHttpSecurity {
 			if (http.exceptionHandling != null) {
 				http.defaultEntryPoints.add(
 						new DelegateEntry(
-								this.bearerTokenServerWebExchangeMatcher,
+								this.authenticationConverterServerWebExchangeMatcher,
 								OAuth2ResourceServerSpec.this.entryPoint
 						)
 				);
@@ -1820,27 +1818,7 @@ public class ServerHttpSecurity {
 							new AndServerWebExchangeMatcher(
 									CsrfWebFilter.DEFAULT_CSRF_MATCHER,
 									new NegatedServerWebExchangeMatcher(
-											this.bearerTokenServerWebExchangeMatcher)));
-			}
-		}
-
-		private class BearerTokenServerWebExchangeMatcher implements ServerWebExchangeMatcher {
-			ServerAuthenticationConverter bearerTokenConverter;
-
-			@Override
-			public Mono<MatchResult> matches(ServerWebExchange exchange) {
-				return this.bearerTokenConverter.convert(exchange)
-						.flatMap(this::nullAuthentication)
-						.onErrorResume(e -> notMatch());
-			}
-
-			public void setBearerTokenConverter(ServerAuthenticationConverter bearerTokenConverter) {
-				Assert.notNull(bearerTokenConverter, "bearerTokenConverter cannot be null");
-				this.bearerTokenConverter = bearerTokenConverter;
-			}
-
-			private Mono<MatchResult> nullAuthentication(Authentication authentication) {
-				return authentication == null ? notMatch() : match();
+											this.authenticationConverterServerWebExchangeMatcher)));
 			}
 		}
 
@@ -4034,4 +4012,5 @@ public class ServerHttpSecurity {
 		private AnonymousSpec() {}
 
 	}
+
 }

+ 51 - 0
web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationConverterServerWebExchangeMatcher.java

@@ -0,0 +1,51 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.authentication;
+
+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 org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
+import org.springframework.util.Assert;
+import org.springframework.web.server.ServerWebExchange;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * Matches if the {@link ServerAuthenticationConverter} can convert a {@link ServerWebExchange} to an {@link Authentication}.
+ *
+ * @author David Kovac
+ * @since 5.4
+ * @see ServerAuthenticationConverter
+ */
+public final class AuthenticationConverterServerWebExchangeMatcher implements ServerWebExchangeMatcher {
+	private final ServerAuthenticationConverter serverAuthenticationConverter;
+
+	public AuthenticationConverterServerWebExchangeMatcher(ServerAuthenticationConverter serverAuthenticationConverter) {
+		Assert.notNull(serverAuthenticationConverter, "serverAuthenticationConverter cannot be null");
+		this.serverAuthenticationConverter = serverAuthenticationConverter;
+	}
+
+	@Override
+	public Mono<MatchResult> matches(ServerWebExchange exchange) {
+		return this.serverAuthenticationConverter.convert(exchange)
+				.flatMap(a -> match())
+				.onErrorResume(e -> notMatch())
+				.switchIfEmpty(notMatch());
+	}
+}

+ 96 - 0
web/src/test/java/org/springframework/security/web/server/authentication/AuthenticationConverterServerWebExchangeMatcherTests.java

@@ -0,0 +1,96 @@
+/*
+ * Copyright 2002-2020 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
+ *
+ *      https://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.authentication;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.Mockito.when;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
+import org.springframework.mock.web.server.MockServerWebExchange;
+import org.springframework.security.core.Authentication;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * @author David Kovac
+ * @since 5.4
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class AuthenticationConverterServerWebExchangeMatcherTests {
+	private MockServerWebExchange exchange;
+	private AuthenticationConverterServerWebExchangeMatcher matcher;
+	@Mock
+	private ServerAuthenticationConverter converter;
+	@Mock
+	private Authentication authentication;
+
+	@Before
+	public void setup() {
+		MockServerHttpRequest request = MockServerHttpRequest.get("/path").build();
+		exchange = MockServerWebExchange.from(request);
+		matcher = new AuthenticationConverterServerWebExchangeMatcher(converter);
+	}
+
+	@Test(expected = IllegalArgumentException.class)
+	public void constructorConverterWhenConverterNullThenThrowsException() {
+		new AuthenticationConverterServerWebExchangeMatcher(null);
+	}
+
+	@Test
+	public void matchesWhenNotEmptyThenReturnTrue() {
+		when(converter.convert(any())).thenReturn(Mono.just(authentication));
+
+		assertThat(matcher.matches(exchange).block().isMatch()).isTrue();
+	}
+
+	@Test
+	public void matchesWhenEmptyThenReturnFalse() {
+		when(converter.convert(any())).thenReturn(Mono.empty());
+
+		assertThat(matcher.matches(exchange).block().isMatch()).isFalse();
+	}
+
+	@Test
+	public void matchesWhenErrorThenReturnFalse() {
+		when(converter.convert(any())).thenReturn(Mono.error(new RuntimeException()));
+
+		assertThat(matcher.matches(exchange).block().isMatch()).isFalse();
+	}
+
+	@Test
+	public void matchesWhenNullThenThrowsException() {
+		when(this.converter.convert(any())).thenReturn(null);
+
+		assertThatCode(() -> matcher.matches(exchange).block())
+				.isInstanceOf(NullPointerException.class);
+	}
+
+	@Test
+	public void matchesWhenExceptionThenPropagates() {
+		when(this.converter.convert(any())).thenThrow(RuntimeException.class);
+
+		assertThatCode(() -> matcher.matches(exchange).block())
+				.isInstanceOf(RuntimeException.class);
+	}
+}