Skip to content

CSRF 攻击的原理是什么?为什么纯 Token 认证的 API 可以关闭 CSRF 防护? ​

  • 标签:Spring Security、认证授权、后端

🏛 业务架构师 ​

CSRF(Cross-Site Request Forgery,跨站请求伪造)原理:利用浏览器自动携带 Cookie 的特性——用户登录 bank.com 后(Cookie 还有效),访问了恶意页面,该页面偷偷发起 POST bank.com/transfer?to=attacker&amount=10000——浏览器自动带上 bank.com 的 Cookie,银行收到一个"带合法会话"的请求,无法区分是用户本意还是被伪造。整个攻击不需要拿到密码,借的是浏览器的"信任自动携带"机制。

为什么纯 Token 认证(Bearer JWT in Header)的 API 可以关闭 CSRF 防护:CSRF 的成立前提是"认证凭证被浏览器自动携带"(Cookie 随请求自动带上,攻击者不需要知道凭证内容)。而纯 Token 方案:Token 存在 JS 变量/localStorage,每次请求由代码显式放入 Authorization Header——恶意页面无法让浏览器自动附带它(跨站 JS 也拿不到你域的 localStorage)。没有"自动携带",伪造请求到达时就是匿名请求,直接 401。所以 Spring Security 中 SessionCreationPolicy.STATELESS 的 JWT API 链通常 .csrf(csrf -> csrf.disable())。

两个必须警惕的例外:

  1. Token 放 Cookie 里就不同了:如果 JWT 通过 Cookie 传递(为了 HttpOnly 防 XSS),浏览器又恢复"自动携带"——CSRF 风险回归,必须保留 CSRF 防护或用 SameSite=Strict/Lax Cookie 缓解;
  2. 跨子域与 SameSite 的边界:SameSite=Lax 挡跨站 POST 但放行顶级导航 GET——GET 型副作用接口(用 GET 改数据)仍有风险。

我的纪律:关 CSRF 的前提写进架构决策记录——"Bearer Header 显式携带";一旦改用 Cookie 传输 Token,同步恢复 CSRF 防护。安全的配置是"成套"的,单点优化破坏组合前提就是事故。

🏗 系统架构师 ​

CSRF 攻击原理 = 利用浏览器"自动携带 Cookie"的机制:

  • 前提:用户已登录 bank.com(会话 Cookie 存在浏览器);
  • 攻击:用户访问恶意网站 evil.com,页面里埋 <img src="https://bank.com/transfer?to=hacker&amount=10000"> 或自动提交的表单(POST 到 bank.com);
  • 浏览器对 bank.com 的请求自动附上 Cookie——银行服务器看到合法会话,误把攻击者构造的请求当成用户本人的操作。本质:** Cookie 的"谁发起请求"与"谁拥有凭据"分离**——凭据自动附加,但意图不是用户的。

为什么纯 Token 认证的 API 可以关闭 CSRF 防护:

  • 令牌不自动携带:JWT/自定义 token 放 Authorization Header——浏览器不会因为"请求指向某域"就自动附加它,必须由 JS 显式设置;
  • 跨站脚本无法读到令牌再代发:设置该 header 需要先拿到 token——token 在 JS 可访问空间(localStorage/内存),但 evil.com 的脚本受同源策略限制读不到 bank.com 域的存储;跨域带自定义 header 的请求会先触发 CORS 预检(OPTIONS),无授权的预检直接失败——请求根本发不出去;
  • 对照 Cookie 的根本差异:Cookie 是"浏览器自动复述凭据"(不论谁发起),Header token 是"应用主动出示凭据"(出示动作在可信页面的 JS 里)——CSRF 利用的正是前者。

注意(架构师的边界提醒):

  • 前提是"纯 header token"——若把 JWT 放 Cookie 里(BFF 模式常用),CSRF 风险回归,仍需防护(SameSite Cookie + CSRF token 双保险);
  • SameSite=Lax/Strict Cookie 是现代浏览器的通用缓解(跨站请求不带 Cookie),但老浏览器/特定跳转场景有漏网,纵深防御不宜只靠它;
  • 表单/Session 应用必须保留 CSRF(token 或 SameSite)——Spring Security 默认开,关掉前先确认认证形态。

💻 全栈开发专家 ​

CSRF 原理:浏览器对同域请求自动携带 Cookie——攻击者页面(evil.com)里藏一个自动提交表单/img 标签,诱使已登录银行网站的用户的浏览器发出转账请求;请求带着用户的 session Cookie 到达银行,服务端看到合法会话就执行了——「借用户的 Cookie 之手」。

三个前提:基于 Cookie 的会话认证、目标接口有副作用、用户已登录且未退出。

为什么纯 Token(Header 携带)的 API 可以关 CSRF 防护: CSRF 的杀伤力来自「凭证被浏览器自动附加」。JWT 方案中:

  1. token 存 localStorage/内存,请求方显式加 Authorization: Bearer 头——浏览器不会「替攻击者」带上它;
  2. evil.com 的跨站请求(form/img)无法自定义这个 Header(跨站表单只能用简单内容类型、不能设任意头);CORS 预检(OPTIONS)也会拦下非简单请求;
  3. 前提成立才成立:若把 JWT 放进了 Cookie(为了 HttpOnly 防 XSS),自动携带回来了,CSRF 风险重现——要么配 SameSite=Strict/Lax Cookie,要么保留 CsrfFilter。

关闭姿势(无状态 API 标配):

java
http.csrf(c -> c.disable())           // 纯 Header token 场景
    .sessionManagement(s -> s.sessionCreationPolicy(STATELESS))
    .authorizeHttpRequests(a -> a.anyRequest().authenticated())
    .oauth2ResourceServer(o -> o.jwt(withDefaults()));

保留 CSRF 的场景:传统服务端渲染表单/Session 认证——6.x 默认 CsrfFilter(CookieCsrfTokenRepository withTokenBindingPattern,SPA 用 XSRF-TOKEN cookie 回传 X-XSRF-TOKEN 头)。

面试加分:说清「CSRF 防的是凭证的自动携带,XSS 防的是脚本偷凭证」——两者是互补关系不是同义反复。