进阶 www.iris-go.com 2026-10-20 02:39:14 · 19 阅读

第67章 Anti-CSRF Token 详解

Anti-CSRF Token

什么是 CSRF?

跨站请求伪造(Cross-Site Request Forgery,简称 CSRF)是一种 Web 安全漏洞。它允许攻击者诱使用户执行其本不打算进行的操作。该漏洞让攻击者能部分绕过同源策略(Same Origin Policy),而这一策略旨在防止不同网站之间互相干扰。

预防 CSRF 攻击

防御 CSRF 攻击最稳健的方法是在相关请求中包含 CSRF Token。该 Token 应满足以下要求:

  • 具有高熵值且不可预测,这与一般的 Session Token 要求一致。
  • 与用户的 Session 绑定。
  • 在执行相关操作前严格校验。

使用 Burp Suite 的 Web 漏洞扫描器发现 CSRF 漏洞

SameSite Cookie 是另一种部分有效的 CSRF 防御措施,可与 CSRF Token 配合使用。

更多信息请参阅:https://portswigger.net/web-security/csrf

CSRF 防护

Iris 让应用防范跨站请求伪造(CSRF)攻击变得轻而易举。您将学习如何使用 Iris 的 CSRF 中间件生成 csrf token,并保护您的 API。

安装

CSRF 中间件的源代码位于 iris-contrib/middleware 仓库中。

go get github.com/iris-contrib/middleware/csrf@master

CSRF 使用非常简单:使用以下代码将中间件添加到您的路由器中:

app := iris.New()
// [...]
CSRF := csrf.Protect([]byte("32-byte-long-auth-key"))
app.Use(CSRF)

...然后在处理程序中通过 csrf.Token(ctx) 获取 Token,并将其传递给模板、JSON 响应体或 HTTP 请求头(详见下文)。

请注意,传递给 csrf.Protect([]byte(key)) 的认证密钥长度必须为 32 字节,且在应用重启后必须持久化。生成随机密钥将无法认证现有 Cookie,并会破坏 CSRF 验证。

CSRF 会依次检查后续 POST/PUT/PATCH/DELETE 等请求的 HTTP 请求头(首选)和表单体(次选)中的 Token。

将路由排除在 CSRF 防护之外

有时您可能希望将某些路由组中的特定路由排除在 CSRF 防护之外。有两种可用方法。请参阅下文...

1. 使用 *Route.RemoveHandler 方法:

app.Post("/unprotected", unprotected).RemoveHandler(CSRF)
2. UnsafeSkipCheck 会跳过任何请求的 CSRF 检查。必须在 CSRF 中间件之前调用:注意:如果没有其他防护手段,请不要随意设置此项。该函数的主要使用场景是针对使用授权令牌访问 API 的非浏览器客户端,关闭 CSRF 检查。goapp.Use(func(ctx iris.Context) { shouldSkipCSRF = [自定义条件...] if shouldSkipCSRF { csrf.UnsafeSkipCheck(ctx) } ctx.Next() }) app.Use(CSRF) app.Use(otherMiddlewares...)1
2
3
4
5
6
7
8
9
HTML 表单 ​最常见的使用场景:为 HTML 表单提供 CSRF 防护,以防恶意 POST 请求。gopackage main import ( "github.com/kataras/iris/v12" "github.com/iris-contrib/middleware/csrf" ) func main() { app := iris.New() app.RegisterView(iris.HTML("./views", ".html")) // 所有未携带有效令牌的 POST 请求 // 都会返回 HTTP 403 Forbidden。 // 我们还应确保修改数据的 // (非幂等)处理器只匹配 POST 请求。 // 可以在这里的路由层面检查, // 也可以在处理器内部通过 r.Method 检查。 userAPI := app.Party("/user") CSRF := csrf.Protect( // 注意:认证密钥 // 的长度应为 32 字节, // 并且要在应用重启后保持不变。 []byte("9AB0F421E53A477C084477AEA06096F5"), // 警告:生产环境的 HTTPS 下应设为 true。 csrf.Secure(false), ) // 将中间件注册到该 Party 匹配的路由上。 userAPI.Use(CSRF) userAPI.Post("/signup", SubmitSignupForm) userAPI.Get("/signup", ShowSignupForm) app.Listen(":8080") } func ShowSignupForm(ctx iris.Context) { // user/signup.html 只需要一个 {{ .csrfField }} 模板标签, // csrf.TemplateField 会把 CSRF 令牌注入到这个位置。 ctx.ViewData(csrf.TemplateTag, csrf.TemplateField(ctx)) ctx.View("user/signup.html") // 也可以直接通过 csrf.Token(ctx) 获取令牌, // 然后设置到请求头中——ctx.Header("X-CSRF-Token", token) // 如果你要向客户端发送 JSON // 或使用前端 JavaScript 框架,这种方式会很有用。 } func SubmitSignupForm(ctx iris.Context) { // 能走到这里的请求, // 我们可以确信它们已满足 CSRF 防护要求。 }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 ```html
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
注意,如果 Token 是通过 POST 表单值传递的,CSRF 中间件将(不可避免地)消费请求体。如果你的处理器也需要消费该请求体,请在链中较早的位置插入自己的中间件以捕获请求体。 JavaScript 应用 ​ 这种方法适用于使用 React、Ember 或 Angular 等前端 JavaScript 框架并提供 JSON API 的场景。具体来说,我们需要为前端 fetch/AJAX 调用提供一种方式,以便在每个 fetch(AJAX/XMLHttpRequest)请求中传递 Token。我们通过以下方式实现:解析由 csrf.TemplateField(ctx) 辅助函数生成的 字段中的 Token,或者通过响应头将其传回。在每个请求中发送此 Token 并确保 Cookie 附带在请求中,以便表单/头中的值可以与 Cookie 中的值进行比较。gofunc GetUser(ctx iris.Context) { // 认证请求,从路由参数中获取 id, // 并从数据库中获取用户等。 // 获取 Token 并将其放入 CSRF 头部。 // 我们的支持 JSON 的客户端或 JavaScript 框架现在可以读取 // 该头部,并在后续的 POST 请求中 // 在自定义的 "X-CSRF-Token" 请求头中返回 Token。 ctx.Header("X-CSRF-Token", csrf.Token(ctx)) ctx.JSON(user) }1
2
3
4
5
6
7
8
9
10
11
在我们的 JavaScript 应用中,应该从响应头中读取 Token,并在所有请求的请求头中传递它。使用流行的 JavaScript HTTP 客户端库 Axios 时,其实现如下:js// 你也可以解析响应头中的 X-CSRF-Token, // 并存储它,前提是你按照上述步骤 // 将 Token 写入响应头。 let csrfToken = document.getElementsByName("csrf.token")[0].value // 参见 https://github.com/axios/axios#creating-an-instance const instance = axios.create({ baseURL: "https://example.com/api/", timeout: 1000, headers: { "X-CSRF-Token": csrfToken } }) // 现在,你发出的任何 HTTP 请求都将包含 // 来自页面的 csrfToken,前提是你每次渲染时 // 更新 csrfToken 变量。 try { let resp = await instance.post(endpoint, formData) // 对 resp 进行处理 } catch (err) { // 处理异常 }1
2
3
4 ```
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
如果你计划将 JavaScript 应用程序部署在另一个域名下,可以使用 Trusted Origins 功能,允许你的 JavaScript 应用宿主向你的 Go 应用发起请求。观察下面的代码片段:goCSRF := csrf.New(csrf.Options{ TrustedOrigins: []string{"ui.domain.com"}, Store: csrf.NewCookieStore( []byte("9AB0F421E53A477C084477AEA06096F5"), csrf.Secure(false)), }).Protect1
2
3
4
5
6
上述示例中,你正在授权来自 ui.domain.com 的请求对你的应用发起合法的 CSRF 请求,这样你就可以把 API 服务器放在另一个域名下而不产生问题。

设置 SameSite

​Go 1.11 引入了在 cookie 中设置 SameSite 属性的选项。如果开发者希望指示浏览器在跨站请求中不携带 cookie,这非常有价值。SameSiteStrictMode 会阻止所有跨站请求包含该 cookie。SameSiteLaxMode 会阻止易受 CSRF 攻击的请求(POST)包含该 cookie,但允许在 GET 请求中包含 cookie 以支持外部链接。goimport ( "net/http" "github.com/kataras/iris/v12" "github.com/iris-contrib/middleware/csrf" ) func main() { CSRF := csrf.Protect( []byte("a-32-byte-long-key-goes-here"), // 指示浏览器在跨站请求中 // 永远不要发送 cookie csrf.SameSite(http.SameSiteStrictMode), ) // [...] }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

设置选项

​如果你想要提供自己的错误处理器或存储,并更改该包在请求中检查的 HTTP 头部怎么办?(例如,将一个现有的 API 移植到 Go)。CSRF 提供了选项,让你可以按自己的喜好更改这些设置:goCSRF := csrf.New(csrf.Options{ TrustedOrigins: []string{"ui.domain.com"}, RequestHeader: "X-CSRF-Token", FieldName: "csrf.token", ErrorHandler: csrf.UnauthorizedHandler, Store: csrf.NewCookieStore( []byte("9AB0F421E53A477C084477AEA06096F5"), csrf.Secure(false)), })1
2
3
4
5
6
7
8
CSRF.Filter 方法。gofunc access(ctx iris.Context) { if CSRF.Filter(ctx) { // 成功。 } else { // 失败。 // 自定义错误响应,ErrorHandler 不会被调用。 } }1
2
3
4
5
6
7
8 或者使用 iris.NewConditionalHandler:goapp.Get(iris.NewConditionalHandler(CSRF.Filter, protectedHandler))1
主要的 CSRF.Protect 方法(与前面示例中看到的 csrf.Protect 包级函数相同)。goapp.Use(CSRF.Protect)1

评论 (0)