CSRF 防护
CSRF(跨站请求伪造)攻击利用浏览器自动携带 Cookie 的特性,诱导已登录用户在不知情的情况下向目标站点发送恶意请求。ACE Framework 提供 csrfMiddleware 开箱即用,基于 Double Submit Cookie 模式实现防护。
何时需要 CSRF 防护
CSRF 仅针对基于 Cookie 的会话认证场景(浏览器表单提交)。如果你的 API 接口使用 JWT Bearer Token 鉴权,浏览器不会自动携带 token,天然免疫 CSRF,无需启用本中间件。
防护原理
服务端只需验证 Cookie 中的 csrf_token 与请求体 _csrf 字段(或 Header X-CSRF-Token)是否一致。攻击者无法读取受 SameSite 保护的 Cookie,因此无法伪造合法请求。
快速开始
1. 挂载中间件
csrfMiddleware 依赖 Session 存储 token,需先挂载 sessionMiddleware:
import ace_web.*
import ace_http.*
import ace_security.csrf.*
import ace_security.session.*
let app = App()
// session 必须在 csrf 之前挂载
app.use(sessionMiddleware(SessionConfig(
secret: "session-secret-key",
maxAge: 86400
)))
app.use(csrfMiddleware())
app.use(router.routes())2. 在表单页面注入 token
router.get("/form") { ctx =>
let token = csrfToken(ctx) // 读取或生成当次 token
ctx.html("""
<form method="POST" action="/submit">
<input type="hidden" name="_csrf" value="${token}">
<input type="text" name="username">
<button type="submit">提交</button>
</form>
""")
}3. 提交校验(自动完成)
csrfMiddleware 拦截所有 POST / PUT / PATCH / DELETE 请求,自动从以下位置读取 token 并校验:
| 优先级 | 来源 | 字段/Header 名 |
|---|---|---|
| 1 | 请求 Header | X-CSRF-Token |
| 2 | 表单 Body | _csrf |
| 3 | JSON Body | csrfToken |
校验失败时返回 403 Forbidden,响应体:
{"error": "invalid csrf token"}配置项
app.use(csrfMiddleware(CsrfConfig(
cookieName: "csrf_token", // Cookie 名称,默认 "_csrf"
headerName: "X-CSRF-Token", // Header 名称
fieldName: "_csrf", // 表单字段名
cookiePath: "/",
secure: true, // 生产环境开启(仅 HTTPS 发送 Cookie)
sameSite: "Strict" // 推荐 Strict 或 Lax
)))CsrfConfig 参数说明
| 参数 | 类型 | 默认值 | 说明 |
|---|---|---|---|
cookieName | String | "_csrf" | 存储 token 的 Cookie 名 |
headerName | String | "X-CSRF-Token" | 前端 Ajax 请求携带的 Header |
fieldName | String | "_csrf" | 表单隐藏字段名 |
secure | Bool | false | 仅 HTTPS 环境发送 Cookie |
sameSite | String | "Lax" | Cookie SameSite 策略 |
tokenLength | Int | 32 | 随机 token 字节长度 |
豁免路径
对于纯 API 路由(使用 JWT 鉴权),可通过 except 参数跳过 CSRF 校验:
app.use(csrfMiddleware(CsrfConfig(
except: ["/api/", "/webhook/"]
)))前缀匹配:所有以 /api/ 开头的路径均不进行 CSRF 校验。
与 Ajax 配合
前端 SPA 可在请求拦截器中从 Cookie 读取 token 并附加到 Header:
// 前端 JavaScript(仅供参考)
const csrfToken = document.cookie
.split('; ')
.find(row => row.startsWith('_csrf='))
?.split('=')[1]
fetch('/api/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': csrfToken
},
body: JSON.stringify(data)
})多标签页场景
Double Submit Cookie 方案下,同一浏览器多标签页共享同一 Cookie token,不存在 token 竞争问题。但若同时打开多个表单且 token 刷新频率过高,可能出现旧表单 token 失效的情况——建议将 token 有效期与 Session 有效期保持一致。
与 sessionMiddleware 的关系
csrfMiddleware 将 token 存入当前 Session,以实现跨请求持久化:
// 内部等价逻辑(框架自动处理,无需手写)
let token = session.get("csrf_token") ?? generateToken(32)
session.set("csrf_token", token)
ctx.setCookie("_csrf", token)若未挂载 sessionMiddleware,则降级为仅验证 Cookie 与请求体的一致性(无服务端存储),安全性略低但仍有效。