Browse Source

Add ServerAuthenticationConverter interface

- Adding an ServerAuthenticationConverter interface
- Retro-fitting ServerOAuth2LoginAuthenticationTokenConverter,
 ServerBearerTokenAuthentivationConverter, ServerFormLoginAuthenticationConverter,
 and ServerHttpBasicAuthenticationConverter to implement ServerAuthenticationConverter
- Deprecate existing AuthenticationWebFilter.setAuthenticationConverter
and add overloaded one which takes ServerAuthenticationConverter

Fixes gh-5338
Eric Deandrea 7 years ago
parent
commit
b6afe66d32

+ 6 - 9
oauth2/oauth2-client/src/main/java/org/springframework/security/oauth2/client/web/ServerOAuth2LoginAuthenticationTokenConverter.java

@@ -16,8 +16,6 @@
 
 package org.springframework.security.oauth2.client.web;
 
-import java.util.function.Function;
-
 import org.springframework.security.core.Authentication;
 import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuthenticationToken;
 import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
@@ -27,6 +25,7 @@ import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationExch
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.util.Assert;
 import org.springframework.util.MultiValueMap;
 import org.springframework.web.server.ServerWebExchange;
@@ -34,16 +33,14 @@ import org.springframework.web.util.UriComponentsBuilder;
 
 import reactor.core.publisher.Mono;
 
-
 /**
  * Converts from a {@link ServerWebExchange} to an {@link OAuth2LoginAuthenticationToken} that can be authenticated. The
  * converter does not validate any errors it only performs a conversion.
  * @author Rob Winch
  * @since 5.1
- * @see org.springframework.security.web.server.authentication.AuthenticationWebFilter#setAuthenticationConverter(Function)
+ * @see org.springframework.security.web.server.authentication.AuthenticationWebFilter#setAuthenticationConverter(ServerAuthenticationConverter)
  */
-public class ServerOAuth2LoginAuthenticationTokenConverter implements
-		Function<ServerWebExchange, Mono<Authentication>> {
+public class ServerOAuth2LoginAuthenticationTokenConverter implements ServerAuthenticationConverter {
 
 	static final String AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE = "authorization_request_not_found";
 
@@ -72,7 +69,7 @@ public class ServerOAuth2LoginAuthenticationTokenConverter implements
 	}
 
 	@Override
-	public Mono<Authentication> apply(ServerWebExchange serverWebExchange) {
+	public Mono<Authentication> convert(ServerWebExchange serverWebExchange) {
 		return this.authorizationRequestRepository.removeAuthorizationRequest(serverWebExchange)
 			.switchIfEmpty(oauth2AuthenticationException(AUTHORIZATION_REQUEST_NOT_FOUND_ERROR_CODE))
 			.flatMap(authorizationRequest -> authenticationRequest(serverWebExchange, authorizationRequest));
@@ -97,14 +94,14 @@ public class ServerOAuth2LoginAuthenticationTokenConverter implements
 				})
 				.switchIfEmpty(oauth2AuthenticationException(CLIENT_REGISTRATION_NOT_FOUND_ERROR_CODE))
 				.map(clientRegistration -> {
-					OAuth2AuthorizationResponse authorizationResponse = convert(exchange);
+					OAuth2AuthorizationResponse authorizationResponse = convertResponse(exchange);
 					OAuth2LoginAuthenticationToken authenticationRequest = new OAuth2LoginAuthenticationToken(
 							clientRegistration, new OAuth2AuthorizationExchange(authorizationRequest, authorizationResponse));
 					return authenticationRequest;
 				});
 	}
 
-	private static OAuth2AuthorizationResponse convert(ServerWebExchange exchange) {
+	private static OAuth2AuthorizationResponse convertResponse(ServerWebExchange exchange) {
 		MultiValueMap<String, String> queryParams = exchange.getRequest()
 				.getQueryParams();
 		String redirectUri = UriComponentsBuilder.fromUri(exchange.getRequest().getURI())

+ 1 - 1
oauth2/oauth2-client/src/test/java/org/springframework/security/oauth2/client/web/ServerOAuth2LoginAuthenticationTokenConverterTest.java

@@ -141,6 +141,6 @@ public class ServerOAuth2LoginAuthenticationTokenConverterTest {
 
 	private OAuth2LoginAuthenticationToken applyConverter() {
 		MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
-		return (OAuth2LoginAuthenticationToken) this.converter.apply(exchange).block();
+		return (OAuth2LoginAuthenticationToken) this.converter.convert(exchange).block();
 	}
 }

+ 3 - 4
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverter.java

@@ -25,11 +25,11 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.BearerTokenError;
 import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.util.StringUtils;
 import org.springframework.web.server.ServerWebExchange;
 import reactor.core.publisher.Mono;
 
-import java.util.function.Function;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
@@ -41,13 +41,12 @@ import java.util.regex.Pattern;
  * @since 5.1
  * @see <a href="https://tools.ietf.org/html/rfc6750#section-2" target="_blank">RFC 6750 Section 2: Authenticated Requests</a>
  */
-public class ServerBearerTokenAuthenticationConverter implements
-		Function<ServerWebExchange, Mono<Authentication>> {
+public class ServerBearerTokenAuthenticationConverter implements ServerAuthenticationConverter {
 	private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[a-zA-Z0-9-._~+/]+)=*$");
 
 	private boolean allowUriQueryParameter = false;
 
-	public Mono<Authentication> apply(ServerWebExchange exchange) {
+	public Mono<Authentication> convert(ServerWebExchange exchange) {
 		return Mono.justOrEmpty(this.token(exchange.getRequest()))
 			.map(BearerTokenAuthenticationToken::new);
 	}

+ 1 - 1
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/server/ServerBearerTokenAuthenticationConverterTests.java

@@ -129,6 +129,6 @@ public class ServerBearerTokenAuthenticationConverterTests {
 
 	private BearerTokenAuthenticationToken convertToToken(MockServerHttpRequest request) {
 		MockServerWebExchange exchange = MockServerWebExchange.from(request);
-		return this.converter.apply(exchange).cast(BearerTokenAuthenticationToken.class).block();
+		return this.converter.convert(exchange).cast(BearerTokenAuthenticationToken.class).block();
 	}
 }

+ 3 - 4
web/src/main/java/org/springframework/security/web/server/ServerFormLoginAuthenticationConverter.java

@@ -15,8 +15,7 @@
  */
 package org.springframework.security.web.server;
 
-import java.util.function.Function;
-
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.util.Assert;
 import reactor.core.publisher.Mono;
 
@@ -32,14 +31,14 @@ import org.springframework.web.server.ServerWebExchange;
  * @author Rob Winch
  * @since 5.0
  */
-public class ServerFormLoginAuthenticationConverter implements Function<ServerWebExchange, Mono<Authentication>> {
+public class ServerFormLoginAuthenticationConverter implements ServerAuthenticationConverter {
 
 	private String usernameParameter = "username";
 
 	private String passwordParameter = "password";
 
 	@Override
-	public Mono<Authentication> apply(ServerWebExchange exchange) {
+	public Mono<Authentication> convert(ServerWebExchange exchange) {
 		return exchange.getFormData()
 			.map( data -> createAuthentication(data));
 	}

+ 3 - 3
web/src/main/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverter.java

@@ -16,12 +16,12 @@
 package org.springframework.security.web.server;
 
 import java.util.Base64;
-import java.util.function.Function;
 
 import org.springframework.http.HttpHeaders;
 import org.springframework.http.server.reactive.ServerHttpRequest;
 import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
 import org.springframework.security.core.Authentication;
+import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
 import org.springframework.web.server.ServerWebExchange;
 
 import reactor.core.publisher.Mono;
@@ -32,12 +32,12 @@ import reactor.core.publisher.Mono;
  * @author Rob Winch
  * @since 5.0
  */
-public class ServerHttpBasicAuthenticationConverter implements Function<ServerWebExchange, Mono<Authentication>> {
+public class ServerHttpBasicAuthenticationConverter implements ServerAuthenticationConverter {
 
 	public static final String BASIC = "Basic ";
 
 	@Override
-	public Mono<Authentication> apply(ServerWebExchange exchange) {
+	public Mono<Authentication> convert(ServerWebExchange exchange) {
 		ServerHttpRequest request = exchange.getRequest();
 
 		String authorization = request.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);

+ 19 - 3
web/src/main/java/org/springframework/security/web/server/authentication/AuthenticationWebFilter.java

@@ -67,7 +67,7 @@ public class AuthenticationWebFilter implements WebFilter {
 
 	private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new WebFilterChainServerAuthenticationSuccessHandler();
 
-	private Function<ServerWebExchange, Mono<Authentication>> authenticationConverter = new ServerHttpBasicAuthenticationConverter();
+	private ServerAuthenticationConverter authenticationConverter = new ServerHttpBasicAuthenticationConverter();
 
 	private ServerAuthenticationFailureHandler authenticationFailureHandler = new ServerAuthenticationEntryPointFailureHandler(new HttpBasicServerAuthenticationEntryPoint());
 
@@ -88,7 +88,7 @@ public class AuthenticationWebFilter implements WebFilter {
 	public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
 		return this.requiresAuthenticationMatcher.matches(exchange)
 			.filter( matchResult -> matchResult.isMatch())
-			.flatMap( matchResult -> this.authenticationConverter.apply(exchange))
+			.flatMap( matchResult -> this.authenticationConverter.convert(exchange))
 			.switchIfEmpty(chain.filter(exchange).then(Mono.empty()))
 			.flatMap( token -> authenticate(exchange, chain, token));
 	}
@@ -138,8 +138,24 @@ public class AuthenticationWebFilter implements WebFilter {
 	 * that no authentication attempt should be made. The default converter is
 	 * {@link ServerHttpBasicAuthenticationConverter}
 	 * @param authenticationConverter the converter to use
+	 * @deprecated As of 5.1 in favor of {@link #setAuthenticationConverter(ServerAuthenticationConverter)}
+	 * @see #setAuthenticationConverter(ServerAuthenticationConverter)
 	 */
+	@Deprecated
 	public void setAuthenticationConverter(Function<ServerWebExchange, Mono<Authentication>> authenticationConverter) {
+		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+		setAuthenticationConverter((ServerAuthenticationConverter) authenticationConverter);
+	}
+
+	/**
+	 * Sets the strategy used for converting from a {@link ServerWebExchange} to an {@link Authentication} used for
+	 * authenticating with the provided {@link ReactiveAuthenticationManager}. If the result is empty, then it signals
+	 * that no authentication attempt should be made. The default converter is
+	 * {@link ServerHttpBasicAuthenticationConverter}
+	 * @param authenticationConverter the converter to use
+	 * @since 5.1
+	 */
+	public void setAuthenticationConverter(ServerAuthenticationConverter authenticationConverter) {
 		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
 		this.authenticationConverter = authenticationConverter;
 	}
@@ -156,7 +172,7 @@ public class AuthenticationWebFilter implements WebFilter {
 
 	/**
 	 * Sets the matcher used to determine when creating an {@link Authentication} from
-	 * {@link #setAuthenticationConverter(Function)} to be authentication. If the converter returns an empty
+	 * {@link #setAuthenticationConverter(ServerAuthenticationConverter)} to be authentication. If the converter returns an empty
 	 * result, then no authentication is attempted. The default is any request
 	 * @param requiresAuthenticationMatcher the matcher to use. Cannot be null.
 	 */

+ 40 - 0
web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationConverter.java

@@ -0,0 +1,40 @@
+/*
+ * Copyright 2002-2018 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.authentication;
+
+import org.springframework.security.core.Authentication;
+import org.springframework.web.server.ServerWebExchange;
+
+import reactor.core.publisher.Mono;
+
+/**
+ * A strategy used for converting from a {@link ServerWebExchange} to an {@link Authentication} used for
+ * authenticating with a provided {@link org.springframework.security.authentication.ReactiveAuthenticationManager}.
+ * If the result is {@link Mono#empty()}, then it signals that no authentication attempt should be made.
+ *
+ * @author Eric Deandrea
+ * @since 5.1
+ */
+@FunctionalInterface
+public interface ServerAuthenticationConverter {
+	/**
+	 * Converts a {@link ServerWebExchange} to an {@link Authentication}
+	 * @param exchange The {@link ServerWebExchange}
+	 * @return A {@link Mono} representing an {@link Authentication}
+	 */
+	Mono<Authentication> convert(ServerWebExchange exchange);
+}

+ 3 - 3
web/src/test/java/org/springframework/security/web/server/ServerFormLoginAuthenticationConverterTests.java

@@ -55,7 +55,7 @@ public class ServerFormLoginAuthenticationConverterTests {
 		this.data.add("username", username);
 		this.data.add("password", password);
 
-		Authentication authentication = this.converter.apply(this.exchange).block();
+		Authentication authentication = this.converter.convert(this.exchange).block();
 
 		assertThat(authentication.getName()).isEqualTo(username);
 		assertThat(authentication.getCredentials()).isEqualTo(password);
@@ -73,7 +73,7 @@ public class ServerFormLoginAuthenticationConverterTests {
 		this.data.add(usernameParameter, username);
 		this.data.add(passwordParameter, password);
 
-		Authentication authentication = this.converter.apply(this.exchange).block();
+		Authentication authentication = this.converter.convert(this.exchange).block();
 
 		assertThat(authentication.getName()).isEqualTo(username);
 		assertThat(authentication.getCredentials()).isEqualTo(password);
@@ -82,7 +82,7 @@ public class ServerFormLoginAuthenticationConverterTests {
 
 	@Test
 	public void applyWhenNoDataThenCreatesTokenSuccess() {
-		Authentication authentication = this.converter.apply(this.exchange).block();
+		Authentication authentication = this.converter.convert(this.exchange).block();
 
 		assertThat(authentication.getName()).isNullOrEmpty();
 		assertThat(authentication.getCredentials()).isNull();

+ 1 - 1
web/src/test/java/org/springframework/security/web/server/ServerHttpBasicAuthenticationConverterTests.java

@@ -96,6 +96,6 @@ public class ServerHttpBasicAuthenticationConverterTests {
 	}
 
 	private Mono<Authentication> apply(MockServerHttpRequest.BaseBuilder<?> request) {
-		return this.converter.apply(MockServerWebExchange.from(this.request.build()));
+		return this.converter.convert(MockServerWebExchange.from(this.request.build()));
 	}
 }

+ 8 - 15
web/src/test/java/org/springframework/security/web/server/authentication/AuthenticationWebFilterTests.java

@@ -16,8 +16,6 @@
 
 package org.springframework.security.web.server.authentication;
 
-import java.util.function.Function;
-
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,16 +32,11 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo
 import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
 import org.springframework.test.web.reactive.server.EntityExchangeResult;
 import org.springframework.test.web.reactive.server.WebTestClient;
-import org.springframework.web.server.ServerWebExchange;
 
 import static org.assertj.core.api.Assertions.assertThat;
 import static org.mockito.Matchers.any;
 import static org.mockito.Matchers.eq;
-import static org.mockito.Mockito.never;
-import static org.mockito.Mockito.verify;
-import static org.mockito.Mockito.verifyZeroInteractions;
-import static org.mockito.Mockito.when;
-
+import static org.mockito.Mockito.*;
 
 /**
  * @author Rob Winch
@@ -54,7 +47,7 @@ public class AuthenticationWebFilterTests {
 	@Mock
 	private ServerAuthenticationSuccessHandler successHandler;
 	@Mock
-	private Function<ServerWebExchange, Mono<Authentication>> authenticationConverter;
+	private ServerAuthenticationConverter authenticationConverter;
 	@Mock
 	private ReactiveAuthenticationManager authenticationManager;
 	@Mock
@@ -136,7 +129,7 @@ public class AuthenticationWebFilterTests {
 
 	@Test
 	public void filterWhenConvertEmptyThenOk() {
-		when(this.authenticationConverter.apply(any())).thenReturn(Mono.empty());
+		when(this.authenticationConverter.convert(any())).thenReturn(Mono.empty());
 
 		WebTestClient client = WebTestClientBuilder
 			.bindToWebFilters(this.filter)
@@ -157,7 +150,7 @@ public class AuthenticationWebFilterTests {
 
 	@Test
 	public void filterWhenConvertErrorThenServerError() {
-		when(this.authenticationConverter.apply(any())).thenReturn(Mono.error(new RuntimeException("Unexpected")));
+		when(this.authenticationConverter.convert(any())).thenReturn(Mono.error(new RuntimeException("Unexpected")));
 
 		WebTestClient client = WebTestClientBuilder
 			.bindToWebFilters(this.filter)
@@ -178,7 +171,7 @@ public class AuthenticationWebFilterTests {
 	@Test
 	public void filterWhenConvertAndAuthenticationSuccessThenSuccess() {
 		Mono<Authentication> authentication = Mono.just(new TestingAuthenticationToken("test", "this", "ROLE_USER"));
-		when(this.authenticationConverter.apply(any())).thenReturn(authentication);
+		when(this.authenticationConverter.convert(any())).thenReturn(authentication);
 		when(this.authenticationManager.authenticate(any())).thenReturn(authentication);
 		when(this.successHandler.onAuthenticationSuccess(any(), any())).thenReturn(Mono.empty());
 		when(this.securityContextRepository.save(any(), any())).thenAnswer( a -> Mono.just(a.getArguments()[0]));
@@ -203,7 +196,7 @@ public class AuthenticationWebFilterTests {
 	@Test
 	public void filterWhenConvertAndAuthenticationEmptyThenServerError() {
 		Mono<Authentication> authentication = Mono.just(new TestingAuthenticationToken("test", "this", "ROLE_USER"));
-		when(this.authenticationConverter.apply(any())).thenReturn(authentication);
+		when(this.authenticationConverter.convert(any())).thenReturn(authentication);
 		when(this.authenticationManager.authenticate(any())).thenReturn(Mono.empty());
 
 		WebTestClient client = WebTestClientBuilder
@@ -245,7 +238,7 @@ public class AuthenticationWebFilterTests {
 	@Test
 	public void filterWhenConvertAndAuthenticationFailThenEntryPoint() {
 		Mono<Authentication> authentication = Mono.just(new TestingAuthenticationToken("test", "this", "ROLE_USER"));
-		when(this.authenticationConverter.apply(any())).thenReturn(authentication);
+		when(this.authenticationConverter.convert(any())).thenReturn(authentication);
 		when(this.authenticationManager.authenticate(any())).thenReturn(Mono.error(new BadCredentialsException("Failed")));
 		when(this.failureHandler.onAuthenticationFailure(any(), any())).thenReturn(Mono.empty());
 
@@ -268,7 +261,7 @@ public class AuthenticationWebFilterTests {
 	@Test
 	public void filterWhenConvertAndAuthenticationExceptionThenServerError() {
 		Mono<Authentication> authentication = Mono.just(new TestingAuthenticationToken("test", "this", "ROLE_USER"));
-		when(this.authenticationConverter.apply(any())).thenReturn(authentication);
+		when(this.authenticationConverter.convert(any())).thenReturn(authentication);
 		when(this.authenticationManager.authenticate(any())).thenReturn(Mono.error(new RuntimeException("Failed")));
 
 		WebTestClient client = WebTestClientBuilder