
1. 项目概述为什么CSRF攻击是Web安全的“隐形杀手”在Web开发和安全领域CSRFCross-Site Request Forgery跨站请求伪造是一个老生常谈却又极易被忽视的漏洞。我见过太多项目前端做得花里胡哨后端逻辑也足够复杂但偏偏在用户身份验证和请求来源校验上栽了跟头。简单来说CSRF攻击就是攻击者利用用户当前已登录的浏览器会话在用户不知情的情况下向目标网站发起一个恶意请求。这个请求会“借用”用户的身份和权限去执行一些非预期的操作比如转账、修改密码、发表评论或者删除数据。想象一下这个场景你正在登录你的网上银行这时你点开了一封看似无害的邮件里的链接或者浏览了一个被攻击者控制的论坛页面。就在这个瞬间一个隐藏的表单可能已经自动提交向银行服务器发送了一条“向攻击者账户转账1000元”的指令。因为你的浏览器里存有有效的登录会话Cookie银行服务器会认为这个请求就是你本人发起的从而执行操作。整个过程你毫无察觉攻击者却已得手。这就是CSRF的可怕之处——它不直接窃取你的密码或Cookie而是“借刀杀人”让服务器自己执行恶意操作。为什么这个话题在今天依然重要因为现代Web应用交互越来越复杂单页面应用SPA、前后端分离架构盛行但很多开发者对HTTP请求的安全边界理解并不深刻。默认情况下浏览器会为每个请求自动带上对应域名的Cookie这为CSRF攻击提供了天然的便利。无论是传统的表单提交还是通过fetch、axios发起的AJAX请求只要缺乏有效的来源验证都可能成为攻击的入口点。接下来我将结合我十多年的实战经验从攻击原理、防御策略到具体代码实现为你彻底拆解CSRF攻击的解决方法让你不仅能理解理论更能直接应用到项目中去。2. 核心原理与攻击场景深度拆解要防御CSRF首先必须彻底理解它的攻击原理和依赖条件。很多防御措施失效根源在于对攻击链的某个环节存在误解。2.1 CSRF攻击成功的三个必要条件根据OWASP的定义和我的实战观察一次成功的CSRF攻击必须同时满足以下三个条件缺一不可关键操作State-Changing Action攻击者诱导用户发起的请求必须是一个能改变服务器状态的请求。这通常是POST、PUT、DELETE或PATCH方法用于执行如创建、更新、删除等操作。单纯的GET请求如果被设计为幂等的即多次执行结果相同如查询数据通常不构成严重威胁但最佳实践是避免用GET执行写操作。基于Cookie的会话管理Cookie-Based Session目标网站完全或主要依赖Cookie尤其是会话Cookie来识别用户身份。当用户登录后服务器会下发一个包含Session ID的Cookie浏览器在后续向该域名发起的所有请求中都会自动携带这个Cookie。攻击者无法直接读取或篡改这个Cookie得益于同源策略但他们可以诱导用户的浏览器去“使用”这个Cookie。请求参数可预测Predictable Request Parameters攻击者能够预先知道或猜出执行目标操作所需的所有HTTP请求参数。例如转账需要recipient收款人和amount金额字段。如果这些参数是固定的或者可以通过其他公开信息推断出来攻击者就能轻易构造出恶意请求。2.2 典型攻击向量与真实案例攻击者会利用各种HTML元素和浏览器行为来发起伪造请求远不止form一种方式。案例一自动提交的隐藏表单最经典这是最直观的攻击方式。攻击者在其控制的恶意网站上嵌入一个隐藏的form其action指向目标网站的关键接口如银行的转账接口并预先填好恶意参数。然后通过JavaScript在页面加载时自动提交表单。!-- 恶意网站 evil.com 上的页面 -- body onloaddocument.forms[0].submit() form actionhttps://bank.com/transfer methodPOST input typehidden nameto valueattacker_account / input typehidden nameamount value10000 / !-- 可能还有其他隐藏字段如CSRF Token如果目标站没有防御这里就不需要 -- /form /body当已登录bank.com的用户访问evil.com时表单会自动提交。浏览器会向bank.com发起一个POST请求并自动附上用户的会话Cookie。服务器看到合法的Cookie便执行了转账。案例二利用img、script等标签的GET请求如果目标网站错误地使用GET请求来执行状态变更操作这是一个严重的反模式攻击将变得更加简单。攻击者甚至不需要表单只需一个诱导用户点击的链接或者一个会自动加载资源的标签。!-- 诱导用户点击的链接 -- a hrefhttps://bank.com/transfer?toattackeramount1000看这个有趣的猫咪视频/a !-- 或者利用图片标签自动发起请求 -- img srchttps://bank.com/transfer?toattackeramount1000 width0 height0 /当用户点击链接或页面加载了那个零尺寸的图片时浏览器就会向目标URL发起一个GET请求再次携带上用户的Cookie。案例三JSON API与复杂AJAX请求在现代前后端分离应用中API通常使用application/json格式。很多人误以为这能天然防御CSRF其实不然。如果服务器没有正确校验Content-Type或实施同源策略攻击依然可能发生。攻击者可以通过构造一个恶意页面使用JavaScript的fetchAPI向目标接口发送JSON格式的POST请求。script fetch(https://api.bank.com/v1/transfer, { method: POST, headers: { Content-Type: application/json, }, body: JSON.stringify({to: attacker, amount: 1000}), credentials: include // 关键指示浏览器发送Cookie }); /script这里的关键是credentials: include它会让浏览器在跨域请求中也包含Cookie。如果目标API的CORS策略配置不当例如设置了Access-Control-Allow-Origin: *且Access-Control-Allow-Credentials: true这个攻击就会成功。实操心得在安全测试中不要只测试表单。对于任何能改变状态的端点无论是表单提交、AJAX调用还是GraphQL查询都要用工具如Burp Suite的CSRF PoC生成器尝试构造跨域请求测试其是否易受攻击。很多时候开发团队会记得给表单加Token却忘了给某个“内部使用”的JSON API加上校验。3. 核心防御策略一CSRF Token同步令牌模式这是目前防御CSRF最主流、最有效的方法被Django、Spring Security、Laravel等众多主流框架内置支持。其核心思想是要求每个状态变更的请求都必须携带一个服务器生成的、不可预测的令牌Token该令牌与当前用户会话绑定。3.1 Token的工作原理与生命周期生成与存储当用户访问包含表单的页面时例如GET /transfer服务器端生成一个高强度的随机字符串作为CSRF Token。这个Token需要与当前用户的会话Session关联存储。通常服务器会将其放入Session中例如session[‘csrf_token’] random_string。下发与携带服务器在渲染HTML页面时将这个Token作为一个隐藏字段插入到表单中。form action/transfer methodPOST input typehidden namecsrf_token valuea1b2c3d4e5f6... !-- 其他表单字段 -- /form对于单页面应用SPAToken可以通过初始的HTML页面或一个专门的API端点如GET /api/csrf-token下发并由前端JavaScript存储通常放在内存或非HttpOnly的Cookie中在后续的AJAX请求中通过自定义HTTP头如X-CSRF-TOKEN携带。验证与销毁当用户提交表单或前端发起状态变更请求时请求必须携带这个Token。服务器收到请求后会从请求中提取Token从表单字段或HTTP头并与当前用户Session中存储的Token进行比对。只有两者一致请求才被允许执行。为了增加安全性Token应在每次验证后失效使用一次即作废或者采用时间戳签名的方式设置较短的有效期。3.2 服务端与前端实现细节后端实现以Node.js/Express为例const crypto require(crypto); const sessions {}; // 简易的Session存储生产环境请用Redis等 // 中间件为每个会话生成并管理CSRF Token function csrfProtection(req, res, next) { let sessionId req.cookies.sessionId; if (!sessionId || !sessions[sessionId]) { sessionId crypto.randomBytes(16).toString(hex); res.cookie(sessionId, sessionId, { httpOnly: true, secure: true }); sessions[sessionId] {}; } req.session sessions[sessionId]; // 如果Session中没有Token则生成一个 if (!req.session.csrfToken) { req.session.csrfToken crypto.randomBytes(32).toString(hex); } // 将Token暴露给视图层例如通过res.locals res.locals.csrfToken req.session.csrfToken; next(); } // 验证Token的中间件 function verifyCsrfToken(req, res, next) { const clientToken req.body.csrf_token || req.headers[x-csrf-token]; const serverToken req.session.csrfToken; if (!clientToken || clientToken ! serverToken) { return res.status(403).json({ error: Invalid CSRF token }); } // 验证通过后可以选择使旧Token失效并生成新Token双重提交Cookie模式常用 // req.session.csrfToken crypto.randomBytes(32).toString(hex); next(); } app.use(csrfProtection); // 渲染带表单的页面 app.get(/transfer, (req, res) { res.render(transfer-form, { csrfToken: res.locals.csrfToken }); }); // 处理表单提交 app.post(/transfer, verifyCsrfToken, (req, res) { // 执行转账逻辑... res.send(Transfer successful); });前端实现传统多页应用在服务端渲染的模板中直接将Token作为隐藏字段输出。!-- transfer-form.ejs 模板 -- form action/transfer methodPOST input typehidden namecsrf_token value% csrfToken % 收款人: input typetext namerecipientbr 金额: input typenumber nameamountbr button typesubmit转账/button /form前端实现SPA AJAX首次加载页面时从一个安全的端点如GET /api/csrf-token获取Token。将Token存储在内存或一个非HttpOnly的Cookie中注意放在Cookie中时需配合“双重提交Cookie”模式。在每个非幂等的AJAX请求POST, PUT, DELETE等的HTTP头中携带该Token。// 假设从初始HTML的meta标签或API获取了Token let csrfToken document.querySelector(meta[namecsrf-token]).getAttribute(content); // 使用fetch发起请求 fetch(/api/transfer, { method: POST, headers: { Content-Type: application/json, X-CSRF-TOKEN: csrfToken // 关键在自定义头中携带Token }, body: JSON.stringify({ recipient: alice, amount: 100 }), credentials: include // 如果需要发送认证Cookie });3.3 关键注意事项与避坑指南Token的强度与随机性必须使用密码学安全的随机数生成器如crypto.randomBytes来生成Token长度建议至少32字节64个十六进制字符。切勿使用时间戳、用户ID等可预测的值。Token的存储与传输安全服务端存储Token必须与用户会话紧密绑定。存储在服务器的Session中是最安全的方式。如果出于无状态架构考虑必须放在客户端则应使用签名或加密的Token如JWT格式并在服务端验证签名防止客户端篡改。客户端传输避免将Token放在URL中GET参数因为URL可能被记录在浏览器历史、服务器日志或Referer头中导致泄露。优先使用HTTP请求体POST表单字段或自定义HTTP头。Token的范围通常为每个会话生成一个主Token即可。但对于极高安全要求的操作如支付确认可以考虑为每个表单或每次请求生成独立的Token。不要依赖请求来源校验Referer/Origin头Referer头可能被浏览器禁用或篡改Origin头对于HTTPS到HTTP的请求不会发送。它们可以作为辅助校验手段但绝不能作为唯一的防御措施。AJAX请求的特殊处理确保你的CSRF保护中间件不仅能解析application/x-www-form-urlencoded格式的请求体也能解析application/json格式。许多框架的默认配置可能只处理前者。踩过的坑在一次项目审计中我发现一个使用Express和body-parser的应用其CSRF中间件只在urlencoded解析之后才执行。而某个API端点只接受application/jsonbody-parser.json()中间件在CSRF中间件之后才被调用。这导致攻击者可以发送一个JSON格式的POST请求轻松绕过CSRF检查。教训确保CSRF校验中间件在所有请求体解析中间件之后执行并且能处理所有支持的内容类型。4. 核心防御策略二同源策略与Fetch元数据头随着浏览器安全特性的演进我们有了更多基于请求上下文Context的防御武器。这些方法的核心是让服务器能够区分“来自我自己网站的合法请求”和“来自其他网站的恶意请求”。4.1 理解“简单请求”与“非简单请求”这是CORS跨源资源共享规范中的核心概念也是防御CSRF的重要基础。简单请求Simple Request满足所有以下条件的请求方法为GET,HEAD,POST之一。仅允许人为设置以下集合中的请求头Accept,Accept-Language,Content-Language,Content-Type。Content-Type的值仅限于application/x-www-form-urlencoded,multipart/form-data,text/plain三者之一。非简单请求Non-simple Request / Preflighted Request不满足上述任一条件的请求例如使用了PUT、DELETE方法或设置了自定义头如X-CSRF-TOKEN或Content-Type为application/json。关键区别对于简单请求浏览器会直接发出请求并在收到响应后根据CORS头决定是否将响应暴露给前端JS。这意味着CSRF攻击可以成功发起简单请求。对于非简单请求浏览器会先发送一个OPTIONS方法的“预检请求”Preflight Request到服务器询问是否允许跨域。只有服务器明确响应允许后浏览器才会发送真正的请求。因此将你应用中的所有状态变更请求都设计为非简单请求是防御CSRF的一道天然屏障。因为攻击者从第三方网站发起的非简单请求会在预检阶段被浏览器拦截如果服务器没有明确允许该第三方来源。4.2 使用Fetch元数据请求头Fetch Metadata这是更现代、更精细的防御手段。浏览器在发起请求时会自动添加一组以Sec-Fetch-*开头的请求头统称为Fetch元数据。它们描述了请求的上下文信息服务器可以据此判断请求是否可疑。最常用的是Sec-Fetch-Site头它直接告诉服务器这个请求的来源与目标的关系same-origin同源请求。最安全。same-site同站请求eTLD1相同如a.example.com和b.example.com。通常也较安全但需注意子域名间的信任问题。cross-site跨站请求。这是CSRF攻击的典型特征。none由用户直接触发如地址栏输入、书签。服务器端校验示例// Express 中间件基于Fetch元数据拦截可疑请求 function checkFetchMetadata(req, res, next) { const secFetchSite req.headers[sec-fetch-site]; const secFetchMode req.headers[sec-fetch-mode]; // 只允许同源或同站的导航请求navigate或非简单的CORS请求 // 对于状态变更的POST/PUT/DELETE请求严格一点只允许同源 if (req.method POST || req.method PUT || req.method DELETE) { if (secFetchSite ! same-origin) { // 记录日志发出警报 console.warn(Potential CSRF attempt from: ${secFetchSite}, mode: ${secFetchMode}); return res.status(403).json({ error: Cross-site request not allowed for this operation. }); } } // 对于GET请求等可以放宽到same-site next(); } app.use(checkFetchMetadata);4.3 结合CORS策略进行防御正确配置CORS是防御通过AJAX发起的CSRF攻击的关键。核心原则是严格限制Access-Control-Allow-Origin并谨慎使用Access-Control-Allow-Credentials。错误配置导致漏洞// 危险允许任意来源且允许携带凭证 app.use((req, res, next) { res.header(Access-Control-Allow-Origin, *); // 通配符 res.header(Access-Control-Allow-Credentials, true); // 允许带Cookie next(); });这样的配置意味着evil.com可以轻易地用JavaScript向你的API发起带用户Cookie的请求。安全配置示例const allowedOrigins [https://www.mytrustedapp.com, https://admin.mytrustedapp.com]; app.use((req, res, next) { const origin req.headers.origin; // 动态检查请求来源是否在白名单中 if (allowedOrigins.includes(origin)) { res.header(Access-Control-Allow-Origin, origin); // 精确设置不用通配符 res.header(Access-Control-Allow-Credentials, true); // 明确允许的HTTP方法 res.header(Access-Control-Allow-Methods, GET, POST, OPTIONS); // 明确允许的自定义头用于CSRF Token res.header(Access-Control-Allow-Headers, Content-Type, X-CSRF-TOKEN); } // 如果是预检请求直接返回成功 if (req.method OPTIONS) { return res.sendStatus(200); } next(); });实操心得Fetch Metadata头是浏览器自动添加的无法被前端JavaScript伪造因此可靠性很高。但它是一个较新的标准Chrome 76 Firefox 79在服务端校验时一定要做好降级处理。可以将其作为一道强力补充防线与CSRF Token结合使用形成纵深防御。对于不支持该特性的旧版浏览器则完全依赖Token进行校验。5. 核心防御策略三SameSite Cookie属性Cookie的SameSite属性是浏览器提供的一种从源头减少CSRF风险的内置机制。它告诉浏览器在什么情况下应该随请求发送某个Cookie。5.1 SameSite的三种模式SameSiteStrict严格模式行为Cookie仅在同站请求即当前页面URL的站点与请求目标站点一致时发送。这意味着从其他网站通过链接跳转过来或者通过form提交只要来源不同站Cookie都不会被发送。安全性最高。能有效阻止所有第三方上下文发起的CSRF攻击。用户体验影响最大。用户从搜索引擎结果页或邮件中的链接点击进入你的网站时由于是跨站导航Cookie不被发送用户会显示为“未登录”状态需要重新登录。这对于需要保持登录状态的网站如社交网络、邮箱可能不友好。SameSiteLax宽松模式现代浏览器的默认值行为在跨站请求中只有安全的顶层导航top-level navigation才会发送Cookie。安全的方法主要指GET。这意味着从其他网站点击链接a href...跳转过来会带Cookie。从其他网站通过form methodGET提交极少见会带Cookie。从其他网站通过form methodPOST提交、通过img、script、fetch等发起的请求不会带Cookie。安全性较高。能阻止大多数常见的CSRF攻击因为攻击通常使用POST表单或自动加载资源。用户体验较好。用户通过链接跳转能保持登录状态。SameSiteNone行为Cookie在所有上下文中都会发送即允许跨站使用。安全性无CSRF防护。必须与Secure属性一起使用即仅限HTTPS否则浏览器会拒绝设置。使用场景主要用于需要被跨站嵌入的组件或服务例如第三方登录插件、支付iframe、跨站AJAX调用的API。5.2 服务端配置与实战策略在设置会话Cookie时明确指定SameSite属性。Node.js/Express示例app.use(session({ secret: your-secret-key, cookie: { httpOnly: true, // 防止XSS读取 secure: process.env.NODE_ENV production, // 生产环境强制HTTPS sameSite: lax, // 或 strict根据业务权衡 maxAge: 24 * 60 * 60 * 1000 // 1天 } }));策略建议对于主要的用户会话Cookie优先设置为SameSiteLax。这是目前的最佳平衡点既能防御大多数POST型CSRF攻击又不影响用户通过链接正常访问网站。Chrome等浏览器已默认将未指定SameSite的Cookie视为Lax。对于执行关键操作如支付、修改密码的专用Cookie或Token可以考虑设置为SameSiteStrict。将这些操作的端点与普通浏览页面分离使用严格的Cookie。当用户从外部链接尝试访问这些端点时由于Cookie不发送他们会看到一个要求重新认证的页面这反而提升了安全性。避免使用SameSiteNone除非你的服务明确需要被跨站嵌入。如果使用务必同时设置Secure: true。5.3 SameSite的局限性依赖SameSite属性作为唯一的CSRF防御手段是危险的浏览器兼容性虽然现代浏览器都已支持但仍需考虑少量旧版本用户。“同站”不等于“同源”SameSite检查的是“站点”eTLD1而不是“源”协议域名端口。这意味着app1.example.com和app2.example.com被视为同站。如果你不能完全信任所有子域名那么子域名间的攻击子域名接管漏洞仍可能构成威胁。GET请求的CSRFSameSiteLax允许跨站的GET请求携带Cookie。如果你的应用用GET请求执行状态变更这是错误的设计攻击者依然可以通过诱导用户点击链接a标签或加载图片img来实施CSRF。因此绝对不要用GET方法执行写操作。注意事项在设置SameSiteStrict时务必进行充分的用户体验测试。特别是对于依赖第三方身份提供商如OAuth登录回调的场景回调URL的请求可能因为Cookie未发送而导致登录失败。此时需要仔细设计流程或对特定的登录回调端点使用Lax或None并配合其他防御措施。6. 防御策略组合与纵深防御体系在实际项目中没有任何一种单一技术是银弹。最稳健的安全策略是建立纵深防御Defense in Depth层层设防即使一层被突破还有其他层提供保护。6.1 推荐防御组合对于大多数Web应用我推荐的防御组合是第一道防线核心CSRF Token。为所有状态变更的请求POST, PUT, DELETE, PATCH实施CSRF Token校验。这是最可靠、最根本的防御。第二道防线加固SameSite Cookie。将主要的会话Cookie设置为SameSiteLax。这能自动拦截大量来自第三方网站的、非用户主动触发的POST请求为Token校验减轻压力并作为Token机制失效时的后备。第三道防线监控与过滤校验请求上下文。检查Origin/Referer头虽然不完全可靠但可以作为辅助校验。对于简单的同源应用可以要求这两个头必须与目标站点匹配。注意处理头信息缺失的情况如从HTTPS跳转到HTTP时Referer不发送或用户隐私设置禁用Referer。利用Fetch元数据头Sec-Fetch-Site在服务端中间件中检查该头如果值为cross-site则记录日志并发出警报甚至可以结合风控系统直接拦截高风险请求。第四道防线架构约束安全的API设计。禁用CORS或严格配置对于不打算被第三方网站调用的API不要设置宽松的CORS头。避免使用Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true的组合。使用非简单请求确保所有写操作的API端点其请求都是“非简单请求”。可以通过要求Content-Type: application/json或添加一个自定义请求头如X-Requested-With: XMLHttpRequest来实现。这能利用浏览器的预检机制阻挡一部分攻击。6.2 针对不同架构的实施方案传统服务端渲染SSR多页应用主要手段CSRF Token同步令牌模式。在服务端渲染每个表单时嵌入Token提交时验证。辅助手段SameSiteLax的会话Cookie。实施要点确保Token与用户会话绑定并保证足够的随机性。前后端分离的单页面应用SPA主要手段CSRF Token通常通过自定义HTTP头传递如X-CSRF-TOKEN。Token获取SPA首次加载时通过一个安全的GET请求如/api/csrf-token从服务器获取Token。服务器可以将Token放在一个非HttpOnly的Cookie中例如XSRF-TOKEN前端JS读取后设置到后续请求的头部。这就是“双重提交Cookie”模式的一种变体服务器下发Token到Cookie前端读取后放到自定义头服务器同时验证Cookie和头中的Token是否一致。辅助手段严格配置CORS仅允许可信的前端域名。设置SameSiteLax或Strict的会话Cookie。特别注意确保获取Token的端点本身不受CSRF攻击通常它是GET方法且不改变状态。基于Token的无状态API如JWT如果应用完全使用JWT等Token进行认证且Token不通过Cookie存储而是通过Authorization头传递那么默认情况下不存在CSRF漏洞。因为浏览器不会自动在跨站请求中携带Authorization头。但是如果为了便利性你将JWT也存放在了Cookie中以便自动发送那么CSRF风险就又回来了。此时必须为所有写操作实施CSRF Token或SameSiteStrictCookie等防御措施。最佳实践对于无状态API坚持将认证Token放在Authorization头中并由前端代码显式设置不要依赖Cookie的自动发送机制。6.3 常见问题排查与实战技巧即使实施了防御也可能因为细节问题导致漏洞。以下是我在渗透测试和代码审计中经常发现的问题问题1Token验证逻辑存在缺陷场景服务器生成了Token也进行了验证但验证逻辑是“请求中只要有Token就行”而不是“请求中的Token必须与Session中的Token匹配”。排查检查验证代码确保是严格的字符串比对并且比对的是服务器端存储的、与当前会话绑定的那个Token。问题2Token未绑定到具体用户或操作场景整个应用使用一个全局的、固定的CSRF Token。攻击者可以在自己的网站上获取到这个Token然后用来构造对其他用户的攻击请求。排查确保每个用户会话都有独立的Token并且对于高敏感操作可以考虑使用一次性的Token。问题3Token在非HTTPS环境下传输场景网站未全站启用HTTPSCSRF Token在明文HTTP请求中传输可能被中间人攻击窃取。解决全站启用HTTPS并在设置Cookie时使用Secure属性。问题4CORS配置过于宽松场景Access-Control-Allow-Origin: *且Access-Control-Allow-Credentials: true。这使得任何网站都可以发起带凭证的AJAX请求。解决永远不要同时使用这两个配置。如果需要跨域带凭证必须明确指定Access-Control-Allow-Origin为具体的、可信的来源而不是通配符。问题5忽略了JSONP或其他古老接口场景应用为了兼容老客户端保留了JSONP接口。JSONP通过script标签加载不受同源策略限制也无法携带自定义头因此传统的CSRF Token防御可能对其无效。解决对JSONP接口实施额外的校验例如检查Referer头或要求请求中包含一个通过其他方式如Cookie下发的、难以猜测的令牌。更好的办法是逐步废弃JSONP迁移到CORS。快速检查清单[ ] 所有状态变更的POST/PUT/DELETE/PATCH端点是否都校验了CSRF Token[ ] Token是否足够随机使用密码学安全的RNG生成[ ] Token是否与用户会话严格绑定[ ] 会话Cookie是否设置了SameSiteLax或Strict属性[ ] CORS策略是否严格是否避免了Access-Control-Allow-Origin: *和Allow-Credentials: true的危险组合[ ] 是否完全杜绝了使用GET方法执行写操作[ ] 是否对application/json等非表单格式的请求也进行了CSRF校验[ ] 安全中间件的顺序是否正确Body Parser之后路由处理之前防御CSRF是一个系统工程需要开发、运维、测试各环节共同关注。将其纳入代码审查清单、自动化安全测试如DAST/SAST工具扫描和渗透测试的常规项目中才能持续有效地保障应用安全。记住安全不是一个功能而是一种属性需要贯穿于软件开发的整个生命周期。