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
This commit is contained in:
Eric Deandrea 2018-08-17 18:53:28 -04:00 committed by Rob Winch
parent 34c8d66017
commit b6afe66d32
11 changed files with 88 additions and 44 deletions

View File

@ -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())

View File

@ -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();
}
}

View File

@ -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);
}

View File

@ -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();
}
}

View File

@ -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));
}

View File

@ -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);

View File

@ -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.
*/

View File

@ -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);
}

View File

@ -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();

View File

@ -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()));
}
}

View File

@ -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