乐闻世界logo
搜索文章和话题

How to implement CSRF protection in Spring Boot?

2月19日 17:52

There are multiple ways to implement CSRF protection in Spring Boot, and Spring Security provides built-in CSRF protection mechanisms.

Spring Security CSRF Protection Overview

Spring Security enables CSRF protection by default, which works through the following:

  • Generates CSRF Token
  • Stores Token in server session
  • Automatically adds Token to forms
  • Verifies Token in requests

Configuration Methods

1. Use Default Configuration (Recommended)

Spring Security enables CSRF protection by default, no additional configuration needed.

java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers("/public/**").permitAll() .anyRequest().authenticated() .and() .formLogin() .loginPage("/login") .permitAll(); } }

2. Customize CSRF Token Storage

By default uses HttpSessionCsrfTokenRepository, can customize storage method.

java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) .and() .authorizeRequests() .anyRequest().authenticated(); } }

Custom Token Repository

java
public class CustomCsrfTokenRepository implements CsrfTokenRepository { @Override public CsrfToken generateToken(HttpServletRequest request) { String token = UUID.randomUUID().toString(); return new DefaultCsrfToken("X-CSRF-TOKEN", "_csrf", token); } @Override public void saveToken(CsrfToken token, HttpServletRequest request, HttpServletResponse response) { // Custom storage logic request.getSession().setAttribute("_csrf", token); } @Override public CsrfToken loadToken(HttpServletRequest request) { // Custom load logic return (CsrfToken) request.getSession().getAttribute("_csrf"); } } @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(new CustomCsrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); } }

3. Disable CSRF Protection (Not Recommended)

In some cases may need to disable CSRF protection (e.g., REST API).

java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .disable() .and() .authorizeRequests() .anyRequest().authenticated(); } }

4. Partially Disable CSRF Protection

Disable CSRF protection only for specific paths.

java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .ignoringAntMatchers("/api/**", "/public/**") .and() .authorizeRequests() .antMatchers("/api/**").permitAll() .anyRequest().authenticated(); } }

Frontend Integration

1. Thymeleaf Template

Spring Security automatically adds CSRF Token in Thymeleaf templates.

html
<!DOCTYPE html> <html xmlns:th="http://www.thymeleaf.org"> <head> <title>Login</title> </head> <body> <form th:action="@{/login}" method="post"> <!-- CSRF Token automatically added --> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button> </form> </body> </html>

2. JSP Template

jsp
<%@ taglib prefix="sec" uri="http://www.springframework.org/security/tags" %> <form action="/login" method="post"> <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/> <input type="text" name="username" placeholder="Username"> <input type="password" name="password" placeholder="Password"> <button type="submit">Login</button> </form>

3. AJAX Requests

javascript
// Get CSRF Token function getCsrfToken() { const metaTag = document.querySelector('meta[name="_csrf"]'); const headerName = document.querySelector('meta[name="_csrf_header"]'); return { token: metaTag ? metaTag.getAttribute('content') : '', headerName: headerName ? headerName.getAttribute('content') : 'X-CSRF-TOKEN' }; } // Send AJAX request function sendAjaxRequest(url, data) { const { token, headerName } = getCsrfToken(); return fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json', [headerName]: token }, body: JSON.stringify(data) }); } // Usage example sendAjaxRequest('/api/data', { name: 'John' }) .then(response => response.json()) .then(data => console.log(data));

4. Add Meta Tags in HTML

html
<!DOCTYPE html> <html> <head> <meta name="_csrf" th:content="${_csrf.token}"/> <meta name="_csrf_header" th:content="${_csrf.headerName}"/> <title>My App</title> </head> <body> <!-- Page content --> </body> </html>

Advanced Configuration

1. Custom CSRF Token Generator

java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenRepository(csrfTokenRepository()) .and() .authorizeRequests() .anyRequest().authenticated(); } @Bean public CsrfTokenRepository csrfTokenRepository() { HttpSessionCsrfTokenRepository repository = new HttpSessionCsrfTokenRepository(); repository.setHeaderName("X-CSRF-TOKEN"); repository.setParameterName("_csrf"); return repository; } }

2. Custom CSRF Token Validator

java
public class CustomCsrfTokenValidator implements CsrfTokenValidator { @Override public boolean validateToken(HttpServletRequest request, CsrfToken token) { // Custom validation logic String requestToken = request.getHeader(token.getHeaderName()); return token.getToken().equals(requestToken); } } @Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http .csrf() .csrfTokenValidator(new CustomCsrfTokenValidator()) .and() .authorizeRequests() .anyRequest().authenticated(); } }
java
@Configuration @EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { @Bean public CookieSerializer cookieSerializer() { DefaultCookieSerializer serializer = new DefaultCookieSerializer(); serializer.setCookieName("SESSION"); serializer.setCookiePath("/"); serializer.setDomainNamePattern("^.+?\\.(\\w+\\.[a-z]+)$"); serializer.setSameSite("Lax"); serializer.setUseHttpOnlyCookie(true); serializer.setUseSecureCookie(true); return serializer; } }

Testing CSRF Protection

1. Unit Tests

java
@RunWith(SpringRunner.class) @SpringBootTest @AutoConfigureMockMvc public class CsrfProtectionTest { @Autowired private MockMvc mockMvc; @Test public void testCsrfProtection() throws Exception { mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .content("{\"amount\":100}")) .andExpect(status().isForbidden()); } @Test public void testWithValidCsrfToken() throws Exception { MvcResult result = mockMvc.perform(get("/csrf")) .andReturn(); String csrfToken = result.getResponse().getContentAsString(); mockMvc.perform(post("/transfer") .contentType(MediaType.APPLICATION_JSON) .header("X-CSRF-TOKEN", csrfToken) .content("{\"amount\":100}")) .andExpect(status().isOk()); } }

2. Integration Tests

java
@RunWith(SpringRunner.class) @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) @AutoConfigureMockMvc public class CsrfIntegrationTest { @Autowired private TestRestTemplate restTemplate; @Test public void testCsrfWithRestTemplate() { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_JSON); // Without CSRF Token HttpEntity<String> request = new HttpEntity<>("{\"amount\":100}", headers); ResponseEntity<String> response = restTemplate.postForEntity("/transfer", request, String.class); assertThat(response.getStatusCode()).isEqualTo(HttpStatus.FORBIDDEN); } }

Common Issues

1. AJAX Request 403 Error

Cause: Missing CSRF Token Solution: Add CSRF Token in request header

javascript
fetch('/api/data', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': getCsrfToken() }, body: JSON.stringify(data) });

2. Token Expiration in Multiple Tabs

Cause: Session expiration or Token mismatch Solution: Ensure all tabs use same session

3. File Upload Failure

Cause: File upload cannot use form Token Solution: Use request header or pre-signed URL

Best Practices

  1. Use default configuration: Spring Security default configuration is secure enough
  2. Use Cookie to store Token: Easier for frontend to access
  3. Add Token for AJAX requests: Ensure all requests include Token
  4. Regularly update Token: Reduce Token leakage risk
  5. Combine with other protection measures: Like SameSite Cookie, Origin verification
  6. Test CSRF protection: Ensure protection mechanisms work properly

Summary

Spring Boot provides comprehensive CSRF protection mechanisms through Spring Security. The default configuration is already secure enough, and you can customize Token storage and validation logic as needed. Frontend needs to ensure all requests include valid CSRF Tokens, especially AJAX requests. Combining with other protection measures can build a stronger security protection system.

标签:CSRFSpring Boot