563 lines
27 KiB
Plaintext
563 lines
27 KiB
Plaintext
[[servlet-architecture]]
|
|
= Architecture
|
|
:figures: servlet/architecture
|
|
|
|
This section discusses Spring Security's high level architecture within Servlet based applications.
|
|
We build on this high level understanding within xref:servlet/authentication/index.adoc#servlet-authentication[Authentication], xref:servlet/authorization/index.adoc#servlet-authorization[Authorization], xref:servlet/exploits/index.adoc#servlet-exploits[Protection Against Exploits] sections of the reference.
|
|
// FIXME: Add links to other sections of architecture
|
|
|
|
[[servlet-filters-review]]
|
|
== A Review of ``Filter``s
|
|
|
|
Spring Security's Servlet support is based on Servlet ``Filter``s, so it is helpful to look at the role of ``Filter``s generally first.
|
|
The picture below shows the typical layering of the handlers for a single HTTP request.
|
|
|
|
.FilterChain
|
|
[[servlet-filterchain-figure]]
|
|
image::{figures}/filterchain.png[]
|
|
|
|
The client sends a request to the application, and the container creates a `FilterChain` which contains the ``Filter``s and `Servlet` that should process the `HttpServletRequest` based on the path of the request URI.
|
|
In a Spring MVC application the `Servlet` is an instance of {spring-framework-reference-url}web.html#mvc-servlet[`DispatcherServlet`].
|
|
At most one `Servlet` can handle a single `HttpServletRequest` and `HttpServletResponse`.
|
|
However, more than one `Filter` can be used to:
|
|
|
|
* Prevent downstream ``Filter``s or the `Servlet` from being invoked.
|
|
In this instance the `Filter` will typically write the `HttpServletResponse`.
|
|
* Modify the `HttpServletRequest` or `HttpServletResponse` used by the downstream ``Filter``s and `Servlet`
|
|
|
|
The power of the `Filter` comes from the `FilterChain` that is passed into it.
|
|
|
|
.`FilterChain` Usage Example
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary"]
|
|
----
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
|
// do something before the rest of the application
|
|
chain.doFilter(request, response); // invoke the rest of the application
|
|
// do something after the rest of the application
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
|
// do something before the rest of the application
|
|
chain.doFilter(request, response) // invoke the rest of the application
|
|
// do something after the rest of the application
|
|
}
|
|
----
|
|
======
|
|
|
|
Since a `Filter` only impacts downstream ``Filter``s and the `Servlet`, the order each `Filter` is invoked is extremely important.
|
|
|
|
|
|
[[servlet-delegatingfilterproxy]]
|
|
== DelegatingFilterProxy
|
|
|
|
Spring provides a `Filter` implementation named {spring-framework-api-url}org/springframework/web/filter/DelegatingFilterProxy.html[`DelegatingFilterProxy`] that allows bridging between the Servlet container's lifecycle and Spring's `ApplicationContext`.
|
|
The Servlet container allows registering ``Filter``s using its own standards, but it is not aware of Spring defined Beans.
|
|
`DelegatingFilterProxy` can be registered via standard Servlet container mechanisms, but delegate all the work to a Spring Bean that implements `Filter`.
|
|
|
|
Here is a picture of how `DelegatingFilterProxy` fits into the <<servlet-filters-review,``Filter``s and the `FilterChain`>>.
|
|
|
|
.DelegatingFilterProxy
|
|
[[servlet-delegatingfilterproxy-figure]]
|
|
image::{figures}/delegatingfilterproxy.png[]
|
|
|
|
`DelegatingFilterProxy` looks up __Bean Filter~0~__ from the `ApplicationContext` and then invokes __Bean Filter~0~__.
|
|
The pseudo code of `DelegatingFilterProxy` can be seen below.
|
|
|
|
.`DelegatingFilterProxy` Pseudo Code
|
|
[tabs]
|
|
======
|
|
Java::
|
|
+
|
|
[source,java,role="primary",subs="+quotes,+macros"]
|
|
----
|
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) {
|
|
// 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~__
|
|
Filter delegate = getFilterBean(someBeanName);
|
|
// delegate work to the Spring Bean
|
|
delegate.doFilter(request, response);
|
|
}
|
|
----
|
|
|
|
Kotlin::
|
|
+
|
|
[source,kotlin,role="secondary",subs="+quotes,+macros"]
|
|
----
|
|
fun doFilter(request: ServletRequest, response: ServletResponse, chain: FilterChain) {
|
|
// 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~__
|
|
val delegate: Filter = getFilterBean(someBeanName)
|
|
// delegate work to the Spring Bean
|
|
delegate.doFilter(request, response)
|
|
}
|
|
----
|
|
======
|
|
|
|
Another benefit of `DelegatingFilterProxy` is that it allows delaying looking `Filter` bean instances.
|
|
This is important because the container needs to register the `Filter` instances before the container can startup.
|
|
However, Spring typically uses a `ContextLoaderListener` to load the Spring Beans which will not be done until after the `Filter` instances need to be registered.
|
|
|
|
[[servlet-filterchainproxy]]
|
|
== FilterChainProxy
|
|
|
|
Spring Security's Servlet support is contained within `FilterChainProxy`.
|
|
`FilterChainProxy` is a special `Filter` provided by Spring Security that allows delegating to many `Filter` instances through <<servlet-securityfilterchain,`SecurityFilterChain`>>.
|
|
Since `FilterChainProxy` is a Bean, it is typically wrapped in a <<servlet-delegatingfilterproxy>>.
|
|
|
|
.FilterChainProxy
|
|
[[servlet-filterchainproxy-figure]]
|
|
image::{figures}/filterchainproxy.png[]
|
|
|
|
[[servlet-securityfilterchain]]
|
|
== SecurityFilterChain
|
|
|
|
{security-api-url}org/springframework/security/web/SecurityFilterChain.html[`SecurityFilterChain`] is used by <<servlet-filterchainproxy>> to determine which Spring Security ``Filter``s should be invoked for this request.
|
|
|
|
.SecurityFilterChain
|
|
[[servlet-securityfilterchain-figure]]
|
|
image::{figures}/securityfilterchain.png[]
|
|
|
|
The <<servlet-security-filters,Security Filters>> in `SecurityFilterChain` are typically Beans, but they are registered with `FilterChainProxy` instead of <<servlet-delegatingfilterproxy>>.
|
|
`FilterChainProxy` provides a number of advantages to registering directly with the Servlet container or <<servlet-delegatingfilterproxy>>.
|
|
First, it provides a starting point for all of Spring Security's Servlet support.
|
|
For that reason, if you are attempting to troubleshoot Spring Security's Servlet support, adding a debug point in `FilterChainProxy` is a great place to start.
|
|
|
|
Second, since `FilterChainProxy` is central to Spring Security usage it can perform tasks that are not viewed as optional.
|
|
// FIXME: Add a link to SecurityContext
|
|
For example, it clears out the `SecurityContext` to avoid memory leaks.
|
|
It also applies Spring Security's xref:servlet/exploits/firewall.adoc#servlet-httpfirewall[`HttpFirewall`] to protect applications against certain types of attacks.
|
|
|
|
In addition, it provides more flexibility in determining when a `SecurityFilterChain` should be invoked.
|
|
In a Servlet container, ``Filter``s are invoked based upon the URL alone.
|
|
// FIXME: Link to RequestMatcher
|
|
However, `FilterChainProxy` can determine invocation based upon anything in the `HttpServletRequest` by leveraging the `RequestMatcher` interface.
|
|
|
|
In fact, `FilterChainProxy` can be used to determine which `SecurityFilterChain` should be used.
|
|
This allows providing a totally separate configuration for different _slices_ of your application.
|
|
|
|
.Multiple SecurityFilterChain
|
|
[[servlet-multi-securityfilterchain-figure]]
|
|
image::{figures}/multi-securityfilterchain.png[]
|
|
|
|
In the <<servlet-multi-securityfilterchain-figure>> Figure `FilterChainProxy` decides which `SecurityFilterChain` should be used.
|
|
Only the first `SecurityFilterChain` that matches will be invoked.
|
|
If a URL of `/api/messages/` is requested, it will first match on ``SecurityFilterChain~0~``'s pattern of `+/api/**+`, so only `SecurityFilterChain~0~` will be invoked even though it also matches on ``SecurityFilterChain~n~``.
|
|
If a URL of `/messages/` is requested, it will not match on ``SecurityFilterChain~0~``'s pattern of `+/api/**+`, so `FilterChainProxy` will continue trying each `SecurityFilterChain`.
|
|
Assuming that no other, `SecurityFilterChain` instances match `SecurityFilterChain~n~` will be invoked.
|
|
// FIXME add link to pattern matching
|
|
|
|
Notice that `SecurityFilterChain~0~` has only three security ``Filter``s instances configured.
|
|
However, `SecurityFilterChain~n~` has four security ``Filter``s configured.
|
|
It is important to note that each `SecurityFilterChain` can be unique and configured in isolation.
|
|
In fact, a `SecurityFilterChain` might have zero security ``Filter``s if the application wants Spring Security to ignore certain requests.
|
|
// FIXME: add link to configuring multiple `SecurityFilterChain` instances
|
|
|
|
[[servlet-security-filters]]
|
|
== Security Filters
|
|
|
|
Spring Security uses a number of Servlet Filters (https://jakarta.ee/specifications/servlet/5.0/jakarta-servlet-spec-5.0.pdf[Jakarta Servlet Spec, Chapter 6]) to provide security to your application.
|
|
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.
|
|
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.
|
|
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].
|
|
|
|
To exemplify the above paragraph, let's consider the following security configuration:
|
|
|
|
====
|
|
.Java
|
|
[source,java,role="primary"]
|
|
----
|
|
@Configuration
|
|
@EnableWebSecurity
|
|
public class SecurityConfig {
|
|
|
|
@Bean
|
|
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
http
|
|
.csrf(Customizer.withDefaults())
|
|
.authorizeHttpRequests(authorize -> authorize
|
|
.anyRequest().authenticated()
|
|
)
|
|
.httpBasic(Customizer.withDefaults())
|
|
.formLogin(Customizer.withDefaults());
|
|
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 { }
|
|
authorizeHttpRequests {
|
|
authorize(anyRequest, authenticated)
|
|
}
|
|
httpBasic { }
|
|
formLogin { }
|
|
}
|
|
return http.build()
|
|
}
|
|
|
|
}
|
|
----
|
|
====
|
|
|
|
The above configuration will result in the following `Filter` ordering:
|
|
|
|
[cols="1,1", options="header"]
|
|
|====
|
|
| Filter | Added by
|
|
| xref:servlet/exploits/csrf.adoc[CsrfFilter] | `HttpSecurity#csrf`
|
|
| xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form[UsernamePasswordAuthenticationFilter] | `HttpSecurity#formLogin`
|
|
| xref:servlet/authentication/passwords/basic.adoc[BasicAuthenticationFilter] | `HttpSecurity#httpBasic`
|
|
| xref:servlet/authorization/authorize-http-requests.adoc[AuthorizationFilter] | `HttpSecurity#authorizeHttpRequests`
|
|
|====
|
|
|
|
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.
|
|
3. Third, the `AuthorizationFilter` is invoked to authorize the request.
|
|
|
|
[NOTE]
|
|
====
|
|
There might be other `Filter` instances that are not listed above.
|
|
If you want to see the list of filters invoked for a particular request, you can <<servlet-print-filters,print them>>.
|
|
====
|
|
|
|
[[servlet-print-filters]]
|
|
=== 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.
|
|
|
|
The list of filters is printed at INFO level on the application startup, so you can see something like the following on the console output for example:
|
|
|
|
[source,text,role="terminal"]
|
|
----
|
|
2023-06-14T08:55:22.321-03:00 INFO 76975 --- [ main] o.s.s.web.DefaultSecurityFilterChain : Will secure any request with [
|
|
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>>.
|
|
|
|
But that is not all, you can also configure your application to print the invocation of each individual filter for each request.
|
|
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 a Custom Filter to the Filter Chain
|
|
|
|
Mostly of the times, the default 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.
|
|
|
|
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`:
|
|
|
|
[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>
|
|
}
|
|
|
|
}
|
|
|
|
----
|
|
|
|
The sample code above does the following:
|
|
|
|
<1> Get the tenant id from the request header.
|
|
<2> Check if the current user has access to the tenant id.
|
|
<3> If the user has access, then invoke the rest of the filters in the chain.
|
|
<4> If the user does not have access, then throw an `AccessDeniedException`.
|
|
|
|
[TIP]
|
|
====
|
|
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.
|
|
|
|
====
|
|
.Java
|
|
[source,java,role="primary"]
|
|
----
|
|
@Bean
|
|
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
|
|
http
|
|
// ...
|
|
.addFilterBefore(new TenantFilter(), AuthorizationFilter.class); <1>
|
|
return http.build();
|
|
}
|
|
----
|
|
.Kotlin
|
|
[source,kotlin,role="secondary"]
|
|
----
|
|
@Bean
|
|
fun filterChain(http: HttpSecurity): SecurityFilterChain {
|
|
http
|
|
// ...
|
|
.addFilterBefore(TenantFilter(), AuthorizationFilter::class.java) <1>
|
|
return http.build()
|
|
}
|
|
----
|
|
====
|
|
|
|
<1> Use `HttpSecurity#addFilterBefore` to add the `TenantFilter` before the `AuthorizationFilter`.
|
|
|
|
By adding the filter before the `AuthorizationFilter` 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.
|
|
|
|
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}web.html#web.servlet.embedded-container.servlets-filters-listeners.beans[register 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.
|
|
|
|
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`:
|
|
|
|
[source,java]
|
|
----
|
|
@Bean
|
|
public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
|
|
FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
|
|
registration.setEnabled(false);
|
|
return registration;
|
|
}
|
|
----
|
|
|
|
|
|
[[servlet-exceptiontranslationfilter]]
|
|
== Handling Security Exceptions
|
|
|
|
|
|
The {security-api-url}org/springframework/security/web/access/ExceptionTranslationFilter.html[`ExceptionTranslationFilter`] allows translation of {security-api-url}org/springframework/security/access/AccessDeniedException.html[`AccessDeniedException`] and {security-api-url}/org/springframework/security/core/AuthenticationException.html[`AuthenticationException`] into HTTP responses.
|
|
|
|
`ExceptionTranslationFilter` is inserted into the <<servlet-filterchainproxy>> as one of the <<servlet-security-filters>>.
|
|
|
|
image::{figures}/exceptiontranslationfilter.png[]
|
|
|
|
|
|
* image:{icondir}/number_1.png[] First, the `ExceptionTranslationFilter` invokes `FilterChain.doFilter(request, response)` to invoke the rest of the application.
|
|
* image:{icondir}/number_2.png[] If the user is not authenticated or it is an `AuthenticationException`, then __Start Authentication__.
|
|
** The xref:servlet/authentication/architecture.adoc#servlet-authentication-securitycontextholder[SecurityContextHolder] is cleared out
|
|
** The `HttpServletRequest` is <<savedrequests,saved>> so that it can be used to replay the original request once authentication is successful.
|
|
// FIXME: add link to authentication success
|
|
** The `AuthenticationEntryPoint` is used to request credentials from the client.
|
|
For example, it might redirect to a log in page or send a `WWW-Authenticate` header.
|
|
// FIXME: link to AuthenticationEntryPoint
|
|
* image:{icondir}/number_3.png[] Otherwise if it is an `AccessDeniedException`, then __Access Denied__.
|
|
The `AccessDeniedHandler` is invoked to handle access denied.
|
|
// FIXME: link to AccessDeniedHandler
|
|
|
|
[NOTE]
|
|
====
|
|
If the application does not throw an `AccessDeniedException` or an `AuthenticationException`, then `ExceptionTranslationFilter` does not do anything.
|
|
====
|
|
|
|
The pseudocode for `ExceptionTranslationFilter` looks something like this:
|
|
|
|
.ExceptionTranslationFilter pseudocode
|
|
[source,java]
|
|
----
|
|
try {
|
|
filterChain.doFilter(request, response); // <1>
|
|
} catch (AccessDeniedException | AuthenticationException ex) {
|
|
if (!authenticated || ex instanceof AuthenticationException) {
|
|
startAuthentication(); // <2>
|
|
} else {
|
|
accessDenied(); // <3>
|
|
}
|
|
}
|
|
----
|
|
<1> You will recall from <<servlet-filters-review>> that invoking `FilterChain.doFilter(request, response)` is the equivalent of invoking the rest of the application.
|
|
This means that if another part of the application, (i.e. xref:servlet/authorization/authorize-requests.adoc#servlet-authorization-filtersecurityinterceptor[`FilterSecurityInterceptor`] or method security) throws an `AuthenticationException` or `AccessDeniedException` it will be caught and handled here.
|
|
<2> If the user is not authenticated or it is an `AuthenticationException`, then __Start Authentication__.
|
|
<3> Otherwise, __Access Denied__
|
|
|
|
[[savedrequests]]
|
|
== Saving Requests Between Authentication
|
|
|
|
As illustrated in <<servlet-exceptiontranslationfilter>>, when a request has no authentication and is for a resource that requires authentication, there is a need to save the request for the authenticated resource to re-request after authentication is successful.
|
|
In Spring Security this is done by saving the `HttpServletRequest` using a <<requestcache,`RequestCache`>> implementation.
|
|
|
|
[[requestcache]]
|
|
=== RequestCache
|
|
|
|
The `HttpServletRequest` is saved in the {security-api-url}org/springframework/security/web/savedrequest/RequestCache.html[`RequestCache`].
|
|
When the user successfully authenticates, the `RequestCache` is used to replay the original request.
|
|
The <<requestcacheawarefilter,`RequestCacheAwareFilter`>> uses the `RequestCache` to get the saved `HttpServletRequest` after the user authenticates, while the `ExceptionTranslationFilter` uses the `RequestCache` to save the `HttpServletRequest` after it detects `AuthenticationException`, before redirecting the user to the login endpoint.
|
|
|
|
By default, an `HttpSessionRequestCache` is used.
|
|
The code below demonstrates how to customize the `RequestCache` implementation that is used to check the `HttpSession` for a saved request if the parameter named `continue` is present.
|
|
|
|
include::partial$servlet/architecture/request-cache-continue.adoc[]
|
|
|
|
[[requestcache-prevent-saved-request]]
|
|
==== Prevent the Request From Being Saved
|
|
|
|
There are a number of reasons you may want to not store the user's unauthenticated request in the session.
|
|
You may want to offload that storage onto the user's browser or store it in a database.
|
|
Or you may want to shut off this feature since you always want to redirect the user to the home page instead of the page they tried to visit before login.
|
|
|
|
To do that, you can use {security-api-url}org/springframework/security/web/savedrequest/NullRequestCache.html[the `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"/>
|
|
----
|
|
======
|
|
|
|
|
|
[[requestcacheawarefilter]]
|
|
=== RequestCacheAwareFilter
|
|
|
|
The {security-api-url}org/springframework/security/web/savedrequest/RequestCacheAwareFilter.html[`RequestCacheAwareFilter`] uses the <<requestcache,`RequestCache`>> to replay the original request.
|
|
|
|
[[servlet-logging]]
|
|
== Logging
|
|
|
|
Spring Security provides comprehensive logging of all security related events at the DEBUG and TRACE level.
|
|
This can be very useful when debugging your application because for security measures Spring Security does not add any detail of why a request has been rejected to the response body.
|
|
If you come across a 401 or 403 error, it is very likely that you will find a log message that will help you understand what is going on.
|
|
|
|
Let's consider an example where a user tries to make a `POST` request to a resource that has xref:servlet/exploits/csrf.adoc[CSRF protection] enabled without the CSRF token.
|
|
With no logs, the user will see a 403 error with no explanation of why the request was rejected.
|
|
However, if you enable logging for Spring Security, you will see a log message like this:
|
|
|
|
[source,text]
|
|
----
|
|
2023-06-14T09:44:25.797-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Securing POST /hello
|
|
2023-06-14T09:44:25.797-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking DisableEncodeUrlFilter (1/15)
|
|
2023-06-14T09:44:25.798-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking WebAsyncManagerIntegrationFilter (2/15)
|
|
2023-06-14T09:44:25.800-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking SecurityContextHolderFilter (3/15)
|
|
2023-06-14T09:44:25.801-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking HeaderWriterFilter (4/15)
|
|
2023-06-14T09:44:25.802-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.security.web.FilterChainProxy : Invoking CsrfFilter (5/15)
|
|
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.security.web.csrf.CsrfFilter : Invalid CSRF token found for http://localhost:8080/hello
|
|
2023-06-14T09:44:25.814-03:00 DEBUG 76975 --- [nio-8080-exec-1] o.s.s.w.access.AccessDeniedHandlerImpl : Responding with 403 status code
|
|
2023-06-14T09:44:25.814-03:00 TRACE 76975 --- [nio-8080-exec-1] o.s.s.w.header.writers.HstsHeaderWriter : Not injecting HSTS header since it did not match request to [Is Secure]
|
|
----
|
|
|
|
It becomes clear that the CSRF token is missing and that is why the request is being denied.
|
|
|
|
To configure your application to log all the security events, you can add the following to your application:
|
|
|
|
====
|
|
.application.properties in Spring Boot
|
|
[source,properties,role="primary"]
|
|
----
|
|
logging.level.org.springframework.security=TRACE
|
|
----
|
|
.logback.xml
|
|
[source,xml,role="secondary"]
|
|
----
|
|
<configuration>
|
|
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
|
|
<!-- ... -->
|
|
</appender>
|
|
<!-- ... -->
|
|
<logger name="org.springframework.security" level="trace" additivity="false">
|
|
<appender-ref ref="Console" />
|
|
</logger>
|
|
</configuration>
|
|
----
|
|
====
|