123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721 |
- [[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 the xref:servlet/authentication/index.adoc#servlet-authentication[Authentication], xref:servlet/authorization/index.adoc#servlet-authorization[Authorization], and 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 Filters
- Spring Security's Servlet support is based on Servlet Filters, so it is helpful to look at the role of Filters generally first.
- The following image shows the typical layering of the handlers for a single HTTP request.
- .FilterChain
- [[servlet-filterchain-figure]]
- [.invert-dark]
- image::{figures}/filterchain.png[]
- The client sends a request to the application, and the container creates a `FilterChain`, which contains the `Filter` instances 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` instances or the `Servlet` from being invoked.
- In this case, the `Filter` typically writes the `HttpServletResponse`.
- * Modify the `HttpServletRequest` or `HttpServletResponse` used by the downstream `Filter` instances and the `Servlet`.
- The power of the `Filter` comes from the `FilterChain` that is passed into it.
- .`FilterChain` Usage Example
- include-code::./FilterChainUsage[tag=dofilter,indent=0]
- Since a `Filter` impacts only downstream `Filter` instances and the `Servlet`, the order in which 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` instances by using its own standards, but it is not aware of Spring-defined Beans.
- You can register `DelegatingFilterProxy` through the 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` instances and the `FilterChain`>>.
- .DelegatingFilterProxy
- [[servlet-delegatingfilterproxy-figure]]
- [.invert-dark]
- image::{figures}/delegatingfilterproxy.png[]
- `DelegatingFilterProxy` looks up __Bean Filter~0~__ from the `ApplicationContext` and then invokes __Bean Filter~0~__.
- 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>
- }
- ----
- ======
- <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.
- Another benefit of `DelegatingFilterProxy` is that it allows delaying looking up `Filter` bean instances.
- This is important because the container needs to register the `Filter` instances before the container can start up.
- However, Spring typically uses a `ContextLoaderListener` to load the Spring Beans, which is not 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>>.
- The following image shows the role of `FilterChainProxy`.
- .FilterChainProxy
- [[servlet-filterchainproxy-figure]]
- [.invert-dark]
- image::{figures}/filterchainproxy.png[]
- [[servlet-securityfilterchain]]
- == SecurityFilterChain
- javadoc:org.springframework.security.web.SecurityFilterChain[] is used by <<servlet-filterchainproxy>> to determine which Spring Security `Filter` instances should be invoked for the current request.
- The following image shows the role of `SecurityFilterChain`.
- .SecurityFilterChain
- [[servlet-securityfilterchain-figure]]
- [.invert-dark]
- 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 try 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` instances are invoked based upon the URL alone.
- // FIXME: Link to RequestMatcher
- However, `FilterChainProxy` can determine invocation based upon anything in the `HttpServletRequest` by using the `RequestMatcher` interface.
- The following image shows multiple `SecurityFilterChain` instances:
- .Multiple SecurityFilterChain
- [[servlet-multi-securityfilterchain-figure]]
- [.invert-dark]
- 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 is invoked.
- If a URL of `/api/messages/` is requested, it first matches on the `SecurityFilterChain~0~` pattern of `+/api/**+`, so only `SecurityFilterChain~0~` is invoked, even though it also matches on ``SecurityFilterChain~n~``.
- If a URL of `/messages/` is requested, it does not match on the `SecurityFilterChain~0~` pattern of `+/api/**+`, so `FilterChainProxy` continues trying each `SecurityFilterChain`.
- Assuming that no other `SecurityFilterChain` instances match, `SecurityFilterChain~n~` is invoked.
- // FIXME: add link to pattern matching
- Notice that `SecurityFilterChain~0~` has only three security `Filter` instances configured.
- However, `SecurityFilterChain~n~` has four security `Filter` instances configured.
- It is important to note that each `SecurityFilterChain` can be unique and can be configured in isolation.
- In fact, a `SecurityFilterChain` might have zero security `Filter` instances if the application wants Spring Security to ignore certain requests.
- // FIXME: add link to configuring multiple `SecurityFilterChain` instances
- [[servlet-security-filters]]
- == Security Filters
- 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/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.
- 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].
- 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()
- }
- }
- ----
- ======
- 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/basic.adoc[BasicAuthenticationFilter] | `HttpSecurity#httpBasic`
- | xref:servlet/authentication/passwords/form.adoc#servlet-authentication-form[UsernamePasswordAuthenticationFilter] | `HttpSecurity#formLogin`
- | 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, xref:servlet/authentication/architecture.adoc[the authentication filters] are invoked to authenticate the request.
- 3. Third, xref:servlet/authorization/authorize-http-requests.adoc[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 DEBUG 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 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]
- ----
- 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 Filters to the Filter Chain
- 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 <<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.
- 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, 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]
- ======
- 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()
- }
- ----
- ======
- <1> Use `HttpSecurity#addFilterAfter` to add the `TenantFilter` after the `AnonymousAuthenticationFilter`.
- 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.
- 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.
- ==== 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.
- 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]
- ----
- @Bean
- public FilterRegistrationBean<TenantFilter> tenantFilterRegistration(TenantFilter filter) {
- FilterRegistrationBean<TenantFilter> registration = new FilterRegistrationBean<>(filter);
- registration.setEnabled(false);
- return registration;
- }
- ----
- 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]]
- == Handling Security Exceptions
- The javadoc:org.springframework.security.web.access.ExceptionTranslationFilter[] allows translation of javadoc:org.springframework.security.access.AccessDeniedException[] and javadoc:org.springframework.security.core.AuthenticationException[] into HTTP responses.
- `ExceptionTranslationFilter` is inserted into the <<servlet-filterchainproxy>> as one of the <<servlet-security-filters>>.
- The following image shows the relationship of `ExceptionTranslationFilter` to other components:
- [.invert-dark]
- 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> As described in <<servlet-filters-review>>, invoking `FilterChain.doFilter(request, response)` is the equivalent of invoking the rest of the application.
- This means that if another part of the application, (xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here.
- <2> If the user is not authenticated or it is an `AuthenticationException`, __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 javadoc:org.springframework.security.web.savedrequest.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 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"/>
- ----
- ======
- [[requestcacheawarefilter]]
- === RequestCacheAwareFilter
- The javadoc:org.springframework.security.web.savedrequest.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>
- ----
- ====
|