CSRF
适用于 Fiber 的 CSRF 中间件提供针对 跨站请求伪造 (CSRF) 攻击的保护。使用 RFC9110#section-9.2.1 定义为“安全”的方法(GET、HEAD、OPTIONS 和 TRACE)之外的方法发起的请求将使用令牌进行验证。如果检测到潜在攻击,中间件将返回默认的 403 Forbidden 错误。
此中间件提供两种 令牌验证模式:双重提交 Cookie 模式(默认),以及 同步器令牌模式(带会话)。
作为一项 纵深防御 措施,此中间件对 HTTPS 请求执行 Referer 检查。
令牌生成
CSRF 令牌在“安全”请求时生成,或现有令牌已过期或尚未设置时生成。如果 SingleUseToken
为 true
,则每次使用后都会生成一个新令牌。使用 c.Locals(contextKey)
获取 CSRF 令牌,其中 contextKey
在配置中定义。
安全考虑
此中间件旨在防御 CSRF 攻击,但不防御其他攻击向量,例如 XSS。应与其他安全措施结合使用。
切勿使用“安全”方法来修改数据,例如,切勿使用 GET 请求修改资源。此中间件不防御针对“安全”方法的 CSRF 攻击。
令牌验证模式
双重提交 Cookie 模式(默认)
默认情况下,此中间件使用 fiber.Storage
接口生成和存储令牌。这些令牌不与任何特定的用户会话关联,并通过双重提交 Cookie 模式进行验证。令牌存储在 Cookie 中,然后作为请求的 Header 发送。中间件通过比较 Cookie 值和 Header 值来验证令牌。这是一种不需要用户会话的安全模式。
当授权状态改变时,必须删除先前颁发的令牌,并生成新令牌。更多信息请参阅 令牌生命周期 删除令牌。
使用此模式时,务必将 CookieSameSite
选项设置为 Lax
或 Strict
,并确保 Extractor 不是 CsrfFromCookie
,且 KeyLookup 不是 cookie:<name>
。
同步器令牌模式(带会话)
将此中间件与用户会话一起使用时,可以将其配置为将令牌存储在会话中。与双重提交 Cookie 模式相比,使用用户会话时通常更安全,因此建议使用此方法。
使用此模式时,重要的是在授权状态改变时重新生成会话,这也会删除令牌。更多信息请参阅:令牌生命周期。
需要预会话(Pre-sessions),如果不存在将自动创建。使用会话值来指示认证状态,而不是依赖会话是否存在。
纵深防御
使用此中间件时,建议通过 HTTPS 提供页面服务,将 CookieSecure
选项设置为 true
,并将 CookieSameSite
选项设置为 Lax
或 Strict
。这确保了 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) bool | Next 定义一个函数,当返回 true 时跳过此中间件。 | nil |
KeyLookup | string | KeyLookup 是一个格式为 "<source>:<key> " 的字符串,用于创建从请求中提取令牌的 Extractor。可能的值: "header:<name> "、"query:<name> "、"param:<name> "、"form:<name> "、"cookie:<name> "。如果显式设置了 Extractor,则忽略此项。 | "header:X-Csrf-Token" |
CookieName | string | csrf cookie 的名称。此 cookie 将存储 csrf 密钥。 | "csrf_" |
CookieDomain | string | CSRF cookie 的域名。 | "" |
CookiePath | string | CSRF cookie 的路径。 | "" |
CookieSecure | bool | 指示 CSRF cookie 是否安全。 | false |
CookieHTTPOnly | bool | 指示 CSRF cookie 是否仅限 HTTP。 | false |
CookieSameSite | string | SameSite cookie 的值。 | "Lax" |
CookieSessionOnly | bool | 决定 cookie 是否仅在浏览器会话期间有效。如果设置为 true,则忽略 Expiration。 | false |
Expiration | time.Duration | Expiration 是 CSRF 令牌过期前的持续时间。 | 1 * time.Hour |
SingleUseToken | bool | SingleUseToken 指示每次使用后 CSRF 令牌是否被销毁并生成一个新令牌。(参见 令牌生命周期) | false |
Storage | fiber.Storage | Store 用于存储中间件的状态。 | nil |
会话 | *session.Store | Session 用于存储中间件的状态。如果设置,则覆盖 Storage。 | nil |
SessionKey | string | SessionKey 是用于在会话中存储令牌的键。 | "fiber.csrf.token" |
ContextKey | inteface{} | 将生成的 CSRF 令牌存储到上下文中的上下文键。如果留空,令牌将不会存储在上下文中。 | "" |
KeyGenerator | func() string | KeyGenerator 创建一个新的 CSRF 令牌。 | utils.UUID |
CookieExpires | time.Duration (已弃用) | 已弃用:请使用 Expiration。 | 0 |
Cookie | *fiber.Cookie (已弃用) | 已弃用:请使用 Cookie* 相关字段。 | nil |
TokenLookup | string (已弃用) | 已弃用:请使用 KeyLookup。 | "" |
ErrorHandler | fiber.ErrorHandler | ErrorHandler 在 fiber.Handler 返回错误时执行。 | DefaultErrorHandler |
Extractor | func(*fiber.Ctx) (string, error) | Extractor 返回 CSRF 令牌。如果设置,将取代基于 KeyLookup 的 Extractor。 | 基于 KeyLookup 的 Extractor |
HandlerContextKey | interface{} | 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,
}))