-
Notifications
You must be signed in to change notification settings - Fork 0
/
csrf.go
103 lines (85 loc) · 2.1 KB
/
csrf.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package csrf
import (
"crypto/subtle"
"net/http"
zeroapi "github.com/zerogo-hub/zero-api"
zeroctx "github.com/zerogo-hub/zero-api/context"
zerocrypto "github.com/zerogo-hub/zero-helper/crypto"
zerorandom "github.com/zerogo-hub/zero-helper/random"
)
// New 为请求填充 csrf
// 需要启用 cache 功能
func New(opts ...Option) zeroapi.Handler {
opt := defaultOption()
if len(opts) > 0 {
option := opts[0]
opt.replace(option)
}
return func(ctx zeroapi.Context) {
if opt.IgnoreFunc != nil && opt.IgnoreFunc(ctx) {
return
}
token := zerocrypto.HmacMd5(zerorandom.String(32), opt.Key)
ctx.SetCookie(
opt.CookieName,
token,
zeroctx.WithCookieMaxAge(opt.CookieMaxAge),
zeroctx.WithCookieDomain(opt.CookieDomain),
zeroctx.WithCookiePath(opt.CookiePath),
zeroctx.WithCookieHTTPOnly(opt.CookieHTTPOnly),
)
}
}
// Verify 验证 csrf
func Verify(opts ...Option) zeroapi.Handler {
opt := defaultOption()
if len(opts) > 0 {
option := opts[0]
opt.replace(option)
}
return func(ctx zeroapi.Context) {
method := ctx.Method()
for _, requiredMethod := range opt.Methods {
if method == requiredMethod {
if !opt.verify(ctx) {
ctx.Stopped()
ctx.SetHTTPCode(http.StatusBadRequest)
ctx.App().Logger().Errorf("invalid csrf token, method: %s, path: %s, ip: %s", ctx.Method(), ctx.Path(), ctx.IP())
}
break
}
}
}
}
func (opt *Option) verify(ctx zeroapi.Context) bool {
clientToken := opt.clientToken(ctx)
if len(clientToken) == 0 {
return false
}
cookieToken := opt.cookieToken(ctx)
if len(cookieToken) == 0 {
return false
}
result := subtle.ConstantTimeCompare([]byte(clientToken), []byte(cookieToken)) == 1
return result
}
func (opt *Option) clientToken(ctx zeroapi.Context) string {
// 顺序: query/body/header
token := ctx.Get(opt.QueryName)
if len(token) == 0 {
token = ctx.Query(opt.BodyName)
if len(token) == 0 {
token = ctx.Header(opt.HeaderName)
}
}
return token
}
func (opt *Option) cookieToken(ctx zeroapi.Context) string {
token, err := ctx.Cookie(
opt.CookieName,
)
if err != nil {
return ""
}
return token
}