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

CSRF

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

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

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

令牌生成

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

安全考虑

此中间件旨在防御 CSRF 攻击,但不防御其他攻击向量,例如 XSS。应与其他安全措施结合使用。

危险

切勿使用“安全”方法来修改数据,例如,切勿使用 GET 请求修改资源。此中间件不防御针对“安全”方法的 CSRF 攻击。

令牌验证模式

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

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

注意

使用此模式时,务必将 CookieSameSite 选项设置为 LaxStrict,并确保 Extractor 不是 CsrfFromCookie,且 KeyLookup 不是 cookie:<name>

注意

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

同步器令牌模式(带会话)

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

使用此模式时,重要的是在授权状态改变时重新生成会话,这也会删除令牌。更多信息请参阅:令牌生命周期

注意

需要预会话(Pre-sessions),如果不存在将自动创建。使用会话值来指示认证状态,而不是依赖会话是否存在。

纵深防御

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

注意

可以使用 Cookie 前缀 __Host-__Secure- 来进一步保护 Cookie。请注意,并非所有浏览器都支持这些前缀,并且存在其他限制。更多信息请参阅 MDN#Set-Cookie#cookie_prefixes

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

Referer 检查

对于 HTTPS 请求,此中间件执行严格的 Referer 检查。即使子域可以在您的域上设置或修改 Cookie,它也无法强制用户向您的应用程序发出 POST 请求,因为该请求不会来自您的精确域名。

注意

当 HTTPS 请求受 CSRF 保护时,始终执行 Referer 检查。

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

令牌生命周期

令牌在过期或被删除之前有效。默认情况下,令牌有效期为 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

需要注意的是,令牌作为 Header 随每个请求发送。如果您将令牌包含在容易受到 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
KeyLookupstringKeyLookup 是一个格式为 "<source>:<key>" 的字符串,用于创建从请求中提取令牌的 Extractor。可能的值: "header:<name>"、"query:<name>"、"param:<name>"、"form:<name>"、"cookie:<name>"。如果显式设置了 Extractor,则忽略此项。"header:X-Csrf-Token"
CookieNamestringcsrf cookie 的名称。此 cookie 将存储 csrf 密钥。"csrf_"
CookieDomainstringCSRF cookie 的域名。""
CookiePathstringCSRF cookie 的路径。""
CookieSecurebool指示 CSRF cookie 是否安全。false
CookieHTTPOnlybool指示 CSRF cookie 是否仅限 HTTP。false
CookieSameSitestringSameSite cookie 的值。"Lax"
CookieSessionOnlybool决定 cookie 是否仅在浏览器会话期间有效。如果设置为 true,则忽略 Expiration。false
Expirationtime.DurationExpiration 是 CSRF 令牌过期前的持续时间。1 * time.Hour
SingleUseTokenboolSingleUseToken 指示每次使用后 CSRF 令牌是否被销毁并生成一个新令牌。(参见 令牌生命周期)false
Storagefiber.StorageStore 用于存储中间件的状态。nil
会话*session.StoreSession 用于存储中间件的状态。如果设置,则覆盖 Storage。nil
SessionKeystringSessionKey 是用于在会话中存储令牌的键。"fiber.csrf.token"
ContextKeyinteface{}将生成的 CSRF 令牌存储到上下文中的上下文键。如果留空,令牌将不会存储在上下文中。""
KeyGeneratorfunc() stringKeyGenerator 创建一个新的 CSRF 令牌。utils.UUID
CookieExpirestime.Duration (已弃用)已弃用:请使用 Expiration。0
Cookie*fiber.Cookie (已弃用)已弃用:请使用 Cookie* 相关字段。nil
TokenLookupstring (已弃用)已弃用:请使用 KeyLookup。""
ErrorHandlerfiber.ErrorHandlerErrorHandler 在 fiber.Handler 返回错误时执行。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 Errors

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

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

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

如果您使用默认错误处理程序,客户端将收到 403 Forbidden 错误,不包含任何额外信息。

自定义错误处理程序

您可以使用自定义错误处理程序来处理 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,
}))