Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ module github.com/ecodeclub/ginx
go 1.21.1

require (
github.com/ecodeclub/ekit v0.0.8
github.com/gin-gonic/gin v1.9.1
github.com/golang-jwt/jwt/v5 v5.0.0
github.com/redis/go-redis/v9 v9.1.0
github.com/stretchr/testify v1.8.3
go.uber.org/mock v0.3.0
Expand Down
11 changes: 10 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
github.com/ecodeclub/ekit v0.0.8 h1:861Aot0GvD5ueREEYDVYc1oIhDuFyg6MTxIyiOa4Pvw=
github.com/ecodeclub/ekit v0.0.8/go.mod h1:OqTojKeKFTxeeAAUwNIPKu339SRkX6KAuoK/8A5BCEs=
github.com/gabriel-vasile/mimetype v1.4.2 h1:w5qFW6JKBz9Y393Y4q372O9A7cUSequkh1Q7OhCmWKU=
github.com/gabriel-vasile/mimetype v1.4.2/go.mod h1:zApsH/mKG4w07erKIaJPFiX0Tsq9BFQgN3qGY5GnNgA=
github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE=
Expand All @@ -31,6 +33,8 @@ github.com/go-playground/validator/v10 v10.14.0 h1:vgvQWe3XCz3gIeFDm/HnTIbj6UGmg
github.com/go-playground/validator/v10 v10.14.0/go.mod h1:9iXMNT7sEkjXb0I+enO7QXmzG6QCsPWY4zveKFVRSyU=
github.com/goccy/go-json v0.10.2 h1:CrxCmQqYDkv1z7lO7Wbh2HN93uovUHgrECaO5ZrCXAU=
github.com/goccy/go-json v0.10.2/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I=
github.com/golang-jwt/jwt/v5 v5.0.0 h1:1n1XNM9hk7O9mnQoNBGolZvzebBQ7p93ULHRc28XJUE=
github.com/golang-jwt/jwt/v5 v5.0.0/go.mod h1:pqrtFR0X4osieyHYxtmOUWsAWrfe1Q5UVIyoH402zdk=
github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk=
github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU=
github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE=
Expand All @@ -40,6 +44,8 @@ github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHm
github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg=
github.com/klauspost/cpuid/v2 v2.2.4 h1:acbojRNwl3o09bUq+yDCtZFc1aiwaAAxtcn8YkZXnvk=
github.com/klauspost/cpuid/v2 v2.2.4/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/leodido/go-urn v1.2.4 h1:XlAE/cm/ms7TE/VMVoduSpNBoyc2dOxHs5MZSwAN63Q=
github.com/leodido/go-urn v1.2.4/go.mod h1:7ZrI8mTSeBSHl/UaRyKQW1qZeMgak41ANeCNaVckg+4=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
Expand All @@ -49,6 +55,8 @@ github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q=
github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M=
github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs=
github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno=
github.com/pelletier/go-toml/v2 v2.0.8 h1:0ctb6s9mE31h0/lhu+J6OPmVeDxJn+kYnJc2jZR9tGQ=
github.com/pelletier/go-toml/v2 v2.0.8/go.mod h1:vuYfssBdrU2XDZ9bYydBu6t+6a6PYNcZljzZR9VXg+4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
Expand Down Expand Up @@ -90,8 +98,9 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw=
google.golang.org/protobuf v1.30.0 h1:kPPoIgf3TsEvrm0PFe15JQ+570QVxYzEvvHqChK+cng=
google.golang.org/protobuf v1.30.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU=
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
Expand Down
69 changes: 69 additions & 0 deletions middlewares/jwt/claims.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package jwt

import (
"time"

"github.com/ecodeclub/ekit/bean/option"
"github.com/golang-jwt/jwt/v5"
)

type RegisteredClaims[T any] struct {
Data T `json:"data"`
jwt.RegisteredClaims
}

type Options struct {
Comment thread
flycash marked this conversation as resolved.
Expire time.Duration // 有效期
EncryptionKey string // 加密密钥
DecryptKey string // 解密密钥
Method jwt.SigningMethod // 签名方式
Issuer string // 签发人
genIDFn func() string // 生成 JWT ID (jti) 的函数
}

// NewOptions 定义一个 JWT 配置.
// DecryptKey: 默认与 EncryptionKey 相同.
// Method: 默认使用 jwt.SigningMethodHS256 签名方式.
func NewOptions(expire time.Duration, encryptionKey string,
opts ...option.Option[Options]) *Options {
dOpts := Options{
Expire: expire,
EncryptionKey: encryptionKey,
DecryptKey: encryptionKey,
Method: jwt.SigningMethodHS256,
genIDFn: func() string { return "" },
}

option.Apply[Options](&dOpts, opts...)

return &dOpts
}

// WithDecryptKey 设置解密密钥.
func WithDecryptKey(decryptKey string) option.Option[Options] {
return func(o *Options) {
o.DecryptKey = decryptKey
}
}

// WithMethod 设置 JWT 的签名方法.
func WithMethod(method jwt.SigningMethod) option.Option[Options] {
return func(o *Options) {
o.Method = method
}
}

// WithIssuer 设置签发人.
func WithIssuer(issuer string) option.Option[Options] {
return func(o *Options) {
o.Issuer = issuer
}
}

// WithGenIDFunc 设置生成 JWT ID 的函数.
// 可以设置成 WithGenIDFunc(uuid.NewString).
func WithGenIDFunc(fn func() string) option.Option[Options] {
return func(o *Options) {
o.genIDFn = fn
}
}
184 changes: 184 additions & 0 deletions middlewares/jwt/claims_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,184 @@
package jwt

import (
"testing"
"time"

"github.com/ecodeclub/ekit/bean/option"
"github.com/golang-jwt/jwt/v5"
"github.com/stretchr/testify/assert"
)

func TestNewOptions(t *testing.T) {
var genIDFn func() string
tests := []struct {
name string
expire time.Duration
encryptionKey string
want *Options
}{
{
name: "normal",
expire: 10 * time.Minute,
encryptionKey: "sign key",
want: &Options{
Expire: 10 * time.Minute,
EncryptionKey: "sign key",
DecryptKey: "sign key",
Method: jwt.SigningMethodHS256,
genIDFn: genIDFn,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := NewOptions(tt.expire, tt.encryptionKey)
got.genIDFn = genIDFn
assert.Equal(t, tt.want, got)
})
}
}

func TestWithDecryptKey(t *testing.T) {
tests := []struct {
name string
fn func() option.Option[Options]
want string
}{
{
name: "normal",
fn: func() option.Option[Options] {
return nil
},
want: encryptionKey,
},
{
name: "set_another_key",
fn: func() option.Option[Options] {
return WithDecryptKey("another sign key")
},
want: "another sign key",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got string
if tt.fn() == nil {
got = NewOptions(defaultExpire, encryptionKey).
DecryptKey
} else {
got = NewOptions(defaultExpire, encryptionKey,
tt.fn()).DecryptKey
}
assert.Equal(t, tt.want, got)
})
}
}

func TestWithMethod(t *testing.T) {
tests := []struct {
name string
fn func() option.Option[Options]
want jwt.SigningMethod
}{
{
name: "normal",
fn: func() option.Option[Options] {
return nil
},
want: jwt.SigningMethodHS256,
},
{
name: "set_another_method",
fn: func() option.Option[Options] {
return WithMethod(jwt.SigningMethodHS384)
},
want: jwt.SigningMethodHS384,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got jwt.SigningMethod
if tt.fn() == nil {
got = NewOptions(defaultExpire, encryptionKey).
Method
} else {
got = NewOptions(defaultExpire, encryptionKey,
tt.fn()).Method
}
assert.Equal(t, tt.want, got)
})
}
}

func TestWithIssuer(t *testing.T) {
tests := []struct {
name string
fn func() option.Option[Options]
want string
}{
{
name: "normal",
fn: func() option.Option[Options] {
return nil
},
},
{
name: "set_another_issuer",
fn: func() option.Option[Options] {
return WithIssuer("foo")
},
want: "foo",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got string
if tt.fn() == nil {
got = NewOptions(defaultExpire, encryptionKey).
Issuer
} else {
got = NewOptions(defaultExpire, encryptionKey,
tt.fn()).Issuer
}
assert.Equal(t, tt.want, got)
})
}
}

func TestWithGenIDFunc(t *testing.T) {
tests := []struct {
name string
fn func() option.Option[Options]
want string
}{
{
name: "normal",
fn: func() option.Option[Options] {
return nil
},
},
{
name: "set_another_gen_id_func",
fn: func() option.Option[Options] {
return WithGenIDFunc(func() string {
return "unique id"
})
},
want: "unique id",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
var got string
if tt.fn() == nil {
got = NewOptions(defaultExpire, encryptionKey).
genIDFn()
} else {
got = NewOptions(defaultExpire, encryptionKey,
tt.fn()).genIDFn()
}
assert.Equal(t, tt.want, got)
})
}
}
Loading