GoFrame 框架(rk-boot): 快速实现 CSRF 验证

2022-02-07 15:13:44 浏览数 (5)

介绍

本文介绍如何通过 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 是否正确。
代码语言:yaml复制
---
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
代码语言:go复制
// 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。
代码语言:txt复制
$ 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。
代码语言:txt复制
$ 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。
代码语言:txt复制
$ 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 中获取
代码语言:go复制
// Optional. Default value "header:X-CSRF-Token".
// Possible values:
// - "header:<name>"
// - "form:<name>"
// - "query:<name>"
// Optional. Default value "header:X-CSRF-Token".

1 人点赞