跳至主要内容
版本:v2.x

CSRF

适用于 Fiber 的 CSRF 中间件可针对 跨站请求伪造 (CSRF) 攻击提供保护。使用 RFC9110#section-9.2.1 定义为“安全”以外的方法(GET、HEAD、OPTIONS 和 TRACE)发出的请求将使用令牌进行验证。如果检测到潜在攻击,中间件将返回默认 403 禁止错误。

此中间件提供两种 令牌验证模式双重提交 Cookie 模式(默认)同步令牌模式(带会话)

作为 纵深防御 措施,此中间件对 HTTPS 请求执行 引用检查

令牌生成

CSRF 令牌在“安全”请求中生成,并且在现有令牌已过期或尚未设置时生成。如果 SingleUseTokentrue,则每次使用后都会生成一个新令牌。使用 c.Locals(contextKey) 检索 CSRF 令牌,其中 contextKey 在配置中定义。

安全注意事项

此中间件旨在防止 CSRF 攻击,但不能防止其他攻击媒介,例如 XSS。它应与其他安全措施结合使用。

危险

切勿使用“安全”方法来修改数据,例如,切勿使用 GET 请求来修改资源。此中间件不会保护“安全”方法免受 CSRF 攻击。

令牌验证模式

默认情况下,中间件使用 fiber.Storage 接口生成和存储令牌。这些令牌不链接到任何特定用户会话,并且使用双重提交 Cookie 模式进行验证。令牌存储在 Cookie 中,然后在请求中作为标头发送。中间件将 Cookie 值与标头值进行比较以验证令牌。这是一个不需要用户会话的安全模式。

当授权状态更改时,必须删除先前颁发的令牌,并生成一个新令牌。有关更多信息,请参阅 令牌生命周期 删除令牌

小心

使用此模式时,重要的是将 CookieSameSite 选项设置为 LaxStrict,并确保提取器不是 CsrfFromCookie,并且 KeyLookup 不是 cookie:<name>

注意

使用此模式时,此中间件使用我们的 Storage 包通过单个接口支持各种数据库。Storage 的默认配置将数据保存到内存中。有关自定义存储的信息,请参阅 自定义存储/数据库

同步令牌模式(带会话)

将此中间件与用户会话一起使用时,可以将中间件配置为将令牌存储在会话中。使用用户会话时推荐此方法,因为它通常比双重提交 Cookie 模式更安全。

使用此模式时,在授权状态更改时重新生成会话非常重要,这也会删除令牌。有关详细信息,请参阅:令牌生命周期

小心

需要预会话,如果不存在,将自动创建。使用会话值来指示身份验证,而不是依赖会话的存在。

纵深防御

使用此中间件时,建议通过 HTTPS 提供页面,将 CookieSecure 选项设置为 true,并将 CookieSameSite 选项设置为 LaxStrict。这可确保仅通过 HTTPS 发送 Cookie,而不通过外部网站的请求发送 Cookie。

注意

Cookie 前缀 __Host-__Secure- 可用于进一步保护 Cookie。请注意,并非所有浏览器都支持这些前缀,并且还有其他限制。有关详细信息,请参阅 MDN#Set-Cookie#cookie_prefixes

要使用这些前缀,将 CookieName 选项设置为 __Host-csrf___Secure-csrf_

Referer 检查

对于 HTTPS 请求,此中间件执行严格的 referer 检查。即使子域可以在你的域上设置或修改 cookie,它也不能强制用户向你的应用程序发布,因为该请求不会来自你自己的确切域。

小心

当 HTTPS 请求受 CSRF 保护时,总是执行 referer 检查。

所有现代浏览器(包括使用 JS Fetch API 进行的浏览器)都会在请求中自动包含 Referer 标头。但是,如果你使用自定义客户端使用此中间件,则务必确保客户端发送有效的 Referer 标头。

令牌生命周期

令牌在过期或被删除之前有效。默认情况下,令牌有效期为 1 小时,并且每次后续请求都会将过期时间延长 1 小时。仅当用户在过期时间内没有提出请求时,令牌才会过期。

令牌重用

默认情况下,令牌可以多次使用。如果你想在使用令牌后删除令牌,可以将 SingleUseToken 选项设置为 true。这将在使用令牌后删除令牌,并在下一次请求中生成新的令牌。

信息

使用 SingleUseToken 会带来可用性权衡,并且默认情况下不会启用。例如,如果用户打开了多个选项卡或使用了后退按钮,它可能会干扰用户体验。

删除令牌

当授权状态更改时,必须删除 CSRF 令牌并生成新的令牌。这可以通过调用 handler.DeleteToken(c) 来完成。

if handler, ok := app.AcquireCtx(ctx).Locals(csrf.ConfigDefault.HandlerContextKey).(*CSRFHandler); ok {
if err := handler.DeleteToken(app.AcquireCtx(ctx)); err != nil {
// handle error
}
}
提示

如果您将此中间件与 fiber 会话中间件一起使用,则可以简单地调用 session.Destroy()session.Regenerate()session.Reset() 来删除会话和其中存储的令牌。

BREACH

需要注意的是,令牌作为每个请求的标头发送。如果您将令牌包含在易受 BREACH 攻击的页面中,攻击者可能能够提取令牌。为了缓解这种情况,请确保您的页面通过 HTTPS 提供,禁用 HTTP 压缩,并对请求实施速率限制。

签名

func New(config ...Config) fiber.Handler

示例

导入 Fiber Web 框架中包含的中间件包

import (
"github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/csrf"
)

初始化 Fiber 应用程序后,可以使用以下代码初始化中间件

// Initialize default config
app.Use(csrf.New())

// Or extend your config for customization
app.Use(csrf.New(csrf.Config{
KeyLookup: "header:X-Csrf-Token",
CookieName: "csrf_",
CookieSameSite: "Lax",
Expiration: 1 * time.Hour,
KeyGenerator: utils.UUIDv4,
}))

配置

属性类型描述默认值
下一步func(*fiber.Ctx) boolNext 定义了一个函数,当返回 true 时跳过此中间件。nil
KeyLookup字符串KeyLookup 是一个 "<source>:<key>" 形式的字符串,用于创建一个从请求中提取令牌的提取器。可能的值:"header:<name>"、"query:<name>"、"param:<name>"、"form:<name>"、"cookie:<name>"。如果明确设置了提取器,则忽略此项。"header:X-Csrf-Token"
CookieName字符串CSRF Cookie 的名称。此 Cookie 将存储 CSRF 密钥。"csrf_"
CookieDomain字符串CSRF Cookie 的域名。""
CookiePath字符串CSRF Cookie 的路径。""
CookieSecure布尔值指示 CSRF Cookie 是否安全。false
CookieHTTPOnly布尔值指示 CSRF Cookie 是否仅限 HTTP。false
CookieSameSite字符串SameSite cookie 的值。"Lax"
CookieSessionOnly布尔值决定 cookie 是否仅持续一个浏览器会话。如果设置为 true,则忽略 Expiration。false
Expirationtime.DurationExpiration 是 CSRF 令牌过期之前的时间间隔。1 * time.Hour
SingleUseToken布尔值SingleUseToken 表示 CSRF 令牌是否在每次使用时销毁并生成新的令牌。(请参阅 TokenLifecycle)false
Storagefiber.StorageStore 用于存储中间件的状态。nil
会话*session.StoreSession 用于存储中间件的状态。如果设置,则覆盖 Storage。nil
SessionKey字符串SessionKey 是用于在会话中存储令牌的键。"fiber.csrf.token"
ContextKeyinteface{}上下文键,用于将生成的 CSRF 令牌存储到上下文中。如果留空,则令牌不会存储在上下文中。""
KeyGeneratorfunc() stringKeyGenerator 创建新的 CSRF 令牌。utils.UUID
CookieExpirestime.Duration(已弃用)已弃用:请使用 Expiration。0
Cookie*fiber.Cookie(已弃用)已弃用:请使用 Cookie* 相关的字段。nil
TokenLookupstring(已弃用)已弃用:请使用 KeyLookup。""
ErrorHandlerfiber.ErrorHandler当从 fiber.Handler 返回错误时,执行 ErrorHandler。DefaultErrorHandler
Extractorfunc(*fiber.Ctx) (string, error)Extractor 返回 CSRF 令牌。如果设置,则将使用此令牌来代替基于 KeyLookup 的 Extractor。基于 KeyLookup 的 Extractor
HandlerContextKeyinterface{}HandlerContextKey 用于将 CSRF Handler 存储到上下文中。"fiber.csrf.handler"

默认配置

var ConfigDefault = Config{
KeyLookup: "header:" + HeaderName,
CookieName: "csrf_",
CookieSameSite: "Lax",
Expiration: 1 * time.Hour,
KeyGenerator: utils.UUIDv4,
ErrorHandler: defaultErrorHandler,
Extractor: CsrfFromHeader(HeaderName),
SessionKey: "fiber.csrf.token",
HandlerContextKey: "fiber.csrf.handler",
}

建议将此中间件与 fiber/middleware/session 一起使用,以将 CSRF 令牌存储在会话中。这通常比默认配置更安全。

var ConfigDefault = Config{
KeyLookup: "header:" + HeaderName,
CookieName: "__Host-csrf_",
CookieSameSite: "Lax",
CookieSecure: true,
CookieSessionOnly: true,
CookieHTTPOnly: true,
Expiration: 1 * time.Hour,
KeyGenerator: utils.UUIDv4,
ErrorHandler: defaultErrorHandler,
Extractor: CsrfFromHeader(HeaderName),
Session: session.Store,
SessionKey: "fiber.csrf.token",
HandlerContextKey: "fiber.csrf.handler",
}

常量

const (
HeaderName = "X-Csrf-Token"
)

Sentinel 错误

CSRF 中间件利用一组 Sentinel 错误来处理各种场景并有效地传达错误。这些错误可以在 自定义错误处理程序 中使用,以处理中间件返回的错误。

返回到错误处理程序的错误

  • ErrTokenNotFound:表示未找到 CSRF 令牌。
  • ErrTokenInvalid:表示 CSRF 令牌无效。
  • ErrNoReferer:表示未提供引用者。
  • ErrBadReferer:表示引用者无效。

如果你使用默认错误处理程序,则客户端将收到一个 403 禁止错误,而没有任何其他信息。

自定义错误处理程序

你可以使用自定义错误处理程序来处理 CSRF 中间件返回的错误。当从中间件返回错误时,将执行错误处理程序。错误处理程序会传递从中间件返回的错误和 fiber.Ctx。

示例,为 API 请求返回 JSON 响应,并为其他请求呈现错误页面

app.Use(csrf.New(csrf.Config{
ErrorHandler: func(c *fiber.Ctx, err error) error {
accepts := c.Accepts("html", "json")
path := c.Path()
if accepts == "json" || strings.HasPrefix(path, "/api/") {
return c.Status(fiber.StatusForbidden).JSON(fiber.Map{
"error": "Forbidden",
})
}
return c.Status(fiber.StatusForbidden).Render("error", fiber.Map{
"Title": "Forbidden",
"Status": fiber.StatusForbidden,
}, "layouts/main")
},
}))

自定义存储/数据库

你可以使用我们 storage 包中的任何存储。

storage := sqlite3.New() // From github.com/gofiber/storage/sqlite3
app.Use(csrf.New(csrf.Config{
Storage: storage,
}))