Skip to content

Commit 4a27235

Browse files
authored
实现 JWT 一键支持 (#8)
1 parent 3702e7b commit 4a27235

File tree

7 files changed

+1565
-1
lines changed

7 files changed

+1565
-1
lines changed

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ module github.com/ecodeclub/ginx
33
go 1.21.1
44

55
require (
6+
github.com/ecodeclub/ekit v0.0.8
67
github.com/gin-gonic/gin v1.9.1
8+
github.com/golang-jwt/jwt/v5 v5.0.0
79
github.com/redis/go-redis/v9 v9.1.0
810
github.com/stretchr/testify v1.8.3
911
go.uber.org/atomic v1.11.0

go.sum

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
1515
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1616
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
1717
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
18+
github.com/ecodeclub/ekit v0.0.8 h1:861Aot0GvD5ueREEYDVYc1oIhDuFyg6MTxIyiOa4Pvw=
19+
github.com/ecodeclub/ekit v0.0.8/go.mod h1:OqTojKeKFTxeeAAUwNIPKu339SRkX6KAuoK/8A5BCEs=
1820
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
1921
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
2022
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
@@ -31,6 +33,8 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
3133
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
3234
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
3335
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
36+
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
37+
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
3438
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
3539
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
3640
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
@@ -40,6 +44,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
4044
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
4145
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
4246
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
47+
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
48+
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
4349
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
4450
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
4551
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
@@ -49,6 +55,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
4955
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
5056
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
5157
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
58+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
59+
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
5260
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
5361
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
5462
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -96,8 +104,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
96104
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
97105
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
98106
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
99-
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
100107
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
108+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
109+
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
101110
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
102111
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
103112
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

middlewares/jwt/claims.go

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package jwt
2+
3+
import (
4+
"time"
5+
6+
"github.com/ecodeclub/ekit/bean/option"
7+
"github.com/golang-jwt/jwt/v5"
8+
)
9+
10+
type RegisteredClaims[T any] struct {
11+
Data T `json:"data"`
12+
jwt.RegisteredClaims
13+
}
14+
15+
type Options struct {
16+
Expire time.Duration // 有效期
17+
EncryptionKey string // 加密密钥
18+
DecryptKey string // 解密密钥
19+
Method jwt.SigningMethod // 签名方式
20+
Issuer string // 签发人
21+
genIDFn func() string // 生成 JWT ID (jti) 的函数
22+
}
23+
24+
// NewOptions 定义一个 JWT 配置.
25+
// DecryptKey: 默认与 EncryptionKey 相同.
26+
// Method: 默认使用 jwt.SigningMethodHS256 签名方式.
27+
func NewOptions(expire time.Duration, encryptionKey string,
28+
opts ...option.Option[Options]) *Options {
29+
dOpts := Options{
30+
Expire: expire,
31+
EncryptionKey: encryptionKey,
32+
DecryptKey: encryptionKey,
33+
Method: jwt.SigningMethodHS256,
34+
genIDFn: func() string { return "" },
35+
}
36+
37+
option.Apply[Options](&dOpts, opts...)
38+
39+
return &dOpts
40+
}
41+
42+
// WithDecryptKey 设置解密密钥.
43+
func WithDecryptKey(decryptKey string) option.Option[Options] {
44+
return func(o *Options) {
45+
o.DecryptKey = decryptKey
46+
}
47+
}
48+
49+
// WithMethod 设置 JWT 的签名方法.
50+
func WithMethod(method jwt.SigningMethod) option.Option[Options] {
51+
return func(o *Options) {
52+
o.Method = method
53+
}
54+
}
55+
56+
// WithIssuer 设置签发人.
57+
func WithIssuer(issuer string) option.Option[Options] {
58+
return func(o *Options) {
59+
o.Issuer = issuer
60+
}
61+
}
62+
63+
// WithGenIDFunc 设置生成 JWT ID 的函数.
64+
// 可以设置成 WithGenIDFunc(uuid.NewString).
65+
func WithGenIDFunc(fn func() string) option.Option[Options] {
66+
return func(o *Options) {
67+
o.genIDFn = fn
68+
}
69+
}

middlewares/jwt/claims_test.go

Lines changed: 184 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,184 @@
1+
package jwt
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/ecodeclub/ekit/bean/option"
8+
"github.com/golang-jwt/jwt/v5"
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestNewOptions(t *testing.T) {
13+
var genIDFn func() string
14+
tests := []struct {
15+
name string
16+
expire time.Duration
17+
encryptionKey string
18+
want *Options
19+
}{
20+
{
21+
name: "normal",
22+
expire: 10 * time.Minute,
23+
encryptionKey: "sign key",
24+
want: &Options{
25+
Expire: 10 * time.Minute,
26+
EncryptionKey: "sign key",
27+
DecryptKey: "sign key",
28+
Method: jwt.SigningMethodHS256,
29+
genIDFn: genIDFn,
30+
},
31+
},
32+
}
33+
for _, tt := range tests {
34+
t.Run(tt.name, func(t *testing.T) {
35+
got := NewOptions(tt.expire, tt.encryptionKey)
36+
got.genIDFn = genIDFn
37+
assert.Equal(t, tt.want, got)
38+
})
39+
}
40+
}
41+
42+
func TestWithDecryptKey(t *testing.T) {
43+
tests := []struct {
44+
name string
45+
fn func() option.Option[Options]
46+
want string
47+
}{
48+
{
49+
name: "normal",
50+
fn: func() option.Option[Options] {
51+
return nil
52+
},
53+
want: encryptionKey,
54+
},
55+
{
56+
name: "set_another_key",
57+
fn: func() option.Option[Options] {
58+
return WithDecryptKey("another sign key")
59+
},
60+
want: "another sign key",
61+
},
62+
}
63+
for _, tt := range tests {
64+
t.Run(tt.name, func(t *testing.T) {
65+
var got string
66+
if tt.fn() == nil {
67+
got = NewOptions(defaultExpire, encryptionKey).
68+
DecryptKey
69+
} else {
70+
got = NewOptions(defaultExpire, encryptionKey,
71+
tt.fn()).DecryptKey
72+
}
73+
assert.Equal(t, tt.want, got)
74+
})
75+
}
76+
}
77+
78+
func TestWithMethod(t *testing.T) {
79+
tests := []struct {
80+
name string
81+
fn func() option.Option[Options]
82+
want jwt.SigningMethod
83+
}{
84+
{
85+
name: "normal",
86+
fn: func() option.Option[Options] {
87+
return nil
88+
},
89+
want: jwt.SigningMethodHS256,
90+
},
91+
{
92+
name: "set_another_method",
93+
fn: func() option.Option[Options] {
94+
return WithMethod(jwt.SigningMethodHS384)
95+
},
96+
want: jwt.SigningMethodHS384,
97+
},
98+
}
99+
for _, tt := range tests {
100+
t.Run(tt.name, func(t *testing.T) {
101+
var got jwt.SigningMethod
102+
if tt.fn() == nil {
103+
got = NewOptions(defaultExpire, encryptionKey).
104+
Method
105+
} else {
106+
got = NewOptions(defaultExpire, encryptionKey,
107+
tt.fn()).Method
108+
}
109+
assert.Equal(t, tt.want, got)
110+
})
111+
}
112+
}
113+
114+
func TestWithIssuer(t *testing.T) {
115+
tests := []struct {
116+
name string
117+
fn func() option.Option[Options]
118+
want string
119+
}{
120+
{
121+
name: "normal",
122+
fn: func() option.Option[Options] {
123+
return nil
124+
},
125+
},
126+
{
127+
name: "set_another_issuer",
128+
fn: func() option.Option[Options] {
129+
return WithIssuer("foo")
130+
},
131+
want: "foo",
132+
},
133+
}
134+
for _, tt := range tests {
135+
t.Run(tt.name, func(t *testing.T) {
136+
var got string
137+
if tt.fn() == nil {
138+
got = NewOptions(defaultExpire, encryptionKey).
139+
Issuer
140+
} else {
141+
got = NewOptions(defaultExpire, encryptionKey,
142+
tt.fn()).Issuer
143+
}
144+
assert.Equal(t, tt.want, got)
145+
})
146+
}
147+
}
148+
149+
func TestWithGenIDFunc(t *testing.T) {
150+
tests := []struct {
151+
name string
152+
fn func() option.Option[Options]
153+
want string
154+
}{
155+
{
156+
name: "normal",
157+
fn: func() option.Option[Options] {
158+
return nil
159+
},
160+
},
161+
{
162+
name: "set_another_gen_id_func",
163+
fn: func() option.Option[Options] {
164+
return WithGenIDFunc(func() string {
165+
return "unique id"
166+
})
167+
},
168+
want: "unique id",
169+
},
170+
}
171+
for _, tt := range tests {
172+
t.Run(tt.name, func(t *testing.T) {
173+
var got string
174+
if tt.fn() == nil {
175+
got = NewOptions(defaultExpire, encryptionKey).
176+
genIDFn()
177+
} else {
178+
got = NewOptions(defaultExpire, encryptionKey,
179+
tt.fn()).genIDFn()
180+
}
181+
assert.Equal(t, tt.want, got)
182+
})
183+
}
184+
}

0 commit comments

Comments
 (0)