面试题手册

梳理高频技术问题,帮助你按主题复习和查漏补缺。

服务端阅读 05月30日 22:17

什么是 CSRF 攻击?它如何工作,又该怎么防护?

CSRF(跨站请求伪造)不是偷 Cookie,而是借浏览器自动带 Cookie 的机制,冒充用户发起操作。典型流程是:用户已经登录 bank.example,浏览器里有登录 Cookie;随后用户打开攻击者页面,页面自动提交一个转账、改邮箱或删除数据的请求;请求发到 bank.example 时,浏览器会按域名自动带上 Cookie。服务器如果只看 Cookie 判断“用户已登录”,却不校验这次操作是不是从本站页面主动发起,就会把伪造请求当成真实操作。CSRF 成立通常要同时满足几个条件:用户处于登录状态;目标站使用 Cookie、Session 这类浏览器会自动携带的凭证;接口会改变状态;服务端没有校验 CSRF Token、Origin、Referer 或 SameSite 等额外信号。少了其中任何一环,攻击难度都会明显上升。追问CSRF 和普通跨域请求有什么关系?很多人以为浏览器有同源策略,所以 CSRF 发不出去,这是误解。同源策略主要限制攻击者读取跨站响应,不能阻止浏览器发送表单、图片、脚本、跳转这类请求。CSRF 往往不需要读响应,只要服务器执行了“改状态”的动作,攻击就已经成功。CORS 也是类似道理:它控制前端脚本能不能读取响应,不等于所有跨站请求都被拦截。尤其是普通表单提交、图片加载、顶级导航,本来就不依赖 CORS 成功读取响应。GET、POST、JSON API 都会被 CSRF 打中吗?GET 如果只做查询,风险相对小;但如果 GET 做删除、改状态、触发任务,就非常危险,因为 <img>、<script>、<a> 都能轻易触发 GET。POST 表单同样能被恶意页面自动提交,所以“改成 POST”不是完整防护。JSON API 的攻击门槛高一点,因为 application/json 和自定义请求头通常会触发 CORS 预检。但如果服务端错误放开 CORS,或者接口同时兼容表单格式,仍然可能被利用。接口设计上应坚持:读操作和写操作分离,写操作必须带额外校验。CSRF Token 为什么有效?CSRF Token 的作用是证明“请求来自服务端渲染或授权过的本站页面”。服务端生成一个不可预测的随机值,放在页面、meta 标签或接口返回里,前端提交写请求时把它放进隐藏字段或 X-CSRF-Token 请求头。攻击者页面不能读取目标站页面内容,也就拿不到这个随机值。Token 不能随便设计:不要放 URL 里,避免进入日志和 Referer;要绑定用户会话或登录态;过期策略要合理;高风险操作可以使用一次性 Token。Token 校验失败时,应该返回明确的 403,并记录来源、用户、接口和 request id,方便排查误杀。SameSite、Origin、Referer 应该怎么配合?现代防护通常是组合拳。Cookie 设置 SameSite=Lax 可以挡住大量跨站 POST 和 iframe 场景;SameSite=Strict 更安全,但会影响外部链接跳转后的登录体验;必须跨站携带 Cookie 时才用 SameSite=None; Secure。服务端还可以校验 Origin,没有 Origin 时再看 Referer。这两者适合做来源判断和日志审计,但不建议作为唯一防线,因为隐私策略、代理、旧浏览器或特殊跳转可能让头部缺失。更稳的策略是:SameSite 降低默认风险,Token 验证操作意图,Origin/Referer 做辅助拦截。XSS 会不会绕过 CSRF 防护?会。只要攻击者能在你的页面里执行脚本,就可能读取非 HttpOnly 的 CSRF Token,或者直接在同源上下文里调用接口。也就是说,CSRF Token 不能替代 XSS 防护。所以安全设计要分层:Cookie 使用 HttpOnly、Secure、SameSite;页面输出要做转义和 CSP;写接口校验 Token 和来源;高风险操作再加二次确认、幂等号或重新输入密码。不要指望一个机制解决所有问题。示例下面是一个最简单的 CSRF 攻击示例。用户只要打开攻击页面,浏览器就会向目标站发起请求:<form action="https://bank.example/transfer" method="POST"> <input name="to" value="attacker"> <input name="amount" value="1000"></form><script>document.forms[0].submit()</script>服务端防护可以这样做:app.post('/transfer', requireLogin, csrfCheck, async (req, res) => { await transfer(req.user.id, req.body.to, req.body.amount); res.json({ ok: true });});function csrfCheck(req, res, next) { const token = req.get('x-csrf-token') || req.body.csrf_token; if (!token || token !== req.session.csrfToken) return res.sendStatus(403); next();}这段逻辑的重点不是代码长短,而是把“用户已登录”和“用户确实从本站页面发起操作”分开验证。CSRF 正是利用了很多系统只验证前者、忽略后者的漏洞。
服务端阅读 05月30日 21:29

CSRF 是冒充用户,XSS 是控制浏览器吗?

可以这样理解:CSRF 是攻击者借用户身份办事,XSS 是攻击者把脚本塞进页面里办事。CSRF 不一定能读取用户数据,它更关心“让服务器执行一个动作”;XSS 则运行在目标网站页面上下文中,能读页面内容、调同源接口、窃取可访问的 Token,危害范围通常更大。追问CSRF 为什么叫冒充用户?因为请求到达服务器时带着用户的登录 Cookie,看起来像用户自己发起。攻击者不需要知道密码,也不需要拿到 Cookie 内容。XSS 为什么叫控制浏览器?XSS 恶意代码运行在目标网站页面里,能访问当前页面允许访问的资源,比如表单内容和同源接口。两者防护会互相替代吗?不会。CSRF Token 解决请求意图校验,输出编码和 CSP 解决脚本注入执行,保护层面不同。实战最常见误判是什么?以为 POST 就不会被 CSRF,或以为 HttpOnly 就没有 XSS 风险。两种判断都不完整。示例<img src="https://app.example.com/change-email?email=a@evil.com"><img src=x onerror="fetch('/api/profile')">
服务端阅读 05月30日 21:29

如何防御 CSRF 攻击才不会只防住一半?

防御 CSRF 的核心,是阻止攻击者借浏览器自动携带 Cookie 的能力替用户发起敏感请求。只要登录态放在 Cookie 里,转账、改邮箱、删数据、改密码这类接口都要按跨站诱导来防。稳妥方案是 CSRF Token 打底,再配合 SameSite、Origin 校验和高风险操作二次确认。追问为什么自定义请求头能缓解 CSRF?普通跨站表单、图片、脚本请求不能随意带 X-CSRF-TOKEN。攻击者若用 fetch 加自定义头,会触发 CORS 预检。SameSite=Lax 够不够?对普通站点很有帮助,但不能当唯一防线。浏览器兼容、跨站跳转、第三方嵌入和历史客户端都会带来边界。双重提交 Cookie 有什么限制?它把 Token 同时放在 Cookie 和请求参数里。若子域可写 Cookie 或站点有 XSS,攻击者可能伪造或读取 Token。发现漏洞后优先修哪里?先修所有依赖 Cookie 登录态的写接口,尤其是资金、权限、账号资料和删除类操作。随后补自动化测试。示例<input type="hidden" name="csrf_token" value="{{token}}">
服务端阅读 05月30日 21:29

SameSite Cookie 为什么能防止 CSRF?实际配置怎么写?

SameSite Cookie 能防止 CSRF,是因为它把“跨站请求是否携带 Cookie”的决定权交给浏览器。攻击者可以在自己的网站里放自动提交表单或隐藏图片,请求你的站点;但如果浏览器因为 SameSite 策略不发送登录 Cookie,服务端就无法把这次请求识别成已登录用户操作。追问SameSite 防的是哪类 CSRF?它主要防攻击者借浏览器自动带 Cookie 发起的跨站状态修改请求,比如隐藏表单 POST、图片触发 GET 副作用等。Lax 为什么适合作默认值?Strict 会让外部跳转进入站点时也不带 Cookie,体验较差。Lax 保留顶级 GET 导航登录体验,同时拦住更危险的跨站提交。哪些 Cookie 可以设置 None?只有确实需要跨站发送的 Cookie 才应该设置 None,比如第三方登录状态、嵌入式组件或跨站 SSO 临时票据。SameSite 和 CORS 是一回事吗?不是。CORS 决定前端脚本能否读取跨源响应,SameSite 决定跨站请求是否携带 Cookie。写段配置Set-Cookie: session=abc; Secure; HttpOnly; SameSite=Lax; Path=/
服务端阅读 05月30日 21:29

Spring Boot 如何正确实现 CSRF 防护?

Spring Boot 里的 CSRF 防护不要一上来就关掉。只要项目还依赖浏览器 Cookie 维持登录态,POST、PUT、DELETE 这类改数据请求就可能被第三方页面借用户身份发出去。正确做法是保留 Spring Security 的 CSRF 校验:服务端生成 Token,前端在表单或请求头里带回,服务端再验证它是否属于当前会话。追问CookieCsrfTokenRepository 为什么要 withHttpOnlyFalse?SPA 需要从 Cookie 读取 Token,再写入 X-CSRF-TOKEN 请求头。代价是脚本也能读到它,所以站点存在 XSS 时风险会被放大。CSRF Token 和 SameSite 是二选一吗?不是。SameSite 是浏览器层面的减风险措施,Token 是服务端验证用户意图。敏感业务最好两者都用。AJAX 一直 403 查哪里?先看 header 名称是否正确,Spring 常用 X-CSRF-TOKEN。再看 Token 是否来自同一会话,登录刷新、多标签旧页面提交都可能不匹配。什么时候可以忽略 CSRF?真正无状态 REST API 使用 Authorization Bearer,浏览器不会自动带这个头,CSRF 风险较低;但 Session Cookie API 不能直接忽略。写段配置.csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
服务端阅读 05月30日 21:29

什么是 CSRF 攻击?它需要满足哪些攻击条件?

CSRF 是跨站请求伪造,核心不是偷走用户信息,而是借用用户已经登录的身份去发请求。只要目标站点依赖 Cookie 判断登录态,浏览器又会在请求目标域名时自动带上 Cookie,攻击者就可能诱导用户打开恶意页面,让目标站误以为这次修改密码、转账、删除数据是用户本人操作。它通常需要:用户已登录、目标操作靠 Cookie 认证、接口缺少额外意图校验、攻击者能构造可触发请求。追问CSRF 为什么能利用 Cookie?浏览器发送 Cookie 看的是请求目标域名,不看请求是不是用户主动点的。恶意站不能读取 Cookie,但能诱导浏览器带 Cookie 发请求。哪些接口最容易被打中?所有会改变状态的接口都要重点看,比如转账、改邮箱、改密码、删除数据、绑定账号。GET 接口如果偷偷做写操作也很危险。CORS 拦住跨域响应还有 CSRF 风险吗?有。CORS 主要限制攻击者读取响应,CSRF 很多时候只需要请求被执行,不需要看到结果。实际项目怎么防?常见组合是 CSRF Token + SameSite Cookie + Origin/Referer 校验。Token 证明页面来自本站,SameSite 降低自动带 Cookie 概率。示例<form action="https://bank.example/transfer" method="POST"> <input name="to" value="attacker"></form><script>document.forms[0].submit()</script>
前端阅读 05月30日 21:29

SameSite Cookie 如何防护 CSRF?Strict、Lax、None 怎么选?

SameSite Cookie 防护 CSRF 的关键是让浏览器在跨站请求里少带或不带 Cookie。CSRF 成立,是因为用户登录后,攻击站能诱导浏览器向目标站发请求,而浏览器会自动附带登录 Cookie。SameSite 改变的就是这个默认行为。追问Strict、Lax、None 怎么选?Strict 最严格,适合后台、支付、改密等安全优先页面,但会影响从外部链接进入的登录体验。Lax 是多数业务默认选择;None 只给确实需要跨站携带 Cookie 的场景,并必须配 Secure。SameSite=Lax 能完全替代 CSRF Token 吗?不能。Lax 能挡住大量跨站 POST,但覆盖不了所有业务边界。资金、权限、账号安全仍应保留 Token。SameSite=None 为什么必须配 Secure?因为 None 允许跨站携带 Cookie,现代浏览器要求它只能在 HTTPS 下使用。少了 Secure,很多浏览器会拒收。子域名之间算跨站吗?通常不算。a.example.com 和 b.example.com 在 SameSite 语义下属于同站,因此子域名安全会影响整体站点。写段配置Set-Cookie: sid=abc; Path=/; HttpOnly; Secure; SameSite=LaxSet-Cookie: admin_sid=def; Path=/admin; HttpOnly; Secure; SameSite=Strict
服务端阅读 05月30日 21:29

CSRF 和 XSS 有什么区别?项目里如何区分和防护?

CSRF 和 XSS 都是 Web 安全高频问题,但攻击点完全不同。CSRF 是“冒充用户发请求”:攻击者利用用户已登录的 Cookie,让服务器执行用户并不想做的操作。XSS 是“让脚本跑进用户页面”:攻击者把恶意 JavaScript 注入目标站上下文里,读取页面、窃取 Token、调用接口甚至进一步发起 CSRF。区分时记一句话:CSRF 主要骗服务器,XSS 主要控制浏览器。追问两者攻击前提有什么不同?CSRF 通常要求用户已登录目标站,且敏感接口只靠 Cookie 认证。XSS 要求页面存在输入输出处理漏洞。为什么 XSS 往往更危险?XSS 拿到的是页面执行权,可以读取 DOM、调用接口、窃取非 HttpOnly Token,还可能绕过很多 CSRF 防护。HttpOnly Cookie 能防哪一种?HttpOnly 防止 XSS 直接读 Cookie,但不能阻止脚本发同源请求,也不能单独防 CSRF。防护策略怎么分工?防 CSRF 用 Token、SameSite、Origin/Referer;防 XSS 用输出编码、CSP、避免危险 API 和 HttpOnly/Secure Cookie。示例<form action="https://bank.example/transfer" method="POST"></form><script>fetch('/api/me')</script>
服务端阅读 05月30日 21:29

Referer 头能防护 CSRF 吗?验证时有哪些局限?

Referer 校验能作为 CSRF 防护的一层辅助判断,但不适合当唯一防线。它的做法是对会改变状态的请求,服务端读取 Referer 请求头,确认发起页面来自可信站点;如果来自陌生域名、格式非法或缺失,就拒绝或进入更严格校验流程。它能挡住不少粗糙的跨站表单攻击,但受浏览器隐私策略、Referrer-Policy、HTTPS/HTTP 跳转、代理和客户端实现影响。追问Referer 和 Origin 有什么区别?Origin 只包含协议、域名和端口,不包含完整路径,隐私暴露更少,也更适合安全校验。Referer 信息更完整,但更容易被策略裁剪。没有 Referer 的请求要不要拒绝?管理后台、转账、改密等敏感操作建议拒绝。普通业务可以要求 CSRF Token 通过,并记录日志观察误杀比例。验证 Referer 最常见绕过点是什么?把 includes('example.com') 当安全判断,可能被 example.com.evil.com 绕过。应解析 URL 后比较 hostname。Referrer-Policy 会影响吗?会。no-referrer 可能完全不发 Referer,strict-origin-when-cross-origin 跨站只发源信息。上线前要检查站点策略。写段代码const url = new URL(req.get('referer') || '');if (url.protocol !== 'https:' || !TRUSTED.has(url.hostname)) return res.sendStatus(403);
服务端阅读 05月30日 21:29

双重提交 Cookie 如何防护 CSRF?实现时要注意什么?

双重提交 Cookie 防护 CSRF 的核心是:服务端生成随机 token,同时让浏览器把它放在 Cookie 里,并要求前端在表单字段或请求头里再提交一份。真正的同站页面能读到这份非 HttpOnly 的 CSRF Cookie,所以能把 token 放进请求;恶意站点虽然能诱导浏览器自动带上 Cookie,却读不到目标站 Cookie,也就很难提交同一份 token。服务端比较两边是否一致,就能拦住大多数跨站伪造请求。追问它和传统 CSRF Token 有什么区别?传统 Token 通常保存在服务端 Session 里,请求来了拿提交值和 Session 值比对。双重提交 Cookie 不存服务端状态,更适合分布式和无状态接口,但要更小心 XSS、子域名和 Cookie 覆盖问题。Cookie 能不能设置 HttpOnly?普通双重提交模式下不能,因为前端需要读取 Cookie 后放进请求头或表单字段。更安全的变体是签名双重提交,避免攻击者伪造结构正确的 token。为什么不建议把 token 放 URL?URL 会进入浏览器历史、代理日志、服务端访问日志和第三方统计系统。Token 更适合放请求头或 POST body。最容易踩什么坑?Domain 配太宽,任意子域问题都可能影响主站;只判断字符串相等,没有处理缺失、长度不等和异常输入,也会留下边界问题。写段代码res.cookie('csrf_token', token, { httpOnly:false, secure:true, sameSite:'lax', path:'/' });if (req.cookies.csrf_token !== req.get('x-csrf-token')) return res.sendStatus(403);
服务端阅读 05月30日 21:21

React、Vue、Angular 中如何正确实现 CSRF 防护?

在 React、Vue、Angular 里做 CSRF 防护,关键不是框架语法,而是把服务端发的 CSRF Token 稳定带到每个状态变更请求里。只要登录态依赖 Cookie,前端就要配合后端完成 Token 获取、请求头注入、过期重试和错误提示;如果认证完全使用 Authorization 头,CSRF 压力会小很多,但 XSS 和 Token 存储风险会变高。追问前端应该从哪里拿 CSRF Token?常见方式是服务端在首屏 HTML meta 标签写入 Token,或提供 /csrf-token 接口初始化获取。也可用非 HttpOnly 的 XSRF-TOKEN Cookie。React、Vue、Angular 差异大吗?差异主要在封装位置,不在安全原理。React/Vue 通常在 axios 或 fetch wrapper 统一加头,Angular HttpClient 有 XSRF 支持。SPA 里 Token 过期怎么办?服务端返回 419 或 403 时,可以刷新一次 Token 后重放请求;支付、转账、删除这类非幂等操作不要自动重放。CORS 配好了还需要 CSRF 吗?需要。CORS 控制脚本读取响应,不阻止浏览器发出带 Cookie 的写请求。写段代码axios.interceptors.request.use(config => { const token = document.querySelector('meta[name="csrf-token"]')?.content; if (!['get','head','options'].includes((config.method || 'get').toLowerCase())) config.headers['X-CSRF-Token'] = token; config.withCredentials = true; return config;});
服务端阅读 05月30日 21:21

企业级应用如何设计一套可靠的 CSRF 防护架构?

企业级 CSRF 防护不要只靠某个接口临时加 Token,应该做成统一安全能力:入口层先挡明显跨站请求,应用层校验会话和 Token,高风险业务再做二次确认。架构上要兼顾多域名、SSO、微服务、灰度发布和审计,否则规则一上线就可能误伤登录、支付或第三方集成。追问校验放网关还是业务服务?最好两层都做。网关做通用拦截,如 Fetch Metadata 和 Origin 白名单;业务服务做 Token 是否绑定当前用户、租户和操作。Token 应该怎么存?服务端状态 Token 可放 Redis,便于吊销和一次性使用;双提交 Cookie 性能好,但密钥轮换和重放控制要设计清楚。SSO 和多域名会影响 SameSite 吗?会遇到边界。跨站登录跳转可能需要 SameSite=None; Secure,但业务写操作仍要校验 Origin 和 Token。落地最大坑是什么?没有资产清单就全站强制。老系统可能有跨域表单、WebView、第三方回调,应先观测日志再分级灰度。写段配置if ($http_sec_fetch_site = "cross-site") { set $csrf_block 1; }if ($request_method !~ ^(GET|HEAD|OPTIONS)$) { set $csrf_block "${csrf_block}1"; }if ($csrf_block = "11") { return 403; }
服务端阅读 05月30日 21:21

移动应用需要防 CSRF 吗?如何设计更安全?

移动应用通常不容易遇到传统 CSRF,因为原生 App 不会像浏览器那样自动把站点 Cookie 带到任意跨站请求里。但如果 App 使用 WebView、共享 Cookie、深链唤起、第三方登录页,或者后端同时服务 Web 和 App,就仍然要检查“请求是不是用户真实发起”。追问原生 App 用 Bearer Token 还需要 CSRF Token 吗?大多数情况下不需要,因为 Bearer Token 不会被浏览器自动附加。但 Token 存储不安全会转成账号接管风险。WebView 为什么要特别小心?WebView 容易把 Web 的 Cookie 机制带回来,尤其是内嵌 H5、登录页和支付页。要限制可加载域名和不必要的 JavaScript Bridge。深链会引入什么风险?深链能成为触发器。危险操作不能打开链接后直接执行,必须让用户确认,并由服务端校验幂等号和权限。后端同时支持 Web 和 App 怎么分策略?Web 使用 Cookie + SameSite + Origin + CSRF Token;App 使用 Authorization 头和设备级安全存储。不要让 App 接口默认信任浏览器 Cookie。
服务端阅读 05月30日 21:21

CSRF 防护未来会怎么演进?现在该如何规划?

CSRF 防护未来不会只靠一个 Token,而是会变成“浏览器默认安全能力 + 服务端显式校验 + 风险分层”的组合。现在规划时,优先把 SameSite、Origin/Referer、Fetch Metadata 和关键操作 Token 做成统一基线,再根据业务是否跨站、是否嵌入第三方页面、是否有移动端或开放 API 做例外配置。追问SameSite 变强后还需要 CSRF Token 吗?需要。SameSite=Lax 能挡住很多普通跨站请求,但挡不住所有复杂场景。Token 仍适合关键写操作。Fetch Metadata 能替代传统校验吗?不能完全替代,但适合网关低成本拦截。服务端可根据 Sec-Fetch-Site、Sec-Fetch-Mode 默认拒绝跨站危险方法。CHIPS 和分区 Cookie 会改变模型吗?会改变第三方嵌入场景,但不解决所有业务写操作授权问题。iframe、SSO、第三方插件要提前梳理 Cookie 策略。企业现在怎么改架构?把 CSRF 策略放到统一安全中间件或 API 网关,会话 Cookie 开 Secure、HttpOnly、SameSite=Lax,高风险操作再校验 Token。
服务端阅读 05月28日 06:58

REST API 的 CSRF 防护怎么做?认证方式不同策略完全不同

REST API 的 CSRF 防护和传统 Web 应用有明显差异。核心问题在于:REST API 可能被浏览器、移动应用、第三方服务等多种客户端调用,认证方式也不统一,防护策略必须因地制宜。REST API 为什么仍需关注 CSRF很多人认为 REST API 只用 JSON 就安全了,但事实并非如此。只要你的 API 满足以下条件,CSRF 风险就存在:使用 Cookie 进行身份认证(浏览器会自动携带)允许跨域请求(CORS 配置宽松)接受 application/json 以外的 Content-Type攻击者的思路很直接:构造一个恶意页面,让已登录用户在不知情的情况下向你的 API 发起请求。如果认证靠 Cookie,浏览器会自动带上,服务端无法区分请求来源。关键判断:你的认证方式决定防护策略Token 认证(JWT / OAuth)— 天然免疫Token 放在 Authorization 头中,浏览器不会自动发送,攻击者无法在跨站请求中携带。这是 REST API 最推荐的认证方式:// 服务端验证function authenticateJWT(req, res, next) { const token = req.headers.authorization?.replace("Bearer ", ""); if (!token) return res.status(401).json({ error: "未提供认证令牌" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { res.status(401).json({ error: "令牌无效或已过期" }); }}需要注意:Token 存储在 localStorage 有 XSS 风险,存 HttpOnly Cookie 又会回到 CSRF 问题。生产中推荐短期 Token + 内存存储,刷新 Token 放 HttpOnly Cookie 并配合 CSRF 防护。Cookie 认证 — 必须防护Cookie 认证在前后端分离架构中仍然常见,尤其是需要与旧系统兼容时。这种场景下 CSRF 防护不可省略。Cookie 认证下的四种防护手段手段一:SameSite Cookie 属性最简单的第一道防线,设置 SameSite 限制跨站 Cookie 发送:app.use(session({ secret: process.env.SESSION_SECRET, cookie: { httpOnly: true, secure: true, sameSite: "strict" // 严格模式,完全禁止跨站发送 }}));strict 最安全但会影响从外部链接进入的体验,lax 是更常见的折中选择——允许 GET 请求的顶级导航携带 Cookie,阻止跨站 POST 请求。局限:并非所有浏览器都完整支持 SameSite(虽然主流浏览器已跟上),且移动端 WebView 行为不一致。不能作为唯一防线。手段二:CSRF Token + 自定义请求头这是最经典的防护方案,双重验证确保请求来源可信:// 生成并下发 CSRF Tokenapp.get("/api/csrf-token", (req, res) => { const token = crypto.randomBytes(32).toString("hex"); req.session.csrfToken = token; res.json({ csrfToken: token });});// 验证:通过自定义请求头传递 Tokenfunction csrfGuard(req, res, next) { // 安全方法跳过 if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const headerToken = req.headers["x-csrf-token"]; if (!headerToken || headerToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } next();}前端配合:// 页面初始化时获取 Token,后续请求放在自定义头中const csrfToken = await fetch("/api/csrf-token").then(r => r.json());await fetch("/api/transfer", { method: "POST", headers: { "Content-Type": "application/json", "X-CSRF-Token": csrfToken.csrfToken }, body: JSON.stringify({ to: "user123", amount: 100 })});为什么用自定义头而非表单字段:跨域请求中浏览器不允许自定义头,攻击者无法通过表单或 iframe 伪造带 X-CSRF-Token 头的请求。这比把 Token 放在请求体中更可靠。手段三:Origin / Referer 头验证作为辅助手段,验证请求来源是否合法:function originGuard(req, res, next) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const origin = req.headers.origin || req.headers.referer; const allowed = ["https://example.com", "https://app.example.com"]; if (!origin || !allowed.some(o => origin.startsWith(o))) { return res.status(403).json({ error: "请求来源非法" }); } next();}注意:Origin 头在某些旧浏览器中可能缺失,Referer 可能被隐私策略截断。只能作为补充,不能作为唯一防线。手段四:CORS 严格配置CORS 配置不当会直接暴露 API:app.use(cors({ origin: (origin, callback) => { const allowed = ["https://example.com", "https://app.example.com"]; // 移动端请求可能无 origin,需要其他认证方式保障 if (!origin || allowed.includes(origin)) { callback(null, true); } else { callback(new Error("CORS 拒绝")); } }, credentials: true, // 允许携带 Cookie methods: ["GET", "POST", "PUT", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "X-CSRF-Token"]}));关键点:credentials: true 时 origin 不能用 *,必须明确指定。Access-Control-Allow-Headers 必须包含 X-CSRF-Token,否则浏览器会阻止预检请求通过。混合客户端场景的分层防护实际项目中,API 往往同时服务浏览器和移动端,认证方式混合:function layeredAuth(req, res, next) { const authHeader = req.headers.authorization; if (authHeader?.startsWith("Bearer ")) { // JWT 认证 — 移动端/第三方,无需 CSRF 防护 try { req.user = jwt.verify(authHeader.slice(7), JWT_SECRET); return next(); } catch { return res.status(401).json({ error: "令牌无效" }); } } // Cookie 认证 — 浏览器端,需要 CSRF 防护 if (req.session?.userId) { if (["GET", "HEAD", "OPTIONS"].includes(req.method)) return next(); const csrfToken = req.headers["x-csrf-token"]; if (!csrfToken || csrfToken !== req.session.csrfToken) { return res.status(403).json({ error: "CSRF 验证失败" }); } req.user = { id: req.session.userId }; return next(); } res.status(401).json({ error: "需要身份认证" });}核心原则:根据认证方式决定是否启用 CSRF 防护,Token 认证跳过,Cookie 认证必检。面试回答要点面试中回答这个问题,抓住三个层次:先说判断依据:REST API 是否需要 CSRF 防护,取决于认证方式。Token 认证天然免疫,Cookie 认证必须防护。再说防护手段:SameSite Cookie 是基线,CSRF Token + 自定义请求头是核心,Origin 验证和 CORS 配置是补充,多层组合最可靠。最后说实践:混合客户端场景下,按认证方式分层处理,Token 走 JWT 验证、Cookie 走 CSRF 校验,不要一刀切。
服务端阅读 05月28日 06:53

CSRF 防护的性能影响有哪些,如何进行优化?

CSRF 防护在生产环境中确实会引入性能开销,但合理的架构设计可以在安全与性能之间取得平衡。理解开销来源并采用分层优化策略,是高并发场景下的关键能力。CSRF 防护的性能开销来源Token 生成与验证CSRF Token 的生成依赖加密安全随机数生成器(CSPRNG)。以 Node.js 为例,crypto.randomBytes(32) 单次调用约 0.02ms,万次批量生成耗时约 234ms,即每秒可生成约 4 万个 Token。单次开销极低,但在 QPS 超过 5000 的高并发场景下,Token 生成会成为不可忽视的 CPU 消耗点。Token 验证的开销取决于存储方式。纯字符串比对耗时微乎其微,但涉及数据库查询时,每次验证需要 10-50ms 的 I/O 延迟。在请求量大的写接口上,这意味着数据库连接池容易被占满。会话加载与存储访问传统 CSRF 防护要求在每个状态变更请求中加载会话,验证 Token 是否匹配。Spring Security 5 及更早版本默认在每次请求时加载 CsrfToken,即使该请求不需要 CSRF 校验(如 GET 请求)。Spring Security 6 已改为延迟加载(Deferred CsrfToken),仅在需要时才访问会话存储,显著降低了不必要的开销。页面缓存失效CSRF Token 是用户级别且动态生成的,包含 Token 的页面无法被 CDN 或反向缓存。这是常被忽视的性能影响——一个本可以命中缓存的页面,因为嵌入了 CSRF Token 而必须回源渲染。Cloudflare 的技术分析指出,CSRF Token 是页面级缓存最大的阻碍之一,尤其对于包含表单的页面。核心优化策略策略一:选择合适的 Token 存储方案| 存储方式 | 读延迟 | 写延迟 | 扩展性 | 适用场景 ||---------|--------|--------|--------|---------|| 内存 | <1ms | <1ms | 低 | 单实例、低流量 || Redis | 1-5ms | 1-5ms | 高 | 分布式系统、高流量 || 数据库 | 10-50ms | 10-50ms | 中 | 简单应用、低流量 |生产环境推荐 Redis + 本地内存二级缓存。本地缓存命中时延迟 <0.1ms,未命中时回退到 Redis,兼顾性能与分布式一致性:class CachedTokenService { constructor(redisClient) { this.redis = redisClient; this.localCache = new Map(); this.localTTL = 300000; // 5 分钟本地缓存 this.maxLocalSize = 10000; } async getToken(userId) { // L1: 本地内存 const cached = this.localCache.get(userId); if (cached && Date.now() - cached.ts < this.localTTL) { return cached.token; } // L2: Redis const redisToken = await this.redis.get(`csrf:${userId}`); if (redisToken) { this._setLocal(userId, redisToken); return redisToken; } // L3: 生成新 Token const token = crypto.randomBytes(32).toString('hex'); await this.redis.setex(`csrf:${userId}`, 3600, token); this._setLocal(userId, token); return token; } _setLocal(userId, token) { if (this.localCache.size >= this.maxLocalSize) { const oldest = this.localCache.keys().next().value; this.localCache.delete(oldest); } this.localCache.set(userId, { token, ts: Date.now() }); }}策略二:使用 HMAC 无状态 Token 消除存储开销传统 Token 需要服务端存储,每次验证都访问存储层。HMAC-based Token 将签名嵌入 Token 本身,验证时只需重新计算签名比对,无需任何存储访问:const crypto = require('crypto');class HMACTokenService { constructor(secret) { this.secret = secret; } // 生成:sessionId + 时间戳 + HMAC签名 generate(sessionId) { const timestamp = Math.floor(Date.now() / 3600000); // 按小时粒度 const payload = `${sessionId}:${timestamp}`; const signature = crypto .createHmac('sha256', this.secret) .update(payload) .digest('hex'); return `${payload}:${signature}`; } // 验证:重新计算签名比对,无需存储 validate(token, sessionId) { const [sid, timestamp, signature] = token.split(':'); if (sid !== sessionId) return false; const payload = `${sid}:${timestamp}`; const expected = crypto .createHmac('sha256', this.secret) .update(payload) .digest('hex'); // 检查当前小时和上一个小时的签名,容忍 Token 在小时边界附近生成 const currentHour = Math.floor(Date.now() / 3600000); if (parseInt(timestamp) < currentHour - 1) return false; return crypto.timingSafeEqual( Buffer.from(signature), Buffer.from(expected) ); }}HMAC Token 的优势在于验证延迟从 1-50ms(存储访问)降到 <0.1ms(纯计算),且无需维护 Token 存储的过期清理。Spring Security 的 CookieCsrfTokenRepository 和 Django 的 django.middleware.csrf 都支持类似的签名验证模式。缺点是 Token 无法主动撤销,需依赖较短的有效期。策略三:避免不必要的 Token 生成关键原则:安全请求(GET、HEAD、OPTIONS)不需要 CSRF Token 验证。确保框架配置仅对状态变更请求启用校验。Spring Security 6 的 CsrfToken 延迟加载机制值得借鉴——Token 对象在请求处理链中懒初始化,仅当实际读取或校验时才触发生成和存储访问。Django 的 {% csrf_token %} 模板标签也有类似优化——仅在模板实际渲染该标签时才从会话中读取或生成 Token,GET 请求访问不含表单的页面时完全不触发 Token 逻辑。策略四:解耦页面缓存与 Token 渲染将 CSRF Token 从缓存的 HTML 中剥离,通过独立接口按需获取:<!-- 可被 CDN 缓存的 HTML 模板 --><form action="/transfer" method="POST"> <input type="hidden" name="_csrf" value="" id="csrfInput"> <!-- 其他表单字段 --></form><script> // 页面加载后异步获取 Token fetch('/api/csrf-token', { credentials: 'same-origin' }) .then(r => r.json()) .then(data => { document.getElementById('csrfInput').value = data.token; });</script>这样页面主体可以被 CDN 缓存,Token 通过轻量 API 单独获取。另一种方案是使用 ESI(Edge Side Includes),在 CDN 边缘节点将 Token 片段注入缓存的页面,Nginx 和 Varnish 均支持此特性。策略五:优先采用免 Token 方案现代浏览器提供了不依赖 Token 的 CSRF 防护手段,可以显著降低服务端开销:SameSite Cookie 属性:将 Cookie 设置为 SameSite=Strict 或 SameSite=Lax,浏览器自动阻止跨站请求携带 Cookie。Chrome 80+ 默认 SameSite=Lax,覆盖了大部分 CSRF 攻击场景。这是目前成本最低的防护方式,OWASP 将其列为推荐的 CSRF 防御手段之一。Fetch Metadata 请求头:Chrome 和 Firefox 支持的 Sec-Fetch-Site、Sec-Fetch-Mode 等头,服务端可据此判断请求来源:function isSafeRequest(req) { const site = req.headers['sec-fetch-site']; const mode = req.headers['sec-fetch-mode']; if (site === 'same-origin') return true; if (site === 'none' && mode === 'navigate') return true; return false;}Origin / Referer 头校验:对于有 Origin 头的请求,验证其值是否在白名单内。这种方法无需 Token 存储,开销几乎为零。组合策略实践:生产环境推荐 SameSite Cookie 作为基础防护层,对安全等级更高的操作(如支付、转账)叠加 Token 验证。这样大部分普通请求的 CSRF 防护零开销,仅关键路径承担 Token 成本。性能监控指标生产环境需关注以下 CSRF 相关指标:Token 生成耗时:P99 应 <5ms,超出则检查 CSPRNG 实现Token 验证耗时:含存储访问时 P99 应 <10ms,HMAC 模式应 <0.5ms缓存命中率:本地缓存命中率 >90% 为健康,低于 70% 需扩大缓存容量会话加载频率:对比总请求数与 CSRF 校验请求数,比值过高说明延迟加载未生效Token 长度与安全性的平衡| 配置 | 长度 | 熵 | 性能 | 适用场景 ||-----|------|-----|------|---------|| minimal | 16 字节 | 64 bit | 最优 | 性能敏感、已有其他防护层 || balanced | 32 字节 | 128 bit | 良好 | 通用场景(推荐) || secure | 64 字节 | 256 bit | 可接受 | 安全等级最高的场景 |128 bit 熵(32 字节 hex)是绝大多数场景的最佳选择——碰撞概率可忽略,性能影响极小。追问:CSRF 防护和 CORS 是什么关系?CSRF 和 CORS 解决的是不同层面的跨域问题。CORS 控制的是浏览器是否允许读取跨域响应,CSRF 控制的是浏览器是否自动携带凭据发起跨域请求。一个请求可能被 CORS 阻止但仍然构成 CSRF 风险(如 form 表单提交不受 CORS 约束)。SameSite Cookie 同时减少了 CSRF 和 CORS 的攻击面,但不互为替代。正确做法是同时配置 CORS 白名单和 CSRF 防护,两者互补而非互斥。
服务端阅读 05月27日 20:33

如何使用 CSRF Token 防护跨站请求伪造攻击?

答案:CSRF Token 防护机制CSRF 利用浏览器自动携带 Cookie 伪造用户请求。防御关键是让请求携带攻击者拿不到的凭证——CSRF Token。服务端生成随机 Token 嵌入表单,提交时验证,攻击者无法获取则伪造请求被拒。生成与存储加密安全随机数生成器,至少 128 位。两种存储:Session:存用户 Session,比对请求值与 Session 值,Django/Rails 默认加密 Cookie:加密写 Cookie,解密验证,无状态适合分布式验证流程生成 Token 存 Session,注入表单隐藏字段提交时 Token 随请求发送,AJAX 放 X-CSRF-Token 头不匹配返回 403const token = crypto.randomBytes(32).toString('hex');req.session.csrfToken = token;const t = req.body._csrf || req.headers['x-csrf-token'];if (t !== req.session.csrfToken) return res.sendStatus(403);安全要点不放 URL,Referer 会泄露敏感操作每次刷新防重放必须走 HTTPS防 CSRF 先防 XSS,XSS 能读 TokenSameSite Cookie 配合SameSite=Strict/Lax 阻止跨站带 Cookie 根源阻断 CSRF,同站攻击和旧浏览器仍有风险,Token + SameSite 双重防护最稳。追问:CSRF 和 XSS 区别?XSS 注入脚本窃取数据,CSRF 借身份执行操作。XSS 靠过滤和 CSP,CSRF 靠 Token 和 SameSite,不可替代。
服务端阅读 05月27日 20:31

如何检测和记录 CSRF 攻击,有哪些监控策略?

CSRF 攻击的检测依赖对请求来源和行为的异常识别,核心监控策略包括 Referer/Origin 校验、Token 失效记录、请求频率异常检测和日志溯源分析。检测 CSRF 攻击的三个关键信号1. 请求头异常——非 GET 请求缺失 Referer 或 Origin 与目标域名不匹配,是最直接的攻击特征。Referer Check 不能作为唯一防御手段,但作为检测指标非常有效。2. Token 校验失败——服务端验证 CSRF Token 不通过时,记录该请求的来源 IP、User-Agent、时间戳和目标路径。高频 Token 失败通常意味着定向攻击。3. 行为模式异常——同一用户短时间内发起大量状态变更请求,或请求来源 IP 与历史地理位置不匹配(如1小时内跨1000公里),需要结合业务场景判断。监控策略怎么落地中间件层拦截+记录:在 CSRF Token 校验中间件中,校验失败时同步写入结构化日志,字段包括 eventType(detected/blocked)、confidence(置信度)、patterns(匹配的异常类型)Referer/Origin 白名单监控:维护合法来源域名列表,非白名单来源的写操作请求触发告警频率阈值告警:对同一 IP 或用户 ID,10秒内超过20次写请求触发异常标记,配合 Prometheus + Alertmanager 推送日志聚合分析:按时间窗口统计攻击趋势、Top 攻击 IP、高频异常模式,生成日报辅助策略调优面试追问Q: 为什么 Referer Check 不能单独做防御?Referer 可被用户隐私设置或浏览器策略省略,正常请求也可能缺失 Referer,单独依赖会误拦合法请求。但它作为检测信号仍然有效——有 Referer 但域名不匹配,基本可以判定可疑。Q: SameSite Cookie 能完全替代 CSRF Token 吗?不能。SameSite=Lax 只保护跨站顶级导航的 POST 请求,同站内的 XSS 配合 CSRF 仍可绕过。此外老旧浏览器不支持 SameSite 属性。两者应该组合使用。
服务端阅读 05月27日 20:29

CSRF Token 是如何工作的?

CSRF Token 是服务端生成并下发给客户端的随机值,客户端提交请求时必须携带,服务端校验通过才放行。攻击者因受同源策略限制无法读取页面中的 Token,因此无法伪造合法请求。CSRF Token 如何防御攻击浏览器同源策略阻止恶意页面读取目标站点的响应内容,攻击者拿不到页面里嵌入的 Token 值,构造的请求自然无法通过服务端校验。Token 防御的本质就是利用同源策略制造信息不对称——你有 Cookie 我也有,但我有你没有的 Token。完整工作流程服务端在会话中生成至少 128 位的加密安全随机 Token,通过表单隐藏字段或自定义请求头(如 X-CSRF-Token)下发给客户端。请求提交时客户端携带 Token,服务端比对该值与会话中存储的是否一致,一致则放行,否则拒绝。两种主流实现模式同步器 Token 模式——Token 存在服务端会话中,客户端通过表单隐藏字段提交。Django 的 {% csrf_token %} 和 Express 的 csurf 中间件都用这种模式。双重提交 Cookie 模式——Token 同时写入 Cookie 和请求参数/请求头,服务端比对两者是否一致。好处是服务端不用存 Token,适合无状态架构。但必须给 Cookie 加 SameSite 属性,防止被恶意页面读取。追问:SameSite Cookie 能否替代 CSRF Token?不能完全替代。SameSite=Strict 会阻止所有跨站请求,从外部链接点进来直接 403,体验差;SameSite=Lax 允许顶级导航的 GET 请求,攻击面仍在。加上旧浏览器和移动端 WebView 对 SameSite 支持不一致,实际部署中推荐两者配合:SameSite 做基础防护,Token 做关键操作的强校验。追问:SPA 中如何处理 CSRF Token?前端通过接口获取 Token 后缓存,在 axios 拦截器里自动注入请求头。注意两个坑:会话过期或服务端返回 403 时必须重新获取 Token;不要把 Token 存 localStorage,XSS 一炸就全完了,建议用 HttpOnly Cookie 配合自定义请求头的双重提交方案。追问:微服务架构下 Token 如何共享?两条路:一是统一网关集中生成和校验 Token,会话状态存 Redis,各微服务从缓存读;二是用 HMAC 签名的加密 Token,服务端不存状态,直接验签就行,更适合无状态服务。选哪种取决于你的架构是否已经依赖分布式缓存。
服务端阅读 05月27日 20:27

CSRF 攻击有哪些绕过手法?如何逐一防范?

CSRF 绕过的核心思路是:让服务端"误以为"伪造请求合法。常见手法分五类——窃取/预测 Token、利用 SameSite 配置失误、伪造或缺失 Referer、子域名攻击双重 Cookie、以及利用 JSONP/DNS Rebinding 等协议特性。防御关键是多层叠加:SameSite=Strict + 服务端 Token 校验 + Origin 白名单,任一层被绕过仍有兜底。绕过 CSRF TokenToken 泄露:站点若存在 XSS 漏洞,攻击者可通过 document.querySelector 直接读取页面中的 Token 并外发。这是 Token 防护最大的短板——XSS 一旦存在,CSRF Token 形同虚设。Token 可预测:部分框架用时间戳或弱随机数生成 Token,攻击者拿到一两个样本就能推算规律。必须使用密码学安全的随机生成器(如 crypto.randomBytes),Token 长度不少于 128 位。Token 删除测试:实际渗透中,直接删掉请求中的 Token 参数,约 40% 的应用仍会放行。服务端必须校验 Token 存在且匹配,而非仅校验"若存在则匹配"。 追问:如果站点同时有 XSS 和 CSRF Token,Token 还有用吗?——没用。XSS 能窃取 Token,此时应优先修复 XSS,同时用 SameSite Cookie 作为第二道防线。绕过 SameSite CookieSameSite=None 配置失误:SameSite=None 必须配合 Secure 属性,否则现代浏览器会拒绝设置。不少开发者只写了 SameSite=None 却漏掉 Secure,等于白设。SameSite=Lax 的 GET 绕过:Lax 模式允许顶级导航的 GET 请求携带 Cookie。若接口接受 GET 方法修改数据(如 GET /api/delete?id=1),攻击者用 <img> 或 <a> 标签即可触发。子域名与站内跳转:同站(Same-Site)的判定基于注册域名,子域名间的请求属于同站。攻击者若控制了子域名的 XSS,仍可在同站上下文中发起请求绕过 SameSite。绕过 Referer/Origin 校验Referer 为空:HTTPS→HTTP 降级、隐私插件、书签访问等场景下 Referer 为空。若服务端逻辑是"Referer 为空则放行",等于开了后门。正确做法是拒绝空 Referer 的状态变更请求。Referer 校验不严:只判断 referer.includes("example.com") 时,evil-example.com 也能通过。必须校验完整 Origin,且用白名单而非黑名单。绕过双重提交 Cookie双重提交的原理是 Cookie 和请求参数中各放一份 Token,服务端比对两者一致。但若 Cookie 设在父域名(domain: .example.com),子域名的 XSS 可以写入伪造值,使 Cookie 和参数中的 Token 都由攻击者控制。协议级绕过JSONP 端点:JSONP 的 callback 参数允许攻击者指定函数名,脚本加载时浏览器自动带上 Cookie,天然绕过 Token 校验。应废弃 JSONP,改用 CORS。DNS Rebinding:攻击者控制 DNS 使域名先解析到恶意 IP(满足同源策略),再解析到目标 IP 发起请求。防御手段是校验 Host 头、启用 DNSSEC。防护 Checklist| 防护层 | 要点 ||---|---|| Token | 密码学随机生成、服务端强制校验存在性、绑定会话 || Cookie | SameSite=Strict、HttpOnly、Secure、精确域名 || Origin | 白名单校验、拒绝空 Origin || 架构 | 废弃 JSONP、状态变更接口仅接受 POST、子域名隔离 |多层防护是唯一可靠策略,任一层被突破时其他层仍能拦截。安全没有银弹,但叠加防御能显著提高攻击成本。