Add SecurityContextHolderStrategy Java Configuration for Defaults

Issue gh-11061
This commit is contained in:
Josh Cummings
2022-05-26 14:19:06 -06:00
parent 31e25b115e
commit 2c09a300b6
17 changed files with 269 additions and 12 deletions

View File

@@ -0,0 +1,50 @@
/*
* Copyright 2002-2022 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;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextImpl;
public class MockSecurityContextHolderStrategy implements SecurityContextHolderStrategy {
private SecurityContext context;
@Override
public void clearContext() {
this.context = null;
}
@Override
public SecurityContext getContext() {
if (this.context == null) {
this.context = createEmptyContext();
}
return this.context;
}
@Override
public void setContext(SecurityContext context) {
this.context = context;
}
@Override
public SecurityContext createEmptyContext() {
return new SecurityContextImpl();
}
}

View File

@@ -0,0 +1,54 @@
/*
* Copyright 2002-2022 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.annotation;
import org.mockito.ArgumentMatcher;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextChangedEvent;
import static org.mockito.ArgumentMatchers.argThat;
public final class SecurityContextChangedListenerArgumentMatchers {
public static SecurityContextChangedEvent setAuthentication(Class<? extends Authentication> authenticationClass) {
return argThat(new ArgumentMatcher<SecurityContextChangedEvent>() {
public boolean matches(SecurityContextChangedEvent event) {
Authentication previous = authentication(event.getOldContext());
Authentication next = authentication(event.getNewContext());
return previous == null && next != null && authenticationClass.isAssignableFrom(next.getClass());
}
public String toString() {
return "authentication set to " + authenticationClass;
}
});
}
private static Authentication authentication(SecurityContext context) {
if (context == null) {
return null;
}
return context.getAuthentication();
}
private SecurityContextChangedListenerArgumentMatchers() {
}
}

View File

@@ -0,0 +1,46 @@
/*
* Copyright 2002-2022 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.annotation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.MockSecurityContextHolderStrategy;
import org.springframework.security.core.context.ListeningSecurityContextHolderStrategy;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
@Configuration
public class SecurityContextChangedListenerConfig {
private SecurityContextHolderStrategy strategy = new MockSecurityContextHolderStrategy();
private SecurityContextChangedListener listener = mock(SecurityContextChangedListener.class);
@Bean
SecurityContextHolderStrategy securityContextHolderStrategy() {
return spy(new ListeningSecurityContextHolderStrategy(this.strategy, this.listener));
}
@Bean
SecurityContextChangedListener securityContextChangedListener() {
return this.listener;
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -20,6 +20,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -27,13 +29,16 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -62,6 +67,16 @@ public class AnonymousConfigurerTests {
this.mockMvc.perform(get("/")).andExpect(content().string("principal"));
}
@Test
public void requestWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
this.spring.register(AnonymousPrincipalInLambdaConfig.class, SecurityContextChangedListenerConfig.class,
PrincipalController.class).autowire();
this.mockMvc.perform(get("/")).andExpect(content().string("principal"));
SecurityContextChangedListener listener = this.spring.getContext()
.getBean(SecurityContextChangedListener.class);
verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class));
}
@Test
public void requestWhenAnonymousDisabledInLambdaThenRespondsWithForbidden() throws Exception {
this.spring.register(AnonymousDisabledInLambdaConfig.class, PrincipalController.class).autowire();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2018 the original author or authors.
* Copyright 2002-2022 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.
@@ -26,7 +26,9 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.authentication.AnonymousAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -34,6 +36,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -47,6 +51,7 @@ import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -192,6 +197,17 @@ public class ExceptionHandlingConfigurerTests {
.resolveMediaTypes(any(NativeWebRequest.class));
}
@Test
public void getWhenCustomSecurityContextHolderStrategyThenUsed() throws Exception {
this.spring.register(SecurityContextChangedListenerConfig.class, DefaultSecurityConfig.class).autowire();
this.mvc.perform(get("/"));
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getContext();
SecurityContextChangedListener listener = this.spring.getContext()
.getBean(SecurityContextChangedListener.class);
verify(listener).securityContextChanged(setAuthentication(AnonymousAuthenticationToken.class));
}
@Test
public void getWhenUsingDefaultsAndUnauthenticatedThenRedirectsToLogin() throws Exception {
this.spring.register(DefaultHttpConfig.class).autowire();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -21,7 +21,9 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
@@ -30,6 +32,8 @@ import org.springframework.security.config.annotation.web.configuration.WebSecur
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.config.users.AuthenticationTestConfiguration;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.core.userdetails.PasswordEncodedUser;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders;
import org.springframework.security.web.PortMapper;
@@ -42,10 +46,12 @@ import org.springframework.test.web.servlet.MockMvc;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.logout;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
@@ -97,6 +103,24 @@ public class FormLoginConfigurerTests {
// @formatter:on
}
@Test
public void formLoginWhenSecurityContextHolderStrategyThenUses() throws Exception {
this.spring.register(FormLoginConfig.class, SecurityContextChangedListenerConfig.class).autowire();
// @formatter:off
SecurityMockMvcRequestBuilders.FormLoginRequestBuilder loginRequest = formLogin()
.user("username", "user")
.password("password", "password");
this.mockMvc.perform(loginRequest)
.andExpect(status().isFound())
.andExpect(redirectedUrl("/"));
// @formatter:on
SecurityContextHolderStrategy strategy = this.spring.getContext().getBean(SecurityContextHolderStrategy.class);
verify(strategy, atLeastOnce()).getContext();
SecurityContextChangedListener listener = this.spring.getContext()
.getBean(SecurityContextChangedListener.class);
verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class));
}
@Test
public void loginWhenFormLoginConfiguredThenHasDefaultFailureUrl() throws Exception {
this.spring.register(FormLoginConfig.class).autowire();

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2019 the original author or authors.
* Copyright 2002-2022 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.
@@ -25,8 +25,10 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
@@ -35,6 +37,7 @@ import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
@@ -53,6 +56,7 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
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.content;
@@ -133,6 +137,17 @@ public class HttpBasicConfigurerTests {
.andExpect(content().string("user"));
}
@Test
public void httpBasicWhenCustomSecurityContextHolderStrategyThenUses() throws Exception {
this.spring.register(HttpBasic.class, Users.class, Home.class, SecurityContextChangedListenerConfig.class)
.autowire();
this.mvc.perform(get("/").with(httpBasic("user", "password"))).andExpect(status().isOk())
.andExpect(content().string("user"));
SecurityContextChangedListener listener = this.spring.getContext()
.getBean(SecurityContextChangedListener.class);
verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class));
}
@EnableWebSecurity
static class ObjectPostProcessorConfig extends WebSecurityConfigurerAdapter {