CSRF攻击

1. 什么是CSRF攻击?

CSRF攻击是一种利用用户在目标网站上已认证的会话执行非预期操作的攻击方式。攻击者通过欺骗用户使其在受信任的网站上执行恶意操作,如转账、修改账户信息等。攻击者利用用户的信任关系和浏览器的自动发送请求的特性,向目标网站发送伪造的请求。

2. CSRF攻击的常见方式

2.1 直接链接方式

攻击者通过诱使用户点击恶意链接,向目标网站发送伪造请求。当用户点击链接时,浏览器会自动发送包含用户认证信息的请求,从而执行攻击者指定的操作。

2.2 图片/资源引用方式

攻击者将恶意请求嵌入到图片、脚本或其他资源引用中,并将其插入到受信任网站的页面中。当用户访问页面时,浏览器会自动加载并发送恶意请求。

2.3 表单提交方式

攻击者创建一个包含恶意请求的表单,并将其隐藏在一个看似正常的页面中。当用户在该页面上执行某些操作(如点击按钮)时,浏览器会自动提交表单,从而执行攻击者指定的操作。

3. CSRF攻击的防御方式

3.1 随机令牌(Synchronizer Token)模式

在用户的会话中生成一个随机的令牌(CSRF令牌),并将其嵌入到每个请求中。服务器在处理请求时,验证请求中的CSRF令牌是否与用户会话中的令牌匹配。

示例代码(Java Servlet):

// 生成CSRF令牌并存储到用户会话中
String csrfToken = UUID.randomUUID().toString();
request.getSession().setAttribute("csrfToken", csrfToken);
// 在表单中添加隐藏字段来包含CSRF令牌
<form action="/process" method="POST">
    <input type="hidden" name="csrfToken" value="<%= request.getSession().getAttribute("csrfToken") %>">
    <!-- 其他表单字段 -->
    <button type="submit">提交</button>
</form>
// 在请求处理中验证CSRF令牌
String csrfToken = request.getParameter("csrfToken");
String sessionToken = (String) request.getSession().getAttribute("csrfToken");
if (csrfToken == null || !csrfToken.equals(sessionToken)) {
    // CSRF令牌验证失败,拒绝请求
    // 可以抛出异常或者返回错误页面等

3.2 双重提交Cookie(Double Submit Cookie)模式

在用户的浏览器中设置两个Cookie,一个是用于认证的Session Cookie,另一个是随机生成的CSRF Cookie。在每个请求中,服务器会比较这两个Cookie的值是否匹配。

示例代码(JavaScript + Express):

// 在登录成功时,将随机生成的CSRF令牌存储到浏览器的Cookie中
const csrfToken = generateCsrfToken();
res.cookie('csrfToken', csrfToken, { httpOnly: true });
// 在每个请求中,比较Session Cookie和CSRF Cookie的值
app.post('/process', (req, res) => {
    const sessionToken = req.session.csrfToken;
    const csrfToken = req.param.csrfToken;
    if (sessionToken !== csrfToken) {
        // CSRF令牌验证失败,拒绝请求
        // 可以返回错误响应或者重定向到错误页面等
    // 处理请求

下面是一个使用Spring Boot框架实现随机令牌(Synchronizer Token)和双重提交Cookie(Double Submit Cookie)防御CSRF攻击的示例代码

@RestController
public class CsrfDemoController {
    private static final String CSRF_TOKEN_ATTR = "csrfToken";
    @GetMapping("/")
    public String index(HttpServletRequest request) {
        // 生成CSRF令牌并存储到用户会话中
        String csrfToken = UUID.randomUUID().toString();
        request.getSession().setAttribute(CSRF_TOKEN_ATTR, csrfToken);
        // 在页面中输出CSRF令牌
        return "<form action=\"/process\" method=\"POST\">\n" +
                "    <input type=\"hidden\" name=\"csrfToken\" value=\"" + csrfToken + "\">\n" +
                "    <button type=\"submit\">提交</button>\n" +
                "</form>";
    @PostMapping("/process")
    //csrfToken是前端从cookie中获取,作为参数传入,而不是服务端直接从cookie获取
    public String process(HttpServletRequest request, @RequestParam("csrfToken") String csrfToken) {
        // 在请求处理中验证CSRF令牌
        String sessionToken = (String) request.getSession().getAttribute(CSRF_TOKEN_ATTR);
        if (!csrfToken.equals(sessionToken)) {
            return "CSRF令牌验证失败,拒绝请求";
        // 处理请求
        return "请求处理成功";
    @Configuration
    public static class CsrfConfig extends WebMvcConfigurerAdapter {
        @Override
        public void addInterceptors(InterceptorRegistry registry) {
            registry.addInterceptor(new CsrfInterceptor());
    public static class CsrfInterceptor extends HandlerInterceptorAdapter {
        private static final String CSRF_COOKIE_NAME = "csrfToken";
        @Override
        public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
            // 生成CSRF令牌并设置到响应的Cookie中
            String csrfToken = UUID.randomUUID().toString();
            Cookie csrfCookie = new Cookie(CSRF_COOKIE_NAME, csrfToken);
            csrfCookie.setPath("/");
            response.addCookie(csrfCookie);
            // 将CSRF令牌存储到用户会话中
            request.getSession().setAttribute(CSRF_TOKEN_ATTR, csrfToken);
            return true;
    public static void main(String[] args) {
        SpringApplication.run(CsrfDemoController.class, args);

在上述示例中, index() 方法用于生成包含CSRF令牌的表单, process() 方法用于处理提交的表单请求。 CsrfInterceptor 拦截器负责在每个请求前生成并设置CSRF令牌的Cookie,并将CSRF令牌存储到用户会话中。

3.3 SameSite Cookie属性

将Cookie的SameSite属性设置为Strict或Lax,以限制Cookie只能在同一站点上发送,从而防止跨站点的CSRF攻击。

SameSite=None:

含义:允许跨站请求发送Cookie,即在跨站情况下也会发送Cookie。
要求:必须同时设置Secure属性,即只在通过HTTPS安全连接传输时发送Cookie。

SameSite=Strict:

含义:禁止跨站请求发送Cookie,只允许在同站点请求中发送Cookie。
作用:Cookie仅在用户直接导航到请求的站点时发送,不会在跨站请求中发送。

SameSite=Lax:

含义:在某些情况下禁止跨站请求发送Cookie,但允许在安全的GET请求中发送Cookie。
作用:除了以下情况,都会在跨站请求中禁止发送Cookie:
在跨站POST请求中,或者POST请求的目标是跨站的。
在跨站的<a>标签点击导航中。

示例代码( ASP.NET Core):

// 在配置Cookie时设置SameSite属性
services.Configure<CookiePolicyOptions>(options =>
    options.MinimumSameSitePolicy = SameSiteMode.Strict;
    options.HttpOnly = HttpOnlyPolicy.Always;
// 在发送Cookie时设置SameSite属性