Allow configuration of HTTP basic through nested builder

Issue: gh-5557
Fixes: gh-6885
This commit is contained in:
Eleftheria Stein 2019-05-22 17:51:12 -04:00 committed by Rob Winch
parent 3f2108921e
commit 12da990b6b
4 changed files with 238 additions and 1 deletions

View File

@ -0,0 +1,46 @@
/*
* 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.
* You may obtain a copy of the License at
*
* https://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.config;
/**
* Callback interface that accepts a single input argument and returns no result,
* with the ability to throw a (checked) exception.
*
* @author Eleftheria Stein
* @param <T> the type of the input to the operation
* @since 5.2
*/
@FunctionalInterface
public interface Customizer<T> {
/**
* Performs the customizations on the input argument.
*
* @param t the input argument
* @throws Exception if any error occurs
*/
void customize(T t) throws Exception;
/**
* Returns a {@link Customizer} that does not alter the input argument.
*
* @return a {@link Customizer} that does not alter the input argument.
*/
static <T> Customizer<T> withDefaults() {
return t -> {};
}
}

View File

@ -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.
@ -19,6 +19,7 @@ import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityBuilder;
@ -1094,6 +1095,41 @@ public final class HttpSecurity extends
return getOrApply(new HttpBasicConfigurer<>());
}
/**
* Configures HTTP Basic authentication.
*
* <h2>Example Configuration</h2>
*
* The example below demonstrates how to configure HTTP Basic authentication for an
* application. The default realm is "Realm", but can be
* customized using {@link HttpBasicConfigurer#realmName(String)}.
*
* <pre>
* &#064;Configuration
* &#064;EnableWebSecurity
* public class HttpBasicSecurityConfig extends WebSecurityConfigurerAdapter {
*
* &#064;Override
* protected void configure(HttpSecurity http) throws Exception {
* http
* .authorizeRequests()
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
* .and()
* .httpBasic(withDefaults());
* }
* }
* </pre>
*
* @param httpBasicCustomizer the {@link Customizer} to provide more options for
* the {@link HttpBasicConfigurer}
* @return the {@link HttpSecurity} for further customizations
* @throws Exception
*/
public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> httpBasicCustomizer) throws Exception {
httpBasicCustomizer.customize(getOrApply(new HttpBasicConfigurer<>()));
return HttpSecurity.this;
}
public <C> void setSharedObject(Class<C> sharedType, C object) {
super.setSharedObject(sharedType, object);
}

View File

@ -40,6 +40,7 @@ import javax.servlet.http.HttpServletResponse;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.*;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ -91,6 +92,37 @@ public class HttpBasicConfigurerTests {
}
}
@Test
public void httpBasicWhenUsingDefaultsInLambdaThenResponseIncludesBasicChallenge() throws Exception {
this.spring.register(DefaultsLambdaEntryPointConfig.class).autowire();
this.mvc.perform(get("/"))
.andExpect(status().isUnauthorized())
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\""));
}
@EnableWebSecurity
static class DefaultsLambdaEntryPointConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.httpBasic(withDefaults());
// @formatter:on
}
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
// @formatter:off
auth
.inMemoryAuthentication();
// @formatter:on
}
}
//SEC-2198
@Test
public void httpBasicWhenUsingDefaultsThenResponseIncludesBasicChallenge() throws Exception {

View File

@ -38,6 +38,7 @@ import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@ -102,6 +103,36 @@ public class NamespaceHttpBasicTests {
}
}
@Test
public void basicAuthenticationWhenUsingDefaultsInLambdaThenMatchesNamespace() throws Exception {
this.spring.register(HttpBasicLambdaConfig.class, UserConfig.class).autowire();
this.mvc.perform(get("/"))
.andExpect(status().isUnauthorized());
this.mvc.perform(get("/")
.with(httpBasic("user", "invalid")))
.andExpect(status().isUnauthorized())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\""));
this.mvc.perform(get("/")
.with(httpBasic("user", "password")))
.andExpect(status().isNotFound());
}
@EnableWebSecurity
static class HttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.httpBasic(withDefaults());
// @formatter:on
}
}
/**
* http@realm equivalent
*/
@ -127,6 +158,30 @@ public class NamespaceHttpBasicTests {
}
}
@Test
public void basicAuthenticationWhenUsingCustomRealmInLambdaThenMatchesNamespace() throws Exception {
this.spring.register(CustomHttpBasicLambdaConfig.class, UserConfig.class).autowire();
this.mvc.perform(get("/")
.with(httpBasic("user", "invalid")))
.andExpect(status().isUnauthorized())
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\""));
}
@EnableWebSecurity
static class CustomHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.httpBasic(httpBasicConfig -> httpBasicConfig.realmName("Custom Realm"));
// @formatter:on
}
}
/**
* http/http-basic@authentication-details-source-ref equivalent
*/
@ -161,6 +216,40 @@ public class NamespaceHttpBasicTests {
}
}
@Test
public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefInLambdaThenMatchesNamespace()
throws Exception {
this.spring.register(AuthenticationDetailsSourceHttpBasicLambdaConfig.class, UserConfig.class).autowire();
AuthenticationDetailsSource<HttpServletRequest, ?> source =
this.spring.getContext().getBean(AuthenticationDetailsSource.class);
this.mvc.perform(get("/")
.with(httpBasic("user", "password")));
verify(source).buildDetails(any(HttpServletRequest.class));
}
@EnableWebSecurity
static class AuthenticationDetailsSourceHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
mock(AuthenticationDetailsSource.class);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(httpBasicConfig ->
httpBasicConfig.authenticationDetailsSource(this.authenticationDetailsSource));
// @formatter:on
}
@Bean
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource() {
return this.authenticationDetailsSource;
}
}
/**
* http/http-basic@entry-point-ref
*/
@ -195,4 +284,38 @@ public class NamespaceHttpBasicTests {
.authenticationEntryPoint(this.authenticationEntryPoint);
}
}
@Test
public void basicAuthenticationWhenUsingEntryPointRefInLambdaThenMatchesNamespace() throws Exception {
this.spring.register(EntryPointRefHttpBasicLambdaConfig.class, UserConfig.class).autowire();
this.mvc.perform(get("/"))
.andExpect(status().is(999));
this.mvc.perform(get("/")
.with(httpBasic("user", "invalid")))
.andExpect(status().is(999));
this.mvc.perform(get("/")
.with(httpBasic("user", "password")))
.andExpect(status().isNotFound());
}
@EnableWebSecurity
static class EntryPointRefHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
AuthenticationEntryPoint authenticationEntryPoint =
(request, response, ex) -> response.setStatus(999);
@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().hasRole("USER")
.and()
.httpBasic(httpBasicConfig ->
httpBasicConfig.authenticationEntryPoint(this.authenticationEntryPoint));
// @formatter:on
}
}
}