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:
parent
34c8d66017
commit
b6afe66d32
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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()));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue