Skip to content

Add ServerAuthenticationConverter interface #5689

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -27,23 +25,22 @@
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;
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";

Expand Down Expand Up @@ -72,7 +69,7 @@ public void setAuthorizationRequestRepository(
}

@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));
Expand All @@ -97,14 +94,14 @@ private Mono<OAuth2LoginAuthenticationToken> authenticationRequest(ServerWebExch
})
.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())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,6 @@ public void applyWhenCodeParameterFoundThenCode() {

private OAuth2LoginAuthenticationToken applyConverter() {
MockServerWebExchange exchange = MockServerWebExchange.from(this.request);
return (OAuth2LoginAuthenticationToken) this.converter.apply(exchange).block();
return (OAuth2LoginAuthenticationToken) this.converter.convert(exchange).block();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@
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;

Expand All @@ -41,13 +41,12 @@
* @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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,6 @@ private BearerTokenAuthenticationToken convertToToken(MockServerHttpRequest.Base

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();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -32,14 +31,14 @@
* @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));
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -32,12 +32,12 @@
* @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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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());

Expand All @@ -88,7 +88,7 @@ public AuthenticationWebFilter(ReactiveAuthenticationManager authenticationManag
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));
}
Expand Down Expand Up @@ -138,8 +138,24 @@ public void setAuthenticationSuccessHandler(ServerAuthenticationSuccessHandler a
* 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;
}
Expand All @@ -156,7 +172,7 @@ public void setAuthenticationFailureHandler(

/**
* 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.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -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);
}
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ public void applyWhenUsernameAndPasswordThenCreatesTokenSuccess() {
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);
Expand All @@ -73,7 +73,7 @@ public void applyWhenCustomParametersAndUsernameAndPasswordThenCreatesTokenSucce
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);
Expand All @@ -82,7 +82,7 @@ public void applyWhenCustomParametersAndUsernameAndPasswordThenCreatesTokenSucce

@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,6 @@ public void applyWhenWrongSchemeThenEmpty() {
}

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()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -34,16 +32,11 @@
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
Expand All @@ -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
Expand Down Expand Up @@ -136,7 +129,7 @@ public void filterWhenDefaultsAndAuthenticationFailThenUnauthorized() {

@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)
Expand All @@ -157,7 +150,7 @@ public void filterWhenConvertEmptyThenOk() {

@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)
Expand All @@ -178,7 +171,7 @@ public void filterWhenConvertErrorThenServerError() {
@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]));
Expand All @@ -203,7 +196,7 @@ public void filterWhenConvertAndAuthenticationSuccessThenSuccess() {
@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
Expand Down Expand Up @@ -245,7 +238,7 @@ public void filterWhenNotMatchAndConvertAndAuthenticationSuccessThenContinues()
@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());

Expand All @@ -268,7 +261,7 @@ public void filterWhenConvertAndAuthenticationFailThenEntryPoint() {
@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
Expand Down