Allow configuration of HTTP basic through nested builder
Issue: gh-5557 Fixes: gh-6885
This commit is contained in:
parent
3f2108921e
commit
12da990b6b
|
@ -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 -> {};
|
||||
}
|
||||
}
|
|
@ -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>
|
||||
* @Configuration
|
||||
* @EnableWebSecurity
|
||||
* public class HttpBasicSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
*
|
||||
* @Override
|
||||
* protected void configure(HttpSecurity http) throws Exception {
|
||||
* http
|
||||
* .authorizeRequests()
|
||||
* .antMatchers("/**").hasRole("USER")
|
||||
* .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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue