本文目录导读:

- 核心思路:统一拦截器 + 统一Token管理
- 方案一:基于Spring Security(最推荐,全自动统一)
- 方案二:自定义统一Filter(手动实现,通用性强)
- 方案三:前后端分离(SPA + API)
- 统一方案核心对比
- 关键注意事项(统一流程的坑)
在Java Web应用中统一防CSRF(跨站请求伪造),核心思路是将Token的生成、校验、更新机制封装到最顶层的框架或过滤器(Filter)/拦截器(Interceptor)中,从而对业务代码透明。
以下是实现统一防CSRF流程的几种主流方案及具体实现步骤:
核心思路:统一拦截器 + 统一Token管理
不再让每个后端接口手动校验Token,而是通过一个统一的全局过滤器(Filter)或Spring MVC拦截器(HandlerInterceptor)来完成。
基于Spring Security(最推荐,全自动统一)
如果你的项目使用了Spring Security,它内置了完备的CSRF防护,且对开发者是透明的,这是最标准的统一方式。
统一流程:
- 开启CSRF保护:Spring Security默认开启。
HttpSecurity.csrf(). - 自动注入Token:Spring Security会自动在每一个
GET请求的响应页面(或通过_csrf属性)中注入一个_csrftoken。 - 统一校验:对于所有
POST、PUT、DELETE等非安全方法,Spring Security的CsrfFilter会自动拦截,并从请求参数或头中提取_csrf值,与Session中的Token比对。 - 失败处理:如果校验失败,自动抛出
AccessDeniedException,返回403或自定义错误页面。
对业务代码的影响: 业务逻辑中不需要写任何校验代码,只需在表单中加入:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
或者在前端JS的请求头中统一设置:
// 从Cookie或meta标签读取CSRF Token
const token = document.querySelector('meta[name="_csrf"]').content;
fetch('/api/data', {
method: 'POST',
headers: { 'X-CSRF-TOKEN': token }
});
优点:零侵入,天然统一,与Spring生态深度集成。
自定义统一Filter(手动实现,通用性强)
适用于不使用Spring Security的场景(如纯Servlet、Spring MVC + Shiro等)。
统一流程设计:
定义统一Filter
创建一个CsrfFilter,实现javax.servlet.Filter。
核心逻辑(在doFilter中)
public class UnifiedCsrfFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) resp;
HttpSession session = request.getSession();
// 步骤A:统一为所有GET/POST初始化Token
if (session.getAttribute("CSRF_TOKEN") == null) {
session.setAttribute("CSRF_TOKEN", UUID.randomUUID().toString());
}
// 步骤B:只拦截需要保护的HTTP方法(如POST、PUT、DELETE)
String method = request.getMethod();
if ("POST".equalsIgnoreCase(method) || "PUT".equalsIgnoreCase(method)
|| "DELETE".equalsIgnoreCase(method)) {
// 从请求中获取Token(支持从Header或Parameter获取)
String clientToken = request.getHeader("X-CSRF-Token");
if (clientToken == null) {
clientToken = request.getParameter("_csrf");
}
String serverToken = (String) session.getAttribute("CSRF_TOKEN");
// 校验:Token不存在 或 不匹配
if (clientToken == null || !clientToken.equals(serverToken)) {
// 返回403 Forbidden
response.sendError(HttpServletResponse.SC_FORBIDDEN, "CSRF Token无效或缺失");
return; // 阻止请求继续
}
// 可选:校验成功后立即刷新Token(增强安全性)
session.setAttribute("CSRF_TOKEN", UUID.randomUUID().toString());
}
// 步骤C:放行合法请求
chain.doFilter(request, response);
}
}
注册Filter到全局
在web.xml或Spring Boot的@Bean中注册,确保对所有URL生效()。
前端配合(统一注入Token)
- Form表单:在页面渲染时,统一在全局布局模板中加入:
<input type="hidden" name="_csrf" value="${session.getAttribute('CSRF_TOKEN')}" /> - Ajax请求:在前端全局拦截器中(如axios、jQuery的
ajaxSend),统一从Cookie或隐藏域读取,并设置请求头:// 假设通过meta标签或某个JS变量获取 let csrfToken = document.querySelector('meta[name="csrf-token"]').content; // 全局设置 axios.defaults.headers.common['X-CSRF-Token'] = csrfToken;
前后端分离(SPA + API)
对于前后端完全分离的项目,通常使用JWT替代Session,此时CSRF防护的逻辑变为:
统一流程: 不使用Session Token防CSRF,改用自定义请求头 + CORS策略。
- 禁用Session:服务端不生成CSRF Token。
- 验证来源(替代方案):
- 检查Origin/Referer:在统一Filter中检查请求的
Origin头是否在允许的白名单内。 - 双重Cookie认证(Double Submit Cookie):服务端生成一个随机Token,同时写入Cookie(
SameSite=Lax或Strict)并作为隐藏请求参数返回,前端发起请求时,从Cookie读取Token并放入Header,服务端校验Cookie中的值与Header中的值是否一致。 - SameSite Cookie:设置Cookie属性
SameSite=Strict或Lax,浏览器会自动阻止跨站请求携带Cookie,从根本上防御CSRF。
- 检查Origin/Referer:在统一Filter中检查请求的
统一代码示例(检查Origin):
public class SameOriginFilter implements Filter {
private static final List<String> ALLOWED_ORIGINS = Arrays.asList("https://yourdomain.com");
@Override
public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
String origin = request.getHeader("Origin");
String method = request.getMethod();
// 只对写操作检查
if ("POST,PUT,DELETE".contains(method)) {
if (origin == null || !ALLOWED_ORIGINS.contains(origin)) {
response.sendError(403);
return;
}
}
chain.doFilter(request, response);
}
}
统一方案核心对比
| 方案 | 统一程度 | 侵入性 | 适用场景 | 推荐指数 |
|---|---|---|---|---|
| Spring Security | ★★★★★ | 几乎0 | 任何Spring Boot/Cloud项目 | ⭐⭐⭐⭐⭐ |
| 自定义Filter | ★★★★☆ | 低,需修改前端模板 | 非Spring生态或自定义需求 | ⭐⭐⭐⭐ |
| 前后端分离 | ★★★★☆ | 中,需前端配合Header | SPA + API | ⭐⭐⭐ |
关键注意事项(统一流程的坑)
- GET请求与CSRF:不要为GET请求校验CSRF Token(不影响状态变更,且容易破坏正常访问)。
- Token刷新策略:如果每个POST/登录后都刷新Token,可能会造成用户体验问题(用户回到上个页面时Token已失效),建议只在登录/登出时刷新。
- 排除特定URL:统一流程中,需要白名单例外(如
/webhook、/callback等来自三方服务的回调接口),可以在Filter配置中添加excludePaths列表。 - 防止Token被窃取:确保Token只通过HTTPS传输,且不使用URL参数传递(优先使用Header)。
- REST API特殊情况:对于非浏览器客户端(如App、Postman),可以选择禁用CSRF或允许自定义Header绕过。
最好的统一方式是方案一(Spring Security),它帮你把生成、校验、更新、失败处理全部封装在框架内部,业务代码完全无需操心,如果你需要手动实现,方案二(自定义Filter) 是最接近“统一”目标的——所有需要保护的请求在Filter内被集中拦截校验,业务层零修改。