Allow custom ReactiveAuthenticationManager for basic and form auth

Prior to this change, "HttpBasicSpec#authenticationManager" and
"FormLoginSpec#authenticationManager" were always overridden by
"ServerHttpSecurity#authenticationManager".

This commit makes sure override only happens when custom authentication
manager was not specified.

Fixes: gh-5660
This commit is contained in:
Rob Winch 2019-06-28 11:21:18 -05:00
parent aac854453e
commit 9d543ce4d1
3 changed files with 70 additions and 3 deletions

View File

@ -1277,11 +1277,15 @@ public class ServerHttpSecurity {
this.cors.configure(this);
}
if (this.httpBasic != null) {
this.httpBasic.authenticationManager(this.authenticationManager);
if (this.httpBasic.authenticationManager == null) {
this.httpBasic.authenticationManager(this.authenticationManager);
}
this.httpBasic.configure(this);
}
if (this.formLogin != null) {
this.formLogin.authenticationManager(this.authenticationManager);
if (this.formLogin.authenticationManager == null) {
this.formLogin.authenticationManager(this.authenticationManager);
}
if (this.securityContextRepository != null) {
this.formLogin.securityContextRepository(this.securityContextRepository);
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2017 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.
@ -23,6 +23,8 @@ import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.support.FindBy;
import org.openqa.selenium.support.PageFactory;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
import org.springframework.security.htmlunit.server.WebTestClientHtmlUnitDriverBuilder;
import org.springframework.security.test.web.reactive.server.WebTestClientBuilder;
@ -40,6 +42,10 @@ import reactor.core.publisher.Mono;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
/**
* @author Rob Winch
@ -152,6 +158,42 @@ public class FormLoginTests {
assertThat(driver.getCurrentUrl()).endsWith("/custom");
}
@Test
public void customAuthenticationManager() {
ReactiveAuthenticationManager defaultAuthenticationManager = mock(ReactiveAuthenticationManager.class);
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
given(defaultAuthenticationManager.authenticate(any())).willThrow(new RuntimeException("should not interact with default auth manager"));
given(customAuthenticationManager.authenticate(any())).willReturn(Mono.just(new TestingAuthenticationToken("user", "password", "ROLE_USER", "ROLE_ADMIN")));
SecurityWebFilterChain securityWebFilter = this.http
.authenticationManager(defaultAuthenticationManager)
.formLogin()
.authenticationManager(customAuthenticationManager)
.and()
.build();
WebTestClient webTestClient = WebTestClientBuilder
.bindToWebFilters(securityWebFilter)
.build();
WebDriver driver = WebTestClientHtmlUnitDriverBuilder
.webTestClientSetup(webTestClient)
.build();
DefaultLoginPage loginPage = DefaultLoginPage.to(driver)
.assertAt();
HomePage homePage = loginPage.loginForm()
.username("user")
.password("password")
.submit(HomePage.class);
homePage.assertAt();
verifyZeroInteractions(defaultAuthenticationManager);
}
public static class CustomLoginPage {
private WebDriver driver;

View File

@ -19,6 +19,8 @@ package org.springframework.security.config.web.server;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verifyZeroInteractions;
import static org.mockito.Mockito.when;
import java.util.Arrays;
@ -190,6 +192,25 @@ public class ServerHttpSecurityTests {
.isEqualTo(Arrays.asList(SecurityContextServerLogoutHandler.class, CsrfServerLogoutHandler.class));
}
@Test
public void basicWithCustomAuthenticationManager() {
ReactiveAuthenticationManager customAuthenticationManager = mock(ReactiveAuthenticationManager.class);
given(customAuthenticationManager.authenticate(any())).willReturn(Mono.just(new TestingAuthenticationToken("rob", "rob", "ROLE_USER", "ROLE_ADMIN")));
SecurityWebFilterChain securityFilterChain = this.http.httpBasic().authenticationManager(customAuthenticationManager).and().build();
WebFilterChainProxy springSecurityFilterChain = new WebFilterChainProxy(securityFilterChain);
WebTestClient client = WebTestClientBuilder.bindToWebFilters(springSecurityFilterChain).build();
client.get()
.uri("/")
.headers(headers -> headers.setBasicAuth("rob", "rob"))
.exchange()
.expectStatus().isOk()
.expectBody(String.class).consumeWith(b -> assertThat(b.getResponseBody()).isEqualTo("ok"));
verifyZeroInteractions(this.authenticationManager);
}
private <T extends WebFilter> Optional<T> getWebFilter(SecurityWebFilterChain filterChain, Class<T> filterClass) {
return (Optional<T>) filterChain.getWebFilters()
.filter(Objects::nonNull)