Зачем и нужно ли выключать csrf в SpringSecurity при использовании авторизации через custom токен
Смотрел обучающее видео по авторизации и аутентификации по токену, и там в методе, который подключает фильтры есть запись http.csrf().disable() . Зачем мы отключаем csrf-защиту, человек не пояснил? Вот полный класс для лучшего понятия, о чем я:
@Configuration @RequiredArgsConstructor public class SecurityConfig < private final JwtTokenProvider jwtTokenProvider; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception < http .httpBasic().disable() .csrf().disable() .sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS) .and() .authorizeHttpRequests(authz -> < try < authz .requestMatchers("/api/auth/**").permitAll() .requestMatchers("/api/admin/sensors/**").hasAuthority("ROLE_ADMIN") .requestMatchers("/api/sensors/**").hasAnyAuthority("ROLE_ADMIN", "ROLE_USER") .anyRequest().authenticated() .and() .apply(new JwtConfigurer(jwtTokenProvider)); >catch (Exception e) < throw new RuntimeException(e); >> ); return http.build(); > >
Отслеживать
задан 15 янв в 23:52
0
Сортировка: Сброс на вариант по умолчанию
Знаете кого-то, кто может ответить? Поделитесь ссылкой на этот вопрос по почте, через Твиттер или Facebook.
- spring-security
- csrf
CSRF-токен
В этой статье рассмотрим, что такое Сross-Site Request Forgery и как включить в Spring Boot приложение CSRF-токен — защиту от этого мошенничества. Назвать этот токен можно было бы «анти-CSRF-токен».
Same Origin Policy и CSRF (cross-site request forgery)
Обычно запросы, сделанные в браузере с одного домена на другой, не проходят, поскольку браузер придерживается Same Origin Policy. Это политика безопасности, защищающая одни сайты от других.
Например, пусть пользователь случайно заходит на домен evil.com (мошеннический сайт) и щелкает там яркую кнопку, которая выполняет либо PUT-запрос, либо DELETE-запрос на bank.com. И так случайно получилось, что этот пользователь зарегистрирован на bank.com и в браузере хранятся куки к нему. Благодаря политике безопасности браузера, ничего плохого не случится. Потому что браузер сначала вышлет так называемый «preflight», то есть предварительный OPTIONS-запрос на bank.com с заголовком
Origin: evil.com
и затем отправит за ним настоящий PUT/DELETE-запрос, но только в том случае, если в ответе пришло разрешение на отправку запросов от evil.com. В противном случае в метод PUT/DELETE банковского сайта мы даже не попадем.
То есть предварительный запрос спрашивает у одного домена разрешение на отправку данных с другого. И чтобы bank.com (получатель запросов) отправил положительный ответ, программист bank.com должен знать домен evil.com (отправителя запросов). Тогда программист делает так, чтобы согласно спецификации CORS bank.com отправлял в ответе в заголовке Access-Control-Allow-Origin адрес evil.com. Это означает буквально «сайту evil.com можно ко мне обращаться». Как настроить CORS в Spring Boot читайте тут. CORS — это своего рода послабление Same Origin Policy. В данной статье CORS нет.
Но есть запросы, которые отправляются сразу, без предварительного запроса, так называемые simple requests. Это GET, HEAD, POST с определенным Content-Type. В частности, POST-запросы с формы. Такой запрос сразу идет в контроллер. А учитывая, что куки браузер отправляет автоматически, запрос попадет в защищенный контроллер и выполнит действие на банковском сайте. Хотя ответ получить и распарсить нельзя, действие будет выполнено.
Ниже рассмотрим, как это происходит. В примере одно приложение на одном домене делает POST-запрос на другой домен, где работает второе приложение. Рассмотрим, как защититься от таких запросов с помощью CSRF-токена.
Пример жульничества
Создадим два приложения на Spring Boot:
- мишень для атаки, «банковское» приложение на порту 8080
- и мошеннический сайт на порту 8081
Поскольку в примере мы будем делать запрос на «другой домен», пропишем для localhost:8080 новое имя в файле hosts:
C:\Windows\System32\drivers\etc\hosts
А именно, добавим в файл hosts строку:
127.0.0.1 bank-server
Теперь к приложению-мишени будем обращаться по адресу http://bank-server:8080/..вместо http://localhost:8080..
Приложение-мишень
Итак, пусть в нашем приложении есть контроллер и форма, с которой что-то добавляется:
@Controller public class DocumentController < @PostMapping("/add") public String add(Document document, Model model) < System.out.println(document.getId()+" "+document.getText()+" added"); model.addAttribute("document", document); model.addAttribute("message", "добавлено"); System.out.println("PostMapping /add"); return "add"; >@GetMapping("/add") public String get() < System.out.println("GetMapping /add"); return "add"; >@GetMapping("/") public String main() < return "redirect:/add"; >>
Форма на Thymeleaf выглядит так:
При этом адрес http://bank-server:8080/add защищен, доступ к нему возможен только благодаря куки JSESSIONID после входа с помощью формы логина.
В приложении задан единственный in-memory пользователь с именем user и паролем user:
@EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter < @Bean public PasswordEncoder passwordEncoder() < return NoOpPasswordEncoder.getInstance(); >@Override public void configure(AuthenticationManagerBuilder auth) throws Exception < auth.inMemoryAuthentication() .withUser("user") .password("user") .authorities("ROLE_USER"); >@Override protected void configure(HttpSecurity http) throws Exception < http.authorizeRequests() .anyRequest().authenticated() .and().formLogin(); http.csrf().disable(); >>
Выше прописано, что все запросы доступны только аутентифицированным пользователям (в том числе наша форма — ее получение по адресу /add методом GET и отправка методом POST). Форма находится в шаблоне add.html
Проверку CSRF-токена мы отключили выше отдельной строкой:
http.csrf().disable();
Это сделано для того, чтобы продемонстрировать атаку с помощью второго приложения ниже. А затем включить CSRF-токен обратно. (По умолчанию он и так включен, просто нужно не забывать добавлять его и на форму, что будет в показано самом конце).
Мошенническое приложение
Второе приложение совсем простое, оно состоит из одного view с кнопкой атаки POST (полный код тут):
.Мошенническая кнопка:
Кнопка отправляет форму со скрытыми полями: и text=«document». Если в браузере мы залогинены в приложении http://bank-server:8080 и откроем мошеннический сайт localhost:8081 с вышеприведенной кнопкой и щелкнем ее, то сохраненный куки JSESSIONID отправляется тоже, и мы попадаем в защищенный метод add() контроллера DocumentController банковского сайта. Документ добавляется.
Защита с помощью CSRF-токена
Чтобы защититься от таких запросов, нужно включить CSRF-токен, то есть убрать строку отключения, которую мы добавили выше:
// http.csrf().disable();
Теперь когда пользователь логинится на сайт http://bank-server:8080, ему выделяется специальный CSRF-токен. Он хранится в сессии и должен отправляться как скрытое поле со всех форм (также он должен прилагаться в XMLHttpRequest-запросах PUT, DELETE, POST — это для JavaScript). Выше мы показали только одну атаку, но при определенных настройках Spring MVC в определенных браузерах возможны и более сложные атаки. И хотя браузеры становятся все более безопасными, как и сам Spring MVC, все равно Spring Security имеет вот такое требование и для PUT, и для DELETE-запросов, хотя для них существуют «preflight» запросы от браузера. Но эти настройки CSRF-токена можно и поменять — убрать некоторые методы или некоторые url — сделать так, чтобы для них не требовался токен.
Итак, токен в POST-запросах сейчас требуется. Если оставить все как есть, то наше собственное банковское приложение не заработает — при попытке отправить форму получим 403. Надо сделать так, чтобы выданный CSRF-токен отправлялся. Для этого добавим его как скрытое поле на форму в наш Thymeleaf-шаблон:
Все, теперь и наше приложение работает, и с чужого сайта форму отправить нельзя, потому что мошенники не знают CSRF-токен. В отличие от JSESSIONID, он не хранится в куки браузера и не отправляется автоматически при запросах на банковский сайт.
Итоги
Исходный код обоих приложений есть на GitHub.
Spring Security (_csrf.token)
Если эти строчки закомментить, то я получаю ошибку HTTP Status 405 — Request method ‘POST’ not supported Можете объяснить, пожалуйста, что это за _csrf.token для чего нужен и с чем его едят? P.S. У меня Spring 3, ниже код моего spring-security.xml
Код login.jsp
Login Page .error < padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; color: #a94442; background-color: #f2dede; border-color: #ebccd1; >.msg < padding: 15px; margin-bottom: 20px; border: 1px solid transparent; border-radius: 4px; color: #31708f; background-color: #d9edf7; border-color: #bce8f1; >#login-box Login with Username and Password
$ $
Отслеживать
GermanSevostyanov
задан 7 фев 2016 в 14:38
GermanSevostyanov GermanSevostyanov
802 9 9 серебряных знаков 26 26 бронзовых знаков
CSRF. Вообще странно, что ошибка именно 405. Выложите лучше код самой формы.
7 фев 2016 в 14:51
секунду сейчас прикреплю выше
7 фев 2016 в 14:52
В общем, csrf-это защита от кросс-доменных запросов. Если она включена, а токен не передается, то спринг считает, что это может быть запрос с чужого сайта и блокирует его. Отключить ее можно, убрав из spring-security.xml.
7 фев 2016 в 14:55
Огромное спасибо, не могли бы подсказать, а что передаётся в этом input’e? Можете скопировать в ответ я отмечу.
7 фев 2016 в 14:57
2 ответа 2
Сортировка: Сброс на вариант по умолчанию
CSRF-это защита от кросс-доменных запросов.
Если она включена, а токен не передается, то spring считает, что это может быть запрос с чужого сайта и блокирует его. Отключить ее можно, убрав из spring-security.xml
В самом инпуте находится токен вида 48248eae-70c9-4911-b587-94abbce08929 , который генерирует spring. Получая форму, он сравнивает, что токен совпадает с тем, который он выставил.
Cross Site Request Forgery (CSRF)
In an application where end users can log in, it is important to consider how to protect against Cross Site Request Forgery (CSRF).
Spring Security protects against CSRF attacks by default for unsafe HTTP methods, such as a POST request, so no additional code is necessary. You can specify the default configuration explicitly using the following:
Configure CSRF Protection
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf(Customizer.withDefaults()); return http.build(); >>
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < >> return http.build() > >
To learn more about CSRF protection for your application, consider the following use cases:
- I want to understand CSRF protection’s components
- I need to migrate an application from Spring Security 5 to 6
- I want to store the CsrfToken in a cookie instead of the session
- I want to store the CsrfToken in a custom location
- I want to opt-out of deferred tokens
- I want to opt-out of BREACH protection
- I need guidance integrating Thymeleaf, JSPs or another view technology with the backend
- I need guidance integrating Angular or another JavaScript framework with the backend
- I need guidance integrating a mobile application or another client with the backend
- I need guidance on handling errors
- I want to test CSRF protection
- I need guidance on disabling CSRF protection
Understanding CSRF Protection’s Components
CSRF protection is provided by several components that are composed within the CsrfFilter :
Figure 1. CsrfFilter Components
CSRF protection is divided into two parts:
- Make the CsrfToken available to the application by delegating to the CsrfTokenRequestHandler .
- Determine if the request requires CSRF protection, load and validate the token, and handle AccessDeniedException .
Figure 2. CsrfFilter Processing
- First, the DeferredCsrfToken is loaded, which holds a reference to the CsrfTokenRepository so that the persisted CsrfToken can be loaded later (in ).
- Second, a Supplier (created from DeferredCsrfToken ) is given to the CsrfTokenRequestHandler , which is responsible for populating a request attribute to make the CsrfToken available to the rest of the application.
- Next, the main CSRF protection processing begins and checks if the current request requires CSRF protection. If not required, the filter chain is continued and processing ends.
- If CSRF protection is required, the persisted CsrfToken is finally loaded from the DeferredCsrfToken .
- Continuing, the actual CSRF token provided by the client (if any) is resolved using the CsrfTokenRequestHandler .
- The actual CSRF token is compared against the persisted CsrfToken . If valid, the filter chain is continued and processing ends.
- If the actual CSRF token is invalid (or missing), an AccessDeniedException is passed to the AccessDeniedHandler and processing ends.
Migrating to Spring Security 6
When migrating from Spring Security 5 to 6, there are a few changes that may impact your application. The following is an overview of the aspects of CSRF protection that have changed in Spring Security 6:
- Loading of the CsrfToken is now deferred by default to improve performance by no longer requiring the session to be loaded on every request.
- The CsrfToken now includes randomness on every request by default to protect the CSRF token from a BREACH attack.
The changes in Spring Security 6 require additional configuration for single-page applications, and as such you may find the Single-Page Applications section particularly useful.
See the Exploit Protection section of the Migration chapter for more information on migrating a Spring Security 5 application.
Persisting the CsrfToken
The CsrfToken is persisted using a CsrfTokenRepository .
By default, the HttpSessionCsrfTokenRepository is used for storing tokens in a session. Spring Security also provides the CookieCsrfTokenRepository for storing tokens in a cookie. You can also specify your own implementation to store tokens wherever you like.
Using the HttpSessionCsrfTokenRepository
By default, Spring Security stores the expected CSRF token in the HttpSession by using HttpSessionCsrfTokenRepository , so no additional code is necessary.
The HttpSessionCsrfTokenRepository reads the token from an HTTP request header named X-CSRF-TOKEN or the request parameter _csrf by default.
You can specify the default configuration explicitly using the following configuration:
Configure HttpSessionCsrfTokenRepository
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRepository(new HttpSessionCsrfTokenRepository()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRepository = HttpSessionCsrfTokenRepository() >> return http.build() > >
Using the CookieCsrfTokenRepository
The CookieCsrfTokenRepository writes to a cookie named XSRF-TOKEN and reads it from an HTTP request header named X-XSRF-TOKEN or the request parameter _csrf by default. These defaults come from Angular and its predecessor AngularJS.
See the Cross-Site Request Forgery (XSRF) protection guide and the HttpClientXsrfModule for more recent information on this topic.
You can configure the CookieCsrfTokenRepository using the following configuration:
Configure CookieCsrfTokenRepository
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRepository = CookieCsrfTokenRepository.withHttpOnlyFalse() >> return http.build() > >
The example explicitly sets HttpOnly to false . This is necessary to let JavaScript frameworks (such as Angular) read it. If you do not need the ability to read the cookie with JavaScript directly, we recommend omitting HttpOnly (by using new CookieCsrfTokenRepository() instead) to improve security.
Customizing the CsrfTokenRepository
There can be cases where you want to implement a custom CsrfTokenRepository .
Once you’ve implemented the CsrfTokenRepository interface, you can configure Spring Security to use it with the following configuration:
Configure Custom CsrfTokenRepository
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRepository(new CustomCsrfTokenRepository()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRepository = CustomCsrfTokenRepository() >> return http.build() > >
Handling the CsrfToken
The CsrfToken is made available to an application using a CsrfTokenRequestHandler . This component is also responsible for resolving the CsrfToken from HTTP headers or request parameters.
By default, the XorCsrfTokenRequestAttributeHandler is used for providing BREACH protection of the CsrfToken . Spring Security also provides the CsrfTokenRequestAttributeHandler for opting out of BREACH protection. You can also specify your own implementation to customize the strategy for handling and resolving tokens.
Using the XorCsrfTokenRequestAttributeHandler (BREACH)
The XorCsrfTokenRequestAttributeHandler makes the CsrfToken available as an HttpServletRequest attribute called _csrf , and additionally provides protection for BREACH.
The CsrfToken is also made available as a request attribute using the name CsrfToken.class.getName() . This name is not configurable, but the name _csrf can be changed using XorCsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName .
This implementation also resolves the token value from the request as either a request header (one of X-CSRF-TOKEN or X-XSRF-TOKEN by default) or a request parameter ( _csrf by default).
BREACH protection is provided by encoding randomness into the CSRF token value to ensure the returned CsrfToken changes on every request. When the token is later resolved as a header value or request parameter, it is decoded to obtain the raw token which is then compared to the persisted CsrfToken .
Spring Security protects the CSRF token from a BREACH attack by default, so no additional code is necessary. You can specify the default configuration explicitly using the following configuration:
Configure BREACH protection
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRequestHandler(new XorCsrfTokenRequestAttributeHandler()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() >> return http.build() > >
Using the CsrfTokenRequestAttributeHandler
The CsrfTokenRequestAttributeHandler makes the CsrfToken available as an HttpServletRequest attribute called _csrf .
The CsrfToken is also made available as a request attribute using the name CsrfToken.class.getName() . This name is not configurable, but the name _csrf can be changed using CsrfTokenRequestAttributeHandler#setCsrfRequestAttributeName .
This implementation also resolves the token value from the request as either a request header (one of X-CSRF-TOKEN or X-XSRF-TOKEN by default) or a request parameter ( _csrf by default).
The primary use of CsrfTokenRequestAttributeHandler is to opt-out of BREACH protection of the CsrfToken , which can be configured using the following configuration:
Opt-out of BREACH protection
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRequestHandler = CsrfTokenRequestAttributeHandler() >> return http.build() > >
Customizing the CsrfTokenRequestHandler
You can implement the CsrfTokenRequestHandler interface to customize the strategy for handling and resolving tokens.
The CsrfTokenRequestHandler interface is a @FunctionalInterface that can be implemented using a lambda expression to customize request handling. You will need to implement the full interface to customize how tokens are resolved from the request. See Configure CSRF for Single-Page Application for an example that uses delegation to implement a custom strategy for handling and resolving tokens.
Once you’ve implemented the CsrfTokenRequestHandler interface, you can configure Spring Security to use it with the following configuration:
Configure Custom CsrfTokenRequestHandler
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRequestHandler(new CustomCsrfTokenRequestHandler()) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf < csrfTokenRequestHandler = CustomCsrfTokenRequestHandler() >> return http.build() > >
Deferred Loading of the CsrfToken
By default, Spring Security defers loading of the CsrfToken until it is needed.
The CsrfToken is needed whenever a request is made with an unsafe HTTP method, such as a POST. Additionally, it is needed by any request that renders the token to the response, such as a web page with a tag that includes a hidden for the CSRF token.
Because Spring Security also stores the CsrfToken in the HttpSession by default, deferred CSRF tokens can improve performance by not requiring the session to be loaded on every request.
In the event that you want to opt-out of deferred tokens and cause the CsrfToken to be loaded on every request, you can do so with the following configuration:
Opt-out of Deferred CSRF Tokens
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < XorCsrfTokenRequestAttributeHandler requestHandler = new XorCsrfTokenRequestAttributeHandler(); // set the name of the attribute the CsrfToken will be populated on requestHandler.setCsrfRequestAttributeName(null); http // . .csrf((csrf) ->csrf .csrfTokenRequestHandler(requestHandler) ); return http.build(); > >
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < val requestHandler = XorCsrfTokenRequestAttributeHandler() // set the name of the attribute the CsrfToken will be populated on requestHandler.setCsrfRequestAttributeName(null) http < // . csrf < csrfTokenRequestHandler = requestHandler >> return http.build() > >
By setting the csrfRequestAttributeName to null , the CsrfToken must first be loaded to determine what attribute name to use. This causes the CsrfToken to be loaded on every request.
Integrating with CSRF Protection
For the synchronizer token pattern to protect against CSRF attacks, we must include the actual CSRF token in the HTTP request. This must be included in a part of the request (a form parameter, an HTTP header, or other part) that is not automatically included in the HTTP request by the browser.
The following sections describe the various ways a frontend or client application can integrate with a CSRF-protected backend application:
- HTML Forms
- JavaScript Applications
- Mobile Applications
HTML Forms
To submit an HTML form, the CSRF token must be included in the form as a hidden input. For example, the rendered HTML might look like:
CSRF Token in HTML Form
The following view technologies automatically include the actual CSRF token in a form that has an unsafe HTTP method, such as a POST:
- Spring’s form tag library
- Thymeleaf
- Any other view technology that integrates with RequestDataValueProcessor (via CsrfRequestDataValueProcessor )
- You can also include the token yourself via the csrfInput tag
If these options are not available, you can take advantage of the fact that the CsrfToken is exposed as an HttpServletRequest attribute named _csrf . The following example does this with a JSP:
CSRF Token in HTML Form with Request Attribute
JavaScript Applications
JavaScript applications typically use JSON instead of HTML. If you use JSON, you can submit the CSRF token within an HTTP request header instead of a request parameter.
In order to obtain the CSRF token, you can configure Spring Security to store the expected CSRF token in a cookie. By storing the expected token in a cookie, JavaScript frameworks such as Angular can automatically include the actual CSRF token as an HTTP request header.
There are special considerations for BREACH protection and deferred tokens when integrating a single-page application (SPA) with Spring Security’s CSRF protection. A full configuration example is provided in the next section.
You can read about different types of JavaScript applications in the following sections:
- Single-Page Applications
- Multi-Page Applications
- Other JavaScript Applications
Single-Page Applications
There are special considerations for integrating a single-page application (SPA) with Spring Security’s CSRF protection.
Recall that Spring Security provides BREACH protection of the CsrfToken by default. When storing the expected CSRF token in a cookie, JavaScript applications will only have access to the plain token value and will not have access to the encoded value. A customized request handler for resolving the actual token value will need to be provided.
In addition, the cookie storing the CSRF token will be cleared upon authentication success and logout success. Spring Security defers loading a new CSRF token by default, and additional work is required to return a fresh cookie.
Refreshing the token after authentication success and logout success is required because the CsrfAuthenticationStrategy and CsrfLogoutHandler will clear the previous token. The client application will not be able to perform an unsafe HTTP request, such as a POST, without obtaining a fresh token.
In order to easily integrate a single-page application with Spring Security, the following configuration can be used:
Configure CSRF for Single-Page Application
@Configuration @EnableWebSecurity public class SecurityConfig < @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception < http // . .csrf((csrf) ->csrf .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) (1) .csrfTokenRequestHandler(new SpaCsrfTokenRequestHandler()) (2) ) .addFilterAfter(new CsrfCookieFilter(), BasicAuthenticationFilter.class); (3) return http.build(); > > final class SpaCsrfTokenRequestHandler extends CsrfTokenRequestAttributeHandler < private final CsrfTokenRequestHandler delegate = new XorCsrfTokenRequestAttributeHandler(); @Override public void handle(HttpServletRequest request, HttpServletResponse response, SuppliercsrfToken) < /* * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of * the CsrfToken when it is rendered in the response body. */ this.delegate.handle(request, response, csrfToken); >@Override public String resolveCsrfTokenValue(HttpServletRequest request, CsrfToken csrfToken) < /* * If the request contains a request header, use CsrfTokenRequestAttributeHandler * to resolve the CsrfToken. This applies when a single-page application includes * the header value automatically, which was obtained via a cookie containing the * raw CsrfToken. */ if (StringUtils.hasText(request.getHeader(csrfToken.getHeaderName()))) < return super.resolveCsrfTokenValue(request, csrfToken); >/* * In all other cases (e.g. if the request contains a request parameter), use * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies * when a server-side rendered form includes the _csrf request parameter as a * hidden input. */ return this.delegate.resolveCsrfTokenValue(request, csrfToken); > > final class CsrfCookieFilter extends OncePerRequestFilter < @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException < CsrfToken csrfToken = (CsrfToken) request.getAttribute("_csrf"); // Render the token value to a cookie by causing the deferred token to be loaded csrfToken.getToken(); filterChain.doFilter(request, response); >>
import org.springframework.security.config.annotation.web.invoke @Configuration @EnableWebSecurity class SecurityConfig < @Bean open fun securityFilterChain(http: HttpSecurity): SecurityFilterChain < http < // . csrf (1) csrfTokenRequestHandler = SpaCsrfTokenRequestHandler() (2) > > http.addFilterAfter(CsrfCookieFilter(), BasicAuthenticationFilter::class.java) (3) return http.build() > > class SpaCsrfTokenRequestHandler : CsrfTokenRequestAttributeHandler() < private val delegate: CsrfTokenRequestHandler = XorCsrfTokenRequestAttributeHandler() override fun handle(request: HttpServletRequest, response: HttpServletResponse, csrfToken: Supplier) < /* * Always use XorCsrfTokenRequestAttributeHandler to provide BREACH protection of * the CsrfToken when it is rendered in the response body. */ delegate.handle(request, response, csrfToken) >override fun resolveCsrfTokenValue(request: HttpServletRequest, csrfToken: CsrfToken): String < /* * If the request contains a request header, use CsrfTokenRequestAttributeHandler * to resolve the CsrfToken. This applies when a single-page application includes * the header value automatically, which was obtained via a cookie containing the * raw CsrfToken. */ return if (StringUtils.hasText(request.getHeader(csrfToken.headerName))) < super.resolveCsrfTokenValue(request, csrfToken) >else < /* * In all other cases (e.g. if the request contains a request parameter), use * XorCsrfTokenRequestAttributeHandler to resolve the CsrfToken. This applies * when a server-side rendered form includes the _csrf request parameter as a * hidden input. */ delegate.resolveCsrfTokenValue(request, csrfToken) >> > class CsrfCookieFilter : OncePerRequestFilter() < @Throws(ServletException::class, IOException::class) override fun doFilterInternal(request: HttpServletRequest, response: HttpServletResponse, filterChain: FilterChain) < val csrfToken = request.getAttribute("_csrf") as CsrfToken // Render the token value to a cookie by causing the deferred token to be loaded csrfToken.token filterChain.doFilter(request, response) >>
(1) request-handler-ref="requestHandler"/> (2) (3)
1
Configure CookieCsrfTokenRepository with HttpOnly set to false so the cookie can be read by the JavaScript application.
2
Configure a custom CsrfTokenRequestHandler that resolves the CSRF token based on whether it is an HTTP request header ( X-XSRF-TOKEN ) or request parameter ( _csrf ).
3
Configure a custom Filter to load the CsrfToken on every request, which will return a new cookie if needed.
Multi-Page Applications
For multi-page applications where JavaScript is loaded on each page, an alternative to exposing the CSRF token in a cookie is to include the CSRF token within your meta tags. The HTML might look something like this:
CSRF Token in HTML Meta Tag
In order to include the CSRF token in the request, you can take advantage of the fact that the CsrfToken is exposed as an HttpServletRequest attribute named _csrf . The following example does this with a JSP:
CSRF Token in HTML Meta Tag with Request Attribute
Once the meta tags contain the CSRF token, the JavaScript code can read the meta tags and include the CSRF token as a header. If you use jQuery, you can do this with the following code:
Include CSRF Token in AJAX Request
$(function () < var token = $("meta[name='_csrf']").attr("content"); var header = $("meta[name='_csrf_header']").attr("content"); $(document).ajaxSend(function(e, xhr, options) < xhr.setRequestHeader(header, token); >); >);
Other JavaScript Applications
Another option for JavaScript applications is to include the CSRF token in an HTTP response header.
One way to achieve this is through the use of a @ControllerAdvice with the CsrfTokenArgumentResolver . The following is an example of @ControllerAdvice that applies to all controller endpoints in the application:
CSRF Token in HTTP Response Header
@ControllerAdvice public class CsrfControllerAdvice < @ModelAttribute public void getCsrfToken(HttpServletResponse response, CsrfToken csrfToken) < response.setHeader(csrfToken.getHeaderName(), csrfToken.getToken()); >>
@ControllerAdvice class CsrfControllerAdvice < @ModelAttribute fun getCsrfToken(response: HttpServletResponse, csrfToken: CsrfToken) < response.setHeader(csrfToken.headerName, csrfToken.token) >>
Because this @ControllerAdvice applies to all endpoints in the application, it will cause the CSRF token to be loaded on every request, which can negate the benefits of deferred tokens when using the HttpSessionCsrfTokenRepository . However, this is not usually an issue when using the CookieCsrfTokenRepository .
It is important to remember that controller endpoints and controller advice are called after the Spring Security filter chain. This means that this @ControllerAdvice will only be applied if the request passes through the filter chain to your application. See the configuration for single-page applications for an example of adding a filter to the filter chain for earlier access to the HttpServletResponse .
The CSRF token will now be available in a response header ( X-CSRF-TOKEN or X-XSRF-TOKEN by default) for any custom endpoints the controller advice applies to. Any request to the backend can be used to obtain the token from the response, and a subsequent request can include the token in a request header with the same name.
Mobile Applications
Like JavaScript applications, mobile applications typically use JSON instead of HTML. A backend application that does not serve browser traffic may choose to disable CSRF. In that case, no additional work is required.
However, a backend application that also serves browser traffic and therefore still requires CSRF protection may continue to store the CsrfToken in the session instead of in a cookie.
In this case, a typical pattern for integrating with the backend is to expose a /csrf endpoint to allow the frontend (mobile or browser client) to request a CSRF token on demand. The benefit of using this pattern is that the CSRF token can continue to be deferred and only needs to be loaded from the session when a request requires CSRF protection. The use of a custom endpoint also means the client application can request that a new token be generated on demand (if necessary) by issuing an explicit request.
This pattern can be used for any type of application that requires CSRF protection, not just mobile applications. While this approach isn’t typically required in those cases, it is another option for integrating with a CSRF-protected backend.
The following is an example of the /csrf endpoint that makes use of the CsrfTokenArgumentResolver :