API安全:CSRF
字数 1453 2025-08-29 08:31:47
API安全:CSRF防护深入解析与最佳实践
1. CSRF基础概念回顾
CSRF(Cross-Site Request Forgery,跨站请求伪造)是一种常见的Web安全威胁,攻击者诱使用户在已认证的Web应用中执行非预期的操作。在API/微服务架构中,CSRF防护尤为重要但常被忽视。
2. 传统CORS防护的局限性
2.1 Access-Control-Allow-Origin的误解
许多开发者误以为设置Access-Control-Allow-Origin头部就能完全防御CSRF攻击,实际上:
- 它仅阻止浏览器读取跨域响应,不会阻止请求到达服务器
- 服务器端仍然会处理请求并执行操作
- 对于修改数据的请求(如POST),攻击者仍可成功实施CSRF
2.2 实验验证
通过以下Servlet示例验证:
@WebServlet("/data/post")
public class PostData extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) {
// 服务器端仍会处理请求
System.out.println("Received data:"+request.getParameter("data"));
}
}
即使设置了:
resp.addHeader("Access-Control-Allow-Origin", "abc.com");
攻击者仍可构造恶意请求,服务器会执行操作但浏览器会阻止响应读取。
3. 有效的API CSRF防护策略
3.1 严格区分HTTP方法
- GET请求:必须设计为只读操作,绝不修改数据
- 避免同一服务同时支持GET和POST方法
- 遵循RESTful原则,方法语义要明确
3.2 强制Content-Type检查
对于POST/PUT等修改操作:
- 必须要求
Content-Type: application/json - 服务器端严格验证:
if (request.getMethod().equals("POST") &&
(request.getHeader("Content-Type") == null ||
!request.getHeader("Content-Type").toLowerCase()
.startsWith("application/json"))) {
resp.setStatus(HttpServletResponse.SC_TEMPORARY_REDIRECT);
return;
}
原理:浏览器对非简单请求(如修改Content-Type)会先发送OPTIONS预检请求。
3.3 谨慎配置CORS头部
常见错误配置:
// 错误示例:允许任意源OPTIONS请求但错误设置Allow-Headers
if(request.getMethod().equals("OPTIONS")){
resp.addHeader("Access-Control-Allow-Origin", "*");
} else {
resp.addHeader("Access-Control-Allow-Origin", "abc.com");
}
resp.addHeader("Access-Control-Allow-Headers", "Content-Type"); // 危险!
问题:在Access-Control-Allow-Headers中包含Content-Type会绕过预检保护。
3.4 预检请求(Preflight)机制
- 浏览器对"非简单请求"会先发送OPTIONS请求
- 触发预检的条件包括:
- 自定义头部
- Content-Type非以下值:
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
注意:IE浏览器对预检的触发条件有所不同,需要特别测试。
4. 特殊场景处理
4.1 文件上传API
挑战:
- 必须使用
multipart/form-dataContent-Type - 不会触发预检请求
- 传统CSRF防护手段失效
解决方案:
- 使用一次性令牌(CSRF Token)
- 检查Origin/Referer头部(非绝对可靠)
- 限制上传接口的调用源
4.2 微服务架构考虑
- 确保所有服务节点采用一致的CORS策略
- API网关统一处理安全策略
- 避免服务间调用因CORS受阻
5. 最佳实践总结
- 方法分离:严格区分只读(GET)和修改(POST/PUT/DELETE)操作
- 内容类型强制:修改操作必须使用
application/json - CORS精细配置:
- 避免过度宽松的
Access-Control-Allow-Headers - 区分OPTIONS和其他方法的源控制
- 避免过度宽松的
- 预检机制理解:清楚哪些请求会触发预检
- 防御纵深:结合多种防护措施:
- CSRF Tokens(传统Web应用)
- SameSite Cookie属性
- 关键操作二次认证
- 浏览器兼容性:特别是IE的特殊行为
- 日志监控:记录所有跨域请求尝试
6. 配置示例
安全响应过滤器示例:
@WebFilter(filterName = "secureApiFilter")
public class SecureApiFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res;
// 处理预检请求
if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setHeader("Access-Control-Allow-Origin", "trusted.com");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS");
response.setHeader("Access-Control-Max-Age", "3600");
// 谨慎设置允许的头部,不要包含Content-Type
response.setHeader("Access-Control-Allow-Headers", "Authorization, X-Requested-With");
return;
}
// 验证POST请求的内容类型
if ("POST".equalsIgnoreCase(request.getMethod()) ||
"PUT".equalsIgnoreCase(request.getMethod())) {
String contentType = request.getHeader("Content-Type");
if (contentType == null || !contentType.startsWith("application/json")) {
response.setStatus(HttpServletResponse.SC_UNSUPPORTED_MEDIA_TYPE);
return;
}
}
// 设置响应头
response.setHeader("Access-Control-Allow-Origin", "trusted.com");
chain.doFilter(request, response);
}
}
通过以上多层次的防护措施,可以显著提高API服务对CSRF攻击的抵御能力,在微服务和API经济时代构建更可靠的安全防护体系。