1
0
mirror of synced 2026-08-05 17:57:15 +00:00

Merge Update servlet/architecture.adoc to use include-code

Update servlet/architecture.adoc to use include-code
This commit is contained in:
Rob Winch
2026-02-23 17:16:28 -06:00
committed by GitHub
19 changed files with 1418 additions and 295 deletions
+14 -295
View File
@@ -51,28 +51,9 @@ image::{figures}/delegatingfilterproxy.png[]
The following listing shows pseudo code of `DelegatingFilterProxy`:
.`DelegatingFilterProxy` Pseudo Code
[tabs]
======
Java::
+
[source,java,role="primary"]
----
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
Filter delegate = getFilterBean(someBeanName); // <1>
delegate.doFilter(request, response); // <2>
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
val delegate: Filter = getFilterBean(someBeanName) // <1>
delegate.doFilter(request, response) // <2>
}
----
======
include-code::./SampleDelegatingFilterProxy[tag=dofilter,indent=0]
<1> Lazily get Filter that was registered as a Spring Bean.
For the example in <<servlet-delegatingfilterproxy-figure>> `delegate` is an instance of __Bean Filter~0~__.
<2> Delegate work to the Spring Bean.
@@ -155,58 +136,7 @@ However, there are times that it is beneficial to know the ordering, if you want
These security filters are most often declared using an javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[`HttpSecurity`] instance.
To exemplify the above paragraph, let's consider the following security configuration:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests((authorize) -> authorize
.anyRequest().authenticated()
);
return http.build();
}
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
import org.springframework.security.config.web.servlet.invoke
@Configuration
@EnableWebSecurity
class SecurityConfig {
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf { }
httpBasic { }
formLogin { }
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
}
----
======
include-code::./SecurityConfig[tag=snippet,indent=0]
The above configuration will result in the following `Filter` ordering:
@@ -233,7 +163,7 @@ If you want to see the list of filters invoked for a particular request, you can
=== Printing the Security Filters
Often times, it is useful to see the list of security ``Filter``s that are invoked for a particular request.
For example, you want to make sure that the <<adding-custom-filter,filter you have added>> is in the list of the security filters.
For example, you want to make sure that the <<adding-filters-to-chain,filter you have added>> is in the list of the security filters.
The list of filters is printed at DEBUG level on the application startup, so you can see something like the following on the console output for example:
@@ -248,7 +178,7 @@ But that is not all, you can also configure your application to print the invoca
That is helpful to see if the filter you have added is invoked for a particular request or to check where an exception is coming from.
To do that, you can configure your application to <<servlet-logging,log the security events>>.
[[adding-custom-filter]]
[[adding-filters-to-chain]]
=== Adding Filters to the Filter Chain
Most of the time, the default <<servlet-security-filters>> are enough to provide security to your application.
@@ -260,6 +190,7 @@ javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity
* `#addFilterAfter(Filter, Class<?>)` adds your filter after another filter
* `#addFilterAt(Filter, Class<?>)` replaces another filter with your filter
[[adding-custom-filter]]
==== Adding a Custom Filter
If you are creating a filter of your own, you will need to determine its location in the filter chain.
@@ -298,39 +229,7 @@ For example, let's say that you want to add a `Filter` that gets a tenant id hea
First, let's create the `Filter`:
[source,java]
----
import java.io.IOException;
import jakarta.servlet.Filter;
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.springframework.security.access.AccessDeniedException;
public class TenantFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String tenantId = request.getHeader("X-Tenant-Id"); <1>
boolean hasAccess = isUserAllowed(tenantId); <2>
if (hasAccess) {
filterChain.doFilter(request, response); <3>
return;
}
throw new AccessDeniedException("Access denied"); <4>
}
}
----
include-code::./TenantFilter[tag=snippet,indent=0]
The sample code above does the following:
@@ -349,34 +248,7 @@ The previous description already gives us a clue on where to add the filter, sin
Based on the rule of thumb, add it after xref:servlet/authentication/anonymous.adoc[ `AnonymousAuthenticationFilter`], the last authentication filter in the chain, like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); <1>
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
// ...
.addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) <1>
return http.build()
}
----
======
include-code::./SecurityConfig[tag=snippet,indent=0]
<1> Use `HttpSecurity#addFilterAfter` to add the `TenantFilter` after the `AnonymousAuthenticationFilter`.
@@ -405,127 +277,23 @@ public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilte
This makes so that `HttpSecurity` is the only one adding it.
[[customizing-filter]]
==== Customizing a Spring Security Filter
Generally, you can use a filter's DSL method to configure Spring Security's filters.
For example, the simplest way to add `BasicAuthenticationFilter` is by asking the DSL to do it:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic(Customizer.withDefaults())
// ...
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic { }
// ...
}
return http.build()
}
----
======
include-code::./CustomizingFilterTests[tag=basic-default,indent=0]
However, in the event that you want to construct a Spring Security filter yourself, you specify it in the DSL using `addFilterAt` like so:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
// ... configure
http
// ...
.addFilterAt(basic, BasicAuthenticationFilter.class);
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
val basic = BasicAuthenticationFilter()
// ... configure
http
// ...
.addFilterAt(basic, BasicAuthenticationFilter::class.java)
return http.build()
}
----
======
include-code::./CustomizingFilterTests[tag=custom-filter,indent=0]
Note that if that filter has already been added, then Spring Security will throw an exception.
For example, calling xref:servlet/authentication/passwords/basic.adoc[ `HttpSecurity#httpBasic`] adds a `BasicAuthenticationFilter` for you.
So, the following arrangement fails since there are two calls that are both trying to add `BasicAuthenticationFilter`:
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
BasicAuthenticationFilter basic = new BasicAuthenticationFilter();
// ... configure
http
.httpBasic(Customizer.withDefaults())
// ... on no! BasicAuthenticationFilter is added twice!
.addFilterAt(basic, BasicAuthenticationFilter.class);
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
fun filterChain(http: HttpSecurity): SecurityFilterChain {
val basic = BasicAuthenticationFilter()
// ... configure
http {
httpBasic { }
}
// ... on no! BasicAuthenticationFilter is added twice!
http.addFilterAt(basic, BasicAuthenticationFilter::class.java)
return http.build()
}
----
======
include-code::./CustomizingFilterTests[tag=incorrect,indent=0]
In this case, remove the call to `httpBasic` since you are constructing `BasicAuthenticationFilter` yourself.
@@ -533,10 +301,7 @@ In this case, remove the call to `httpBasic` since you are constructing `BasicAu
====
In the event that you are unable to reconfigure `HttpSecurity` to not add a certain filter, you can typically disable the Spring Security filter by calling its DSL's `disable` method like so:
[source,java]
----
.httpBasic((basic) -> basic.disable())
----
include-code::./CustomizingFilterTests[tag=disable,indent=0]
====
[[servlet-exceptiontranslationfilter]]
@@ -618,53 +383,7 @@ Or you may want to shut off this feature since you always want to redirect the u
To do that, you can use the javadoc:org.springframework.security.web.savedrequest.NullRequestCache[NullRequestCache] implementation.
.Prevent the Request From Being Saved
[tabs]
======
Java::
+
[source,java,role="primary"]
----
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}
----
Kotlin::
+
[source,kotlin,role="secondary"]
----
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
val nullRequestCache = NullRequestCache()
http {
requestCache {
requestCache = nullRequestCache
}
}
return http.build()
}
----
XML::
+
[source,xml,role="secondary"]
----
<http auto-config="true">
<!-- ... -->
<request-cache ref="nullRequestCache"/>
</http>
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
----
======
include-code::./SecurityConfig[tag=snippet,indent=0]
[[requestcacheawarefilter]]
=== RequestCacheAwareFilter
@@ -0,0 +1,102 @@
/*
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
import org.junit.jupiter.api.BeforeEach;
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.security.access.AccessDeniedException;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = {
CustomFilterTests.UserDetailsConfig.class,
CustomFilterTests.ApiController.class,
SecurityConfig.class })
@WebAppConfiguration
public class CustomFilterTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@BeforeEach
void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.defaultRequest(get("/api").with(user("user")))
.apply(springSecurity())
.build();
}
@Test
void tenantFilterWhenHeaderMissingThenAccessDenied() {
assertThatExceptionOfType(AccessDeniedException.class)
.isThrownBy(() -> this.mvc.perform(get("/api")).andReturn());
}
@Test
void tenantFilterWhenHeaderPresentThenContinuesFilterChain() throws Exception {
this.mvc.perform(get("/api").header("X-Tenant-Id", "some-tenant-id"))
.andExpect(status().isOk())
.andExpect(authenticated().withUsername("user"));
}
@Configuration
static class UserDetailsConfig {
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
}
@RestController
static class ApiController {
@GetMapping("/api")
String api() {
return "ok";
}
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import org.springframework.test.context.ContextConfiguration;
@Configuration
@ContextConfiguration(classes = { SecurityConfig.class })
@EnableWebSecurity
public class SecurityConfig {
// tag::snippet[]
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// ...
.addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); // <1>
return http.build();
}
// end::snippet[]
}
@@ -0,0 +1,53 @@
/*
* Copyright 2004-present 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.docs.servlet.addingcustomfilter;
// tag::snippet[]
import java.io.IOException;
import jakarta.servlet.Filter;
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.springframework.security.access.AccessDeniedException;
public class TenantFilter implements Filter {
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String tenantId = request.getHeader("X-Tenant-Id"); // <1>
boolean hasAccess = isUserAllowed(tenantId); // <2>
if (hasAccess) {
filterChain.doFilter(request, response); // <3>
return;
}
throw new AccessDeniedException("Access denied"); // <4>
}
private boolean isUserAllowed(String tenantId) {
return "some-tenant-id".equals(tenantId);
}
}
// end::snippet[]
@@ -0,0 +1,203 @@
/*
* Copyright 2004-present 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.docs.servlet.customizingfilter;
import java.io.IOException;
import java.util.List;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
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.http.HttpHeaders;
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.userdetails.User;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.security.web.FilterChainProxy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
import org.springframework.test.web.servlet.MockMvc;
import static org.assertj.core.api.Assertions.assertThat;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.header;
/**
* Tests for customizing security filters.
*
*/
@ExtendWith(SpringTestContextExtension.class)
public class CustomizingFilterTests {
public final SpringTestContext spring = new SpringTestContext(this);
@Autowired
MockMvc mvc;
@Autowired
FilterChainProxy filterChainProxy;
@Test
public void filterChainWhenBasicDefaultThenBasicAuthenticationFilterPresent() {
this.spring.register(SecurityConfigBasicDefault.class).autowire();
List<Filter> filters = this.filterChainProxy.getFilters("/");
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter.class);
}
@Test
public void filterChainWhenCustomFilterThenCustomFilterPresent() {
this.spring.register(SecurityConfigCustom.class).autowire();
List<Filter> filters = this.filterChainProxy.getFilters("/");
assertThat(filters).extracting("class").contains(SecurityConfigCustom.MyBasicAuthenticationFilter.class);
assertThat(filters).extracting("class").doesNotContain(BasicAuthenticationFilter.class);
}
@Test
public void requestWhenDisableThenNoWwwAuthenticateHeader() throws Exception {
this.spring.register(SecurityConfigDisable.class).autowire();
this.mvc.perform(get("/")).andExpect(header().doesNotExist(HttpHeaders.WWW_AUTHENTICATE));
}
@Test
public void filterChainWhenIncorrectThenBothFiltersPresent() {
this.spring.register(SecurityConfigIncorrect.class).autowire();
List<Filter> filters = this.filterChainProxy.getFilters("/");
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter.class);
assertThat(filters).extracting("class").contains(SecurityConfigIncorrect.MyBasicAuthenticationFilter.class);
}
@Configuration
@EnableWebSecurity
static class SecurityConfigBasicDefault {
// tag::basic-default[]
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic(Customizer.withDefaults());
// ...
return http.build();
}
// end::basic-default[]
}
@Configuration
@EnableWebSecurity
static class SecurityConfigCustom {
// tag::custom-filter[]
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
MyBasicAuthenticationFilter basic = new MyBasicAuthenticationFilter();
// ... configure
http
// ...
.addFilterAt(basic, BasicAuthenticationFilter.class);
return http.build();
}
// end::custom-filter[]
static class MyBasicAuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
}
}
@Configuration
@EnableWebSecurity
static class SecurityConfigDisable {
// tag::disable[]
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.httpBasic((basic) -> basic.disable());
// ...
return http.build();
}
// end::disable[]
}
@Configuration
@EnableWebSecurity
static class SecurityConfigIncorrect {
// tag::incorrect[]
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
MyBasicAuthenticationFilter basic = new MyBasicAuthenticationFilter();
// ... configure
http
.httpBasic(Customizer.withDefaults())
// ... on no! BasicAuthenticationFilter is added twice!
.addFilterAt(basic, BasicAuthenticationFilter.class);
return http.build();
}
// end::incorrect[]
static class MyBasicAuthenticationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
chain.doFilter(request, response);
}
}
}
@Configuration
static class UserDetailsConfig {
@Bean
InMemoryUserDetailsManager userDetailsManager() {
return new InMemoryUserDetailsManager(User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build());
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2004-present 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.docs.servlet.requestcachepreventsavedrequest;
import org.springframework.context.annotation.Bean;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.savedrequest.NullRequestCache;
import org.springframework.security.web.savedrequest.RequestCache;
public class SecurityConfig {
// tag::snippet[]
@Bean
SecurityFilterChain springSecurity(HttpSecurity http) throws Exception {
RequestCache nullRequestCache = new NullRequestCache();
http
// ...
.requestCache((cache) -> cache
.requestCache(nullRequestCache)
);
return http.build();
}
// end::snippet[]
}
@@ -0,0 +1,53 @@
/*
* Copyright 2004-present 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.docs.servlet.servletdelegatingfilterproxy;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.filter.GenericFilterBean;
import java.io.IOException;
/**
* A very simple implementation of a DelegatingFilterProxy.
*/
public class SampleDelegatingFilterProxy extends GenericFilterBean {
private StaticWebApplicationContext wac;
private final String someBeanName;
public SampleDelegatingFilterProxy(String someBeanName, StaticWebApplicationContext webApplicationContext) {
this.wac = webApplicationContext;
this.someBeanName = someBeanName;
}
// tag::dofilter[]
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
Filter delegate = getFilterBean(someBeanName); // <1>
delegate.doFilter(request, response, chain); // <2>
}
// end::dofilter[]
private Filter getFilterBean(String someBeanName) {
return this.wac.getBean(someBeanName, Filter.class);
}
}
@@ -0,0 +1,129 @@
/*
* Copyright 2004-present 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.docs.servlet.servletdelegatingfilterproxy;
import jakarta.servlet.Filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.FilterConfig;
import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import org.junit.jupiter.api.Test;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
import org.springframework.security.web.servlet.MockServletContext;
import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.StaticWebApplicationContext;
import java.io.IOException;
import java.util.Collections;
import java.util.Enumeration;
import java.util.LinkedHashMap;
import java.util.Map;
import static org.assertj.core.api.Assertions.assertThat;
public class SampleDelegatingFilterProxyTests {
@Test
void testFilter() throws ServletException, IOException {
ServletContext sc = new MockServletContext();
StaticWebApplicationContext wac = new StaticWebApplicationContext();
wac.registerSingleton("targetFilter", MockFilter.class);
wac.setServletContext(sc);
wac.refresh();
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac);
MockFilter targetFilter = (MockFilter) wac.getBean("targetFilter");
MockFilterConfig proxyConfig = new MockFilterConfig(sc);
proxyConfig.addInitParameter("targetBeanName", "targetFilter");
SampleDelegatingFilterProxy filterProxy = new SampleDelegatingFilterProxy("targetFilter", wac);
filterProxy.init(proxyConfig);
MockHttpServletRequest request = new MockHttpServletRequest();
MockHttpServletResponse response = new MockHttpServletResponse();
filterProxy.doFilter(request, response, null);
assertThat(targetFilter.filterConfig).isNull();
assertThat(request.getAttribute("called")).isEqualTo(Boolean.TRUE);
filterProxy.destroy();
assertThat(targetFilter.filterConfig).isNull();
}
private static class MockFilter implements Filter {
private FilterConfig filterConfig;
@Override
public void init(FilterConfig filterConfig) throws ServletException {
this.filterConfig = filterConfig;
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws java.io.IOException, ServletException {
request.setAttribute("called", Boolean.TRUE);
}
@Override
public void destroy() {
this.filterConfig = null;
}
}
private static class MockFilterConfig implements FilterConfig {
private final ServletContext servletContext;
private final String filterName;
private final Map<String, String> initParameters = new LinkedHashMap<>();
public MockFilterConfig(ServletContext servletContext) {
this.servletContext = servletContext;
this.filterName = "";
}
@Override
public String getFilterName() {
return this.filterName;
}
@Override
public ServletContext getServletContext() {
return this.servletContext;
}
public void addInitParameter(String name, String value) {
Assert.notNull(name, "Parameter name must not be null");
this.initParameters.put(name, value);
}
@Override
public String getInitParameter(String name) {
Assert.notNull(name, "Parameter name must not be null");
return this.initParameters.get(name);
}
@Override
public Enumeration<String> getInitParameterNames() {
return Collections.enumeration(this.initParameters.keySet());
}
}
}
@@ -0,0 +1,96 @@
/*
* Copyright 2004-present 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.docs.servlet.servletsecurityfilters;
import org.junit.jupiter.api.BeforeEach;
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.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = { SampleSecurityConfigTests.UserDetailsConfig.class, SecurityConfig.class })
@WebAppConfiguration
public class SampleSecurityConfigTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@BeforeEach
public void setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.defaultRequest(get("/api").with(user("user")))
.defaultRequest(post("/api").with(csrf()))
.apply(springSecurity())
.build();
}
@Test
void testGet() throws Exception {
this.mvc.perform(get("/api")
.with(httpBasic("user", "password")))
// Security check was successful
.andExpect(status().isNotFound())
.andExpect(authenticated().withUsername("user"));
}
@Test
void testUnauthenticated() throws Exception {
this.mvc.perform(get("/api"))
// Security check was successful
.andExpect(status().isUnauthorized());
}
@Test
void testCsrf() throws Exception {
this.mvc.perform(post("/api")
.with(csrf())
.with(httpBasic("user", "password"))
).andExpect(status().isNotFound());
}
@Configuration
static class UserDetailsConfig {
@Bean
UserDetailsService userDetailsService() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.build();
return new InMemoryUserDetailsManager(user);
}
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2004-present 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.docs.servlet.servletsecurityfilters;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.web.SecurityFilterChain;
// tag::snippet[]
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(Customizer.withDefaults())
.httpBasic(Customizer.withDefaults())
.formLogin(Customizer.withDefaults())
.authorizeHttpRequests(authorize -> authorize
.anyRequest().authenticated()
);
return http.build();
}
}
// end::snippet[]
@@ -0,0 +1,106 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
import org.assertj.core.api.Assertions.assertThatExceptionOfType
import org.junit.jupiter.api.BeforeEach
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.security.core.userdetails.User
import org.springframework.security.core.userdetails.UserDetails
import org.springframework.security.core.userdetails.UserDetailsService
import org.springframework.security.provisioning.InMemoryUserDetailsManager
import org.springframework.test.context.ContextConfiguration
import org.springframework.test.context.junit.jupiter.SpringExtension
import org.springframework.test.context.web.WebAppConfiguration
import org.springframework.test.web.servlet.MockMvc
import org.springframework.test.web.servlet.setup.MockMvcBuilders
import org.springframework.web.context.WebApplicationContext
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user
import org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get
import org.springframework.test.web.servlet.result.MockMvcResultMatchers.status
import org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.authenticated
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
@ExtendWith(SpringExtension::class)
@ContextConfiguration(
classes = [
CustomFilterTests.UserDetailsConfig::class,
CustomFilterTests.ApiController::class,
SecurityConfig::class
]
)
@WebAppConfiguration
class CustomFilterTests {
@Autowired
private lateinit var context: WebApplicationContext
private lateinit var mvc: MockMvc
@BeforeEach
fun setup() {
this.mvc = MockMvcBuilders.webAppContextSetup(this.context)
.apply<DefaultMockMvcBuilder>(springSecurity())
.build();
}
@Test
fun tenantFilterWhenHeaderMissingThenAccessDenied() {
assertThatExceptionOfType(Exception::class.java)
.isThrownBy { this.mvc.perform(get("/api").with(user("user"))).andReturn() }
}
@Test
fun tenantFilterWhenHeaderPresentThenContinuesFilterChain() {
this.mvc.perform(get("/api")
.with(user("user"))
.header("X-Tenant-Id", "some-tenant-id"))
.andExpect(status().isOk)
.andExpect(authenticated().withUsername("user"))
}
@Configuration
open class UserDetailsConfig {
@Bean
open fun userDetailsService(): UserDetailsService {
@Suppress("DEPRECATION")
val user: UserDetails = User.withDefaultPasswordEncoder()
.username("user")
.password("password")
.roles("USER")
.build()
return InMemoryUserDetailsManager(user)
}
}
@RestController
class ApiController {
@GetMapping("/api")
fun api(): String {
return "ok"
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter
@Configuration
@EnableWebSecurity
open class SecurityConfig {
// tag::snippet[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http
// ...
.addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) // <1>
return http.build()
}
// end::snippet[]
}
@@ -0,0 +1,51 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.addingcustomfilter
import jakarta.servlet.Filter
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.springframework.security.access.AccessDeniedException
import java.io.IOException
// tag::snippet[]
class TenantFilter : Filter {
@Throws(IOException::class, ServletException::class)
override fun doFilter(servletRequest: ServletRequest, servletResponse: ServletResponse, filterChain: FilterChain) {
val request = servletRequest as HttpServletRequest
val response = servletResponse as HttpServletResponse
val tenantId = request.getHeader("X-Tenant-Id") // <1>
val hasAccess = isUserAllowed(tenantId) // <2>
if (hasAccess) {
filterChain.doFilter(request, response) // <3>
return
}
throw AccessDeniedException("Access denied") // <4>
}
private fun isUserAllowed(tenantId: String?): Boolean {
return "some-tenant-id" == tenantId
}
}
// end::snippet[]
@@ -0,0 +1,167 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.customizingfilter
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import org.assertj.core.api.Assertions.assertThat
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.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.config.test.SpringTestContext
import org.springframework.security.config.test.SpringTestContextExtension
import org.springframework.security.web.FilterChainProxy
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
import org.springframework.test.web.servlet.MockMvc
/**
* Tests for customizing security filters.
*
*/
@ExtendWith(SpringTestContextExtension::class)
class CustomizingFilterTests {
@JvmField
val spring = SpringTestContext(this)
@Autowired
lateinit var mvc: MockMvc
@Autowired
lateinit var filterChainProxy: FilterChainProxy
@Test
fun `filter chain when basic default then BasicAuthenticationFilter present`() {
spring.register(SecurityConfigBasicDefault::class.java).autowire()
val filters = filterChainProxy.getFilters("/")
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter::class.java)
}
@Test
fun `filter chain when custom filter then custom filter present`() {
spring.register(SecurityConfigCustom::class.java).autowire()
val filters = filterChainProxy.getFilters("/")
assertThat(filters).extracting("class").contains(SecurityConfigCustom.MyBasicAuthenticationFilter::class.java)
assertThat(filters).extracting("class").doesNotContain(BasicAuthenticationFilter::class.java)
}
@Test
fun `filter chain when incorrect then both filters present`() {
spring.register(SecurityConfigIncorrect::class.java).autowire()
val filters = filterChainProxy.getFilters("/")
assertThat(filters).extracting("class").contains(BasicAuthenticationFilter::class.java)
assertThat(filters).extracting("class").contains(SecurityConfigIncorrect.MyBasicAuthenticationFilter::class.java)
}
@Configuration
@EnableWebSecurity
open class SecurityConfigBasicDefault {
// tag::basic-default[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic { }
// ...
}
return http.build()
}
// end::basic-default[]
}
@Configuration
@EnableWebSecurity
open class SecurityConfigCustom {
// tag::custom-filter[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val basic = MyBasicAuthenticationFilter()
// ... configure
http
// ...
.addFilterAt(basic, BasicAuthenticationFilter::class.java)
return http.build()
}
// end::custom-filter[]
class MyBasicAuthenticationFilter : Filter {
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
chain.doFilter(request, response)
}
}
}
@Configuration @EnableWebSecurity
open class SecurityConfigDisable {
// tag::disable[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
httpBasic {
disable()
}
// ...
}
return http.build()
}
// end::disable[]
}
@Configuration
@EnableWebSecurity
open class SecurityConfigIncorrect {
// tag::incorrect[]
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
val basic = MyBasicAuthenticationFilter()
// ... configure
http {
httpBasic { }
}
// ... on no! BasicAuthenticationFilter is added twice!
http.addFilterAt(basic, BasicAuthenticationFilter::class.java)
return http.build()
}
// end::incorrect[]
class MyBasicAuthenticationFilter : Filter {
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
chain.doFilter(request, response)
}
}
}
}
@@ -0,0 +1,40 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.requestcachepreventsavedrequest
import org.springframework.context.annotation.Bean
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
import org.springframework.security.web.savedrequest.NullRequestCache
open class SecurityConfig {
// tag::snippet[]
@Bean
open fun springSecurity(http: HttpSecurity): SecurityFilterChain {
val nullRequestCache = NullRequestCache()
http {
requestCache {
requestCache = nullRequestCache
}
}
return http.build()
}
// end::snippet[]
}
@@ -0,0 +1,49 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.servletdelegatingfilterproxy;
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.ServletException
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import org.springframework.web.context.support.StaticWebApplicationContext;
import org.springframework.web.filter.GenericFilterBean;
import java.io.IOException;
/**
* A very simple implementation of a DelegatingFilterProxy.
*/
class SampleDelegatingFilterProxy(
private val someBeanName: String,
private var wac: StaticWebApplicationContext
) : GenericFilterBean() {
// tag::dofilter[]
@Throws(IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain?) {
val delegate: Filter = getFilterBean(someBeanName) // <1>
delegate.doFilter(request, response, chain) // <2>
}
// end::dofilter[]
private fun getFilterBean(someBeanName: String): Filter {
return wac.getBean(someBeanName, Filter::class.java)
}
}
@@ -0,0 +1,109 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.servletdelegatingfilterproxy
import jakarta.servlet.Filter
import jakarta.servlet.FilterChain
import jakarta.servlet.FilterConfig
import jakarta.servlet.ServletContext
import jakarta.servlet.ServletException
import jakarta.servlet.ServletRequest
import jakarta.servlet.ServletResponse
import org.junit.jupiter.api.Test
import org.springframework.mock.web.MockHttpServletRequest
import org.springframework.mock.web.MockHttpServletResponse
import org.springframework.security.web.servlet.MockServletContext
import org.springframework.util.Assert
import org.springframework.web.context.WebApplicationContext
import org.springframework.web.context.support.StaticWebApplicationContext
import java.io.IOException
import org.assertj.core.api.Assertions.assertThat
import java.util.Enumeration
import java.util.Collections
import kotlin.collections.LinkedHashMap
class SampleDelegatingFilterProxyTests {
@Test
@Throws(ServletException::class, IOException::class)
fun testFilter() {
val sc: ServletContext = MockServletContext()
val wac = StaticWebApplicationContext()
wac.registerSingleton("targetFilter", MockFilter::class.java)
wac.setServletContext(sc)
wac.refresh()
sc.setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, wac)
val targetFilter = wac.getBean("targetFilter") as MockFilter
val proxyConfig = MockFilterConfig(sc)
proxyConfig.addInitParameter("targetBeanName", "targetFilter")
val filterProxy = SampleDelegatingFilterProxy("targetFilter", wac)
filterProxy.init(proxyConfig)
val request = MockHttpServletRequest()
val response = MockHttpServletResponse()
filterProxy.doFilter(request, response, null)
assertThat(targetFilter.filterConfig).isNull()
assertThat(request.getAttribute("called")).isEqualTo(true)
filterProxy.destroy()
assertThat(targetFilter.filterConfig).isNull()
}
private class MockFilter : Filter {
var filterConfig: FilterConfig? = null
@Throws(ServletException::class)
override fun init(filterConfig: FilterConfig) {
this.filterConfig = filterConfig
}
@Throws(IOException::class, ServletException::class)
override fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain?) {
request.setAttribute("called", true)
}
override fun destroy() {
filterConfig = null
}
}
private class MockFilterConfig(
private val servletContext: ServletContext
) : FilterConfig {
private val filterName: String = ""
private val initParameters = LinkedHashMap<String, String>()
override fun getFilterName(): String = filterName
override fun getServletContext(): ServletContext = servletContext
fun addInitParameter(name: String, value: String) {
Assert.notNull(name, "Parameter name must not be null")
initParameters[name] = value
}
override fun getInitParameter(name: String): String? {
Assert.notNull(name, "Parameter name must not be null")
return initParameters[name]
}
override fun getInitParameterNames(): Enumeration<String> =
Collections.enumeration(initParameters.keys)
}
}
@@ -0,0 +1,45 @@
/*
* Copyright 2004-present 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.kt.docs.servlet.servletsecurityfilters
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.security.config.annotation.web.builders.HttpSecurity
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity
import org.springframework.security.config.annotation.web.invoke
import org.springframework.security.web.SecurityFilterChain
// tag::snippet[]
@Configuration
@EnableWebSecurity
open class SecurityConfig {
@Bean
open fun filterChain(http: HttpSecurity): SecurityFilterChain {
http {
csrf { }
httpBasic { }
formLogin { }
authorizeHttpRequests {
authorize(anyRequest, authenticated)
}
}
return http.build()
}
}
// end::snippet[]
@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<!--
~ Copyright 2004-present 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.
-->
<b:beans xmlns:b="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/security"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/security https://www.springframework.org/schema/security/spring-security.xsd
http://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">
<!-- tag::snippet[] -->
<http auto-config="true">
<!-- ... -->
<request-cache ref="nullRequestCache"/>
</http>
<b:bean id="nullRequestCache" class="org.springframework.security.web.savedrequest.NullRequestCache"/>
<!-- end::snippet[] -->
</b:beans>