Mastering CSRF Protection in Spring Boot: From Theory to Code
This guide explains what CSRF attacks are, outlines common defense strategies such as captchas, referer checks, and token validation, and provides a complete Spring Boot implementation—including custom annotations, token storage with Guava or Redis, an interceptor, configuration, and a token‑generation endpoint—complete with testing steps.
What is CSRF?
CSRF (Cross‑Site Request Forgery) is an attack where the attacker tricks a user's browser into sending authenticated requests to a target site, performing actions such as sending emails, messages, or even transferring money. Because the browser has a valid session, the target site assumes the request is legitimate.
The attack exploits the fact that simple authentication verifies the request originates from a user's browser but cannot confirm the user intentionally initiated it.
Typical consequences include privacy leaks, account takeover, and unauthorized purchases or transfers.
How to Defend Against CSRF
Submit a CAPTCHA: add a random code to forms to force user interaction.
Referer Check: reject requests that do not originate from trusted pages.
Token Validation: include a randomly generated token in the request (parameter or header) and verify it on the server side.
API Security Implementation (Without Spring Security)
Below is a custom implementation using Spring Boot, Guava, and Redis.
Dependencies
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>18.0</version>
</dependency>
</dependencies>Guava stores tokens in memory, while Redis serves as a distributed token store.
Custom Annotation
@Documented
@Inherited
@Retention(RUNTIME)
@Target({ METHOD, TYPE })
public @interface ApiIdempotent {}TokenStore Interface
public interface TokenStore {
void store(String token);
boolean exists(String token);
}MemoryTokenStore (Guava)
public class MemoryTokenStore implements TokenStore {
private Cache<String, String> cache = CacheBuilder.newBuilder()
.maximumSize(Integer.MAX_VALUE)
.expireAfterWrite(2, TimeUnit.MINUTES)
.build();
@Override
public void store(String token) {
cache.put(token, token);
}
@Override
public boolean exists(String token) {
boolean result = cache.getIfPresent(token) != null;
cache.invalidate(token);
return result;
}
}RedisTokenStore
public class RedisTokenStore implements TokenStore {
@Resource
private StringRedisTemplate stringRedisTemplate;
@Override
public void store(String token) {
stringRedisTemplate.opsForValue().set(token, token, 2, TimeUnit.MINUTES);
}
@Override
public boolean exists(String token) {
return stringRedisTemplate.delete(token);
}
}Token Validation Interceptor
public class MethodIdempotentCheck implements HandlerInterceptor {
private Logger logger = LoggerFactory.getLogger(MethodIdempotentCheck.class);
@Resource
private StringRedisTemplate stringRedisTemplate;
@Resource
private TokenStore tokenStore;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if (handler instanceof HandlerMethod) {
HandlerMethod hm = (HandlerMethod) handler;
Method method = hm.getMethod();
Class<?> clazz = method.getClass();
if (clazz.isAnnotationPresent(ApiIdempotent.class) || method.isAnnotationPresent(ApiIdempotent.class)) {
if (!checkToken(request)) {
failure(response);
return false;
}
}
}
return true;
}
private void failure(HttpServletResponse response) throws Exception {
response.setContentType("application/json;charset=utf-8");
response.getWriter().write("{\"code\": -1, \"message\": \"非法操作\"}");
}
private boolean checkToken(HttpServletRequest request) {
logger.info("验证token");
String token = request.getParameter("csrf-token");
if (token == null || token.length() == 0) {
token = request.getHeader("csrf-token");
}
logger.info("获取token:{}", token);
if (token == null || token.length() == 0) {
return false;
}
return tokenStore.exists(token);
}
}Configuration
@Configuration
public class CsrfConfig implements WebMvcConfigurer {
@Value("${csrf.patterns:}")
private List<String> patterns = new ArrayList<>();
@Override
public void addInterceptors(InterceptorRegistry registry) {
registry.addInterceptor(tokenInterceptor()).addPathPatterns(patterns);
}
@Bean
public HandlerInterceptor tokenInterceptor() {
return new MethodIdempotentCheck();
}
}Token Generation Endpoint
@RestController
@RequestMapping("/csrf/token")
public class GenerateTokenEndpoint {
@Resource
private TokenStore tokenStore;
@GetMapping("/create")
public Object create() {
String token = UUID.randomUUID().toString().replaceAll("-", "");
tokenStore.store(token);
return token;
}
}Apply @ApiIdempotent to any controller method or class that requires token verification.
Testing the Implementation
1. Request the token from /csrf/token/create (see diagram).
2. Call a protected business API without the token – the request fails.
3. Add the obtained token to the request header and retry – the request succeeds.
All code and steps above constitute a complete CSRF protection solution in Spring Boot.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
Spring Full-Stack Practical Cases
Full-stack Java development with Vue 2/3 front-end suite; hands-on examples and source code analysis for Spring, Spring Boot 2/3, and Spring Cloud.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
