Revert unnecessary merges on 6.0.x

This commit removes unnecessary main-branch merges starting from
8750608b5b and adds the following
needed commit(s) that were made afterward:

- 5dce82c48b
This commit is contained in:
Steve Riesenberg
2023-10-31 15:11:45 -05:00
parent e9d4223402
commit 9db33f33c7
676 changed files with 6306 additions and 43249 deletions

View File

@@ -55,11 +55,6 @@ public class MockServletContext extends org.springframework.mock.web.MockServlet
return this.registrations;
}
@Override
public ServletRegistration getServletRegistration(String servletName) {
return this.registrations.get(servletName);
}
private static class MockServletRegistration implements ServletRegistration.Dynamic {
private final String name;

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2002-2023 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 jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.MappingMatch;
import org.springframework.mock.web.MockHttpServletMapping;
public final class TestMockHttpServletMappings {
private TestMockHttpServletMappings() {
}
public static MockHttpServletMapping extension(HttpServletRequest request, String extension) {
String uri = request.getRequestURI();
String matchValue = uri.substring(0, uri.lastIndexOf(extension));
return new MockHttpServletMapping(matchValue, "*" + extension, "extension", MappingMatch.EXTENSION);
}
public static MockHttpServletMapping path(HttpServletRequest request, String path) {
String uri = request.getRequestURI();
String matchValue = uri.substring(path.length());
return new MockHttpServletMapping(matchValue, path + "/*", "path", MappingMatch.PATH);
}
public static MockHttpServletMapping defaultMapping() {
return new MockHttpServletMapping("", "/", "default", MappingMatch.DEFAULT);
}
}

View File

@@ -17,13 +17,11 @@
package org.springframework.security.config.annotation.authentication.configurers.provisioning;
import java.util.Arrays;
import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
@@ -75,7 +73,7 @@ public class UserDetailsManagerConfigurerTests {
.authorities(authority)
.build();
// @formatter:on
assertThat((Optional<GrantedAuthority>) userDetails.getAuthorities().stream().findFirst()).contains(authority);
assertThat(userDetails.getAuthorities().stream().findFirst().get()).isEqualTo(authority);
}
@Test
@@ -101,7 +99,7 @@ public class UserDetailsManagerConfigurerTests {
.authorities(Arrays.asList(authority))
.build();
// @formatter:on
assertThat((Optional<GrantedAuthority>) userDetails.getAuthorities().stream().findFirst()).contains(authority);
assertThat(userDetails.getAuthorities().stream().findFirst().get()).isEqualTo(authority);
}
private UserDetailsManagerConfigurer<AuthenticationManagerBuilder, InMemoryUserDetailsManagerConfigurer<AuthenticationManagerBuilder>> configurer() {

View File

@@ -66,7 +66,7 @@ public class SecurityConfig {
@Bean
public AuthenticationProvider authenticationProvider() {
Assert.notNull(this.myUserRepository, "myUserRepository cannot be null");
Assert.notNull(this.myUserRepository);
return new AuthenticationProvider() {
@Override
public boolean supports(Class<?> authentication) {

View File

@@ -1,41 +0,0 @@
/*
* Copyright 2002-2023 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.method.configuration;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.context.annotation.AdviceMode;
import org.springframework.core.annotation.AliasFor;
/**
* @author Evgeniy Cheban
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@EnableMethodSecurity
public @interface EnableCustomMethodSecurity {
@AliasFor(annotation = EnableMethodSecurity.class, attribute = "proxyTargetClass")
boolean proxyTargetClass() default false;
@AliasFor(annotation = EnableMethodSecurity.class, attribute = "mode")
AdviceMode mode() default AdviceMode.PROXY;
}

View File

@@ -95,21 +95,6 @@ public class PrePostMethodSecurityConfigurationTests {
@Autowired(required = false)
BusinessService businessService;
@WithMockUser
@Test
public void customMethodSecurityPreAuthorizeAdminWhenRoleUserThenAccessDeniedException() {
this.spring.register(CustomMethodSecurityServiceConfig.class).autowire();
assertThatExceptionOfType(AccessDeniedException.class).isThrownBy(this.methodSecurityService::preAuthorizeAdmin)
.withMessage("Access Denied");
}
@WithMockUser(roles = "ADMIN")
@Test
public void customMethodSecurityPreAuthorizeAdminWhenRoleAdminThenPasses() {
this.spring.register(CustomMethodSecurityServiceConfig.class).autowire();
this.methodSecurityService.preAuthorizeAdmin();
}
@WithMockUser(roles = "ADMIN")
@Test
public void preAuthorizeWhenRoleAdminThenAccessDeniedException() {
@@ -451,17 +436,6 @@ public class PrePostMethodSecurityConfigurationTests {
return (context) -> ((AnnotationConfigWebApplicationContext) context).setAllowBeanDefinitionOverriding(false);
}
@Configuration
@EnableCustomMethodSecurity
static class CustomMethodSecurityServiceConfig {
@Bean
MethodSecurityService methodSecurityService() {
return new MethodSecurityServiceImpl();
}
}
@Configuration
@EnableMethodSecurity
static class MethodSecurityServiceConfig {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 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,6 @@ import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.SecurityConfigurer;
@@ -150,19 +149,6 @@ public class AbstractConfiguredSecurityBuilderTests {
assertThat(builder.getConfigurers(DelegateSecurityConfigurer.class)).hasSize(2);
}
@Test
public void withWhenConfigurerThenConfigurerAdded() throws Exception {
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
assertThat(this.builder.getConfigurers(TestSecurityConfigurer.class)).hasSize(1);
}
@Test
public void withWhenDuplicateConfigurerAddedThenDuplicateConfigurerRemoved() throws Exception {
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
this.builder.with(new TestSecurityConfigurer(), Customizer.withDefaults());
assertThat(this.builder.getConfigurers(TestSecurityConfigurer.class)).hasSize(1);
}
private static class ApplyAndRemoveSecurityConfigurer
extends SecurityConfigurerAdapter<Object, TestConfiguredSecurityBuilder> {

View File

@@ -25,12 +25,8 @@ import org.springframework.http.HttpMethod;
import org.springframework.security.test.support.ClassPathExclusions;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AbstractRequestMatcherRegistry} with no Spring MVC in the classpath
@@ -45,16 +41,13 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
@BeforeEach
public void setUp() {
this.matcherRegistry = new TestRequestMatcherRegistry();
WebApplicationContext context = mock(WebApplicationContext.class);
given(context.getBeanNamesForType((Class<?>) any())).willReturn(new String[0]);
this.matcherRegistry.setApplicationContext(context);
}
@Test
public void requestMatchersWhenPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@@ -62,7 +55,7 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
public void requestMatchersWhenHttpMethodAndPatternAndMvcNotPresentThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@@ -70,7 +63,7 @@ public class AbstractRequestMatcherRegistryNoMvcTests {
public void requestMatchersWhenHttpMethodAndMvcNotPresentThenReturnAntPathMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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,11 +26,8 @@ import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry.DispatcherServletDelegatingRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.DispatcherTypeRequestMatcher;
@@ -43,9 +40,6 @@ import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoInteractions;
import static org.mockito.Mockito.verifyNoMoreInteractions;
/**
* Tests for {@link AbstractRequestMatcherRegistry}.
@@ -81,7 +75,7 @@ public class AbstractRequestMatcherRegistryTests {
List<RequestMatcher> requestMatchers = this.matcherRegistry
.requestMatchers(new RegexRequestMatcher("/a.*", HttpMethod.GET.name()));
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(RegexRequestMatcher.class);
}
@@ -90,7 +84,7 @@ public class AbstractRequestMatcherRegistryTests {
List<RequestMatcher> requestMatchers = this.matcherRegistry
.requestMatchers(new RegexRequestMatcher("/a.*", null));
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(RegexRequestMatcher.class);
}
@@ -99,7 +93,7 @@ public class AbstractRequestMatcherRegistryTests {
List<RequestMatcher> requestMatchers = this.matcherRegistry
.requestMatchers(new AntPathRequestMatcher("/a.*", HttpMethod.GET.name()));
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@@ -107,7 +101,7 @@ public class AbstractRequestMatcherRegistryTests {
public void antMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(new AntPathRequestMatcher("/a.*"));
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@@ -116,7 +110,7 @@ public class AbstractRequestMatcherRegistryTests {
List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(HttpMethod.GET,
DispatcherType.ASYNC);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
}
@@ -124,7 +118,7 @@ public class AbstractRequestMatcherRegistryTests {
public void dispatcherMatchersWhenPatternParamThenReturnAntPathRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.dispatcherTypeMatchers(DispatcherType.INCLUDE);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(DispatcherTypeRequestMatcher.class);
}
@@ -132,7 +126,7 @@ public class AbstractRequestMatcherRegistryTests {
public void requestMatchersWhenPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@@ -140,7 +134,7 @@ public class AbstractRequestMatcherRegistryTests {
public void requestMatchersWhenHttpMethodAndPatternAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET, "/path");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@@ -148,7 +142,7 @@ public class AbstractRequestMatcherRegistryTests {
public void requestMatchersWhenHttpMethodAndMvcPresentThenReturnMvcRequestMatcherType() {
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers(HttpMethod.GET);
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.size()).isEqualTo(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(MvcRequestMatcher.class);
}
@@ -165,12 +159,16 @@ public class AbstractRequestMatcherRegistryTests {
public void requestMatchersWhenNoDispatcherServletThenAntPathRequestMatcherType() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("servletOne", Servlet.class).addMapping("/one");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/two");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
servletContext.addServlet("servletOne", Servlet.class);
servletContext.addServlet("servletTwo", Servlet.class);
requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).isNotEmpty();
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isExactlyInstanceOf(AntPathRequestMatcher.class);
}
@Test
@@ -178,26 +176,7 @@ public class AbstractRequestMatcherRegistryTests {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
servletContext.addServlet("servletTwo", DispatcherServlet.class).addMapping("/servlet/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@Test
public void requestMatchersWhenMultipleDispatcherServletMappingsThenException() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/mvc/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@Test
public void requestMatchersWhenPathDispatcherServletAndOtherServletsThenException() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("servletTwo", Servlet.class).addMapping("/servlet/**");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> this.matcherRegistry.requestMatchers("/**"));
}
@@ -214,67 +193,6 @@ public class AbstractRequestMatcherRegistryTests {
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenOnlyDispatcherServletThenAllows() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
}
@Test
public void requestMatchersWhenImplicitServletsThenAllows() {
mockMvcIntrospector(true);
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("defaultServlet", Servlet.class);
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/**");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
}
@Test
public void requestMatchersWhenPathBasedNonDispatcherServletThenAllows() {
MockServletContext servletContext = new MockServletContext();
given(this.context.getServletContext()).willReturn(servletContext);
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
List<RequestMatcher> requestMatchers = this.matcherRegistry.requestMatchers("/services/*");
assertThat(requestMatchers).hasSize(1);
assertThat(requestMatchers.get(0)).isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
request.setServletPath("/services");
request.setPathInfo("/endpoint");
assertThat(requestMatchers.get(0).matcher(request).isMatch()).isTrue();
}
@Test
public void matchesWhenDispatcherServletThenMvc() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", DispatcherServlet.class).addMapping("/");
servletContext.addServlet("path", Servlet.class).addMapping("/services/*");
MvcRequestMatcher mvc = mock(MvcRequestMatcher.class);
AntPathRequestMatcher ant = mock(AntPathRequestMatcher.class);
DispatcherServletDelegatingRequestMatcher requestMatcher = new DispatcherServletDelegatingRequestMatcher(ant,
mvc, servletContext);
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/services/endpoint");
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
assertThat(requestMatcher.matches(request)).isFalse();
verify(mvc).matches(request);
verifyNoInteractions(ant);
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/services"));
assertThat(requestMatcher.matches(request)).isFalse();
verify(ant).matches(request);
verifyNoMoreInteractions(mvc);
}
private void mockMvcIntrospector(boolean isPresent) {
ApplicationContext context = this.matcherRegistry.getApplicationContext();
given(context.containsBean("mvcHandlerMappingIntrospector")).willReturn(isPresent);

View File

@@ -20,8 +20,6 @@ import java.io.IOException;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.Test;
@@ -31,7 +29,6 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.cas.web.CasAuthenticationFilter;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
@@ -45,9 +42,6 @@ import org.springframework.web.filter.OncePerRequestFilter;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -74,16 +68,6 @@ public class HttpConfigurationTests {
+ " Consider using addFilterBefore or addFilterAfter instead.");
}
// https://github.com/spring-projects/spring-security-javaconfig/issues/104
@Test
public void configureWhenAddFilterCasAuthenticationFilterThenFilterAdded() throws Exception {
CasAuthenticationFilterConfig.CAS_AUTHENTICATION_FILTER = spy(new CasAuthenticationFilter());
this.spring.register(CasAuthenticationFilterConfig.class).autowire();
this.mockMvc.perform(get("/"));
verify(CasAuthenticationFilterConfig.CAS_AUTHENTICATION_FILTER).doFilter(any(ServletRequest.class),
any(ServletResponse.class), any(FilterChain.class));
}
@Test
public void configureWhenConfigIsRequestMatchersJavadocThenAuthorizationApplied() throws Exception {
this.spring.register(RequestMatcherRegistryConfigs.class).autowire();
@@ -123,22 +107,6 @@ public class HttpConfigurationTests {
}
@EnableWebSecurity
static class CasAuthenticationFilterConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.addFilter(CAS_AUTHENTICATION_FILTER);
// @formatter:on
return http.build();
}
static CasAuthenticationFilter CAS_AUTHENTICATION_FILTER;
}
@Configuration
@EnableWebSecurity
@EnableWebMvc

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -47,7 +47,6 @@ import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.authentication.event.AbstractAuthenticationEvent;
import org.springframework.security.authentication.event.AbstractAuthenticationFailureEvent;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.SecurityContextChangedListenerConfig;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
@@ -64,11 +63,9 @@ import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.test.web.servlet.RequestCacheResultMatcher;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
import org.springframework.security.web.header.writers.frameoptions.XFrameOptionsHeaderWriter;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
@@ -76,10 +73,6 @@ import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -92,8 +85,6 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.unauthenticated;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
@@ -361,40 +352,6 @@ public class HttpSecurityConfigurationTests {
DefaultLogoutPageGeneratingFilter.class);
}
@Test
public void configureWhenCorsConfigurationSourceThenApplyCors() {
this.spring.register(CorsConfigurationSourceConfig.class, DefaultWithFilterChainConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
CorsFilter corsFilter = (CorsFilter) filterChain.getFilters()
.stream()
.filter((f) -> f instanceof CorsFilter)
.findFirst()
.get();
Object configSource = ReflectionTestUtils.getField(corsFilter, "configSource");
assertThat(configSource).isInstanceOf(UrlBasedCorsConfigurationSource.class);
}
@Test
public void configureWhenAddingCustomDslUsingWithThenApplied() throws Exception {
this.spring.register(WithCustomDslConfig.class, UserDetailsConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
List<Filter> filters = filterChain.getFilters();
assertThat(filters).hasAtLeastOneElementOfType(UsernamePasswordAuthenticationFilter.class);
this.mockMvc.perform(formLogin()).andExpectAll(redirectedUrl("/"), authenticated());
}
@Test
public void configureWhenCustomDslAddedFromFactoriesAndDisablingUsingWithThenNotApplied() throws Exception {
this.springFactoriesLoader
.when(() -> SpringFactoriesLoader.loadFactories(AbstractHttpConfigurer.class, getClass().getClassLoader()))
.thenReturn(List.of(new WithCustomDsl()));
this.spring.register(WithCustomDslDisabledConfig.class, UserDetailsConfig.class).autowire();
SecurityFilterChain filterChain = this.spring.getContext().getBean(SecurityFilterChain.class);
List<Filter> filters = filterChain.getFilters();
assertThat(filters).doesNotHaveAnyElementsOfTypes(UsernamePasswordAuthenticationFilter.class);
this.mockMvc.perform(formLogin()).andExpectAll(status().isNotFound(), unauthenticated());
}
@RestController
static class NameController {
@@ -659,20 +616,6 @@ public class HttpSecurityConfigurationTests {
}
@Configuration
static class CorsConfigurationSourceConfig {
@Bean
CorsConfigurationSource corsConfigurationSource() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration();
corsConfiguration.setAllowedOrigins(List.of("http://localhost:8080"));
source.registerCorsConfiguration("/**", corsConfiguration);
return source;
}
}
static class DefaultConfigurer extends AbstractHttpConfigurer<DefaultConfigurer, HttpSecurity> {
boolean init;
@@ -691,45 +634,4 @@ public class HttpSecurityConfigurationTests {
}
@Configuration
@EnableWebSecurity
static class WithCustomDslConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.with(new WithCustomDsl(), Customizer.withDefaults())
.httpBasic(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
static class WithCustomDslDisabledConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.with(new WithCustomDsl(), (dsl) -> dsl.disable())
.httpBasic(Customizer.withDefaults());
// @formatter:on
return http.build();
}
}
static class WithCustomDsl extends AbstractHttpConfigurer<WithCustomDsl, HttpSecurity> {
@Override
public void init(HttpSecurity builder) throws Exception {
builder.formLogin(Customizer.withDefaults());
}
}
}

View File

@@ -1,547 +0,0 @@
/*
* Copyright 2002-2023 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.web.configuration;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserService;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.core.user.OAuth2User;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link OAuth2ClientConfiguration.OAuth2AuthorizedClientManagerConfiguration}.
*
* @author Joe Grandja
* @author Steve Riesenberg
*/
public class OAuth2AuthorizedClientManagerConfigurationTests {
private static OAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private OAuth2AuthorizedClientManager authorizedClientManager;
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private OAuth2AuthorizedClientRepository authorizedClientRepository;
@Autowired(required = false)
private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class);
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
}
@Test
public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
this.spring.register(MinimalOAuth2ClientConfig.class).autowire();
assertThat(this.authorizedClientManager).isNotNull();
}
@Test
public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("google")
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest))
.extracting(OAuth2AuthorizationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo("client_authorization_required");
// @formatter:on
verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
}
@Test
public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testRefreshTokenGrant();
}
@Test
public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testRefreshTokenGrant();
}
private void testRefreshTokenGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request,
this.response);
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withAuthorizedClient(existingAuthorizedClient)
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2RefreshTokenGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
}
@Test
public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testClientCredentialsGrant();
}
@Test
public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testClientCredentialsGrant();
}
private void testClientCredentialsGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2ClientCredentialsGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
}
@Test
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testPasswordGrant();
}
@Test
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testPasswordGrant();
}
private void testPasswordGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2PasswordGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
assertThat(grantRequest.getUsername()).isEqualTo("user");
assertThat(grantRequest.getPassword()).isEqualTo("password");
}
@Test
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
this.spring.register(CustomAccessTokenResponseClientsConfig.class).autowire();
testJwtBearerGrant();
}
@Test
public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
this.spring.register(CustomAuthorizedClientProvidersConfig.class).autowire();
testJwtBearerGrant();
}
private void testJwtBearerGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse);
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
}
private static OAuth2AccessToken getExpiredAccessToken() {
Instant expiresAt = Instant.now().minusSeconds(60);
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
new HashSet<>(Arrays.asList("read", "write")));
}
private static Jwt getJwt() {
Instant issuedAt = Instant.now();
return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
Collections.singletonMap(JwtClaimNames.SUB, "user"));
}
@Configuration
@EnableWebSecurity
static class MinimalOAuth2ClientConfig extends OAuth2ClientBaseConfig {
}
@Configuration
@EnableWebSecurity
static class CustomAccessTokenResponseClientsConfig extends OAuth2ClientBaseConfig {
@Bean
OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> authorizationCodeTokenResponseClient() {
return new MockAuthorizationCodeClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> refreshTokenTokenResponseClient() {
return new MockRefreshTokenClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsTokenResponseClient() {
return new MockClientCredentialsClient();
}
@Bean
OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordTokenResponseClient() {
return new MockPasswordClient();
}
@Bean
OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerTokenResponseClient() {
return new MockJwtBearerClient();
}
@Bean
OAuth2UserService<OAuth2UserRequest, OAuth2User> oauth2UserService() {
return mock(DefaultOAuth2UserService.class);
}
@Bean
OAuth2UserService<OidcUserRequest, OidcUser> oidcUserService() {
return mock(OidcUserService.class);
}
}
@Configuration
@EnableWebSecurity
static class CustomAuthorizedClientProvidersConfig extends OAuth2ClientBaseConfig {
@Bean
AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeProvider() {
return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
}
@Bean
RefreshTokenOAuth2AuthorizedClientProvider refreshTokenProvider() {
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockRefreshTokenClient());
return authorizedClientProvider;
}
@Bean
ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsProvider() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockClientCredentialsClient());
return authorizedClientProvider;
}
@Bean
PasswordOAuth2AuthorizedClientProvider passwordProvider() {
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockPasswordClient());
return authorizedClientProvider;
}
@Bean
JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(new MockJwtBearerClient());
return authorizedClientProvider;
}
}
abstract static class OAuth2ClientBaseConfig {
@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults())
.oauth2Client(Customizer.withDefaults());
return http.build();
// @formatter:on
}
@Bean
ClientRegistrationRepository clientRegistrationRepository() {
// @formatter:off
return new InMemoryClientRegistrationRepository(Arrays.asList(
CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.build(),
CommonOAuth2Provider.GITHUB.getBuilder("github")
.clientId("github-client-id")
.clientSecret("github-client-secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build(),
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
.clientId("facebook-client-id")
.clientSecret("facebook-client-secret")
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build(),
CommonOAuth2Provider.OKTA.getBuilder("okta")
.clientId("okta-client-id")
.clientSecret("okta-client-secret")
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.build()));
// @formatter:on
}
@Bean
OAuth2AuthorizedClientRepository authorizedClientRepository() {
return mock(OAuth2AuthorizedClientRepository.class);
}
@Bean
Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
return (authorizedClientManager) -> authorizedClientManager
.setContextAttributesMapper((authorizeRequest) -> {
HttpServletRequest request = Objects
.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
String username = request.getParameter(OAuth2ParameterNames.USERNAME);
String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
Map<String, Object> attributes = Collections.emptyMap();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
attributes = new HashMap<>();
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return attributes;
});
}
}
private static class MockAuthorizationCodeClient
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockRefreshTokenClient
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockClientCredentialsClient
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
}

View File

@@ -180,10 +180,9 @@ public class OAuth2ClientConfigurationTests {
@Test
public void loadContextWhenAccessTokenResponseClientRegisteredTwiceThenThrowNoUniqueBeanDefinitionException() {
// @formatter:off
assertThatExceptionOfType(BeanCreationException.class)
assertThatExceptionOfType(Exception.class)
.isThrownBy(() -> this.spring.register(AccessTokenResponseClientRegisteredTwiceConfig.class).autowire())
.havingRootCause()
.isInstanceOf(NoUniqueBeanDefinitionException.class)
.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class)
.withMessageContaining(
"expected single matching bean but found 2: accessTokenResponseClient1,accessTokenResponseClient2");
// @formatter:on

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -17,20 +17,14 @@
package org.springframework.security.config.annotation.web.configuration;
import java.net.URI;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadFactory;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.condition.DisabledOnJre;
import org.junit.jupiter.api.condition.JRE;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.CoreSubscriber;
import reactor.core.publisher.BaseSubscriber;
@@ -41,8 +35,6 @@ import reactor.util.context.Context;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.task.SimpleAsyncTaskExecutor;
import org.springframework.core.task.VirtualThreadTaskExecutor;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpStatus;
import org.springframework.mock.web.MockHttpServletRequest;
@@ -54,7 +46,6 @@ import org.springframework.security.config.annotation.web.configuration.Security
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.oauth2.client.web.reactive.function.client.MockExchangeFunction;
@@ -280,58 +271,6 @@ public class SecurityReactorContextConfigurationTests {
verify(strategy, times(2)).getContext();
}
@Test
public void createPublisherWhenThreadFactoryIsPlatformThenSecurityContextAttributesAvailable() throws Exception {
this.spring.register(SecurityConfig.class).autowire();
ThreadFactory threadFactory = Executors.defaultThreadFactory();
assertContextAttributesAvailable(threadFactory);
}
@Test
@DisabledOnJre(JRE.JAVA_17)
public void createPublisherWhenThreadFactoryIsVirtualThenSecurityContextAttributesAvailable() throws Exception {
this.spring.register(SecurityConfig.class).autowire();
ThreadFactory threadFactory = new VirtualThreadTaskExecutor().getVirtualThreadFactory();
assertContextAttributesAvailable(threadFactory);
}
private void assertContextAttributesAvailable(ThreadFactory threadFactory) throws Exception {
Map<Object, Object> expectedContextAttributes = new HashMap<>();
expectedContextAttributes.put(HttpServletRequest.class, this.servletRequest);
expectedContextAttributes.put(HttpServletResponse.class, this.servletResponse);
expectedContextAttributes.put(Authentication.class, this.authentication);
try (SimpleAsyncTaskExecutor taskExecutor = new SimpleAsyncTaskExecutor(threadFactory)) {
Future<Map<Object, Object>> future = taskExecutor.submit(this::propagateRequestAttributes);
assertThat(future.get()).isEqualTo(expectedContextAttributes);
}
}
private Map<Object, Object> propagateRequestAttributes() {
RequestAttributes requestAttributes = new ServletRequestAttributes(this.servletRequest, this.servletResponse);
RequestContextHolder.setRequestAttributes(requestAttributes);
SecurityContext securityContext = SecurityContextHolder.createEmptyContext();
securityContext.setAuthentication(this.authentication);
SecurityContextHolder.setContext(securityContext);
// @formatter:off
return Mono.deferContextual(Mono::just)
.filter((ctx) -> ctx.hasKey(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
.map((ctx) -> ctx.<Map<Object, Object>>get(SecurityReactorContextSubscriber.SECURITY_CONTEXT_ATTRIBUTES))
.map((attributes) -> {
Map<Object, Object> map = new HashMap<>();
// Copy over items from lazily loaded map
Arrays.asList(HttpServletRequest.class, HttpServletResponse.class, Authentication.class)
.forEach((key) -> map.put(key, attributes.get(key)));
return map;
})
.block();
// @formatter:on
}
@Configuration
@EnableWebSecurity
static class SecurityConfig {

View File

@@ -1,349 +0,0 @@
/*
* Copyright 2002-2023 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.web.configurers;
import java.util.List;
import java.util.function.Consumer;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import org.assertj.core.api.AbstractObjectAssert;
import org.assertj.core.api.ObjectAssert;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.ApplicationContext;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link AbstractRequestMatcherBuilderRegistry}
*/
class AbstractRequestMatcherBuilderRegistryTests {
@Test
void defaultServletMatchersWhenDefaultDispatcherServletThenMvc() {
MockServletContext servletContext = MockServletContext.mvc();
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/mvc").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/mvc");
assertThatMvc(matchers).method().isNull();
}
@Test
void defaultServletHttpMethodMatchersWhenDefaultDispatcherServletThenMvc() {
MockServletContext servletContext = MockServletContext.mvc();
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/mvc").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/mvc");
assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET);
}
@Test
void servletMatchersWhenPathDispatcherServletThenMvc() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
.requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
assertThatMvc(matchers).pattern().isEqualTo("/controller");
}
@Test
void servletMatchersWhenAlsoExtraServletContainerMappingsThenMvc() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class);
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
servletContext.addServlet("facesServlet", Servlet.class).addMapping("/faces/", "*.jsf", "*.faces", "*.xhtml");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
.requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
assertThatMvc(matchers).pattern().isEqualTo("/controller");
}
@Test
void defaultServletMatchersWhenOnlyDefaultServletThenAnt() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).pattern().isEqualTo("/controller");
}
@Test
void defaultDispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() {
MockServletContext servletContext = MockServletContext.mvc();
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> defaultServlet(servletContext, (context) -> {
}));
}
@Test
void dispatcherServletMatchersWhenNoHandlerMappingIntrospectorThenException() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
assertThatExceptionOfType(NoSuchBeanDefinitionException.class)
.isThrownBy(() -> servletPattern(servletContext, (context) -> {
}, "/mvc/*"));
}
@Test
void matchersWhenNoDispatchServletThenAnt() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/services/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
}
@Test
void servletMatchersWhenMixedServletsThenDeterminesByServletPath() {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
List<RequestMatcher> matchers = servletPattern(servletContext, "/services/*")
.requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/controller");
}
@Test
void servletMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*")
.requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = defaultServlet(servletContext).requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).pattern().isEqualTo("/endpoint");
}
@Test
void servletHttpMatchersWhenDispatcherServletNotDefaultThenDeterminesByServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
List<RequestMatcher> matchers = servletPattern(servletContext, "/mvc/*").requestMatchers(HttpMethod.GET,
"/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).method().isEqualTo(HttpMethod.GET);
assertThatMvc(matchers).servletPath().isEqualTo("/mvc");
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = defaultServlet(servletContext).requestMatchers(HttpMethod.GET, "/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).method().isEqualTo(HttpMethod.GET);
assertThatAnt(matchers).pattern().isEqualTo("/endpoint");
}
@Test
void servletMatchersWhenTwoDispatcherServletsThenDeterminesByServletPath() {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("two", DispatcherServlet.class).addMapping("/other/*");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = servletPattern(servletContext, "/other/*").requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/other");
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
}
@Test
void servletMatchersWhenMoreThanOneMappingThenDeterminesByServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/two");
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
}
@Test
void servletMatchersWhenMoreThanOneMappingAndDefaultServletsThenDeterminesByServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = servletPattern(servletContext, "/two/*").requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isEqualTo("/two");
assertThatMvc(matchers).pattern().isEqualTo("/endpoint");
}
@Test
void defaultServletWhenDispatcherServletThenMvc() {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
List<RequestMatcher> matchers = defaultServlet(servletContext).requestMatchers("/controller").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(MvcRequestMatcher.class);
assertThatMvc(matchers).servletPath().isNull();
assertThatMvc(matchers).pattern().isEqualTo("/controller");
matchers = servletPattern(servletContext, "/services/*").requestMatchers("/endpoint").matchers;
assertThat(matchers).hasSize(1).hasOnlyElementsOfType(AntPathRequestMatcher.class);
assertThatAnt(matchers).pattern().isEqualTo("/services/endpoint");
}
@Test
void defaultServletWhenNoDefaultServletThenException() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
assertThatExceptionOfType(IllegalArgumentException.class).isThrownBy(() -> defaultServlet(servletContext));
}
@Test
void servletPathWhenNoMatchingServletThenException() {
MockServletContext servletContext = MockServletContext.mvc();
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> servletPattern(servletContext, "/wrong/*"));
}
TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext) {
return servletPattern(servletContext, "/");
}
TestServletRequestMatcherRegistry defaultServlet(ServletContext servletContext,
Consumer<GenericWebApplicationContext> consumer) {
return servletPattern(servletContext, consumer, "/");
}
TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext, String pattern) {
return servletPattern(servletContext, (context) -> {
context.registerBean("mvcHandlerMappingIntrospector", HandlerMappingIntrospector.class);
context.registerBean(ObjectPostProcessor.class, () -> mock(ObjectPostProcessor.class));
}, pattern);
}
TestServletRequestMatcherRegistry servletPattern(ServletContext servletContext,
Consumer<GenericWebApplicationContext> consumer, String pattern) {
GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext);
consumer.accept(context);
context.refresh();
return new TestServletRequestMatcherRegistry(context, pattern);
}
static MvcRequestMatcherAssert assertThatMvc(List<RequestMatcher> matchers) {
RequestMatcher matcher = matchers.get(0);
if (matcher instanceof AndRequestMatcher matching) {
List<RequestMatcher> and = (List<RequestMatcher>) ReflectionTestUtils.getField(matching, "requestMatchers");
assertThat(and).hasSize(2);
assertThat(and.get(1)).isInstanceOf(MvcRequestMatcher.class);
return new MvcRequestMatcherAssert((MvcRequestMatcher) and.get(1));
}
assertThat(matcher).isInstanceOf(MvcRequestMatcher.class);
return new MvcRequestMatcherAssert((MvcRequestMatcher) matcher);
}
static AntPathRequestMatcherAssert assertThatAnt(List<RequestMatcher> matchers) {
RequestMatcher matcher = matchers.get(0);
if (matcher instanceof AndRequestMatcher matching) {
List<RequestMatcher> and = (List<RequestMatcher>) ReflectionTestUtils.getField(matching, "requestMatchers");
assertThat(and).hasSize(2);
assertThat(and.get(1)).isInstanceOf(AntPathRequestMatcher.class);
return new AntPathRequestMatcherAssert((AntPathRequestMatcher) and.get(1));
}
assertThat(matcher).isInstanceOf(AntPathRequestMatcher.class);
return new AntPathRequestMatcherAssert((AntPathRequestMatcher) matcher);
}
static final class TestServletRequestMatcherRegistry
extends AbstractRequestMatcherBuilderRegistry<TestServletRequestMatcherRegistry> {
List<RequestMatcher> matchers;
TestServletRequestMatcherRegistry(ApplicationContext context, String pattern) {
super(context, RequestMatcherBuilders.createForServletPattern(context, pattern));
}
@Override
protected TestServletRequestMatcherRegistry chainRequestMatchers(List<RequestMatcher> requestMatchers) {
this.matchers = requestMatchers;
return this;
}
}
static final class MvcRequestMatcherAssert extends ObjectAssert<MvcRequestMatcher> {
private MvcRequestMatcherAssert(MvcRequestMatcher matcher) {
super(matcher);
}
AbstractObjectAssert<?, ?> servletPath() {
return extracting("servletPath");
}
AbstractObjectAssert<?, ?> pattern() {
return extracting("pattern");
}
AbstractObjectAssert<?, ?> method() {
return extracting("method");
}
}
static final class AntPathRequestMatcherAssert extends ObjectAssert<AntPathRequestMatcher> {
private AntPathRequestMatcherAssert(AntPathRequestMatcher matcher) {
super(matcher);
}
AbstractObjectAssert<?, ?> pattern() {
return extracting("pattern");
}
AbstractObjectAssert<?, ?> method() {
return extracting("httpMethod");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -18,7 +18,6 @@ package org.springframework.security.config.annotation.web.configurers;
import java.util.function.Supplier;
import jakarta.servlet.Servlet;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
@@ -27,21 +26,15 @@ import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.access.hierarchicalroles.RoleHierarchy;
import org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl;
import org.springframework.security.authentication.RememberMeAuthenticationToken;
import org.springframework.security.authentication.TestAuthentication;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.authorization.AuthorizationEventPublisher;
import org.springframework.security.authorization.AuthorizationManager;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.TestMockHttpServletMappings;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.AbstractRequestMatcherRegistry;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.core.GrantedAuthorityDefaults;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.authority.AuthorityUtils;
@@ -62,7 +55,6 @@ import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
@@ -76,7 +68,6 @@ import static org.springframework.security.test.web.servlet.request.SecurityMock
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.head;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -127,7 +118,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
public void configureWhenMvcMatcherAfterAnyRequestThenException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(AfterAnyRequestConfig.class).autowire())
.withMessageContaining("Can't configure requestMatchers after anyRequest");
.withMessageContaining("Can't configure mvcMatchers after anyRequest");
}
@Test
@@ -282,17 +273,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithAdmin).andExpect(status().isForbidden());
}
@Test
public void getWhenHasRoleUserAndRoleHierarchyConfiguredThenGreaterRoleTakesPrecedence() throws Exception {
this.spring.register(RoleHierarchyUserConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithAdmin = get("/")
.with(user("user")
.roles("ADMIN"));
// @formatter:on
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
}
@Test
public void getWhenRoleUserOrAdminConfiguredAndRoleIsUserThenRespondsWithOk() throws Exception {
this.spring.register(RoleUserOrAdminConfig.class, BasicController.class).autowire();
@@ -368,7 +348,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Test
public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserThenRespondsWithForbidden() throws Exception {
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/spring/")
.servletPath("/spring")
@@ -381,7 +361,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Test
public void getWhenServletPathRoleAdminConfiguredAndRoleIsUserAndWithoutServletPathThenRespondsWithForbidden()
throws Exception {
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
@@ -392,7 +372,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Test
public void getWhenServletPathRoleAdminConfiguredAndRoleIsAdminThenRespondsWithOk() throws Exception {
this.spring.register(MvcServletPathConfig.class, BasicController.class).autowire();
this.spring.register(ServletPathConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithAdmin = get("/spring/")
.servletPath("/spring")
@@ -483,43 +463,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithRoleOther).andExpect(status().isForbidden());
}
@Test
public void getWhenCustomRolePrefixAndRoleHasDifferentPrefixThenRespondsWithForbidden() throws Exception {
this.spring.register(GrantedAuthorityDefaultHasRoleConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("ROLE_USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
@Test
public void getWhenCustomRolePrefixAndHasRoleThenRespondsWithOk() throws Exception {
this.spring.register(GrantedAuthorityDefaultHasRoleConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_USER")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
}
@Test
public void getWhenCustomRolePrefixAndHasAnyRoleThenRespondsWithOk() throws Exception {
this.spring.register(GrantedAuthorityDefaultHasAnyRoleConfig.class, BasicController.class).autowire();
// @formatter:off
MockHttpServletRequestBuilder requestWithUser = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_USER")));
MockHttpServletRequestBuilder requestWithAdmin = get("/")
.with(user("user")
.authorities(new SimpleGrantedAuthority("CUSTOM_PREFIX_ADMIN")));
// @formatter:on
this.mvc.perform(requestWithUser).andExpect(status().isOk());
this.mvc.perform(requestWithAdmin).andExpect(status().isOk());
}
@Test
public void getWhenExpressionHasIpAddressLocalhostConfiguredIpAddressIsLocalhostThenRespondsWithOk()
throws Exception {
@@ -602,232 +545,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
this.mvc.perform(requestWithUser).andExpect(status().isForbidden());
}
@Test
public void configureWhenNoDispatcherServletThenSucceeds() throws Exception {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
this.spring.register(AuthorizeHttpRequestsConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/path")).andExpect(status().isNotFound());
}
@Test
public void configureWhenOnlyDispatcherServletThenSucceeds() throws Exception {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
this.spring.register(AuthorizeHttpRequestsConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/mvc/path").servletPath("/mvc")).andExpect(status().isNotFound());
this.mvc.perform(get("/mvc")).andExpect(status().isUnauthorized());
}
@Test
public void configureWhenMultipleServletsThenSucceeds() throws Exception {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
this.spring.register(AuthorizeHttpRequestsConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/path").with(servletPath("/path"))).andExpect(status().isNotFound());
}
@Test
public void configureWhenAmbiguousServletsThenWiringException() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(AuthorizeHttpRequestsConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire());
}
@Test
void defaultServletMatchersWhenDefaultServletThenPermits() throws Exception {
this.spring.register(DefaultServletConfig.class)
.postProcessor((context) -> context.setServletContext(MockServletContext.mvc()))
.autowire();
this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isNotFound());
this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isUnauthorized());
}
@Test
void defaultServletHttpMethodMatchersWhenDefaultServletThenPermits() throws Exception {
this.spring.register(DefaultServletConfig.class)
.postProcessor((context) -> context.setServletContext(MockServletContext.mvc()))
.autowire();
this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isNotFound());
this.mvc.perform(head("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized());
this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized());
}
@Test
void defaultServletWhenNoDefaultServletThenWiringException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(DefaultServletConfig.class)
.postProcessor((context) -> context.setServletContext(new MockServletContext()))
.autowire());
}
@Test
void servletPathMatchersWhenMatchingServletThenPermits() throws Exception {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
this.spring.register(ServletPathConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/path/path").with(servletPath("/path"))).andExpect(status().isNotFound());
this.mvc.perform(get("/path/path").with(defaultServlet())).andExpect(status().isUnauthorized());
}
@Test
void servletPathHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("path", Servlet.class).addMapping("/path/*");
this.spring.register(ServletPathConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/path/method").with(servletPath("/path"))).andExpect(status().isNotFound());
this.mvc.perform(head("/path/method").with(servletPath("/path"))).andExpect(status().isUnauthorized());
this.mvc.perform(get("/path/method").with(defaultServlet())).andExpect(status().isUnauthorized());
}
@Test
void servletPathWhenNoMatchingPathThenWiringException() {
MockServletContext servletContext = MockServletContext.mvc();
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(ServletPathConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire());
}
@Test
void servletMappingMatchersWhenMatchingServletThenPermits() throws Exception {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp");
this.spring.register(ServletMappingConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/path/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound());
this.mvc.perform(get("/path/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized());
}
@Test
void servletMappingHttpMethodMatchersWhenMatchingServletThenPermits() throws Exception {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("jsp", Servlet.class).addMapping("*.jsp");
this.spring.register(ServletMappingConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isNotFound());
this.mvc.perform(head("/method/file.jsp").with(servletExtension(".jsp"))).andExpect(status().isUnauthorized());
this.mvc.perform(get("/method/file.jsp").with(defaultServlet())).andExpect(status().isUnauthorized());
}
@Test
void servletMappingWhenNoMatchingExtensionThenWiringException() {
MockServletContext servletContext = MockServletContext.mvc();
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(ServletMappingConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire());
}
@Test
void anyRequestWhenUsedWithDefaultServletThenDoesNotWire() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(MixedServletEndpointConfig.class).autowire())
.withMessageContaining("forServletPattern");
}
@Test
void servletWhenNoMatchingPathThenDenies() throws Exception {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
this.spring.register(DefaultServletAndServletPathConfig.class)
.postProcessor((context) -> context.setServletContext(servletContext))
.autowire();
this.mvc.perform(get("/js/color.js").with(servletPath("/js"))).andExpect(status().isUnauthorized());
this.mvc.perform(get("/mvc/controller").with(defaultServlet())).andExpect(status().isUnauthorized());
this.mvc.perform(get("/js/color.js").with(defaultServlet())).andExpect(status().isNotFound());
this.mvc.perform(get("/mvc/controller").with(servletPath("/mvc"))).andExpect(status().isUnauthorized());
this.mvc.perform(get("/mvc/controller").with(user("user")).with(servletPath("/mvc")))
.andExpect(status().isNotFound());
}
@Test
void permitAllWhenDefaultServletThenDoesNotWire() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(() -> this.spring.register(MixedServletPermitAllConfig.class).autowire())
.withMessageContaining("forServletPattern");
}
static RequestPostProcessor defaultServlet() {
return (request) -> {
String uri = request.getRequestURI();
request.setHttpServletMapping(TestMockHttpServletMappings.defaultMapping());
request.setServletPath(uri);
request.setPathInfo("");
return request;
};
}
static RequestPostProcessor servletPath(String path) {
return (request) -> {
String uri = request.getRequestURI();
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, path));
request.setServletPath(path);
request.setPathInfo(uri.substring(path.length()));
return request;
};
}
static RequestPostProcessor servletExtension(String extension) {
return (request) -> {
String uri = request.getRequestURI();
request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, extension));
request.setServletPath(uri);
request.setPathInfo("");
return request;
};
}
@Configuration
@EnableWebSecurity
static class GrantedAuthorityDefaultHasRoleConfig {
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("CUSTOM_PREFIX_");
}
@Bean
SecurityFilterChain myFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests((c) -> c.anyRequest().hasRole("USER")).build();
}
}
@Configuration
@EnableWebSecurity
static class GrantedAuthorityDefaultHasAnyRoleConfig {
@Bean
GrantedAuthorityDefaults grantedAuthorityDefaults() {
return new GrantedAuthorityDefaults("CUSTOM_PREFIX_");
}
@Bean
SecurityFilterChain myFilterChain(HttpSecurity http) throws Exception {
return http.authorizeHttpRequests((c) -> c.anyRequest().hasAnyRole("USER", "ADMIN")).build();
}
}
@Configuration
@EnableWebSecurity
static class NoRequestsConfig {
@@ -893,7 +610,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class AfterAnyRequestConfig {
@Bean
@@ -1055,30 +771,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class RoleHierarchyUserConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
return http
.authorizeHttpRequests((requests) -> requests
.anyRequest().hasRole("USER")
)
.build();
// @formatter:on
}
@Bean
RoleHierarchy roleHierarchy() {
RoleHierarchyImpl roleHierarchy = new RoleHierarchyImpl();
roleHierarchy.setHierarchy("ROLE_ADMIN > ROLE_USER");
return roleHierarchy;
}
}
@Configuration
@EnableWebSecurity
static class RoleUserOrAdminConfig {
@@ -1155,7 +847,7 @@ public class AuthorizeHttpRequestsConfigurerTests {
@Configuration
@EnableWebMvc
@EnableWebSecurity
static class MvcServletPathConfig {
static class ServletPathConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
@@ -1337,163 +1029,6 @@ public class AuthorizeHttpRequestsConfigurerTests {
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class AuthorizeHttpRequestsConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.requestMatchers("/path/**").permitAll()
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class DefaultServletConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("/", (root) -> root
.requestMatchers(HttpMethod.GET, "/path/method/**").permitAll()
.requestMatchers("/path/path/**").permitAll()
.anyRequest().authenticated()
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class ServletPathConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("/path/*", (root) -> root
.requestMatchers(HttpMethod.GET, "/method/**").permitAll()
.requestMatchers("/path/**").permitAll()
.anyRequest().authenticated()
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class ServletMappingConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("*.jsp", (jsp) -> jsp
.requestMatchers(HttpMethod.GET, "/method/**").permitAll()
.requestMatchers("/path/**").permitAll()
.anyRequest().authenticated()
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class MixedServletEndpointConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("/", (root) -> root.anyRequest().permitAll())
.anyRequest().authenticated()
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class MixedServletPermitAllConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.formLogin((form) -> form.loginPage("/page").permitAll())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("/", (root) -> root
.anyRequest().authenticated()
)
);
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
static class DefaultServletAndServletPathConfig {
@Bean
SecurityFilterChain chain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic(withDefaults())
.authorizeHttpRequests((requests) -> requests
.forServletPattern("/", (root) -> root
.requestMatchers("/js/**", "/css/**").permitAll()
)
.forServletPattern("/mvc/*", (mvc) -> mvc
.requestMatchers("/controller/**").authenticated()
)
.forServletPattern("*.jsp", (jsp) -> jsp
.anyRequest().authenticated()
)
);
// @formatter:on
return http.build();
}
}
@Configuration
static class AuthorizationEventPublisherConfig {

View File

@@ -85,10 +85,10 @@ public class DefaultFiltersTests {
List<SecurityFilterChain> filterChains = this.spring.getContext()
.getBean(FilterChainProxy.class)
.getFilterChains();
assertThat(filterChains).hasSize(1);
assertThat(filterChains.size()).isEqualTo(1);
DefaultSecurityFilterChain filterChain = (DefaultSecurityFilterChain) filterChains.get(0);
assertThat(filterChain.getRequestMatcher()).isInstanceOf(AnyRequestMatcher.class);
assertThat(filterChain.getFilters()).hasSize(1);
assertThat(filterChain.getFilters().size()).isEqualTo(1);
long filter = filterChain.getFilters()
.stream()
.filter((it) -> it instanceof UsernamePasswordAuthenticationFilter)
@@ -102,25 +102,25 @@ public class DefaultFiltersTests {
List<SecurityFilterChain> filterChains = this.spring.getContext()
.getBean(FilterChainProxy.class)
.getFilterChains();
assertThat(filterChains).hasSize(2);
assertThat(filterChains.size()).isEqualTo(2);
DefaultSecurityFilterChain firstFilter = (DefaultSecurityFilterChain) filterChains.get(0);
DefaultSecurityFilterChain secondFilter = (DefaultSecurityFilterChain) filterChains.get(1);
assertThat(firstFilter.getFilters().isEmpty()).isEqualTo(true);
assertThat(secondFilter.getRequestMatcher()).isInstanceOf(AnyRequestMatcher.class);
List<Class<? extends Filter>> classes = secondFilter.getFilters()
List<? extends Class<? extends Filter>> classes = secondFilter.getFilters()
.stream()
.map(Filter::getClass)
.collect(Collectors.toList());
assertThat(classes).contains(WebAsyncManagerIntegrationFilter.class);
assertThat(classes).contains(SecurityContextHolderFilter.class);
assertThat(classes).contains(HeaderWriterFilter.class);
assertThat(classes).contains(LogoutFilter.class);
assertThat(classes).contains(CsrfFilter.class);
assertThat(classes).contains(RequestCacheAwareFilter.class);
assertThat(classes).contains(SecurityContextHolderAwareRequestFilter.class);
assertThat(classes).contains(AnonymousAuthenticationFilter.class);
assertThat(classes).contains(ExceptionTranslationFilter.class);
assertThat(classes).contains(FilterSecurityInterceptor.class);
assertThat(classes.contains(WebAsyncManagerIntegrationFilter.class)).isTrue();
assertThat(classes.contains(SecurityContextHolderFilter.class)).isTrue();
assertThat(classes.contains(HeaderWriterFilter.class)).isTrue();
assertThat(classes.contains(LogoutFilter.class)).isTrue();
assertThat(classes.contains(CsrfFilter.class)).isTrue();
assertThat(classes.contains(RequestCacheAwareFilter.class)).isTrue();
assertThat(classes.contains(SecurityContextHolderAwareRequestFilter.class)).isTrue();
assertThat(classes.contains(AnonymousAuthenticationFilter.class)).isTrue();
assertThat(classes.contains(ExceptionTranslationFilter.class)).isTrue();
assertThat(classes.contains(FilterSecurityInterceptor.class)).isTrue();
}
@Test

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -96,7 +96,7 @@ public class DefaultLoginPageConfigurerTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"
@@ -145,7 +145,7 @@ public class DefaultLoginPageConfigurerTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"
@@ -197,7 +197,7 @@ public class DefaultLoginPageConfigurerTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"
@@ -250,7 +250,7 @@ public class DefaultLoginPageConfigurerTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"

View File

@@ -31,8 +31,6 @@ import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.header.HeaderWriterFilter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
@@ -52,7 +50,7 @@ public class HeadersConfigurerEagerHeadersTests {
@Test
public void requestWhenHeadersEagerlyConfiguredThenHeadersAreWritten() throws Exception {
this.spring.register(HeadersAtTheBeginningOfRequestConfig.class, HomeController.class).autowire();
this.spring.register(HeadersAtTheBeginningOfRequestConfig.class).autowire();
this.mvc.perform(get("/").secure(true))
.andExpect(header().string("X-Content-Type-Options", "nosniff"))
.andExpect(header().string("X-Frame-Options", "DENY"))
@@ -85,14 +83,4 @@ public class HeadersConfigurerEagerHeadersTests {
}
@RestController
private static class HomeController {
@GetMapping("/")
String ok() {
return "ok";
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 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.
@@ -34,7 +34,6 @@ 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.SecurityContext;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
@@ -43,7 +42,6 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.bind.annotation.GetMapping;
@@ -68,7 +66,6 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
*
* @author Rob Winch
* @author Eleftheria Stein
* @author Evgeniy Cheban
*/
@ExtendWith(SpringTestContextExtension.class)
public class HttpBasicConfigurerTests {
@@ -107,7 +104,6 @@ public class HttpBasicConfigurerTests {
@Test
public void httpBasicWhenUsingCustomAuthenticationEntryPointThenResponseIncludesBasicChallenge() throws Exception {
CustomAuthenticationEntryPointConfig.ENTRY_POINT = mock(AuthenticationEntryPoint.class);
this.spring.register(CustomAuthenticationEntryPointConfig.class).autowire();
this.mvc.perform(get("/"));
verify(CustomAuthenticationEntryPointConfig.ENTRY_POINT).commence(any(HttpServletRequest.class),
@@ -125,7 +121,7 @@ public class HttpBasicConfigurerTests {
// SEC-3019
@Test
public void httpBasicWhenRememberMeConfiguredThenSetsRememberMeCookie() throws Exception {
this.spring.register(BasicUsesRememberMeConfig.class, Home.class).autowire();
this.spring.register(BasicUsesRememberMeConfig.class).autowire();
MockHttpServletRequestBuilder rememberMeRequest = get("/").with(httpBasic("user", "password"))
.param("remember-me", "true");
this.mvc.perform(rememberMeRequest).andExpect(cookie().exists("remember-me"));
@@ -151,16 +147,6 @@ public class HttpBasicConfigurerTests {
verify(listener).securityContextChanged(setAuthentication(UsernamePasswordAuthenticationToken.class));
}
@Test
public void httpBasicWhenUsingCustomSecurityContextRepositoryThenUses() throws Exception {
this.spring.register(CustomSecurityContextRepositoryConfig.class, Users.class, Home.class).autowire();
this.mvc.perform(get("/").with(httpBasic("user", "password")))
.andExpect(status().isOk())
.andExpect(content().string("user"));
verify(CustomSecurityContextRepositoryConfig.SECURITY_CONTEXT_REPOSITORY)
.saveContext(any(SecurityContext.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
}
@Configuration
@EnableWebSecurity
static class ObjectPostProcessorConfig {
@@ -243,7 +229,7 @@ public class HttpBasicConfigurerTests {
@EnableWebSecurity
static class CustomAuthenticationEntryPointConfig {
static AuthenticationEntryPoint ENTRY_POINT;
static AuthenticationEntryPoint ENTRY_POINT = mock(AuthenticationEntryPoint.class);
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
@@ -337,24 +323,6 @@ public class HttpBasicConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class CustomSecurityContextRepositoryConfig {
static final SecurityContextRepository SECURITY_CONTEXT_REPOSITORY = mock(SecurityContextRepository.class);
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.httpBasic()
.securityContextRepository(SECURITY_CONTEXT_REPOSITORY);
// @formatter:on
return http.build();
}
}
@Configuration
static class Users {

View File

@@ -382,7 +382,7 @@ public class RequestCacheConfigurerTests {
.anyRequest().authenticated()
)
.formLogin(Customizer.withDefaults())
.requestCache(RequestCacheConfigurer::disable);
.requestCache((cache) -> cache.disable());
// @formatter:on
return http.build();
}

View File

@@ -1,198 +0,0 @@
/*
* Copyright 2002-2023 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.web.configurers;
import java.util.List;
import java.util.function.Consumer;
import jakarta.servlet.Servlet;
import jakarta.servlet.ServletContext;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.MockServletContext;
import org.springframework.security.config.annotation.ObjectPostProcessor;
import org.springframework.security.config.annotation.web.configurers.DispatcherServletDelegatingRequestMatcherBuilder.DispatcherServletDelegatingRequestMatcher;
import org.springframework.security.web.servlet.util.matcher.MvcRequestMatcher;
import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.test.util.ReflectionTestUtils;
import org.springframework.web.context.support.GenericWebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.Mockito.mock;
public class RequestMatcherBuildersTests {
@Test
void matchersWhenDefaultDispatcherServletThenMvc() {
MockServletContext servletContext = MockServletContext.mvc();
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
List<RequestMatcher> matchers = builder.matchers("/mvc");
assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isNull();
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/mvc");
}
@Test
void httpMethodMatchersWhenDefaultDispatcherServletThenMvc() {
MockServletContext servletContext = MockServletContext.mvc();
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
List<RequestMatcher> matchers = builder.matchers(HttpMethod.GET, "/mvc");
assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isNull();
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/mvc");
assertThat(ReflectionTestUtils.getField(matcher, "method")).isEqualTo(HttpMethod.GET);
}
@Test
void matchersWhenPathDispatcherServletThenMvc() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
List<RequestMatcher> matchers = builder.matchers("/controller");
assertThat(matchers.get(0)).isInstanceOf(MvcRequestMatcher.class);
MvcRequestMatcher matcher = (MvcRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "servletPath")).isEqualTo("/mvc");
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller");
}
@Test
void matchersWhenAlsoExtraServletContainerMappingsThenRequiresServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
servletContext.addServlet("facesServlet", Servlet.class).addMapping("/faces/", "*.jsf", "*.faces", "*.xhtml");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path"))
.withMessageContaining(".forServletPattern");
}
@Test
void matchersWhenOnlyDefaultServletThenAnt() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
List<RequestMatcher> matchers = builder.matchers("/controller");
assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class);
AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller");
}
@Test
void matchersWhenNoHandlerMappingIntrospectorThenAnt() {
MockServletContext servletContext = MockServletContext.mvc();
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext, (context) -> {
});
List<RequestMatcher> matchers = builder.matchers("/controller");
assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class);
AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/controller");
}
@Test
void matchersWhenNoDispatchServletThenAnt() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
List<RequestMatcher> matchers = builder.matchers("/services/endpoint");
assertThat(matchers.get(0)).isInstanceOf(AntPathRequestMatcher.class);
AntPathRequestMatcher matcher = (AntPathRequestMatcher) matchers.get(0);
assertThat(ReflectionTestUtils.getField(matcher, "pattern")).isEqualTo("/services/endpoint");
}
@Test
void matchersWhenMixedServletsThenServletPathDelegating() {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("messageDispatcherServlet", Servlet.class).addMapping("/services/*");
RequestMatcherBuilder builder = requestMatchersBuilder(servletContext);
assertThat(builder.matchers("/services/endpoint").get(0))
.isInstanceOf(DispatcherServletDelegatingRequestMatcher.class);
}
@Test
void matchersWhenDispatcherServletNotDefaultAndOtherServletsThenRequiresServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**"))
.withMessageContaining(".forServletPattern");
}
@Test
void httpMatchersWhenDispatcherServletNotDefaultAndOtherServletsThenRequiresServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("default", Servlet.class).addMapping("/");
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/mvc/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/pattern"))
.withMessageContaining(".forServletPattern");
}
@Test
void matchersWhenTwoDispatcherServletsThenException() {
MockServletContext servletContext = MockServletContext.mvc();
servletContext.addServlet("two", DispatcherServlet.class).addMapping("/other/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**"))
.withMessageContaining(".forServletPattern");
}
@Test
void matchersWhenMoreThanOneMappingThenException() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**"))
.withMessageContaining(".forServletPattern");
}
@Test
void matchersWhenMoreThanOneMappingAndDefaultServletsThenRequiresServletPath() {
MockServletContext servletContext = new MockServletContext();
servletContext.addServlet("dispatcherServlet", DispatcherServlet.class).addMapping("/", "/two/*");
servletContext.addServlet("jspServlet", Servlet.class).addMapping("*.jsp", "*.jspx");
assertThatExceptionOfType(IllegalArgumentException.class)
.isThrownBy(() -> requestMatchersBuilder(servletContext).matcher("/path/**"))
.withMessageContaining(".forServletPattern");
}
RequestMatcherBuilder requestMatchersBuilder(ServletContext servletContext) {
return requestMatchersBuilder(servletContext, (context) -> {
context.registerBean("mvcHandlerMappingIntrospector", HandlerMappingIntrospector.class,
() -> mock(HandlerMappingIntrospector.class));
context.registerBean(ObjectPostProcessor.class, () -> mock(ObjectPostProcessor.class));
});
}
RequestMatcherBuilder requestMatchersBuilder(ServletContext servletContext,
Consumer<GenericWebApplicationContext> consumer) {
GenericWebApplicationContext context = new GenericWebApplicationContext(servletContext);
consumer.accept(context);
context.refresh();
return RequestMatcherBuilders.createDefault(context);
}
}

View File

@@ -1,64 +0,0 @@
/*
* Copyright 2002-2023 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.web.configurers;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.config.TestMockHttpServletMappings;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServletPatternRequestMatcher}
*/
class ServletPatternRequestMatcherTests {
ServletPatternRequestMatcher matcher = new ServletPatternRequestMatcher("*.jsp");
@Test
void matchesWhenDefaultServletThenTrue() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp");
request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, ".jsp"));
assertThat(this.matcher.matches(request)).isTrue();
}
@Test
void matchesWhenNotDefaultServletThenFalse() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp");
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/a"));
request.setServletPath("/a/uri.jsp");
assertThat(this.matcher.matches(request)).isFalse();
}
@Test
void matcherWhenDefaultServletThenTrue() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp");
request.setHttpServletMapping(TestMockHttpServletMappings.extension(request, ".jsp"));
request.setServletPath("/a/uri.jsp");
assertThat(this.matcher.matcher(request).isMatch()).isTrue();
}
@Test
void matcherWhenNotDefaultServletThenFalse() {
MockHttpServletRequest request = new MockHttpServletRequest("GET", "/a/uri.jsp");
request.setHttpServletMapping(TestMockHttpServletMappings.path(request, "/a"));
request.setServletPath("/a/uri.jsp");
assertThat(this.matcher.matcher(request).isMatch()).isFalse();
}
}

View File

@@ -556,7 +556,9 @@ public class SessionManagementConfigurerTests {
.sessionManagement((sessionManagement) ->
sessionManagement
.requireExplicitAuthenticationStrategy(false)
.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::newSession)
.sessionFixation((sessionFixation) ->
sessionFixation.newSession()
)
)
.httpBasic(withDefaults());
// @formatter:on

View File

@@ -1,596 +0,0 @@
/*
* Copyright 2002-2023 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.web.configurers.oauth2.client;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import com.gargoylesoftware.htmlunit.util.UrlUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
import jakarta.annotation.PreDestroy;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpSession;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.mock.web.MockServletContext;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
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.userdetails.User;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames;
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens;
import org.springframework.security.oauth2.client.oidc.session.InMemoryOidcSessionRegistry;
import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.InMemoryClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.logout.LogoutHandler;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.willThrow;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
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.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link OidcLogoutConfigurer}
*/
@ExtendWith(SpringTestContextExtension.class)
public class OidcLogoutConfigurerTests {
@Autowired
private MockMvc mvc;
@Autowired(required = false)
private MockWebServer web;
@Autowired
private ClientRegistration clientRegistration;
public final SpringTestContext spring = new SpringTestContext(this);
@Test
void logoutWhenDefaultsThenRemotelyInvalidatesSessions() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession session = login();
String logoutToken = this.mvc.perform(get("/token/logout").session(session))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isOk());
this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized());
}
@Test
void logoutWhenInvalidLogoutTokenThenBadRequest() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized());
String registrationId = this.clientRegistration.getRegistrationId();
MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId))
.andExpect(status().isFound())
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl());
String state = this.mvc
.perform(get(redirectUrl)
.with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret())))
.andReturn()
.getResponse()
.getContentAsString();
result = this.mvc
.perform(get("/login/oauth2/code/" + registrationId).param("code", "code")
.param("state", state)
.session(session))
.andExpect(status().isFound())
.andReturn();
session = (MockHttpSession) result.getRequest().getSession();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", "invalid"))
.andExpect(status().isBadRequest());
this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isOk());
}
@Test
void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession one = login();
MockHttpSession two = login();
MockHttpSession three = login();
String logoutToken = this.mvc.perform(get("/token/logout").session(one))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isOk());
this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isUnauthorized());
this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isOk());
logoutToken = this.mvc.perform(get("/token/logout/all").session(three))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isOk());
this.mvc.perform(get("/token/logout").session(two)).andExpect(status().isUnauthorized());
this.mvc.perform(get("/token/logout").session(three)).andExpect(status().isUnauthorized());
}
@Test
void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire();
LogoutHandler logoutHandler = this.spring.getContext().getBean(LogoutHandler.class);
willThrow(IllegalStateException.class).given(logoutHandler).logout(any(), any(), any());
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession one = login();
String logoutToken = this.mvc.perform(get("/token/logout/all").session(one))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isBadRequest())
.andExpect(content().string(containsString("partial_logout")));
this.mvc.perform(get("/token/logout").session(one)).andExpect(status().isOk());
}
@Test
void logoutWhenCustomComponentsThenUses() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class)
.autowire();
String registrationId = this.clientRegistration.getRegistrationId();
MockHttpSession session = login();
String logoutToken = this.mvc.perform(get("/token/logout").session(session))
.andExpect(status().isOk())
.andReturn()
.getResponse()
.getContentAsString();
this.mvc
.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.param("logout_token", logoutToken))
.andExpect(status().isOk());
this.mvc.perform(get("/token/logout").session(session)).andExpect(status().isUnauthorized());
OidcSessionRegistry sessionRegistry = this.spring.getContext().getBean(OidcSessionRegistry.class);
verify(sessionRegistry).saveSessionInformation(any());
verify(sessionRegistry).removeSessionInformation(any(OidcLogoutToken.class));
}
private MockHttpSession login() throws Exception {
MockMvcDispatcher dispatcher = (MockMvcDispatcher) this.web.getDispatcher();
this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized());
String registrationId = this.clientRegistration.getRegistrationId();
MvcResult result = this.mvc.perform(get("/oauth2/authorization/" + registrationId))
.andExpect(status().isFound())
.andReturn();
MockHttpSession session = (MockHttpSession) result.getRequest().getSession();
String redirectUrl = UrlUtils.decode(result.getResponse().getRedirectedUrl());
String state = this.mvc
.perform(get(redirectUrl)
.with(httpBasic(this.clientRegistration.getClientId(), this.clientRegistration.getClientSecret())))
.andReturn()
.getResponse()
.getContentAsString();
result = this.mvc
.perform(get("/login/oauth2/code/" + registrationId).param("code", "code")
.param("state", state)
.session(session))
.andExpect(status().isFound())
.andReturn();
session = (MockHttpSession) result.getRequest().getSession();
dispatcher.registerSession(session);
return session;
}
@Configuration
static class RegistrationConfig {
@Autowired(required = false)
MockWebServer web;
@Bean
ClientRegistration clientRegistration() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().build();
}
String issuer = this.web.url("/").toString();
return TestClientRegistrations.clientRegistration()
.issuerUri(issuer)
.jwkSetUri(issuer + "jwks")
.tokenUri(issuer + "token")
.userInfoUri(issuer + "user")
.scope("openid")
.build();
}
@Bean
ClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryClientRegistrationRepository(clientRegistration);
}
}
@Configuration
@EnableWebSecurity
@Import(RegistrationConfig.class)
static class DefaultConfig {
@Bean
@Order(1)
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebSecurity
@Import(RegistrationConfig.class)
static class WithCustomComponentsConfig {
OidcSessionRegistry sessionRegistry = spy(new InMemoryOidcSessionRegistry());
@Bean
@Order(1)
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.oauth2Login((oauth2) -> oauth2.oidcSessionRegistry(this.sessionRegistry))
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean
OidcSessionRegistry sessionRegistry() {
return this.sessionRegistry;
}
}
@Configuration
@EnableWebSecurity
@Import(RegistrationConfig.class)
static class WithBrokenLogoutConfig {
private final LogoutHandler logoutHandler = mock(LogoutHandler.class);
@Bean
@Order(1)
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.logout((logout) -> logout.addLogoutHandler(this.logoutHandler))
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean
LogoutHandler logoutHandler() {
return this.logoutHandler;
}
}
@Configuration
@EnableWebSecurity
@EnableWebMvc
@RestController
static class OidcProviderConfig {
private static final RSAKey key = key();
private static final JWKSource<SecurityContext> jwks = jwks(key);
private static RSAKey key() {
try {
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static JWKSource<SecurityContext> jwks(RSAKey key) {
try {
return new ImmutableJWKSet<>(new JWKSet(key));
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private final String username = "user";
private final JwtEncoder encoder = new NimbusJwtEncoder(jwks);
private String nonce;
@Autowired
ClientRegistration registration;
@Bean
@Order(0)
SecurityFilterChain authorizationServer(HttpSecurity http, ClientRegistration registration) throws Exception {
// @formatter:off
http
.securityMatcher("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user")
.authorizeHttpRequests((authorize) -> authorize
.requestMatchers("/jwks").permitAll()
.anyRequest().authenticated()
)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri()))
);
// @formatter:off
return http.build();
}
@Bean
UserDetailsService users(ClientRegistration registration) {
return new InMemoryUserDetailsManager(User.withUsername(registration.getClientId())
.password("{noop}" + registration.getClientSecret()).authorities("APP").build());
}
@GetMapping("/login/oauth/authorize")
String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) {
this.nonce = nonce;
return state;
}
@PostMapping("/token")
Map<String, Object> accessToken(HttpServletRequest request) {
HttpSession session = request.getSession();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().id("id").subject(this.username)
.issuer(this.registration.getProviderDetails().getIssuerUri()).issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build());
String token = this.encoder.encode(parameters).getTokenValue();
return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null)
.toJSONObject();
}
String idToken(String sessionId) {
OidcIdToken token = TestOidcIdTokens.idToken().issuer(this.registration.getProviderDetails().getIssuerUri())
.subject(this.username).expiresAt(Instant.now().plusSeconds(86400))
.audience(List.of(this.registration.getClientId())).nonce(this.nonce)
.claim(LogoutTokenClaimNames.SID, sessionId).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
@GetMapping("/user")
Map<String, Object> userinfo() {
return Map.of("sub", this.username, "id", this.username);
}
@GetMapping("/jwks")
String jwks() {
return new JWKSet(key).toString();
}
@GetMapping("/token/logout")
String logoutToken(@AuthenticationPrincipal OidcUser user) {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId())).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
@GetMapping("/token/logout/all")
String logoutTokenAll(@AuthenticationPrincipal OidcUser user) {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId()))
.claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
}
@Configuration
static class WebServerConfig {
private final MockWebServer server = new MockWebServer();
@Bean
MockWebServer web(ObjectProvider<MockMvc> mvc) {
this.server.setDispatcher(new MockMvcDispatcher(mvc));
return this.server;
}
@PreDestroy
void shutdown() throws IOException {
this.server.shutdown();
}
}
private static class MockMvcDispatcher extends Dispatcher {
private final Map<String, MockHttpSession> session = new ConcurrentHashMap<>();
private final ObjectProvider<MockMvc> mvcProvider;
private MockMvc mvc;
MockMvcDispatcher(ObjectProvider<MockMvc> mvc) {
this.mvcProvider = mvc;
}
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
this.mvc = this.mvcProvider.getObject();
String method = request.getMethod();
String path = request.getPath();
String csrf = request.getHeader("X-CSRF-TOKEN");
MockHttpSession session = session(request);
MockHttpServletRequestBuilder builder;
if ("GET".equals(method)) {
builder = get(path);
}
else {
builder = post(path).content(request.getBody().readUtf8());
if (csrf != null) {
builder.header("X-CSRF-TOKEN", csrf);
}
else {
builder.with(csrf());
}
}
for (Map.Entry<String, List<String>> header : request.getHeaders().toMultimap().entrySet()) {
builder.header(header.getKey(), header.getValue().iterator().next());
}
try {
MockHttpServletResponse mvcResponse = this.mvc.perform(builder.session(session)).andReturn().getResponse();
return toMockResponse(mvcResponse);
}
catch (Exception ex) {
MockResponse response = new MockResponse();
response.setResponseCode(500);
return response;
}
}
void registerSession(MockHttpSession session) {
this.session.put(session.getId(), session);
}
private MockHttpSession session(RecordedRequest request) {
String cookieHeaderValue = request.getHeader("Cookie");
if (cookieHeaderValue == null) {
return new MockHttpSession();
}
String[] cookies = cookieHeaderValue.split(";");
for (String cookie : cookies) {
String[] parts = cookie.split("=");
if ("JSESSIONID".equals(parts[0])) {
return this.session.computeIfAbsent(parts[1],
(k) -> new MockHttpSession(new MockServletContext(), parts[1]));
}
}
return new MockHttpSession();
}
private MockResponse toMockResponse(MockHttpServletResponse mvcResponse) {
MockResponse response = new MockResponse();
response.setResponseCode(mvcResponse.getStatus());
for (String name : mvcResponse.getHeaderNames()) {
response.addHeader(name, mvcResponse.getHeaderValue(name));
}
response.setBody(getContentAsString(mvcResponse));
return response;
}
private String getContentAsString(MockHttpServletResponse response) {
try {
return response.getContentAsString();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
}
}

View File

@@ -873,7 +873,8 @@ public class OAuth2ResourceServerConfigurerTests {
context.registerBean("decoderTwo", JwtDecoder.class, () -> decoder);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer.JwtConfigurer jwtConfigurer = new OAuth2ResourceServerConfigurer(context).jwt();
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class).isThrownBy(jwtConfigurer::getJwtDecoder);
assertThatExceptionOfType(NoUniqueBeanDefinitionException.class)
.isThrownBy(() -> jwtConfigurer.getJwtDecoder());
}
@Test
@@ -1896,7 +1897,9 @@ public class OAuth2ResourceServerConfigurerTests {
.anyRequest().authenticated()
)
.oauth2Login(withDefaults())
.oauth2ResourceServer((oauth2) -> oauth2.jwt(withDefaults()));
.oauth2ResourceServer((oauth2) -> oauth2
.jwt()
);
return http.build();
// @formatter:on
}

View File

@@ -18,26 +18,20 @@ package org.springframework.security.config.annotation.web.configurers.saml2;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Collections;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Response;
import org.w3c.dom.Element;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
@@ -63,7 +57,6 @@ import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.context.SecurityContextChangedListener;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2ErrorCodes;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -73,7 +66,6 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
import org.springframework.security.saml2.provider.service.authentication.Saml2Authentication;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
@@ -101,6 +93,7 @@ import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.atLeastOnce;
@@ -122,17 +115,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@ExtendWith(SpringTestContextExtension.class)
public class Saml2LoginConfigurerTests {
static {
OpenSamlInitializationService.initialize();
}
private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential()))
.assertingPartyDetails((party) -> party
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
private static String SIGNED_RESPONSE;
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";
private static final AuthenticationConverter AUTHENTICATION_CONVERTER = mock(AuthenticationConverter.class);
@@ -159,23 +142,6 @@ public class Saml2LoginConfigurerTests {
private MockFilterChain filterChain;
@BeforeAll
static void createResponse() throws Exception {
String destination = registration.getAssertionConsumerServiceLocation();
String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
String relyingPartyEntityId = registration.getEntityId();
Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId);
Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId,
relyingPartyEntityId, destination);
response.getAssertions().add(assertion);
Response signed = TestOpenSamlObjects.signed(response,
registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId);
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed);
Element element = marshaller.marshall(signed);
String serialized = SerializeSupport.nodeToString(element);
SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8));
}
@BeforeEach
public void setup() {
this.request = new MockHttpServletRequest("POST", "");
@@ -344,9 +310,13 @@ public class Saml2LoginConfigurerTests {
}
@Test
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenAutowires()
throws Exception {
this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class).autowire();
public void saml2LoginWhenLoginProcessingUrlWithoutRegistrationIdAndDefaultAuthenticationConverterThenValidates() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> this.spring.register(CustomLoginProcessingUrlDefaultAuthenticationConverter.class).autowire())
.havingRootCause()
.isInstanceOf(IllegalStateException.class)
.withMessage("loginProcessingUrl must contain {registrationId} path variable");
}
@Test
@@ -414,7 +384,7 @@ public class Saml2LoginConfigurerTests {
Authentication authentication = this.securityContextRepository
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
.getAuthentication();
assertThat(authentication).as("Expected a valid authentication object.").isNotNull();
Assertions.assertNotNull(authentication, "Expected a valid authentication object.");
assertThat(authentication.getAuthorities()).hasSize(1);
assertThat(authentication.getAuthorities()).first()
.isInstanceOf(SimpleGrantedAuthority.class)
@@ -746,6 +716,11 @@ public class Saml2LoginConfigurerTests {
@Bean
RelyingPartyRegistrationRepository relyingPartyRegistrationRepository() {
RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartySigningCredential()))
.assertingPartyDetails((party) -> party.verificationX509Credentials(
(c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
return spy(new InMemoryRelyingPartyRegistrationRepository(registration));
}

View File

@@ -1,215 +0,0 @@
/*
* Copyright 2002-2023 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.web.configurers.saml2;
import com.google.common.net.HttpHeaders;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
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.context.annotation.Import;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.saml2.provider.service.metadata.OpenSamlMetadataResolver;
import org.springframework.security.saml2.provider.service.metadata.RequestMatcherMetadataResponseResolver;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponse;
import org.springframework.security.saml2.provider.service.metadata.Saml2MetadataResponseResolver;
import org.springframework.security.saml2.provider.service.registration.InMemoryRelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.test.web.servlet.MockMvc;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
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.header;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
* Tests for {@link Saml2MetadataConfigurer}
*/
@ExtendWith(SpringTestContextExtension.class)
public class Saml2MetadataConfigurerTests {
static RelyingPartyRegistration registration = TestRelyingPartyRegistrations.relyingPartyRegistration().build();
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired(required = false)
MockMvc mvc;
@Test
void saml2MetadataRegistrationIdWhenDefaultsThenReturnsMetadata() throws Exception {
this.spring.register(DefaultConfig.class).autowire();
String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml";
this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId()))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename)))
.andExpect(content().string(containsString("md:EntityDescriptor")));
}
@Test
void saml2MetadataRegistrationIdWhenWrongIdThenUnauthorized() throws Exception {
this.spring.register(DefaultConfig.class).autowire();
this.mvc.perform(get("/saml2/metadata/" + registration.getRegistrationId() + "wrong"))
.andExpect(status().isUnauthorized());
}
@Test
void saml2MetadataWhenDefaultsThenReturnsMetadata() throws Exception {
this.spring.register(DefaultConfig.class).autowire();
this.mvc.perform(get("/saml2/metadata"))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("-metadata.xml")))
.andExpect(content().string(containsString("md:EntityDescriptor")));
}
@Test
void saml2MetadataWhenMetadataResponseResolverThenUses() throws Exception {
this.spring.register(DefaultConfig.class, MetadataResponseResolverConfig.class).autowire();
Saml2MetadataResponseResolver metadataResponseResolver = this.spring.getContext()
.getBean(Saml2MetadataResponseResolver.class);
given(metadataResponseResolver.resolve(any(HttpServletRequest.class)))
.willReturn(new Saml2MetadataResponse("metadata", "filename"));
this.mvc.perform(get("/saml2/metadata"))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename")))
.andExpect(content().string(containsString("metadata")));
verify(metadataResponseResolver).resolve(any(HttpServletRequest.class));
}
@Test
void saml2MetadataWhenMetadataResponseResolverDslThenUses() throws Exception {
this.spring.register(MetadataResponseResolverDslConfig.class).autowire();
this.mvc.perform(get("/saml2/metadata"))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString("filename")))
.andExpect(content().string(containsString("metadata")));
}
@Test
void saml2MetadataWhenMetadataUrlThenUses() throws Exception {
this.spring.register(MetadataUrlConfig.class).autowire();
String filename = "saml-" + registration.getRegistrationId() + "-metadata.xml";
this.mvc.perform(get("/saml/metadata"))
.andExpect(status().isOk())
.andExpect(header().string(HttpHeaders.CONTENT_DISPOSITION, containsString(filename)))
.andExpect(content().string(containsString("md:EntityDescriptor")));
this.mvc.perform(get("/saml2/metadata")).andExpect(status().isForbidden());
}
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
static class DefaultConfig {
@Bean
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Metadata(Customizer.withDefaults());
return http.build();
// @formatter:on
}
}
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
static class MetadataUrlConfig {
@Bean
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Metadata((saml2) -> saml2.metadataUrl("/saml/metadata"));
return http.build();
// @formatter:on
}
// should ignore
@Bean
Saml2MetadataResponseResolver metadataResponseResolver(RelyingPartyRegistrationRepository registrations) {
return new RequestMatcherMetadataResponseResolver(registrations, new OpenSamlMetadataResolver());
}
}
@EnableWebSecurity
@Configuration
@Import(RelyingPartyRegistrationConfig.class)
static class MetadataResponseResolverDslConfig {
Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class);
{
given(this.metadataResponseResolver.resolve(any(HttpServletRequest.class)))
.willReturn(new Saml2MetadataResponse("metadata", "filename"));
}
@Bean
SecurityFilterChain filters(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
.saml2Metadata((saml2) -> saml2.metadataResponseResolver(this.metadataResponseResolver));
return http.build();
// @formatter:on
}
}
@Configuration
static class MetadataResponseResolverConfig {
Saml2MetadataResponseResolver metadataResponseResolver = mock(Saml2MetadataResponseResolver.class);
@Bean
Saml2MetadataResponseResolver metadataResponseResolver() {
return this.metadataResponseResolver;
}
}
@Configuration
static class RelyingPartyRegistrationConfig {
RelyingPartyRegistrationRepository registrations = new InMemoryRelyingPartyRegistrationRepository(registration);
@Bean
RelyingPartyRegistrationRepository registrations() {
return this.registrations;
}
}
}

View File

@@ -286,8 +286,8 @@ public class AbstractSecurityWebSocketMessageBrokerConfigurerTests {
private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
assertThat(handshakeHandler.attributes).containsEntry(this.sessionAttr,
request.getSession().getAttribute(this.sessionAttr));
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
}
private HttpRequestHandler handler(HttpServletRequest request) throws Exception {

View File

@@ -27,7 +27,8 @@ public class SyncExecutorSubscribableChannelPostProcessor implements BeanPostPro
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof ExecutorSubscribableChannel original) {
if (bean instanceof ExecutorSubscribableChannel) {
ExecutorSubscribableChannel original = (ExecutorSubscribableChannel) bean;
ExecutorSubscribableChannel channel = new ExecutorSubscribableChannel();
channel.setInterceptors(original.getInterceptors());
return channel;

View File

@@ -377,8 +377,8 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
private void assertHandshake(HttpServletRequest request) {
TestHandshakeHandler handshakeHandler = this.context.getBean(TestHandshakeHandler.class);
assertThatCsrfToken(handshakeHandler.attributes.get(CsrfToken.class.getName())).isEqualTo(this.token);
assertThat(handshakeHandler.attributes).containsEntry(this.sessionAttr,
request.getSession().getAttribute(this.sessionAttr));
assertThat(handshakeHandler.attributes.get(this.sessionAttr))
.isEqualTo(request.getSession().getAttribute(this.sessionAttr));
}
private HttpRequestHandler handler(HttpServletRequest request) throws Exception {
@@ -633,8 +633,9 @@ public class WebSocketMessageBrokerSecurityConfigurationTests {
public boolean doHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws HandshakeFailureException {
this.attributes = attributes;
if (wsHandler instanceof SockJsWebSocketHandler sockJs) {
if (wsHandler instanceof SockJsWebSocketHandler) {
// work around SPR-12716
SockJsWebSocketHandler sockJs = (SockJsWebSocketHandler) wsHandler;
WebSocketServerSockJsSession session = (WebSocketServerSockJsSession) ReflectionTestUtils
.getField(sockJs, "sockJsSession");
this.attributes = session.getAttributes();

View File

@@ -181,7 +181,7 @@ public class SpringSecurityXsdParser {
*/
private Element elmt(XmlNode n) {
String name = n.attribute("ref");
if (!StringUtils.hasLength(name)) {
if (StringUtils.isEmpty(name)) {
name = n.attribute("name");
}
else {
@@ -201,7 +201,7 @@ public class SpringSecurityXsdParser {
e.getAttrs().forEach((attr) -> attr.setElmt(e));
e.getChildElmts().values().forEach((element) -> element.getParentElmts().put(e.getName(), e));
String subGrpName = n.attribute("substitutionGroup");
if (StringUtils.hasLength(subGrpName)) {
if (!StringUtils.isEmpty(subGrpName)) {
Element subGrp = elmt(findNode(n, subGrpName.split(":")[1]));
subGrp.getSubGrps().add(e);
}

View File

@@ -58,7 +58,8 @@ public class XmlNode {
public Optional<XmlNode> parent() {
// @formatter:off
return Optional.ofNullable(this.node.getParentNode()).map(XmlNode::new);
return Optional.ofNullable(this.node.getParentNode())
.map((parent) -> new XmlNode(parent));
// @formatter:on
}
@@ -66,7 +67,7 @@ public class XmlNode {
// @formatter:off
return Optional.ofNullable(this.node.getAttributes())
.map((attrs) -> attrs.getNamedItem(name))
.map(Node::getTextContent)
.map((attr) -> attr.getTextContent())
.orElse(null);
// @formatter:on
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2021 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.
@@ -65,7 +65,7 @@ public class XsdDocumentedTests {
String schema31xDocumentLocation = "org/springframework/security/config/spring-security-3.1.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.2.xsd";
String schemaDocumentLocation = "org/springframework/security/config/spring-security-6.0.xsd";
XmlSupport xml = new XmlSupport();
@@ -150,9 +150,8 @@ public class XsdDocumentedTests {
.getParentFile()
.list((dir, name) -> name.endsWith(".xsd"));
// @formatter:on
assertThat(schemas.length)
.withFailMessage("the count is equal to 24, if not then schemaDocument needs updating")
.isEqualTo(24);
assertThat(schemas.length).isEqualTo(22)
.withFailMessage("the count is equal to 22, if not then schemaDocument needs updating");
}
/**

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2018 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.
@@ -63,7 +63,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"
@@ -104,7 +104,7 @@ public class FormLoginBeanDefinitionParserTests {
+ " <meta name=\"author\" content=\"\">\n"
+ " <title>Please sign in</title>\n"
+ " <link href=\"https://maxcdn.bootstrapcdn.com/bootstrap/4.0.0-beta/css/bootstrap.min.css\" rel=\"stylesheet\" integrity=\"sha384-/Y6pD6FV/Vv2HJnA6t+vslU6fwYXjCFtcEpHbNJ0lyAFsXTsjBbfaDjzALeQsN6M\" crossorigin=\"anonymous\">\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" integrity=\"sha384-oOE/3m0LUMPub4kaC09mrdEhIc+e3exm4xOGxAmuFXhBNF4hcg/6MiAXAf5p0P56\" crossorigin=\"anonymous\"/>\n"
+ " <link href=\"https://getbootstrap.com/docs/4.0/examples/signin/signin.css\" rel=\"stylesheet\" crossorigin=\"anonymous\"/>\n"
+ " </head>\n"
+ " <body>\n"
+ " <div class=\"container\">\n"

View File

@@ -417,7 +417,7 @@ public class MiscHttpConfigTests {
this.spring.configLocations(xml("DeleteCookies")).autowire();
MvcResult result = this.mvc.perform(post("/logout").with(csrf())).andReturn();
List<String> values = result.getResponse().getHeaders("Set-Cookie");
assertThat(values).hasSize(2);
assertThat(values.size()).isEqualTo(2);
assertThat(values).extracting((value) -> value.split("=")[0]).contains("JSESSIONID", "mycookie");
}

View File

@@ -1,475 +0,0 @@
/*
* Copyright 2002-2023 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.http;
import java.time.Duration;
import java.time.Instant;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.function.Consumer;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.oauth2.client.AuthorizationCodeOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.ClientAuthorizationRequiredException;
import org.springframework.security.oauth2.client.ClientCredentialsOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.JwtBearerOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.OAuth2AuthorizationContext;
import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.PasswordOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.RefreshTokenOAuth2AuthorizedClientProvider;
import org.springframework.security.oauth2.client.endpoint.AbstractOAuth2AuthorizationGrantRequest;
import org.springframework.security.oauth2.client.endpoint.JwtBearerGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2PasswordGrantRequest;
import org.springframework.security.oauth2.client.endpoint.OAuth2RefreshTokenGrantRequest;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager;
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.AuthorizationGrantType;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.OAuth2AuthorizationException;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.TestOAuth2RefreshTokens;
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
import org.springframework.security.oauth2.core.endpoint.TestOAuth2AccessTokenResponses;
import org.springframework.security.oauth2.jwt.JoseHeaderNames;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.util.StringUtils;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link OAuth2AuthorizedClientManagerRegistrar}.
*
* @author Steve Riesenberg
*/
public class OAuth2AuthorizedClientManagerRegistrarTests {
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/OAuth2AuthorizedClientManagerRegistrarTests";
private static OAuth2AccessTokenResponseClient<? super AbstractOAuth2AuthorizationGrantRequest> MOCK_RESPONSE_CLIENT;
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
private OAuth2AuthorizedClientManager authorizedClientManager;
@Autowired
private ClientRegistrationRepository clientRegistrationRepository;
@Autowired
private OAuth2AuthorizedClientRepository authorizedClientRepository;
@Autowired(required = false)
private AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider;
private MockHttpServletRequest request;
private MockHttpServletResponse response;
@BeforeEach
@SuppressWarnings("unchecked")
public void setUp() {
MOCK_RESPONSE_CLIENT = mock(OAuth2AccessTokenResponseClient.class);
this.request = new MockHttpServletRequest();
this.response = new MockHttpServletResponse();
}
@Test
public void loadContextWhenOAuth2ClientEnabledThenConfigured() {
this.spring.configLocations(xml("minimal")).autowire();
assertThat(this.authorizedClientManager).isNotNull();
}
@Test
public void authorizeWhenAuthorizationCodeAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId("google")
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
assertThatExceptionOfType(ClientAuthorizationRequiredException.class)
.isThrownBy(() -> this.authorizedClientManager.authorize(authorizeRequest))
.extracting(OAuth2AuthorizationException::getError)
.extracting(OAuth2Error::getErrorCode)
.isEqualTo("client_authorization_required");
// @formatter:on
verify(this.authorizationCodeAuthorizedClientProvider).authorize(any(OAuth2AuthorizationContext.class));
}
@Test
public void authorizeWhenRefreshTokenAccessTokenResponseClientBeanThenUsed() {
this.spring.configLocations(xml("clients")).autowire();
testRefreshTokenGrant();
}
@Test
public void authorizeWhenRefreshTokenAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
testRefreshTokenGrant();
}
private void testRefreshTokenGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2RefreshTokenGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
OAuth2AuthorizedClient existingAuthorizedClient = new OAuth2AuthorizedClient(clientRegistration,
authentication.getName(), getExpiredAccessToken(), TestOAuth2RefreshTokens.refreshToken());
this.authorizedClientRepository.saveAuthorizedClient(existingAuthorizedClient, authentication, this.request,
this.response);
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withAuthorizedClient(existingAuthorizedClient)
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2RefreshTokenGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2RefreshTokenGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2RefreshTokenGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.REFRESH_TOKEN);
assertThat(grantRequest.getAccessToken()).isEqualTo(existingAuthorizedClient.getAccessToken());
assertThat(grantRequest.getRefreshToken()).isEqualTo(existingAuthorizedClient.getRefreshToken());
}
@Test
public void authorizeWhenClientCredentialsAccessTokenResponseClientBeanThenUsed() {
this.spring.configLocations(xml("clients")).autowire();
testClientCredentialsGrant();
}
@Test
public void authorizeWhenClientCredentialsAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
testClientCredentialsGrant();
}
private void testClientCredentialsGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2ClientCredentialsGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", null);
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("github");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2ClientCredentialsGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2ClientCredentialsGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2ClientCredentialsGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.CLIENT_CREDENTIALS);
}
@Test
public void authorizeWhenPasswordAccessTokenResponseClientBeanThenUsed() {
this.spring.configLocations(xml("clients")).autowire();
testPasswordGrant();
}
@Test
public void authorizeWhenPasswordAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
testPasswordGrant();
}
private void testPasswordGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(OAuth2PasswordGrantRequest.class)))
.willReturn(accessTokenResponse);
TestingAuthenticationToken authentication = new TestingAuthenticationToken("user", "password");
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("facebook");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
this.request.setParameter(OAuth2ParameterNames.USERNAME, "user");
this.request.setParameter(OAuth2ParameterNames.PASSWORD, "password");
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<OAuth2PasswordGrantRequest> grantRequestCaptor = ArgumentCaptor
.forClass(OAuth2PasswordGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
OAuth2PasswordGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.PASSWORD);
assertThat(grantRequest.getUsername()).isEqualTo("user");
assertThat(grantRequest.getPassword()).isEqualTo("password");
}
@Test
public void authorizeWhenJwtBearerAccessTokenResponseClientBeanThenUsed() {
this.spring.configLocations(xml("clients")).autowire();
testJwtBearerGrant();
}
@Test
public void authorizeWhenJwtBearerAuthorizedClientProviderBeanThenUsed() {
this.spring.configLocations(xml("providers")).autowire();
testJwtBearerGrant();
}
private void testJwtBearerGrant() {
OAuth2AccessTokenResponse accessTokenResponse = TestOAuth2AccessTokenResponses.accessTokenResponse().build();
given(MOCK_RESPONSE_CLIENT.getTokenResponse(any(JwtBearerGrantRequest.class))).willReturn(accessTokenResponse);
JwtAuthenticationToken authentication = new JwtAuthenticationToken(getJwt());
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("okta");
// @formatter:off
OAuth2AuthorizeRequest authorizeRequest = OAuth2AuthorizeRequest
.withClientRegistrationId(clientRegistration.getRegistrationId())
.principal(authentication)
.attribute(HttpServletRequest.class.getName(), this.request)
.attribute(HttpServletResponse.class.getName(), this.response)
.build();
// @formatter:on
OAuth2AuthorizedClient authorizedClient = this.authorizedClientManager.authorize(authorizeRequest);
assertThat(authorizedClient).isNotNull();
ArgumentCaptor<JwtBearerGrantRequest> grantRequestCaptor = ArgumentCaptor.forClass(JwtBearerGrantRequest.class);
verify(MOCK_RESPONSE_CLIENT).getTokenResponse(grantRequestCaptor.capture());
JwtBearerGrantRequest grantRequest = grantRequestCaptor.getValue();
assertThat(grantRequest.getClientRegistration().getRegistrationId())
.isEqualTo(clientRegistration.getRegistrationId());
assertThat(grantRequest.getGrantType()).isEqualTo(AuthorizationGrantType.JWT_BEARER);
assertThat(grantRequest.getJwt().getSubject()).isEqualTo("user");
}
private static OAuth2AccessToken getExpiredAccessToken() {
Instant expiresAt = Instant.now().minusSeconds(60);
Instant issuedAt = expiresAt.minus(Duration.ofDays(1));
return new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, "scopes", issuedAt, expiresAt,
new HashSet<>(Arrays.asList("read", "write")));
}
private static Jwt getJwt() {
Instant issuedAt = Instant.now();
return new Jwt("token", issuedAt, issuedAt.plusSeconds(300),
Collections.singletonMap(JoseHeaderNames.ALG, "RS256"),
Collections.singletonMap(JwtClaimNames.SUB, "user"));
}
private static String xml(String configName) {
return CONFIG_LOCATION_PREFIX + "-" + configName + ".xml";
}
public static List<ClientRegistration> getClientRegistrations() {
// @formatter:off
return Arrays.asList(
CommonOAuth2Provider.GOOGLE.getBuilder("google")
.clientId("google-client-id")
.clientSecret("google-client-secret")
.authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE)
.build(),
CommonOAuth2Provider.GITHUB.getBuilder("github")
.clientId("github-client-id")
.clientSecret("github-client-secret")
.authorizationGrantType(AuthorizationGrantType.CLIENT_CREDENTIALS)
.build(),
CommonOAuth2Provider.FACEBOOK.getBuilder("facebook")
.clientId("facebook-client-id")
.clientSecret("facebook-client-secret")
.authorizationGrantType(AuthorizationGrantType.PASSWORD)
.build(),
CommonOAuth2Provider.OKTA.getBuilder("okta")
.clientId("okta-client-id")
.clientSecret("okta-client-secret")
.authorizationGrantType(AuthorizationGrantType.JWT_BEARER)
.build());
// @formatter:on
}
public static Consumer<DefaultOAuth2AuthorizedClientManager> authorizedClientManagerConsumer() {
return (authorizedClientManager) -> authorizedClientManager.setContextAttributesMapper((authorizeRequest) -> {
HttpServletRequest request = Objects
.requireNonNull(authorizeRequest.getAttribute(HttpServletRequest.class.getName()));
String username = request.getParameter(OAuth2ParameterNames.USERNAME);
String password = request.getParameter(OAuth2ParameterNames.PASSWORD);
Map<String, Object> attributes = Collections.emptyMap();
if (StringUtils.hasText(username) && StringUtils.hasText(password)) {
attributes = new HashMap<>();
attributes.put(OAuth2AuthorizationContext.USERNAME_ATTRIBUTE_NAME, username);
attributes.put(OAuth2AuthorizationContext.PASSWORD_ATTRIBUTE_NAME, password);
}
return attributes;
});
}
public static AuthorizationCodeOAuth2AuthorizedClientProvider authorizationCodeAuthorizedClientProvider() {
return spy(new AuthorizationCodeOAuth2AuthorizedClientProvider());
}
public static RefreshTokenOAuth2AuthorizedClientProvider refreshTokenAuthorizedClientProvider() {
RefreshTokenOAuth2AuthorizedClientProvider authorizedClientProvider = new RefreshTokenOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(refreshTokenAccessTokenResponseClient());
return authorizedClientProvider;
}
public static MockRefreshTokenClient refreshTokenAccessTokenResponseClient() {
return new MockRefreshTokenClient();
}
public static ClientCredentialsOAuth2AuthorizedClientProvider clientCredentialsAuthorizedClientProvider() {
ClientCredentialsOAuth2AuthorizedClientProvider authorizedClientProvider = new ClientCredentialsOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(clientCredentialsAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> clientCredentialsAccessTokenResponseClient() {
return new MockClientCredentialsClient();
}
public static PasswordOAuth2AuthorizedClientProvider passwordAuthorizedClientProvider() {
PasswordOAuth2AuthorizedClientProvider authorizedClientProvider = new PasswordOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(passwordAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> passwordAccessTokenResponseClient() {
return new MockPasswordClient();
}
public static JwtBearerOAuth2AuthorizedClientProvider jwtBearerAuthorizedClientProvider() {
JwtBearerOAuth2AuthorizedClientProvider authorizedClientProvider = new JwtBearerOAuth2AuthorizedClientProvider();
authorizedClientProvider.setAccessTokenResponseClient(jwtBearerAccessTokenResponseClient());
return authorizedClientProvider;
}
public static OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> jwtBearerAccessTokenResponseClient() {
return new MockJwtBearerClient();
}
private static class MockAuthorizationCodeClient
implements OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2AuthorizationCodeGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockRefreshTokenClient
implements OAuth2AccessTokenResponseClient<OAuth2RefreshTokenGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2RefreshTokenGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockClientCredentialsClient
implements OAuth2AccessTokenResponseClient<OAuth2ClientCredentialsGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(
OAuth2ClientCredentialsGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockPasswordClient implements OAuth2AccessTokenResponseClient<OAuth2PasswordGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(OAuth2PasswordGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
private static class MockJwtBearerClient implements OAuth2AccessTokenResponseClient<JwtBearerGrantRequest> {
@Override
public OAuth2AccessTokenResponse getTokenResponse(JwtBearerGrantRequest authorizationGrantRequest) {
return MOCK_RESPONSE_CLIENT.getTokenResponse(authorizationGrantRequest);
}
}
}

View File

@@ -24,9 +24,6 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
@@ -222,12 +219,8 @@ public class OAuth2ClientBeanDefinitionParserTests {
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google");
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user",
TestOAuth2AccessTokens.noScopes());
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response);
this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession()))
.andExpect(status().isOk())
.andExpect(content().string("resolved"));
given(this.authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(authorizedClient);
this.mvc.perform(get("/authorized-client")).andExpect(status().isOk()).andExpect(content().string("resolved"));
}
private static OAuth2AuthorizationRequest createAuthorizationRequest(ClientRegistration clientRegistration) {

View File

@@ -27,9 +27,6 @@ import org.mockito.ArgumentCaptor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.mock.web.MockHttpSession;
import org.springframework.security.authentication.event.AuthenticationSuccessEvent;
import org.springframework.security.config.test.SpringTestContext;
import org.springframework.security.config.test.SpringTestContextExtension;
@@ -536,11 +533,9 @@ public class OAuth2LoginBeanDefinitionParserTests {
ClientRegistration clientRegistration = this.clientRegistrationRepository.findByRegistrationId("google-login");
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, "user",
TestOAuth2AccessTokens.noScopes());
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
this.authorizedClientRepository.saveAuthorizedClient(authorizedClient, null, request, response);
given(this.authorizedClientRepository.loadAuthorizedClient(any(), any(), any())).willReturn(authorizedClient);
// @formatter:off
this.mvc.perform(get("/authorized-client").session((MockHttpSession) request.getSession()))
this.mvc.perform(get("/authorized-client"))
.andExpect(status().isOk())
.andExpect(content().string("resolved"));
// @formatter:on
@@ -555,7 +550,7 @@ public class OAuth2LoginBeanDefinitionParserTests {
@GetMapping("/authorized-client")
String authorizedClient(Model model,
@RegisteredOAuth2AuthorizedClient("google-login") OAuth2AuthorizedClient authorizedClient) {
@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient) {
return (authorizedClient != null) ? "resolved" : "not-resolved";
}

View File

@@ -16,20 +16,11 @@
package org.springframework.security.config.http;
import java.nio.charset.StandardCharsets;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import net.shibboleth.utilities.java.support.xml.SerializeSupport;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.opensaml.core.xml.config.XMLObjectProviderRegistrySupport;
import org.opensaml.core.xml.io.Marshaller;
import org.opensaml.saml.saml2.core.Assertion;
import org.opensaml.saml.saml2.core.Response;
import org.w3c.dom.Element;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
@@ -42,7 +33,6 @@ import org.springframework.security.config.test.SpringTestContextExtension;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.saml2.core.OpenSamlInitializationService;
import org.springframework.security.saml2.core.Saml2ParameterNames;
import org.springframework.security.saml2.core.Saml2Utils;
import org.springframework.security.saml2.core.TestSaml2X509Credentials;
@@ -51,7 +41,6 @@ import org.springframework.security.saml2.provider.service.authentication.Saml2A
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationException;
import org.springframework.security.saml2.provider.service.authentication.Saml2AuthenticationToken;
import org.springframework.security.saml2.provider.service.authentication.Saml2RedirectAuthenticationRequest;
import org.springframework.security.saml2.provider.service.authentication.TestOpenSamlObjects;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistration;
import org.springframework.security.saml2.provider.service.registration.RelyingPartyRegistrationRepository;
import org.springframework.security.saml2.provider.service.registration.TestRelyingPartyRegistrations;
@@ -90,19 +79,9 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
@SecurityTestExecutionListeners
public class Saml2LoginBeanDefinitionParserTests {
static {
OpenSamlInitializationService.initialize();
}
private static final String CONFIG_LOCATION_PREFIX = "classpath:org/springframework/security/config/http/Saml2LoginBeanDefinitionParserTests";
private static final RelyingPartyRegistration registration = TestRelyingPartyRegistrations.noCredentials()
.signingX509Credentials((c) -> c.add(TestSaml2X509Credentials.assertingPartySigningCredential()))
.assertingPartyDetails((party) -> party
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
private static String SIGNED_RESPONSE;
private static final String SIGNED_RESPONSE = "PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0iVVRGLTgiPz48c2FtbDJwOlJlc3BvbnNlIHhtbG5zOnNhbWwycD0idXJuOm9hc2lzOm5hbWVzOnRjOlNBTUw6Mi4wOnByb3RvY29sIiBEZXN0aW5hdGlvbj0iaHR0cHM6Ly9ycC5leGFtcGxlLm9yZy9hY3MiIElEPSJfYzE3MzM2YTAtNTM1My00MTQ5LWI3MmMtMDNkOWY5YWYzMDdlIiBJc3N1ZUluc3RhbnQ9IjIwMjAtMDgtMDRUMjI6MDQ6NDUuMDE2WiIgVmVyc2lvbj0iMi4wIj48c2FtbDI6SXNzdWVyIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48ZHM6U2lnbmF0dXJlIHhtbG5zOmRzPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwLzA5L3htbGRzaWcjIj4KPGRzOlNpZ25lZEluZm8+CjxkczpDYW5vbmljYWxpemF0aW9uTWV0aG9kIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMS8xMC94bWwtZXhjLWMxNG4jIi8+CjxkczpTaWduYXR1cmVNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGRzaWctbW9yZSNyc2Etc2hhMjU2Ii8+CjxkczpSZWZlcmVuY2UgVVJJPSIjX2MxNzMzNmEwLTUzNTMtNDE0OS1iNzJjLTAzZDlmOWFmMzA3ZSI+CjxkczpUcmFuc2Zvcm1zPgo8ZHM6VHJhbnNmb3JtIEFsZ29yaXRobT0iaHR0cDovL3d3dy53My5vcmcvMjAwMC8wOS94bWxkc2lnI2VudmVsb3BlZC1zaWduYXR1cmUiLz4KPGRzOlRyYW5zZm9ybSBBbGdvcml0aG09Imh0dHA6Ly93d3cudzMub3JnLzIwMDEvMTAveG1sLWV4Yy1jMTRuIyIvPgo8L2RzOlRyYW5zZm9ybXM+CjxkczpEaWdlc3RNZXRob2QgQWxnb3JpdGhtPSJodHRwOi8vd3d3LnczLm9yZy8yMDAxLzA0L3htbGVuYyNzaGEyNTYiLz4KPGRzOkRpZ2VzdFZhbHVlPjYzTmlyenFzaDVVa0h1a3NuRWUrM0hWWU5aYWFsQW1OQXFMc1lGMlRuRDA9PC9kczpEaWdlc3RWYWx1ZT4KPC9kczpSZWZlcmVuY2U+CjwvZHM6U2lnbmVkSW5mbz4KPGRzOlNpZ25hdHVyZVZhbHVlPgpLMVlvWWJVUjBTclY4RTdVMkhxTTIvZUNTOTNoV25mOExnNnozeGZWMUlyalgzSXhWYkNvMVlYcnRBSGRwRVdvYTJKKzVOMmFNbFBHJiMxMzsKN2VpbDBZRC9xdUVRamRYbTNwQTBjZmEvY25pa2RuKzVhbnM0ZWQwanU1amo2dkpvZ2w2Smt4Q25LWUpwTU9HNzhtampmb0phengrWCYjMTM7CkM2NktQVStBYUdxeGVwUEQ1ZlhRdTFKSy9Jb3lBaitaa3k4Z2Jwc3VyZHFCSEJLRWxjdnVOWS92UGY0OGtBeFZBKzdtRGhNNUMvL1AmIzEzOwp0L084Y3NZYXB2UjZjdjZrdk45QXZ1N3FRdm9qVk1McHVxZWNJZDJwTUVYb0NSSnE2Nkd4MStNTUVPeHVpMWZZQlRoMEhhYjRmK3JyJiMxMzsKOEY2V1NFRC8xZllVeHliRkJqZ1Q4d2lEWHFBRU8wSVY4ZWRQeEE9PQo8L2RzOlNpZ25hdHVyZVZhbHVlPgo8L2RzOlNpZ25hdHVyZT48c2FtbDI6QXNzZXJ0aW9uIHhtbG5zOnNhbWwyPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6YXNzZXJ0aW9uIiBJRD0iQWUzZjQ5OGI4LTliMTctNDA3OC05ZDM1LTg2YTA4NDA4NDk5NSIgSXNzdWVJbnN0YW50PSIyMDIwLTA4LTA0VDIyOjA0OjQ1LjA3N1oiIFZlcnNpb249IjIuMCI+PHNhbWwyOklzc3Vlcj5hcC1lbnRpdHktaWQ8L3NhbWwyOklzc3Vlcj48c2FtbDI6U3ViamVjdD48c2FtbDI6TmFtZUlEPnRlc3RAc2FtbC51c2VyPC9zYW1sMjpOYW1lSUQ+PHNhbWwyOlN1YmplY3RDb25maXJtYXRpb24gTWV0aG9kPSJ1cm46b2FzaXM6bmFtZXM6dGM6U0FNTDoyLjA6Y206YmVhcmVyIj48c2FtbDI6U3ViamVjdENvbmZpcm1hdGlvbkRhdGEgTm90QmVmb3JlPSIyMDIwLTA4LTA0VDIxOjU5OjQ1LjA5MFoiIE5vdE9uT3JBZnRlcj0iMjA0MC0wNy0zMFQyMjowNTowNi4wODhaIiBSZWNpcGllbnQ9Imh0dHBzOi8vcnAuZXhhbXBsZS5vcmcvYWNzIi8+PC9zYW1sMjpTdWJqZWN0Q29uZmlybWF0aW9uPjwvc2FtbDI6U3ViamVjdD48c2FtbDI6Q29uZGl0aW9ucyBOb3RCZWZvcmU9IjIwMjAtMDgtMDRUMjE6NTk6NDUuMDgwWiIgTm90T25PckFmdGVyPSIyMDQwLTA3LTMwVDIyOjA1OjA2LjA4N1oiLz48L3NhbWwyOkFzc2VydGlvbj48L3NhbWwycDpSZXNwb25zZT4=";
private static final String IDP_SSO_URL = "https://sso-url.example.com/IDP/SSO";
@@ -138,23 +117,6 @@ public class Saml2LoginBeanDefinitionParserTests {
@Autowired
private MockMvc mvc;
@BeforeAll
static void createResponse() throws Exception {
String destination = registration.getAssertionConsumerServiceLocation();
String assertingPartyEntityId = registration.getAssertingPartyDetails().getEntityId();
String relyingPartyEntityId = registration.getEntityId();
Response response = TestOpenSamlObjects.response(destination, assertingPartyEntityId);
Assertion assertion = TestOpenSamlObjects.assertion("test@saml.user", assertingPartyEntityId,
relyingPartyEntityId, destination);
response.getAssertions().add(assertion);
Response signed = TestOpenSamlObjects.signed(response,
registration.getSigningX509Credentials().iterator().next(), relyingPartyEntityId);
Marshaller marshaller = XMLObjectProviderRegistrySupport.getMarshallerFactory().getMarshaller(signed);
Element element = marshaller.marshall(signed);
String serialized = SerializeSupport.nodeToString(element);
SIGNED_RESPONSE = Saml2Utils.samlEncode(serialized.getBytes(StandardCharsets.UTF_8));
}
@Test
public void requestWhenSingleRelyingPartyRegistrationThenAutoRedirect() throws Exception {
this.spring.configLocations(this.xml("SingleRelyingPartyRegistration")).autowire();
@@ -336,9 +298,13 @@ public class Saml2LoginBeanDefinitionParserTests {
throws Exception {
this.spring.configLocations(this.xml("WithCustomLoginProcessingUrl-WithCustomAuthenticationConverter"))
.autowire();
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
String response = new String(Saml2Utils.samlDecode(SIGNED_RESPONSE));
given(this.authenticationConverter.convert(any(HttpServletRequest.class)))
.willReturn(new Saml2AuthenticationToken(registration, response));
.willReturn(new Saml2AuthenticationToken(relyingPartyRegistration, response));
// @formatter:off
MockHttpServletRequestBuilder request = post("/my/custom/url").param("SAMLResponse", SIGNED_RESPONSE);
// @formatter:on
@@ -347,8 +313,12 @@ public class Saml2LoginBeanDefinitionParserTests {
}
private RelyingPartyRegistration relyingPartyRegistrationWithVerifyingCredential() {
given(this.repository.findByRegistrationId(anyString())).willReturn(registration);
return registration;
RelyingPartyRegistration relyingPartyRegistration = TestRelyingPartyRegistrations.noCredentials()
.assertingPartyDetails((party) -> party
.verificationX509Credentials((c) -> c.add(TestSaml2X509Credentials.relyingPartyVerifyingCredential())))
.build();
given(this.repository.findByRegistrationId(anyString())).willReturn(relyingPartyRegistration);
return relyingPartyRegistration;
}
private String xml(String configName) {

View File

@@ -31,7 +31,7 @@ public class SpringTestContextExtension implements BeforeEachCallback, AfterEach
@Override
public void afterEach(ExtensionContext context) throws Exception {
TestSecurityContextHolder.clearContext();
getContexts(context.getRequiredTestInstance()).forEach(SpringTestContext::close);
getContexts(context.getRequiredTestInstance()).forEach((springTestContext) -> springTestContext.close());
}
@Override

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2020 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.
@@ -39,7 +39,6 @@ import org.springframework.security.oauth2.client.registration.InMemoryReactiveC
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizedClientRepository;
import org.springframework.security.oauth2.core.OAuth2AccessToken;
import org.springframework.security.oauth2.core.TestOAuth2AccessTokens;
@@ -135,7 +134,6 @@ public class OAuth2ClientSpecTests {
ServerAuthenticationConverter converter = config.authenticationConverter;
ReactiveAuthenticationManager manager = config.manager;
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = config.authorizationRequestRepository;
ServerOAuth2AuthorizationRequestResolver resolver = config.resolver;
ServerRequestCache requestCache = config.requestCache;
OAuth2AuthorizationRequest authorizationRequest = TestOAuth2AuthorizationRequests.request()
.redirectUri("/authorize/oauth2/code/registration-id")
@@ -150,7 +148,6 @@ public class OAuth2ClientSpecTests {
this.registration, authorizationExchange, accessToken);
given(authorizationRequestRepository.loadAuthorizationRequest(any()))
.willReturn(Mono.just(authorizationRequest));
given(resolver.resolve(any())).willReturn(Mono.empty());
given(converter.convert(any())).willReturn(Mono.just(new TestingAuthenticationToken("a", "b", "c")));
given(manager.authenticate(any())).willReturn(Mono.just(result));
given(requestCache.getRedirectUri(any())).willReturn(Mono.just(URI.create("/saved-request")));
@@ -168,7 +165,6 @@ public class OAuth2ClientSpecTests {
verify(converter).convert(any());
verify(manager).authenticate(any());
verify(requestCache).getRedirectUri(any());
verify(resolver).resolve(any());
}
@Test
@@ -275,8 +271,6 @@ public class OAuth2ClientSpecTests {
ServerAuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = mock(
ServerAuthorizationRequestRepository.class);
ServerOAuth2AuthorizationRequestResolver resolver = mock(ServerOAuth2AuthorizationRequestResolver.class);
ServerRequestCache requestCache = mock(ServerRequestCache.class);
@Bean
@@ -287,7 +281,6 @@ public class OAuth2ClientSpecTests {
.authenticationConverter(this.authenticationConverter)
.authenticationManager(this.manager)
.authorizationRequestRepository(this.authorizationRequestRepository)
.authorizationRequestResolver(this.resolver)
.and()
.requestCache((c) -> c.requestCache(this.requestCache));
// @formatter:on

View File

@@ -1,685 +0,0 @@
/*
* Copyright 2002-2021 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.web.server;
import java.io.IOException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.interfaces.RSAPublicKey;
import java.time.Duration;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import com.gargoylesoftware.htmlunit.util.UrlUtils;
import com.nimbusds.jose.jwk.JWKSet;
import com.nimbusds.jose.jwk.RSAKey;
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
import com.nimbusds.jose.jwk.source.JWKSource;
import com.nimbusds.jose.proc.SecurityContext;
import com.nimbusds.oauth2.sdk.Scope;
import com.nimbusds.oauth2.sdk.token.BearerAccessToken;
import com.nimbusds.openid.connect.sdk.token.OIDCTokens;
import jakarta.annotation.PreDestroy;
import okhttp3.mockwebserver.Dispatcher;
import okhttp3.mockwebserver.MockResponse;
import okhttp3.mockwebserver.MockWebServer;
import okhttp3.mockwebserver.RecordedRequest;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import reactor.core.publisher.Mono;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.annotation.Order;
import org.springframework.http.ResponseCookie;
import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.security.authentication.TestingAuthenticationToken;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
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.userdetails.MapReactiveUserDetailsService;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.oauth2.client.oidc.authentication.logout.LogoutTokenClaimNames;
import org.springframework.security.oauth2.client.oidc.authentication.logout.OidcLogoutToken;
import org.springframework.security.oauth2.client.oidc.authentication.logout.TestOidcLogoutTokens;
import org.springframework.security.oauth2.client.oidc.server.session.InMemoryReactiveOidcSessionRegistry;
import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry;
import org.springframework.security.oauth2.client.registration.ClientRegistration;
import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
import org.springframework.security.oauth2.core.oidc.OidcIdToken;
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
import org.springframework.security.oauth2.jwt.JwtEncoder;
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.logout.ServerLogoutHandler;
import org.springframework.security.web.server.util.matcher.OrServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.PathPatternParserServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatcher;
import org.springframework.test.web.reactive.server.FluxExchangeResult;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.test.web.reactive.server.WebTestClientConfigurer;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.ConfigurableWebApplicationContext;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.server.WebSession;
import org.springframework.web.server.adapter.WebHttpHandlerBuilder;
import static org.hamcrest.Matchers.containsString;
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.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.mockAuthentication;
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.springSecurity;
/**
* Tests for {@link ServerHttpSecurity.OAuth2ResourceServerSpec}
*/
@ExtendWith({ SpringTestContextExtension.class })
public class OidcLogoutSpecTests {
private static final String SESSION_COOKIE_NAME = "SESSION";
private WebTestClient test;
@Autowired(required = false)
private MockWebServer web;
@Autowired
private ClientRegistration clientRegistration;
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
public void setApplicationContext(ApplicationContext context) {
this.test = WebTestClient.bindToApplicationContext(context)
.apply(springSecurity())
.configureClient()
.responseTimeout(Duration.ofDays(1))
.build();
if (context instanceof ConfigurableWebApplicationContext configurable) {
configurable.getBeanFactory().registerResolvableDependency(WebTestClient.class, this.test);
}
}
@Test
void logoutWhenDefaultsThenRemotelyInvalidatesSessions() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String session = login();
String logoutToken = this.test.mutateWith(session(session))
.get()
.uri("/token/logout")
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isOk();
this.test.mutateWith(session(session)).get().uri("/token/logout").exchange().expectStatus().isUnauthorized();
}
@Test
void logoutWhenInvalidLogoutTokenThenBadRequest() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized();
String registrationId = this.clientRegistration.getRegistrationId();
FluxExchangeResult<String> result = this.test.get()
.uri("/oauth2/authorization/" + registrationId)
.exchange()
.expectStatus()
.isFound()
.returnResult(String.class);
String session = sessionId(result);
String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString());
String state = this.test
.mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(),
this.clientRegistration.getClientSecret(), "APP")))
.get()
.uri(redirectUrl)
.exchange()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
result = this.test.get()
.uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state)
.cookie("SESSION", session)
.exchange()
.expectStatus()
.isFound()
.returnResult(String.class);
session = sessionId(result);
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", "invalid"))
.exchange()
.expectStatus()
.isBadRequest();
this.test.get().uri("/token/logout").cookie("SESSION", session).exchange().expectStatus().isOk();
}
@Test
void logoutWhenLogoutTokenSpecifiesOneSessionThenRemotelyInvalidatesOnlyThatSession() throws Exception {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, DefaultConfig.class).autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String one = login();
String two = login();
String three = login();
String logoutToken = this.test.get()
.uri("/token/logout")
.cookie("SESSION", one)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isOk();
this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isUnauthorized();
this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isOk();
logoutToken = this.test.get()
.uri("/token/logout/all")
.cookie("SESSION", three)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isOk();
this.test.get().uri("/token/logout").cookie("SESSION", two).exchange().expectStatus().isUnauthorized();
this.test.get().uri("/token/logout").cookie("SESSION", three).exchange().expectStatus().isUnauthorized();
}
@Test
void logoutWhenRemoteLogoutFailsThenReportsPartialLogout() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithBrokenLogoutConfig.class).autowire();
ServerLogoutHandler logoutHandler = this.spring.getContext().getBean(ServerLogoutHandler.class);
given(logoutHandler.logout(any(), any())).willReturn(Mono.error(() -> new IllegalStateException("illegal")));
String registrationId = this.clientRegistration.getRegistrationId();
String one = login();
String logoutToken = this.test.get()
.uri("/token/logout/all")
.cookie("SESSION", one)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isBadRequest()
.expectBody(String.class)
.value(containsString("partial_logout"));
this.test.get().uri("/token/logout").cookie("SESSION", one).exchange().expectStatus().isOk();
}
@Test
void logoutWhenCustomComponentsThenUses() {
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithCustomComponentsConfig.class)
.autowire();
String registrationId = this.clientRegistration.getRegistrationId();
String sessionId = login();
String logoutToken = this.test.get()
.uri("/token/logout")
.cookie("SESSION", sessionId)
.exchange()
.expectStatus()
.isOk()
.returnResult(String.class)
.getResponseBody()
.blockFirst();
this.test.post()
.uri(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
.body(BodyInserters.fromFormData("logout_token", logoutToken))
.exchange()
.expectStatus()
.isOk();
this.test.get().uri("/token/logout").cookie("SESSION", sessionId).exchange().expectStatus().isUnauthorized();
ReactiveOidcSessionRegistry sessionRegistry = this.spring.getContext()
.getBean(ReactiveOidcSessionRegistry.class);
verify(sessionRegistry, atLeastOnce()).saveSessionInformation(any());
verify(sessionRegistry, atLeastOnce()).removeSessionInformation(any(OidcLogoutToken.class));
}
private String login() {
this.test.get().uri("/token/logout").exchange().expectStatus().isUnauthorized();
String registrationId = this.clientRegistration.getRegistrationId();
FluxExchangeResult<String> result = this.test.get()
.uri("/oauth2/authorization/" + registrationId)
.exchange()
.expectStatus()
.isFound()
.returnResult(String.class);
String sessionId = sessionId(result);
String redirectUrl = UrlUtils.decode(result.getResponseHeaders().getLocation().toString());
result = this.test
.mutateWith(mockAuthentication(new TestingAuthenticationToken(this.clientRegistration.getClientId(),
this.clientRegistration.getClientSecret(), "APP")))
.get()
.uri(redirectUrl)
.exchange()
.returnResult(String.class);
String state = result.getResponseBody().blockFirst();
result = this.test.mutateWith(session(sessionId))
.get()
.uri("/login/oauth2/code/" + registrationId + "?code=code&state=" + state)
.exchange()
.expectStatus()
.isFound()
.returnResult(String.class);
return sessionId(result);
}
private String sessionId(FluxExchangeResult<?> result) {
List<ResponseCookie> cookies = result.getResponseCookies().get(SESSION_COOKIE_NAME);
if (cookies == null || cookies.isEmpty()) {
return null;
}
return cookies.get(0).getValue();
}
static SessionMutator session(String session) {
return new SessionMutator(session);
}
private record SessionMutator(String session) implements WebTestClientConfigurer {
@Override
public void afterConfigurerAdded(WebTestClient.Builder builder, WebHttpHandlerBuilder httpHandlerBuilder,
ClientHttpConnector connector) {
builder.defaultCookie(SESSION_COOKIE_NAME, this.session);
}
}
@Configuration
static class RegistrationConfig {
@Autowired(required = false)
MockWebServer web;
@Bean
ClientRegistration clientRegistration() {
if (this.web == null) {
return TestClientRegistrations.clientRegistration().build();
}
String issuer = this.web.url("/").toString();
return TestClientRegistrations.clientRegistration()
.issuerUri(issuer)
.jwkSetUri(issuer + "jwks")
.tokenUri(issuer + "token")
.userInfoUri(issuer + "user")
.scope("openid")
.build();
}
@Bean
ReactiveClientRegistrationRepository clientRegistrationRepository(ClientRegistration clientRegistration) {
return new InMemoryReactiveClientRegistrationRepository(clientRegistration);
}
}
@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
static class DefaultConfig {
@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
}
@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
static class WithCustomComponentsConfig {
ReactiveOidcSessionRegistry sessionRegistry = spy(new InMemoryReactiveOidcSessionRegistry());
@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2Login((oauth2) -> oauth2.oidcSessionRegistry(this.sessionRegistry))
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean
ReactiveOidcSessionRegistry sessionRegistry() {
return this.sessionRegistry;
}
}
@Configuration
@EnableWebFluxSecurity
@Import(RegistrationConfig.class)
static class WithBrokenLogoutConfig {
private final ServerLogoutHandler logoutHandler = mock(ServerLogoutHandler.class);
@Bean
@Order(1)
SecurityWebFilterChain filters(ServerHttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.logout((logout) -> logout.logoutHandler(this.logoutHandler))
.oauth2Login(Customizer.withDefaults())
.oidcLogout((oidc) -> oidc.backChannel(Customizer.withDefaults()));
// @formatter:on
return http.build();
}
@Bean
ServerLogoutHandler logoutHandler() {
return this.logoutHandler;
}
}
@Configuration
@EnableWebFluxSecurity
@EnableWebFlux
@RestController
static class OidcProviderConfig {
private static final RSAKey key = key();
private static final JWKSource<SecurityContext> jwks = jwks(key);
private static RSAKey key() {
try {
KeyPair pair = KeyPairGenerator.getInstance("RSA").generateKeyPair();
return new RSAKey.Builder((RSAPublicKey) pair.getPublic()).privateKey(pair.getPrivate()).build();
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private static JWKSource<SecurityContext> jwks(RSAKey key) {
try {
return new ImmutableJWKSet<>(new JWKSet(key));
}
catch (Exception ex) {
throw new RuntimeException(ex);
}
}
private final String username = "user";
private final JwtEncoder encoder = new NimbusJwtEncoder(jwks);
private String nonce;
@Autowired
ClientRegistration registration;
static ServerWebExchangeMatcher or(String... patterns) {
List<ServerWebExchangeMatcher> matchers = new ArrayList<>();
for (String pattern : patterns) {
matchers.add(new PathPatternParserServerWebExchangeMatcher(pattern));
}
return new OrServerWebExchangeMatcher(matchers);
}
@Bean
@Order(0)
SecurityWebFilterChain authorizationServer(ServerHttpSecurity http, ClientRegistration registration)
throws Exception {
// @formatter:off
http
.securityMatcher(or("/jwks", "/login/oauth/authorize", "/nonce", "/token", "/token/logout", "/user"))
.authorizeExchange((authorize) -> authorize
.pathMatchers("/jwks").permitAll()
.anyExchange().authenticated()
)
.httpBasic(Customizer.withDefaults())
.oauth2ResourceServer((oauth2) -> oauth2
.jwt((jwt) -> jwt.jwkSetUri(registration.getProviderDetails().getJwkSetUri()))
);
// @formatter:off
return http.build();
}
@Bean
ReactiveUserDetailsService users(ClientRegistration registration) {
return new MapReactiveUserDetailsService(User.withUsername(registration.getClientId())
.password("{noop}" + registration.getClientSecret()).authorities("APP").build());
}
@GetMapping("/login/oauth/authorize")
String nonce(@RequestParam("nonce") String nonce, @RequestParam("state") String state) {
this.nonce = nonce;
return state;
}
@PostMapping("/token")
Map<String, Object> accessToken(WebSession session) {
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().id("id").subject(this.username)
.issuer(this.registration.getProviderDetails().getIssuerUri()).issuedAt(Instant.now())
.expiresAt(Instant.now().plusSeconds(86400)).claim("scope", "openid").build());
String token = this.encoder.encode(parameters).getTokenValue();
return new OIDCTokens(idToken(session.getId()), new BearerAccessToken(token, 86400, new Scope("openid")), null)
.toJSONObject();
}
String idToken(String sessionId) {
OidcIdToken token = TestOidcIdTokens.idToken().issuer(this.registration.getProviderDetails().getIssuerUri())
.subject(this.username).expiresAt(Instant.now().plusSeconds(86400))
.audience(List.of(this.registration.getClientId())).nonce(this.nonce)
.claim(LogoutTokenClaimNames.SID, sessionId).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
@GetMapping("/user")
Map<String, Object> userinfo() {
return Map.of("sub", this.username, "id", this.username);
}
@GetMapping("/jwks")
String jwks() {
return new JWKSet(key).toString();
}
@GetMapping("/token/logout")
String logoutToken(@AuthenticationPrincipal OidcUser user) {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId())).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
@GetMapping("/token/logout/all")
String logoutTokenAll(@AuthenticationPrincipal OidcUser user) {
OidcLogoutToken token = TestOidcLogoutTokens.withUser(user)
.audience(List.of(this.registration.getClientId()))
.claims((claims) -> claims.remove(LogoutTokenClaimNames.SID)).build();
JwtEncoderParameters parameters = JwtEncoderParameters
.from(JwtClaimsSet.builder().claims((claims) -> claims.putAll(token.getClaims())).build());
return this.encoder.encode(parameters).getTokenValue();
}
}
@Configuration
static class WebServerConfig {
private final MockWebServer server = new MockWebServer();
@Bean
MockWebServer web(ObjectProvider<WebTestClient> web) {
this.server.setDispatcher(new WebTestClientDispatcher(web));
return this.server;
}
@PreDestroy
void shutdown() throws IOException {
this.server.shutdown();
}
}
private static class WebTestClientDispatcher extends Dispatcher {
private final ObjectProvider<WebTestClient> webProvider;
private WebTestClient web;
WebTestClientDispatcher(ObjectProvider<WebTestClient> web) {
this.webProvider = web;
}
@Override
public MockResponse dispatch(RecordedRequest request) throws InterruptedException {
this.web = this.webProvider.getObject();
String method = request.getMethod();
String path = request.getPath();
String csrf = request.getHeader("X-CSRF-TOKEN");
String sessionId = session(request);
WebTestClient.RequestHeadersSpec<?> r;
if ("GET".equals(method)) {
r = this.web.get().uri(path);
}
else {
WebTestClient.RequestBodySpec body;
if (csrf == null) {
body = this.web.mutateWith(csrf()).post().uri(path);
}
else {
body = this.web.post().uri(path).header("X-CSRF-TOKEN", csrf);
}
body.body(BodyInserters.fromValue(request.getBody().readUtf8()));
r = body;
}
for (Map.Entry<String, List<String>> header : request.getHeaders().toMultimap().entrySet()) {
if (header.getKey().equalsIgnoreCase("Cookie")) {
continue;
}
r.header(header.getKey(), header.getValue().iterator().next());
}
if (sessionId != null) {
r.cookie(SESSION_COOKIE_NAME, sessionId);
}
try {
FluxExchangeResult<String> result = r.exchange().returnResult(String.class);
return toMockResponse(result);
}
catch (Exception ex) {
MockResponse response = new MockResponse();
response.setResponseCode(500);
response.setBody(ex.getMessage());
return response;
}
}
private String session(RecordedRequest request) {
String cookieHeaderValue = request.getHeader("Cookie");
if (cookieHeaderValue == null) {
return null;
}
String[] cookies = cookieHeaderValue.split(";");
for (String cookie : cookies) {
String[] parts = cookie.split("=");
if (SESSION_COOKIE_NAME.equals(parts[0])) {
return parts[1];
}
}
return null;
}
private MockResponse toMockResponse(FluxExchangeResult<String> result) {
MockResponse response = new MockResponse();
response.setResponseCode(result.getStatus().value());
for (String name : result.getResponseHeaders().keySet()) {
response.addHeader(name, result.getResponseHeaders().getFirst(name));
}
String body = result.getResponseBody().blockFirst();
if (body != null) {
response.setBody(body);
}
return response;
}
}
}

View File

@@ -746,7 +746,7 @@ public class ServerHttpSecurityTests {
private <T extends WebFilter> Optional<T> getWebFilter(SecurityWebFilterChain filterChain, Class<T> filterClass) {
return (Optional<T>) filterChain.getWebFilters()
.filter(Objects::nonNull)
.filter((filter) -> filterClass.isAssignableFrom(filter.getClass()))
.filter((filter) -> filter.getClass().isAssignableFrom(filterClass))
.singleOrEmpty()
.blockOptional();
}