JWT 鉴权

This commit is contained in:
Max 2021-11-22 00:42:53 +08:00
parent be9b0007cb
commit 2c597509c7
3 changed files with 64 additions and 39 deletions

View file

@ -6,19 +6,28 @@ import (
"github.com/golang-jwt/jwt" "github.com/golang-jwt/jwt"
"github.com/yaoapp/gou" "github.com/yaoapp/gou"
"github.com/yaoapp/gou/session"
"github.com/yaoapp/kun/any"
"github.com/yaoapp/kun/exception" "github.com/yaoapp/kun/exception"
"github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/config"
) )
// JwtClaims 用户Token // JwtClaims 用户Token
type JwtClaims struct { type JwtClaims struct {
ID int ID int `json:"id"`
Data map[string]interface{} SID string `json:"sid"`
Data map[string]interface{} `json:"data"`
jwt.StandardClaims jwt.StandardClaims
} }
// JwtToken JWT令牌
type JwtToken struct {
Token string `json:"token"`
ExpiresAt int64 `json:"expires_at"`
}
// JwtValidate JWT 校验 // JwtValidate JWT 校验
func JwtValidate(tokenString string) map[string]interface{} { func JwtValidate(tokenString string) *JwtClaims {
token, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenString, &JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.Conf.JWT.Secret), nil return []byte(config.Conf.JWT.Secret), nil
}) })
@ -29,7 +38,7 @@ func JwtValidate(tokenString string) map[string]interface{} {
} }
if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid { if claims, ok := token.Claims.(*JwtClaims); ok && token.Valid {
return claims.Data return claims
} }
exception.New("令牌无效", 403).Ctx(token.Claims).Throw() exception.New("令牌无效", 403).Ctx(token.Claims).Throw()
@ -37,26 +46,42 @@ func JwtValidate(tokenString string) map[string]interface{} {
} }
// JwtMake 生成 JWT // JwtMake 生成 JWT
// subject options[0], audience options[1], issuer options[1] // option: {"subject":"<主题>", "audience": "<接收人>", "issuer":"<签发人>", "timeout": "<有效期,单位秒>", "sid":"<会话ID>"}
func JwtMake(id int, data map[string]interface{}, timeout int64, options ...string) map[string]interface{} { func JwtMake(id int, data map[string]interface{}, option map[string]interface{}) JwtToken {
now := time.Now().Unix() now := time.Now().Unix()
expiresAt := now + timeout sid := ""
timeout := int64(3600)
uid := fmt.Sprintf("%d", id) uid := fmt.Sprintf("%d", id)
subject := "User Token" subject := "User Token"
audience := "Xiang Metadata Admin Panel" audience := "Xiang Metadata Admin Panel"
issuer := fmt.Sprintf("xiang:%d", id) issuer := fmt.Sprintf("xiang:%d", id)
length := len(options) if v, has := option["subject"]; has {
if length > 0 { subject = fmt.Sprintf("%v", v)
subject = options[0]
} }
if length > 1 { if v, has := option["audience"]; has {
audience = options[1] audience = fmt.Sprintf("%v", v)
} }
if length > 2 { if v, has := option["issuer"]; has {
issuer = options[2] issuer = fmt.Sprintf("%v", v)
} }
if v, has := option["sid"]; has {
sid = fmt.Sprintf("%v", v)
}
if v, has := option["timeout"]; has {
timeout = int64(any.Of(v).CInt())
}
expiresAt := now + timeout
if sid == "" {
sid = session.ID()
}
// 设定会话过期时间 (并写需要加锁,这个逻辑需要优化)
// session.Global().Expire(time.Duration(timeout) * time.Second)
claims := &JwtClaims{ claims := &JwtClaims{
ID: id, ID: id,
SID: sid, // 会话ID
Data: data, Data: data,
StandardClaims: jwt.StandardClaims{ StandardClaims: jwt.StandardClaims{
Id: uid, // 唯一ID Id: uid, // 唯一ID
@ -73,23 +98,22 @@ func JwtMake(id int, data map[string]interface{}, timeout int64, options ...stri
if err != nil { if err != nil {
exception.New("生成令牌失败", 500).Ctx(err).Throw() exception.New("生成令牌失败", 500).Ctx(err).Throw()
} }
return map[string]interface{}{ return JwtToken{
"token": tokenString, Token: tokenString,
"expires_at": expiresAt, ExpiresAt: expiresAt,
} }
} }
// ProcessJwtMake xiang.helper.JwtMake 生成JWT // ProcessJwtMake xiang.helper.JwtMake 生成JWT
func ProcessJwtMake(process *gou.Process) interface{} { func ProcessJwtMake(process *gou.Process) interface{} {
process.ValidateArgNums(3) process.ValidateArgNums(2)
id := process.ArgsInt(0) id := process.ArgsInt(0)
data := process.ArgsMap(1) data := process.ArgsMap(1)
timeout := int64(process.ArgsInt(2)) option := map[string]interface{}{}
args := []string{} if process.NumOfArgsIs(3) {
for i := 3; i < len(process.Args); i++ { option = process.ArgsMap(2)
args = append(args, fmt.Sprintf("%v", process.Args[i]))
} }
return JwtMake(id, data, timeout, args...) return JwtMake(id, data, option)
} }
// ProcessJwtValidate xiang.helper.JwtValidate 校验JWT // ProcessJwtValidate xiang.helper.JwtValidate 校验JWT

View file

@ -10,24 +10,27 @@ import (
func TestJwt(t *testing.T) { func TestJwt(t *testing.T) {
data := map[string]interface{}{"hello": "world", "id": 1} data := map[string]interface{}{"hello": "world", "id": 1}
token := JwtMake(1, data, 1, "Unit Test", "Test", "UnitTest") option := map[string]interface{}{"subject": "Unit Test", "audience": "Test", "issuer": "UnitTest", "timeout": 1, "sid": ""}
tokenString := token["token"].(string) token := JwtMake(1, data, option)
tokenString := token.Token
res := JwtValidate(tokenString) res := JwtValidate(tokenString)
assert.Equal(t, float64(1), res["id"]) assert.NotNil(t, res)
assert.Equal(t, "world", res["hello"]) assert.Equal(t, float64(1), res.Data["id"])
assert.Equal(t, "world", res.Data["hello"])
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
assert.Panics(t, func() { JwtValidate(tokenString) }) assert.Panics(t, func() { JwtValidate(tokenString) })
} }
func TestProcessJwt(t *testing.T) { func TestProcessJwt(t *testing.T) {
data := map[string]interface{}{"hello": "world", "id": 1} data := map[string]interface{}{"hello": "world", "id": 1}
args := []interface{}{1, data, 1, "Unit Test", "Test", "UnitTest"} option := map[string]interface{}{"subject": "Unit Test", "audience": "Test", "issuer": "UnitTest", "timeout": 1, "sid": ""}
args := []interface{}{1, data, option}
process := gou.NewProcess("xiang.helper.JwtMake", args...) process := gou.NewProcess("xiang.helper.JwtMake", args...)
token := process.Run().(map[string]interface{}) token := process.Run().(JwtToken)
tokenString := token["token"].(string) tokenString := token.Token
res := gou.NewProcess("xiang.helper.JwtValidate", tokenString).Run().(map[string]interface{}) res := gou.NewProcess("xiang.helper.JwtValidate", tokenString).Run().(*JwtClaims)
assert.Equal(t, float64(1), res["id"]) assert.Equal(t, float64(1), res.Data["id"])
assert.Equal(t, "world", res["hello"]) assert.Equal(t, "world", res.Data["hello"])
time.Sleep(2 * time.Second) time.Sleep(2 * time.Second)
assert.Panics(t, func() { gou.NewProcess("xiang.helper.JwtValidate", tokenString).Run() }) assert.Panics(t, func() { gou.NewProcess("xiang.helper.JwtValidate", tokenString).Run() })
} }

View file

@ -7,7 +7,7 @@ import (
"github.com/dgrijalva/jwt-go" "github.com/dgrijalva/jwt-go"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/yaoapp/xiang/config" "github.com/yaoapp/xiang/config"
"github.com/yaoapp/xiang/user" "github.com/yaoapp/xiang/helper"
"github.com/yaoapp/xiang/xlog" "github.com/yaoapp/xiang/xlog"
) )
@ -29,7 +29,7 @@ func bearerJWT(c *gin.Context) {
if config.Conf.Mode == "debug" { if config.Conf.Mode == "debug" {
xlog.Printf("JWT: %s Secret: %s", tokenString, config.Conf.JWT.Secret) xlog.Printf("JWT: %s Secret: %s", tokenString, config.Conf.JWT.Secret)
} }
token, err := jwt.ParseWithClaims(tokenString, &user.JwtClaims{}, func(token *jwt.Token) (interface{}, error) { token, err := jwt.ParseWithClaims(tokenString, &helper.JwtClaims{}, func(token *jwt.Token) (interface{}, error) {
return []byte(config.Conf.JWT.Secret), nil return []byte(config.Conf.JWT.Secret), nil
}) })
@ -40,10 +40,8 @@ func bearerJWT(c *gin.Context) {
return return
} }
if claims, ok := token.Claims.(*user.JwtClaims); ok && token.Valid { if claims, ok := token.Claims.(*helper.JwtClaims); ok && token.Valid {
c.Set("id", claims.Subject) c.Set("__sid", claims.SID)
c.Set("type", claims.Type)
c.Set("name", claims.Name)
c.Next() c.Next()
return return
} }