spring-security/docs/modules/ROOT/pages/servlet/architecture.adoc

570 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 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]]
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
[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` 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]]
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]]
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` instances should be invoked for the current request.
The following image shows the role of `SecurityFilterChain`.
.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 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]]
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/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:
[tabs]
======
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
Most of the time, 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.
[tabs]
======
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>>.
The following image shows the relationship of `ExceptionTranslationFilter` to other components:
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, (<<servlet-authorization-filtersecurityinterceptor,`FilterSecurityInterceptor`>> 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 {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>
----
====