CSRF
适用于 Fiber 的 CSRF 中间件可针对 跨站请求伪造 (CSRF) 攻击提供保护。使用 RFC9110#section-9.2.1 定义为“安全”以外的方法(GET、HEAD、OPTIONS 和 TRACE)发出的请求将使用令牌进行验证。如果检测到潜在攻击,中间件将返回默认 403 禁止错误。
此中间件提供两种 令牌验证模式:双重提交 Cookie 模式(默认) 和 同步令牌模式(带会话)。
作为 纵深防御 措施,此中间件对 HTTPS 请求执行 引用检查。
令牌生成
CSRF 令牌在“安全”请求中生成,并且在现有令牌已过期或尚未设置时生成。如果 SingleUseToken
为 true
,则每次使用后都会生成一个新令牌。使用 c.Locals(contextKey)
检索 CSRF 令牌,其中 contextKey
在配置中定义。
安全注意事项
此中间件旨在防止 CSRF 攻击,但不能防止其他攻击媒介,例如 XSS。它应与其他安全措施结合使用。
切勿使用“安全”方法来修改数据,例如,切勿使用 GET 请求来修改资源。此中间件不会保护“安全”方法免受 CSRF 攻击。
令牌验证模式
双重提交 Cookie 模式(默认)
默认情况下,中间件使用 fiber.Storage
接口生成和存储令牌。这些令牌不链接到任何特定用户会话,并且使用双重提交 Cookie 模式进行验证。令牌存储在 Cookie 中,然后在请求中作为标头发送。中间件将 Cookie 值与标头值进行比较以验证令牌。这是一个不需要用户会话的安全模式。
当授权状态更改时,必须删除先前颁发的令牌,并生成一个新令牌。有关更多信息,请参阅 令牌生命周期 删除令牌。
使用此模式时,重要的是将 CookieSameSite
选项设置为 Lax
或 Strict
,并确保提取器不是 CsrfFromCookie
,并且 KeyLookup 不是 cookie:<name>
。
同步令牌模式(带会话)
将此中间件与用户会话一起使用时,可以将中间件配置为将令牌存储在会话中。使用用户会话时推荐此方法,因为它通常比双重提交 Cookie 模式更安全。
使用此模式时,在授权状态更改时重新生成会话非常重要,这也会删除令牌。有关详细信息,请参阅:令牌生命周期。
需要预会话,如果不存在,将自动创建。使用会话值来指示身份验证,而不是依赖会话的存在。
纵深防御
使用此中间件时,建议通过 HTTPS 提供页面,将 CookieSecure
选项设置为 true
,并将 CookieSameSite
选项设置为 Lax
或 Strict
。这可确保仅通过 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) bool | Next 定义了一个函数,当返回 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 |
Expiration | time.Duration | Expiration 是 CSRF 令牌过期之前的时间间隔。 | 1 * time.Hour |
SingleUseToken | 布尔值 | SingleUseToken 表示 CSRF 令牌是否在每次使用时销毁并生成新的令牌。(请参阅 TokenLifecycle) | false |
Storage | fiber.Storage | Store 用于存储中间件的状态。 | nil |
会话 | *session.Store | Session 用于存储中间件的状态。如果设置,则覆盖 Storage。 | nil |
SessionKey | 字符串 | 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 | 当从 fiber.Handler 返回错误时,执行 ErrorHandler。 | 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 错误
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,
}))