Revert unnecessary merges on 6.0.x
This commit removes unnecessary main-branch merges starting from8750608b5band adds the following needed commit(s) that were made afterward: -5dce82c48b
This commit is contained in:
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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() {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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> {
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -382,7 +382,7 @@ public class RequestCacheConfigurerTests {
|
||||
.anyRequest().authenticated()
|
||||
)
|
||||
.formLogin(Customizer.withDefaults())
|
||||
.requestCache(RequestCacheConfigurer::disable);
|
||||
.requestCache((cache) -> cache.disable());
|
||||
// @formatter:on
|
||||
return http.build();
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -556,7 +556,9 @@ public class SessionManagementConfigurerTests {
|
||||
.sessionManagement((sessionManagement) ->
|
||||
sessionManagement
|
||||
.requireExplicitAuthenticationStrategy(false)
|
||||
.sessionFixation(SessionManagementConfigurer.SessionFixationConfigurer::newSession)
|
||||
.sessionFixation((sessionFixation) ->
|
||||
sessionFixation.newSession()
|
||||
)
|
||||
)
|
||||
.httpBasic(withDefaults());
|
||||
// @formatter:on
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user