Skip to content

Commit e1eaaa9

Browse files
committed
Allow configuration of HTTP basic through nested builder
Issue: gh-5557 Fixes: gh-6885
1 parent 6e76df8 commit e1eaaa9

File tree

4 files changed

+238
-1
lines changed

4 files changed

+238
-1
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
/*
2+
* Copyright 2002-2019 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.config;
18+
19+
/**
20+
* Callback interface that accepts a single input argument and returns no result,
21+
* with the ability to throw a (checked) exception.
22+
*
23+
* @author Eleftheria Stein
24+
* @param <T> the type of the input to the operation
25+
* @since 5.2
26+
*/
27+
@FunctionalInterface
28+
public interface Customizer<T> {
29+
30+
/**
31+
* Performs the customizations on the input argument.
32+
*
33+
* @param t the input argument
34+
* @throws Exception if any error occurs
35+
*/
36+
void customize(T t) throws Exception;
37+
38+
/**
39+
* Returns a {@link Customizer} that does not alter the input argument.
40+
*
41+
* @return a {@link Customizer} that does not alter the input argument.
42+
*/
43+
static <T> Customizer<T> withDefaults() {
44+
return t -> {};
45+
}
46+
}

config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

+37-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2018 the original author or authors.
2+
* Copyright 2002-2019 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -19,6 +19,7 @@
1919
import org.springframework.http.HttpMethod;
2020
import org.springframework.security.authentication.AuthenticationManager;
2121
import org.springframework.security.authentication.AuthenticationProvider;
22+
import org.springframework.security.config.Customizer;
2223
import org.springframework.security.config.annotation.AbstractConfiguredSecurityBuilder;
2324
import org.springframework.security.config.annotation.ObjectPostProcessor;
2425
import org.springframework.security.config.annotation.SecurityBuilder;
@@ -1094,6 +1095,41 @@ public HttpBasicConfigurer<HttpSecurity> httpBasic() throws Exception {
10941095
return getOrApply(new HttpBasicConfigurer<>());
10951096
}
10961097

1098+
/**
1099+
* Configures HTTP Basic authentication.
1100+
*
1101+
* <h2>Example Configuration</h2>
1102+
*
1103+
* The example below demonstrates how to configure HTTP Basic authentication for an
1104+
* application. The default realm is "Realm", but can be
1105+
* customized using {@link HttpBasicConfigurer#realmName(String)}.
1106+
*
1107+
* <pre>
1108+
* &#064;Configuration
1109+
* &#064;EnableWebSecurity
1110+
* public class HttpBasicSecurityConfig extends WebSecurityConfigurerAdapter {
1111+
*
1112+
* &#064;Override
1113+
* protected void configure(HttpSecurity http) throws Exception {
1114+
* http
1115+
* .authorizeRequests()
1116+
* .antMatchers(&quot;/**&quot;).hasRole(&quot;USER&quot;)
1117+
* .and()
1118+
* .httpBasic(withDefaults());
1119+
* }
1120+
* }
1121+
* </pre>
1122+
*
1123+
* @param httpBasicCustomizer the {@link Customizer} to provide more options for
1124+
* the {@link HttpBasicConfigurer}
1125+
* @return the {@link HttpSecurity} for further customizations
1126+
* @throws Exception
1127+
*/
1128+
public HttpSecurity httpBasic(Customizer<HttpBasicConfigurer<HttpSecurity>> httpBasicCustomizer) throws Exception {
1129+
httpBasicCustomizer.customize(getOrApply(new HttpBasicConfigurer<>()));
1130+
return HttpSecurity.this;
1131+
}
1132+
10971133
public <C> void setSharedObject(Class<C> sharedType, C object) {
10981134
super.setSharedObject(sharedType, object);
10991135
}

config/src/test/java/org/springframework/security/config/annotation/web/configurers/HttpBasicConfigurerTests.java

+32
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,7 @@
4040

4141
import static org.mockito.ArgumentMatchers.any;
4242
import static org.mockito.Mockito.*;
43+
import static org.springframework.security.config.Customizer.withDefaults;
4344
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
4445
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4546
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@@ -91,6 +92,37 @@ public <O> O postProcess(O object) {
9192
}
9293
}
9394

95+
@Test
96+
public void httpBasicWhenUsingDefaultsInLambdaThenResponseIncludesBasicChallenge() throws Exception {
97+
this.spring.register(DefaultsLambdaEntryPointConfig.class).autowire();
98+
99+
this.mvc.perform(get("/"))
100+
.andExpect(status().isUnauthorized())
101+
.andExpect(header().string("WWW-Authenticate", "Basic realm=\"Realm\""));
102+
}
103+
104+
@EnableWebSecurity
105+
static class DefaultsLambdaEntryPointConfig extends WebSecurityConfigurerAdapter {
106+
@Override
107+
protected void configure(HttpSecurity http) throws Exception {
108+
// @formatter:off
109+
http
110+
.authorizeRequests()
111+
.anyRequest().authenticated()
112+
.and()
113+
.httpBasic(withDefaults());
114+
// @formatter:on
115+
}
116+
117+
@Override
118+
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
119+
// @formatter:off
120+
auth
121+
.inMemoryAuthentication();
122+
// @formatter:on
123+
}
124+
}
125+
94126
//SEC-2198
95127
@Test
96128
public void httpBasicWhenUsingDefaultsThenResponseIncludesBasicChallenge() throws Exception {

config/src/test/java/org/springframework/security/config/annotation/web/configurers/NamespaceHttpBasicTests.java

+123
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import static org.mockito.ArgumentMatchers.any;
3939
import static org.mockito.Mockito.mock;
4040
import static org.mockito.Mockito.verify;
41+
import static org.springframework.security.config.Customizer.withDefaults;
4142
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
4243
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
4344
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -102,6 +103,36 @@ protected void configure(HttpSecurity http) throws Exception {
102103
}
103104
}
104105

106+
@Test
107+
public void basicAuthenticationWhenUsingDefaultsInLambdaThenMatchesNamespace() throws Exception {
108+
this.spring.register(HttpBasicLambdaConfig.class, UserConfig.class).autowire();
109+
110+
this.mvc.perform(get("/"))
111+
.andExpect(status().isUnauthorized());
112+
113+
this.mvc.perform(get("/")
114+
.with(httpBasic("user", "invalid")))
115+
.andExpect(status().isUnauthorized())
116+
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Realm\""));
117+
118+
this.mvc.perform(get("/")
119+
.with(httpBasic("user", "password")))
120+
.andExpect(status().isNotFound());
121+
}
122+
123+
@EnableWebSecurity
124+
static class HttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
125+
protected void configure(HttpSecurity http) throws Exception {
126+
// @formatter:off
127+
http
128+
.authorizeRequests()
129+
.anyRequest().hasRole("USER")
130+
.and()
131+
.httpBasic(withDefaults());
132+
// @formatter:on
133+
}
134+
}
135+
105136
/**
106137
* http@realm equivalent
107138
*/
@@ -127,6 +158,30 @@ protected void configure(HttpSecurity http) throws Exception {
127158
}
128159
}
129160

161+
@Test
162+
public void basicAuthenticationWhenUsingCustomRealmInLambdaThenMatchesNamespace() throws Exception {
163+
this.spring.register(CustomHttpBasicLambdaConfig.class, UserConfig.class).autowire();
164+
165+
this.mvc.perform(get("/")
166+
.with(httpBasic("user", "invalid")))
167+
.andExpect(status().isUnauthorized())
168+
.andExpect(header().string(HttpHeaders.WWW_AUTHENTICATE, "Basic realm=\"Custom Realm\""));
169+
}
170+
171+
@EnableWebSecurity
172+
static class CustomHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
173+
@Override
174+
protected void configure(HttpSecurity http) throws Exception {
175+
// @formatter:off
176+
http
177+
.authorizeRequests()
178+
.anyRequest().hasRole("USER")
179+
.and()
180+
.httpBasic(httpBasicConfig -> httpBasicConfig.realmName("Custom Realm"));
181+
// @formatter:on
182+
}
183+
}
184+
130185
/**
131186
* http/http-basic@authentication-details-source-ref equivalent
132187
*/
@@ -161,6 +216,40 @@ protected void configure(HttpSecurity http) throws Exception {
161216
}
162217
}
163218

219+
@Test
220+
public void basicAuthenticationWhenUsingAuthenticationDetailsSourceRefInLambdaThenMatchesNamespace()
221+
throws Exception {
222+
this.spring.register(AuthenticationDetailsSourceHttpBasicLambdaConfig.class, UserConfig.class).autowire();
223+
224+
AuthenticationDetailsSource<HttpServletRequest, ?> source =
225+
this.spring.getContext().getBean(AuthenticationDetailsSource.class);
226+
227+
this.mvc.perform(get("/")
228+
.with(httpBasic("user", "password")));
229+
230+
verify(source).buildDetails(any(HttpServletRequest.class));
231+
}
232+
233+
@EnableWebSecurity
234+
static class AuthenticationDetailsSourceHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
235+
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource =
236+
mock(AuthenticationDetailsSource.class);
237+
238+
@Override
239+
protected void configure(HttpSecurity http) throws Exception {
240+
// @formatter:off
241+
http
242+
.httpBasic(httpBasicConfig ->
243+
httpBasicConfig.authenticationDetailsSource(this.authenticationDetailsSource));
244+
// @formatter:on
245+
}
246+
247+
@Bean
248+
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource() {
249+
return this.authenticationDetailsSource;
250+
}
251+
}
252+
164253
/**
165254
* http/http-basic@entry-point-ref
166255
*/
@@ -195,4 +284,38 @@ protected void configure(HttpSecurity http) throws Exception {
195284
.authenticationEntryPoint(this.authenticationEntryPoint);
196285
}
197286
}
287+
288+
@Test
289+
public void basicAuthenticationWhenUsingEntryPointRefInLambdaThenMatchesNamespace() throws Exception {
290+
this.spring.register(EntryPointRefHttpBasicLambdaConfig.class, UserConfig.class).autowire();
291+
292+
this.mvc.perform(get("/"))
293+
.andExpect(status().is(999));
294+
295+
this.mvc.perform(get("/")
296+
.with(httpBasic("user", "invalid")))
297+
.andExpect(status().is(999));
298+
299+
this.mvc.perform(get("/")
300+
.with(httpBasic("user", "password")))
301+
.andExpect(status().isNotFound());
302+
}
303+
304+
@EnableWebSecurity
305+
static class EntryPointRefHttpBasicLambdaConfig extends WebSecurityConfigurerAdapter {
306+
AuthenticationEntryPoint authenticationEntryPoint =
307+
(request, response, ex) -> response.setStatus(999);
308+
309+
@Override
310+
protected void configure(HttpSecurity http) throws Exception {
311+
// @formatter:off
312+
http
313+
.authorizeRequests()
314+
.anyRequest().hasRole("USER")
315+
.and()
316+
.httpBasic(httpBasicConfig ->
317+
httpBasicConfig.authenticationEntryPoint(this.authenticationEntryPoint));
318+
// @formatter:on
319+
}
320+
}
198321
}

0 commit comments

Comments
 (0)