介绍
本文介绍如何通过 rk-boot 实现服务端 CSRF 验证逻辑。
什么是 CSRF?
跨站请求伪造(英语:Cross-site request forgery),也被称为 one-click attack 或者 session riding,通常缩写为 CSRF 或者 XSRF, 是一种挟制用户在当前已登录的Web应用程序上执行非本意的操作的攻击方法。
跟跨网站脚本(XSS)相比,XSS 利用的是用户对指定网站的信任,CSRF 利用的是网站对用户网页浏览器的信任。
有什么防御方法?
流行的防御方法有如下几种,我们通过例子实现【添加校验 Token】的防御。
1: 令牌同步模式 2:检查 Referer 字段 3:添加校验 Token
请访问如下地址获取完整教程:
- https://rkdocs.netlify.app/cn
安装
代码语言:go复制go get github.com/rookie-ninja/rk-boot/gf
快速开始
1.创建 boot.yaml
boot.yaml 文件会告诉 rk-boot 如何启动 gogf/gf 服务。
在下面的 YAML 文件中,我们声明了一件事:
- 开启 CSRF 拦截器,使用默认参数。拦截器会检查请求 Header 里 X-CSRF-Token 的值,判断 Token 是否正确。
---
gf:
- name: greeter # Required
port: 8080 # Required
enabled: true # Required
interceptors:
csrf:
enabled: true # Optional, default: false
2.创建 main.go
我们在 gogf/gf 里添加两个 Restful API。
- GET /v1/hello: 返回服务端生成的 CSRF Token
- POST /v1/hello: 验证 CSRF Token
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"github.com/gogf/gf/v2/net/ghttp"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-boot/gf"
"net/http"
)
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Register handler
entry := rkbootgf.GetGfEntry("greeter")
entry.Server.BindHandler("/v1/hello", hello)
// Bootstrap
boot.Bootstrap(context.TODO())
boot.WaitForShutdownSig(context.TODO())
}
func hello(ctx *ghttp.Request) {
ctx.Response.WriteHeader(http.StatusOK)
ctx.Response.WriteJson(map[string]string{
"message": "hello!",
})
}
3.文件夹结构
代码语言:txt复制.
├── boot.yaml
├── go.mod
├── go.sum
└── main.go
0 directories, 4 files
4.启动 main.go
代码语言:txt复制$ go run main.go
2022-02-07T15:02:03.187 0800 INFO boot/gf_entry.go:600 Bootstrap gfEntry {"eventId": "8238e90e-5cd0-4da7-9f9b-7bb9b1946978", "entryName": "greeter", "entryType": "GoFrame"}
------------------------------------------------------------------------
endTime=2022-02-07T15:02:03.188021 08:00
startTime=2022-02-07T15:02:03.187943 08:00
elapsedNano=78089
timezone=CST
ids={"eventId":"8238e90e-5cd0-4da7-9f9b-7bb9b1946978"}
app={"appName":"rk","appVersion":"","entryName":"greeter","entryType":"GoFrame"}
env={"arch":"amd64","az":"*","domain":"*","hostname":"lark.local","localIP":"10.8.0.2","os":"darwin","realm":"*","region":"*"}
payloads={"gfPort":8080}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE
5.验证
- 发送 GET 请求到 /v1/hello,我们将获得 CSRF Token。
$ curl -X GET -vs localhost:8080/v1/hello
...
> Cookie: _csrf=my-test-csrf-token
> X-CSRF-Token:my-test-csrf-token
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Server: GoFrame HTTP Server
< Set-Cookie: _csrf=my-test-csrf-token; Expires=Tue, 08 Feb 2022 07:02:45 GMT
< Trace-Id: 104af099fb6ed11600722376ab2d8a82
< Vary: Cookie
< Date: Mon, 07 Feb 2022 07:02:45 GMT
< Content-Length: 20
<
* Connection #0 to host localhost left intact
{"message":"hello!"}
- 发送 POST 请求到 /v1/hello,提供合法 CSRF Token。
$ curl -X POST -v --cookie "_csrf=my-test-csrf-token" -H "X-CSRF-Token:my-test-csrf-token" localhost:8080/v1/hello
...
> Cookie: _csrf=my-test-csrf-token
> X-CSRF-Token:my-test-csrf-token
>
< HTTP/1.1 200 OK
< Content-Type: application/json
< Server: GoFrame HTTP Server
< Set-Cookie: _csrf=my-test-csrf-token; Expires=Tue, 08 Feb 2022 07:03:31 GMT
< Trace-Id: 90ded13e066fd1160172237663ed8fbb
< Vary: Cookie
< Date: Mon, 07 Feb 2022 07:03:31 GMT
< Content-Length: 20
<
* Connection #0 to host localhost left intact
{"message":"hello!"}
- 发送 POST 请求到 /v1/hello,提供非法 CSRF Token。
$ curl -X POST -v -H "X-CSRF-Token:my-test-csrf-token" localhost:8080/v1/hello
...
> X-CSRF-Token:my-test-csrf-token
>
< HTTP/1.1 403 Forbidden
< Server: GoFrame HTTP Server
< Trace-Id: c88c53630b6fd116027223761a59ee69
< Date: Mon, 07 Feb 2022 07:03:53 GMT
< Content-Length: 91
< Content-Type: text/plain; charset=utf-8
<
* Connection #0 to host localhost left intact
{"error":{"code":403,"status":"Forbidden","message":"invalid csrf token","details":[null]}}*
CSRF 拦截器选项
rk-boot 提供了若干 CSRF 拦截器选项,除非是有特殊需要,不推荐覆盖选项。
选项 | 描述 | 类型 | 默认值 |
---|---|---|---|
gf.interceptors.csrf.enabled | 启动 CSRF 拦截器 | boolean | false |
gf.interceptors.csrf.tokenLength | Token 长度 | int | 32 |
gf.interceptors.csrf.tokenLookup | 从哪里获取 Token,请参考下面的介绍 | string | “header:X-CSRF-Token” |
gf.interceptors.csrf.cookieName | Cookie 名字 | string | _csrf |
gf.interceptors.csrf.cookieDomain | Cookie domain | string | "" |
gf.interceptors.csrf.cookiePath | Cookie path | string | "" |
gf.interceptors.csrf.cookieMaxAge | Cookie MaxAge(秒) | int | 86400 (24小时) |
gf.interceptors.csrf.cookieHttpOnly | Cookie HTTP Only 选项 | bool | false |
gf.interceptors.csrf.cookieSameSite | Cookie SameSite 选项, 支持 lax, strict, none, default | string | "lax" |
gf.interceptors.csrf.ignorePrefix | 忽略 CSRF 验证的 Restful API Path | []string | [] |
tokenLookup 格式
目前支持如下三种方式,拦截器会使用下面方式的一种,在请求中寻找 Token。
- 从 HTTP Header 中获取
- 从 HTTP Form 中获取
- 从 HTTP Query 中获取
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>"
// - "form:<name>"
// - "query:<name>"
// Optional. Default value "header:X-CSRF-Token".