mirror of
https://github.com/spring-projects/spring-security.git
synced 2025-06-22 20:12:14 +00:00
Polish Filter Chain Documentation
Closes gh-15893
This commit is contained in:
parent
cf03f2fed9
commit
a36756929b
@ -164,11 +164,13 @@ In fact, a `SecurityFilterChain` might have zero security `Filter` instances if
|
|||||||
== Security Filters
|
== Security Filters
|
||||||
|
|
||||||
The Security Filters are inserted into the <<servlet-filterchainproxy>> with the <<servlet-securityfilterchain>> API.
|
The Security Filters are inserted into the <<servlet-filterchainproxy>> with the <<servlet-securityfilterchain>> API.
|
||||||
Those filters can be used for a number of different purposes, like xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], xref:servlet/exploits/index.adoc[exploit protection], and more.
|
Those filters can be used for a number of different purposes, like
|
||||||
|
xref:servlet/exploits/index.adoc[exploit protection],xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], and more.
|
||||||
The filters are executed in a specific order to guarantee that they are invoked at the right time, for example, the `Filter` that performs authentication should be invoked before the `Filter` that performs authorization.
|
The filters are executed in a specific order to guarantee that they are invoked at the right time, for example, the `Filter` that performs authentication should be invoked before the `Filter` that performs authorization.
|
||||||
It is typically not necessary to know the ordering of Spring Security's ``Filter``s.
|
It is typically not necessary to know the ordering of Spring Security's ``Filter``s.
|
||||||
However, there are times that it is beneficial to know the ordering, if you want to know them, you can check the {gh-url}/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java[`FilterOrderRegistration` code].
|
However, there are times that it is beneficial to know the ordering, if you want to know them, you can check the {gh-url}/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java[`FilterOrderRegistration` code].
|
||||||
|
|
||||||
|
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:
|
To exemplify the above paragraph, let's consider the following security configuration:
|
||||||
|
|
||||||
[tabs]
|
[tabs]
|
||||||
@ -185,11 +187,12 @@ public class SecurityConfig {
|
|||||||
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
.csrf(Customizer.withDefaults())
|
.csrf(Customizer.withDefaults())
|
||||||
|
.httpBasic(Customizer.withDefaults())
|
||||||
|
.formLogin(Customizer.withDefaults())
|
||||||
.authorizeHttpRequests(authorize -> authorize
|
.authorizeHttpRequests(authorize -> authorize
|
||||||
.anyRequest().authenticated()
|
.anyRequest().authenticated()
|
||||||
)
|
);
|
||||||
.httpBasic(Customizer.withDefaults())
|
|
||||||
.formLogin(Customizer.withDefaults());
|
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,11 +213,11 @@ class SecurityConfig {
|
|||||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http {
|
http {
|
||||||
csrf { }
|
csrf { }
|
||||||
|
httpBasic { }
|
||||||
|
formLogin { }
|
||||||
authorizeHttpRequests {
|
authorizeHttpRequests {
|
||||||
authorize(anyRequest, authenticated)
|
authorize(anyRequest, authenticated)
|
||||||
}
|
}
|
||||||
httpBasic { }
|
|
||||||
formLogin { }
|
|
||||||
}
|
}
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
@ -235,8 +238,8 @@ The above configuration will result in the following `Filter` ordering:
|
|||||||
|====
|
|====
|
||||||
|
|
||||||
1. First, the `CsrfFilter` is invoked to protect against xref:servlet/exploits/csrf.adoc[CSRF attacks].
|
1. First, the `CsrfFilter` is invoked to protect against xref:servlet/exploits/csrf.adoc[CSRF attacks].
|
||||||
2. Second, the authentication filters are invoked to authenticate the request.
|
2. Second, xref:servlet/authentication/architecture.adoc[the authentication filters] are invoked to authenticate the request.
|
||||||
3. Third, the `AuthorizationFilter` is invoked to authorize the request.
|
3. Third, xref:servlet/authorization/authorize-http-requests.adoc[the `AuthorizationFilter`] is invoked to authorize the request.
|
||||||
|
|
||||||
[NOTE]
|
[NOTE]
|
||||||
====
|
====
|
||||||
@ -254,22 +257,7 @@ The list of filters is printed at DEBUG level on the application startup, so you
|
|||||||
|
|
||||||
[source,text,role="terminal"]
|
[source,text,role="terminal"]
|
||||||
----
|
----
|
||||||
2023-06-14T08:55:22.321-03:00 DEBUG 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
|
2023-06-14T08:55:22.321-03:00 DEBUG 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [ DisableEncodeUrlFilter, WebAsyncManagerIntegrationFilter, SecurityContextHolderFilter, HeaderWriterFilter, CsrfFilter, LogoutFilter, UsernamePasswordAuthenticationFilter, DefaultLoginPageGeneratingFilter, DefaultLogoutPageGeneratingFilter, BasicAuthenticationFilter, RequestCacheAwareFilter, SecurityContextHolderAwareRequestFilter, AnonymousAuthenticationFilter, ExceptionTranslationFilter, AuthorizationFilter]
|
||||||
org.springframework.security.web.session.DisableEncodeUrlFilter@404db674,
|
|
||||||
org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@50f097b5,
|
|
||||||
org.springframework.security.web.context.SecurityContextHolderFilter@6fc6deb7,
|
|
||||||
org.springframework.security.web.header.HeaderWriterFilter@6f76c2cc,
|
|
||||||
org.springframework.security.web.csrf.CsrfFilter@c29fe36,
|
|
||||||
org.springframework.security.web.authentication.logout.LogoutFilter@ef60710,
|
|
||||||
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@7c2dfa2,
|
|
||||||
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@4397a639,
|
|
||||||
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@7add838c,
|
|
||||||
org.springframework.security.web.authentication.www.BasicAuthenticationFilter@5cc9d3d0,
|
|
||||||
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@7da39774,
|
|
||||||
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@32b0876c,
|
|
||||||
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@3662bdff,
|
|
||||||
org.springframework.security.web.access.ExceptionTranslationFilter@77681ce4,
|
|
||||||
org.springframework.security.web.access.intercept.AuthorizationFilter@169268a7]
|
|
||||||
----
|
----
|
||||||
|
|
||||||
And that will give a pretty good idea of the security filters that are configured for <<servlet-securityfilterchain,each filter chain>>.
|
And that will give a pretty good idea of the security filters that are configured for <<servlet-securityfilterchain,each filter chain>>.
|
||||||
@ -279,13 +267,52 @@ That is helpful to see if the filter you have added is invoked for a particular
|
|||||||
To do that, you can configure your application to <<servlet-logging,log the security events>>.
|
To do that, you can configure your application to <<servlet-logging,log the security events>>.
|
||||||
|
|
||||||
[[adding-custom-filter]]
|
[[adding-custom-filter]]
|
||||||
=== Adding a Custom Filter to the Filter Chain
|
=== Adding Filters to the Filter Chain
|
||||||
|
|
||||||
Most of the time, the default security filters are enough to provide security to your application.
|
Most of the time, the default <<servlet-security-filters>> are enough to provide security to your application.
|
||||||
However, there might be times that you want to add a custom `Filter` to the security filter chain.
|
However, there might be times that you want to add a custom `Filter` to the <<servlet-securityfilterchain>>.
|
||||||
|
|
||||||
|
javadoc:org.springframework.security.config.annotation.web.builders.HttpSecurity[] comes with three methods for adding filters:
|
||||||
|
|
||||||
|
* `#addFilterBefore(Filter, Class<?>)` adds your filter before another filter
|
||||||
|
* `#addFilterAfter(Filter, Class<?>)` adds your filter after another filter
|
||||||
|
* `#addFilterAt(Filter, Class<?>)` replaces another filter with your 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.
|
||||||
|
Please take a look at the following key events that occur in the filter chain:
|
||||||
|
|
||||||
|
1. xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[`SecurityContext`] is loaded from the session
|
||||||
|
2. Request is protected from common exploits; xref:features/exploits/headers.adoc[secure headers], xref:servlet/integrations/cors.adoc[CORS], xref:servlet/exploits/csrf.adoc[CSRF]
|
||||||
|
3. Request is xref:servlet/authentication/architecture.adoc[authenticated]
|
||||||
|
4. Request is xref:servlet/authorization/architecture.adoc[authorized]
|
||||||
|
|
||||||
|
Consider which events you need to have happened in order to locate your filter.
|
||||||
|
The following is a rule of thumb:
|
||||||
|
|
||||||
|
[cols="1,1,1"]
|
||||||
|
|===
|
||||||
|
| If your filter is a(n) | Then place it after | As these events have already occurred
|
||||||
|
|
||||||
|
| exploit protection filter
|
||||||
|
| SecurityContextHolderFilter
|
||||||
|
| 1
|
||||||
|
|
||||||
|
| authentication filter
|
||||||
|
| LogoutFilter
|
||||||
|
| 1, 2
|
||||||
|
|
||||||
|
| authorization filter
|
||||||
|
| AnonymousAuthenticationFilter
|
||||||
|
| 1, 2, 3
|
||||||
|
|===
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
Most commonly, applications add a custom authentication.
|
||||||
|
This means they should be placed after xref:servlet/authentication/logout.adoc[`LogoutFilter`].
|
||||||
|
|
||||||
For example, let's say that you want to add a `Filter` that gets a tenant id header and check if the current user has access to that tenant.
|
For example, let's say that you want to add a `Filter` that gets a tenant id header and check if the current user has access to that tenant.
|
||||||
The previous description already gives us a clue on where to add the filter, since we need to know the current user, we need to add it after the authentication filters.
|
|
||||||
|
|
||||||
First, let's create the `Filter`:
|
First, let's create the `Filter`:
|
||||||
|
|
||||||
@ -335,7 +362,11 @@ The sample code above does the following:
|
|||||||
Instead of implementing `Filter`, you can extend from {spring-framework-api-url}org/springframework/web/filter/OncePerRequestFilter.html[OncePerRequestFilter] which is a base class for filters that are only invoked once per request and provides a `doFilterInternal` method with the `HttpServletRequest` and `HttpServletResponse` parameters.
|
Instead of implementing `Filter`, you can extend from {spring-framework-api-url}org/springframework/web/filter/OncePerRequestFilter.html[OncePerRequestFilter] which is a base class for filters that are only invoked once per request and provides a `doFilterInternal` method with the `HttpServletRequest` and `HttpServletResponse` parameters.
|
||||||
====
|
====
|
||||||
|
|
||||||
Now, we need to add the filter to the security filter chain.
|
Now, you need to add the filter to the <<servlet-securityfilterchain>>.
|
||||||
|
The previous description already gives us a clue on where to add the filter, since we need to know the current user, we need to add it after the authentication filters.
|
||||||
|
|
||||||
|
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]
|
[tabs]
|
||||||
======
|
======
|
||||||
Java::
|
Java::
|
||||||
@ -346,7 +377,7 @@ Java::
|
|||||||
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class); <1>
|
.addFilterAfter(new TenantFilter(), AnonymousAuthenticationFilter.class); <1>
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
@ -359,23 +390,26 @@ Kotlin::
|
|||||||
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
||||||
http
|
http
|
||||||
// ...
|
// ...
|
||||||
.addFilterBefore(TenantFilter(), AuthorizationFilter::class.java) <1>
|
.addFilterAfter(TenantFilter(), AnonymousAuthenticationFilter::class.java) <1>
|
||||||
return http.build()
|
return http.build()
|
||||||
}
|
}
|
||||||
----
|
----
|
||||||
======
|
======
|
||||||
|
|
||||||
<1> Use `HttpSecurity#addFilterBefore` to add the `TenantFilter` before the `AuthorizationFilter`.
|
<1> Use `HttpSecurity#addFilterAfter` to add the `TenantFilter` after the `AnonymousAuthenticationFilter`.
|
||||||
|
|
||||||
By adding the filter before the `AuthorizationFilter` we are making sure that the `TenantFilter` is invoked after the authentication filters.
|
By adding the filter after the xref:servlet/authentication/anonymous.adoc[`AnonymousAuthenticationFilter`] we are making sure that the `TenantFilter` is invoked after the authentication filters.
|
||||||
You can also use `HttpSecurity#addFilterAfter` to add the filter after a particular filter or `HttpSecurity#addFilterAt` to add the filter at a particular filter position in the filter chain.
|
|
||||||
|
|
||||||
And that's it, now the `TenantFilter` will be invoked in the filter chain and will check if the current user has access to the tenant id.
|
And that's it, now the `TenantFilter` will be invoked in the filter chain and will check if the current user has access to the tenant id.
|
||||||
|
|
||||||
Be careful when you declare your filter as a Spring bean, either by annotating it with `@Component` or by declaring it as a bean in your configuration, because Spring Boot will automatically {spring-boot-reference-url}reference/web/servlet.html#web.servlet.embedded-container.servlets-filters-listeners.beans[register it with the embedded container].
|
==== Declaring Your Filter as a Bean
|
||||||
|
|
||||||
|
When you declare a `Filter` as a Spring bean, either by annotating it with `@Component` or by declaring it as a bean in your configuration, Spring Boot automatically {spring-boot-reference-url}reference/web/servlet.html#web.servlet.embedded-container.servlets-filters-listeners.beans[registers it with the embedded container].
|
||||||
That may cause the filter to be invoked twice, once by the container and once by Spring Security and in a different order.
|
That may cause the filter to be invoked twice, once by the container and once by Spring Security and in a different order.
|
||||||
|
|
||||||
If you still want to declare your filter as a Spring bean to take advantage of dependency injection for example, and avoid the duplicate invocation, you can tell Spring Boot to not register it with the container by declaring a `FilterRegistrationBean` bean and setting its `enabled` property to `false`:
|
Because of that, filters are often not Spring beans.
|
||||||
|
|
||||||
|
However, if your filter needs to be a Spring bean (to take advantage of dependency injection, for example) you can tell Spring Boot to not register it with the container by declaring a `FilterRegistrationBean` bean and setting its `enabled` property to `false`:
|
||||||
|
|
||||||
[source,java]
|
[source,java]
|
||||||
----
|
----
|
||||||
@ -387,6 +421,141 @@ public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilte
|
|||||||
}
|
}
|
||||||
----
|
----
|
||||||
|
|
||||||
|
This makes so that `HttpSecurity` is the only one adding it.
|
||||||
|
|
||||||
|
==== 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()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
----
|
||||||
|
======
|
||||||
|
|
||||||
|
In this case, remove the call to `httpBasic` since you are constructing `BasicAuthenticationFilter` yourself.
|
||||||
|
|
||||||
|
[TIP]
|
||||||
|
====
|
||||||
|
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())
|
||||||
|
----
|
||||||
|
====
|
||||||
|
|
||||||
[[servlet-exceptiontranslationfilter]]
|
[[servlet-exceptiontranslationfilter]]
|
||||||
== Handling Security Exceptions
|
== Handling Security Exceptions
|
||||||
|
Loading…
x
Reference in New Issue
Block a user