本文目录导读:

我来为你详细介绍Java CSRF防护的完整案例开发。
什么是CSRF攻击
CSRF(Cross-Site Request Forgery)跨站请求伪造,攻击者诱导用户点击链接,在用户不知情的情况下执行恶意操作。
完整的CSRF防护实现案例
1 项目结构
src/main/java/com/example/csrf/
├── config/
│ └── SecurityConfig.java
├── controller/
│ └── TransferController.java
├── util/
│ └── CSRFTokenUtil.java
├── filter/
│ └── CSRFValidationFilter.java
└── Application.java
2 核心工具类 - Token生成与管理
package com.example.csrf.util;
import java.security.SecureRandom;
import java.util.Base64;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
public class CSRFTokenUtil {
private static final String CSRF_TOKEN_KEY = "csrf_token";
private static final SecureRandom random = new SecureRandom();
/**
* 生成CSRF Token
*/
public static String generateToken() {
byte[] tokenBytes = new byte[32];
random.nextBytes(tokenBytes);
return Base64.getUrlEncoder().withoutPadding().encodeToString(tokenBytes);
}
/**
* 存储Token到Session
*/
public static void storeToken(HttpSession session, String token) {
Map<String, String> tokenMap = (Map<String, String>) session.getAttribute(CSRF_TOKEN_KEY);
if (tokenMap == null) {
tokenMap = new HashMap<>();
session.setAttribute(CSRF_TOKEN_KEY, tokenMap);
}
// 存储在session中,key可以是token哈希值或唯一标识
tokenMap.put(token, token);
}
/**
* 验证Token
*/
public static boolean validateToken(HttpServletRequest request, String token) {
HttpSession session = request.getSession(false);
if (session == null) {
return false;
}
Map<String, String> tokenMap = (Map<String, String>) session.getAttribute(CSRF_TOKEN_KEY);
if (tokenMap == null || !tokenMap.containsKey(token)) {
return false;
}
// 验证成功后移除已使用的token(一次性)
tokenMap.remove(token);
return true;
}
/**
* 从请求中获取Token
*/
public static String getTokenFromRequest(HttpServletRequest request) {
// 先从Header获取
String token = request.getHeader("X-CSRF-Token");
if (token == null || token.isEmpty()) {
// 再从请求参数获取
token = request.getParameter("_csrf");
}
return token;
}
}
3 CSRF过滤器
package com.example.csrf.filter;
import com.example.csrf.util.CSRFTokenUtil;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
public class CSRFValidationFilter implements Filter {
// 排除CSRF验证的路径
private static final Set<String> EXCLUDED_PATHS = new HashSet<>(
Arrays.asList("/login", "/logout", "/api/public", "/error")
);
// 需要CSRF保护的HTTP方法
private static final Set<String> PROTECTED_METHODS = new HashSet<>(
Arrays.asList("POST", "PUT", "DELETE", "PATCH")
);
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// 初始化操作
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse,
FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) servletRequest;
HttpServletResponse response = (HttpServletResponse) servletResponse;
String requestURI = request.getRequestURI();
String method = request.getMethod();
// 检查是否需要CSRF保护
if (shouldValidateCSRF(requestURI, method)) {
String token = CSRFTokenUtil.getTokenFromRequest(request);
if (token == null || !CSRFTokenUtil.validateToken(request, token)) {
// Token验证失败
response.setStatus(HttpServletResponse.SC_FORBIDDEN);
response.setContentType("application/json");
response.getWriter().write("{\"error\":\"CSRF token validation failed\"}");
return;
}
}
filterChain.doFilter(request, response);
}
private boolean shouldValidateCSRF(String requestURI, String method) {
// 排除特定路径
for (String excludedPath : EXCLUDED_PATHS) {
if (requestURI.startsWith(excludedPath)) {
return false;
}
}
// 仅对修改操作进行验证
return PROTECTED_METHODS.contains(method.toUpperCase());
}
@Override
public void destroy() {
// 清理操作
}
}
4 安全配置
package com.example.csrf.config;
import com.example.csrf.filter.CSRFValidationFilter;
import com.example.csrf.util.CSRFTokenUtil;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
@Configuration
public class SecurityConfig implements WebMvcConfigurer {
/**
* 注册CSRF过滤器
*/
@Bean
public FilterRegistrationBean<CSRFValidationFilter> csrfFilterRegistration() {
FilterRegistrationBean<CSRFValidationFilter> registration = new FilterRegistrationBean<>();
registration.setFilter(new CSRFValidationFilter());
registration.addUrlPatterns("/*");
registration.setName("csrfValidationFilter");
registration.setOrder(1);
return registration;
}
/**
* 生成并注入CSRF Token到响应
*/
public static void injectCSRFToken(HttpServletRequest request, HttpServletResponse response) {
HttpSession session = request.getSession();
String token = CSRFTokenUtil.generateToken();
CSRFTokenUtil.storeToken(session, token);
// 在响应头中添加Token
response.setHeader("X-CSRF-Token", token);
// 也可以在Cookie中设置(但需要注意Cookie的安全性)
// Cookie cookie = new Cookie("csrf_token", token);
// cookie.setHttpOnly(true);
// cookie.setSecure(true);
// response.addCookie(cookie);
}
}
5 控制器示例
package com.example.csrf.controller;
import com.example.csrf.config.SecurityConfig;
import com.example.csrf.util.CSRFTokenUtil;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Controller
public class TransferController {
/**
* 转账页面 - 生成并传递CSRF Token
*/
@GetMapping("/transfer")
public String transferPage(HttpServletRequest request,
HttpServletResponse response,
Model model) {
// 生成并注入CSRF Token
SecurityConfig.injectCSRFToken(request, response);
// 从session获取当前token
String token = (String) request.getSession().getAttribute("csrf_token");
model.addAttribute("csrfToken", token);
return "transfer";
}
/**
* 转账操作 - 需要CSRF Token验证
*/
@PostMapping("/transfer")
@ResponseBody
public String processTransfer(@RequestParam String amount,
@RequestParam String toAccount,
HttpServletRequest request) {
// 实际业务逻辑
return "Transfer completed successfully";
}
/**
* 获取新的CSRF Token(用于AJAX请求)
*/
@GetMapping("/api/csrf-token")
@ResponseBody
public String getCsrfToken(HttpServletRequest request,
HttpServletResponse response) {
SecurityConfig.injectCSRFToken(request, response);
String token = (String) request.getSession().getAttribute("csrf_token");
return token;
}
}
6 前端HTML模板
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">转账页面</title>
<script src="https://code.jquery.com/jquery-3.6.0.min.js"></script>
</head>
<body>
<h2>转账</h2>
<!-- 表单方式 -->
<form id="transferForm" action="/transfer" method="POST">
<input type="hidden" name="_csrf" th:value="${csrfToken}" />
<div>
<label>金额:</label>
<input type="text" name="amount" required />
</div>
<div>
<label>收款账户:</label>
<input type="text" name="toAccount" required />
</div>
<button type="submit">提交转账</button>
</form>
<!-- AJAX方式 -->
<button onclick="submitTransfer()">AJAX转账</button>
<script>
// 设置全局AJAX CSRF Token
$(document).ready(function() {
var csrfToken = $('input[name="_csrf"]').val();
$(document).ajaxSend(function(e, xhr, options) {
if (options.type.toUpperCase() === 'POST' ||
options.type.toUpperCase() === 'PUT' ||
options.type.toUpperCase() === 'DELETE') {
xhr.setRequestHeader('X-CSRF-Token', csrfToken);
}
});
});
function submitTransfer() {
$.ajax({
url: '/transfer',
type: 'POST',
data: {
amount: 1000,
toAccount: '123456789'
},
success: function(response) {
alert('转账成功');
// 刷新Token
refreshCSRFToken();
},
error: function(xhr) {
if (xhr.status === 403) {
alert('CSRF验证失败');
refreshCSRFToken();
}
}
});
}
function refreshCSRFToken() {
$.get('/api/csrf-token', function(token) {
$('input[name="_csrf"]').val(token);
});
}
</script>
</body>
</html>
7 Spring Boot入口
package com.example.csrf;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
使用Spring Security内置CSRF防护
如果使用Spring Security,可以直接使用其内置的CSRF防护:
package com.example.csrf.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SpringSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf
.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse())
.ignoringRequestMatchers("/api/public/**")
.csrfTokenRequestHandler(new CsrfTokenRequestAttributeHandler())
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/login", "/logout", "/api/public/**").permitAll()
.anyRequest().authenticated()
)
.formLogin(form -> form
.loginPage("/login")
.permitAll()
);
return http.build();
}
}
最佳实践建议
1 Token安全性
- 使用强随机数生成器
- Token应具有足够长度(至少32字节)
- 限制Token有效期
- 每次使用后刷新Token
2 Cookie安全配置
Cookie cookie = new Cookie("csrf_token", token);
cookie.setHttpOnly(true); // 防止XSS读取
cookie.setSecure(true); // 仅HTTPS传输
cookie.setSameSite("Strict"); // 防止跨站请求
cookie.setPath("/");
cookie.setMaxAge(3600); // 1小时过期
3 双重验证
// 验证Referer和Origin头部
public boolean validateReferer(HttpServletRequest request) {
String referer = request.getHeader("Referer");
String origin = request.getHeader("Origin");
if (referer != null && !referer.startsWith("https://yourdomain.com")) {
return false;
}
if (origin != null && !origin.equals("https://yourdomain.com")) {
return false;
}
return true;
}
4 日志记录
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class CSRFValidationFilter extends OncePerRequestFilter {
private static final Logger logger = LoggerFactory.getLogger(CSRFValidationFilter.class);
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain) {
try {
// CSRF验证逻辑
if (!validateCSRF(request)) {
logger.warn("CSRF validation failed for request: {} from IP: {}",
request.getRequestURI(),
request.getRemoteAddr());
response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF validation failed");
return;
}
filterChain.doFilter(request, response);
} catch (Exception e) {
logger.error("CSRF filter error", e);
}
}
}
测试CSRF防护
package com.example.csrf;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.web.servlet.MockMvc;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@SpringBootTest
@AutoConfigureMockMvc
public class CSRFProtectionTest {
@Autowired
private MockMvc mockMvc;
@Test
public void testTransferWithoutCSRFToken() throws Exception {
// 测试无CSRF Token的请求应被拒绝
mockMvc.perform(post("/transfer")
.param("amount", "1000")
.param("toAccount", "123456"))
.andExpect(status().isForbidden());
}
@Test
public void testTransferWithInvalidCSRFToken() throws Exception {
// 测试无效CSRF Token应被拒绝
mockMvc.perform(post("/transfer")
.header("X-CSRF-Token", "invalid-token")
.param("amount", "1000")
.param("toAccount", "123456"))
.andExpect(status().isForbidden());
}
@Test
public void testTransferWithValidCSRFToken() throws Exception {
// 测试有效CSRF Token应成功
// 这里需要先获取有效的Token
String token = fetchCSRFToken();
mockMvc.perform(post("/transfer")
.header("X-CSRF-Token", token)
.param("amount", "1000")
.param("toAccount", "123456"))
.andExpect(status().isOk());
}
private String fetchCSRFToken() throws Exception {
// 从/token接口获取有效的CSRF Token
var result = mockMvc.perform(get("/api/csrf-token"))
.andExpect(status().isOk())
.andReturn();
return result.getResponse().getContentAsString();
}
}
这个完整的案例涵盖了CSRF防护的主要实现方式,包括自定义实现和Spring Security集成方案,建议根据项目需求选择合适的实现方式。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播