Skip to content
Merged
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 @@ -53,7 +53,6 @@
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
Expand Down Expand Up @@ -102,7 +101,6 @@
import org.springframework.security.web.server.MatcherSecurityWebFilterChain;
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.AuthenticationWebFilter;
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
Expand Down Expand Up @@ -232,6 +230,7 @@
* @author Rob Winch
* @author Vedran Pavic
* @author Rafiullah Hamedy
* @author Eddú Meléndez
* @since 5.0
*/
public class ServerHttpSecurity {
Expand Down Expand Up @@ -717,6 +716,8 @@ public class OAuth2LoginSpec {

private ServerAuthenticationSuccessHandler authenticationSuccessHandler = new RedirectServerAuthenticationSuccessHandler();

private ServerAuthenticationFailureHandler authenticationFailureHandler = (webFilterExchange, exception) -> Mono.error(exception);

/**
* Configures the {@link ReactiveAuthenticationManager} to use. The default is
* {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
Expand All @@ -742,6 +743,19 @@ public OAuth2LoginSpec authenticationSuccessHandler(ServerAuthenticationSuccessH
return this;
}

/**
* The {@link ServerAuthenticationFailureHandler} used after authentication failure.
*
* @since 5.2
* @param authenticationFailureHandler the failure handler to use
* @return the {@link OAuth2LoginSpec} to customize
*/
public OAuth2LoginSpec authenticationFailureHandler(ServerAuthenticationFailureHandler authenticationFailureHandler) {
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
this.authenticationFailureHandler = authenticationFailureHandler;
return this;
}

/**
* Gets the {@link ReactiveAuthenticationManager} to use. First tries an explicitly configured manager, and
* defaults to {@link OAuth2AuthorizationCodeReactiveAuthenticationManager}
Expand Down Expand Up @@ -859,13 +873,7 @@ protected void configure(ServerHttpSecurity http) {
authenticationFilter.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));

authenticationFilter.setAuthenticationSuccessHandler(this.authenticationSuccessHandler);
authenticationFilter.setAuthenticationFailureHandler(new ServerAuthenticationFailureHandler() {
@Override
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange,
AuthenticationException exception) {
return Mono.error(exception);
}
});
authenticationFilter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
authenticationFilter.setSecurityContextRepository(new WebSessionServerSecurityContextRepository());

MediaTypeServerWebExchangeMatcher htmlMatcher = new MediaTypeServerWebExchangeMatcher(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,14 @@
import org.junit.Test;
import org.mockito.stubbing.Answer;
import org.openqa.selenium.WebDriver;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.RedirectServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import reactor.core.publisher.Mono;

Expand Down Expand Up @@ -97,6 +103,7 @@

/**
* @author Rob Winch
* @author Eddú Meléndez
* @since 5.1
*/
public class OAuth2LoginTests {
Expand Down Expand Up @@ -233,6 +240,59 @@ public void oauth2LoginWhenCustomObjectsThenUsed() {
verify(successHandler).onAuthenticationSuccess(any(), any());
}

@Test
public void oauth2LoginFailsWhenCustomObjectsThenUsed() {
this.spring.register(OAuth2LoginWithSingleClientRegistrations.class,
OAuth2LoginMockAuthenticationManagerConfig.class).autowire();

String redirectLocation = "/custom-redirect-location";
String failureRedirectLocation = "/failure-redirect-location";

WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(this.springSecurity)
.build();

OAuth2LoginMockAuthenticationManagerConfig config = this.spring.getContext()
.getBean(OAuth2LoginMockAuthenticationManagerConfig.class);
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
ServerWebExchangeMatcher matcher = config.matcher;
ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
ServerAuthenticationSuccessHandler successHandler = config.successHandler;
ServerAuthenticationFailureHandler failureHandler = config.failureHandler;

when(converter.convert(any())).thenReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
when(manager.authenticate(any())).thenReturn(Mono.error(new OAuth2AuthenticationException(new OAuth2Error("error"), "message")));
when(matcher.matches(any())).thenReturn(ServerWebExchangeMatcher.MatchResult.match());
when(resolver.resolve(any())).thenReturn(Mono.empty());
when(successHandler.onAuthenticationSuccess(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
WebFilterExchange webFilterExchange = invocation.getArgument(0);
Authentication authentication = invocation.getArgument(1);

return new RedirectServerAuthenticationSuccessHandler(redirectLocation)
.onAuthenticationSuccess(webFilterExchange, authentication);
});
when(failureHandler.onAuthenticationFailure(any(), any())).thenAnswer((Answer<Mono<Void>>) invocation -> {
WebFilterExchange webFilterExchange = invocation.getArgument(0);
AuthenticationException authenticationException = invocation.getArgument(1);

return new RedirectServerAuthenticationFailureHandler(failureRedirectLocation)
.onAuthenticationFailure(webFilterExchange, authenticationException);
});

webTestClient.get()
.uri("/login/oauth2/code/github")
.exchange()
.expectStatus().is3xxRedirection()
.expectHeader().valueEquals("Location", failureRedirectLocation);

verify(converter).convert(any());
verify(manager).authenticate(any());
verify(matcher).matches(any());
verify(resolver).resolve(any());
verify(failureHandler).onAuthenticationFailure(any(), any());
}

@Configuration
static class OAuth2LoginMockAuthenticationManagerConfig {
ReactiveAuthenticationManager manager = mock(ReactiveAuthenticationManager.class);
Expand All @@ -245,6 +305,8 @@ static class OAuth2LoginMockAuthenticationManagerConfig {

ServerAuthenticationSuccessHandler successHandler = mock(ServerAuthenticationSuccessHandler.class);

ServerAuthenticationFailureHandler failureHandler = mock(ServerAuthenticationFailureHandler.class);

@Bean
public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
http
Expand All @@ -256,7 +318,8 @@ public SecurityWebFilterChain springSecurityFilter(ServerHttpSecurity http) {
.authenticationManager(manager)
.authenticationMatcher(matcher)
.authorizationRequestResolver(resolver)
.authenticationSuccessHandler(successHandler);
.authenticationSuccessHandler(successHandler)
.authenticationFailureHandler(failureHandler);
return http.build();
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2019 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.
Expand All @@ -18,6 +18,7 @@

/**
* @author Rob Winch
* @author Eddú Meléndez
* @since 5.1
*/
public class TestOAuth2AuthorizationExchanges {
Expand All @@ -27,4 +28,10 @@ public static OAuth2AuthorizationExchange success() {
OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.success().build();
return new OAuth2AuthorizationExchange(request, response);
}

public static OAuth2AuthorizationExchange failure() {
OAuth2AuthorizationRequest request = TestOAuth2AuthorizationRequests.request().build();
OAuth2AuthorizationResponse response = TestOAuth2AuthorizationResponses.error().build();
return new OAuth2AuthorizationExchange(request, response);
}
}