前言
在Golang语言中,jwt-go库提供了一些jwt编码和验证的工具,因此我们很容易使用该库来实现token认证。另外,gin框架中支持用户自定义middleware,我们可以很好的将jwt相关的逻辑封装在middleware中,然后对具体的接口进行认证。
用户登陆 -> token
定义路由
v1 := router.Group("/apis/v1/")
{
v1.POST("/register", controller.RegisterUser) // 完成用户的注册
v1.POST("/login", controller.Login) // 实现用户的登陆
}
请求处理
用户校验
// 登陆逻辑校验
isPass, user, err := model.LoginCheck(loginReq)
// LoginCheck验证
func LoginCheck(login LoginReq) (bool, User, error) {
//...
var user User
dbErr := DB.Where("name = ?", login.Name).Find(&user).Error
//...
if login.Name == user.Name && login.Pwd == user.Pwd {
//...
}
//...
}
token 生成器
// 构造SignKey: 签名和解签名需要使用一个值
j := md.NewJWT()
// 构造用户claims信息(负荷)
claims := middleware.CustomClaims{
user.Name,
user.Email,
jwtgo.StandardClaims{
NotBefore: int64(time.Now().Unix() - 1000), // 签名生效时间
ExpiresAt: int64(time.Now().Unix() + 3600), // 签名过期时间
Issuer: "bgbiao.top", // 签名颁发者
},
}
// 根据claims生成token对象
token, err := j.CreateToken(claims)
返回 token
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "登陆成功",
"data": data, // 存放userId, userName, token...
})
自定义中间件
创建 token
// 创建Token(基于用户的基本信息claims)
// 使用HS256算法进行token生成
// 使用用户基本信息claims以及签名key(signkey)生成token
func (j *JWT) CreateToken(claims CustomClaims) (string, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#Token
// 返回一个token的结构体指针
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(j.SigningKey)
}
token -> 用户信息
定义路由
// secure v1
sv1 := router.Group("/apis/v1/auth/")
sv1.Use(middleware.JWTAuth())
{
sv1.GET("/userInfo", controller.GetUserInfo)
}
请求过滤
获取 token
token := c.Request.Header.Get("Authorization")
解析 token
// token 解析
func (j *JWT) ParserToken(tokenString string) (*CustomClaims, error) {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ParseWithClaims
// 输入用户自定义的Claims结构体对象,token,以及自定义函数来解析token字符串为jwt的Token结构体指针
// Keyfunc是匿名函数类型: type Keyfunc func(*Token) (interface{}, error)
// func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {}
token, err := jwt.ParseWithClaims(tokenString, &CustomClaims{}, func(token *jwt.Token) (interface{}, error) {
return j.SigningKey, nil
})
if err != nil {
// https://gowalker.org/github.com/dgrijalva/jwt-go#ValidationError
// jwt.ValidationError 是一个无效token的错误结构
if ve, ok := err.(*jwt.ValidationError); ok {
// ValidationErrorMalformed是一个uint常量,表示token不可用
if ve.Errors&jwt.ValidationErrorMalformed != 0 {
return nil, TokenMalformed
// ValidationErrorExpired表示Token过期
} else if ve.Errors&jwt.ValidationErrorExpired != 0 {
return nil, TokenExpired
// ValidationErrorNotValidYet表示无效token
} else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 {
return nil, TokenNotValidYet
} else {
return nil, TokenInvalid
}
}
}
// 将token中的claims信息解析出来和用户原始数据进行校验
// 做以下类型断言,将token.Claims转换成具体用户自定义的Claims结构体
if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
return claims, nil
}
return nil, TokenInvalid
}
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
请求响应
claims := c.MustGet("claims").(*md.CustomClaims)
if claims != nil {
c.JSON(http.StatusOK, gin.H{
"status": 0,
"msg": "token有效",
"data": claims,
})
}
jwt-go 源码分析
github.com/dgrijalva/jwt-go
我在这里通过dubug调试,去了解了jwt-go
toekn 的实现过程。挑出一些函数分享一下:
tolen 结构体
// A JWT Token. Different fields will be used depending on whether you're
// creating or parsing/verifying a token.
type Token struct {
Raw string // The raw token. Populated when you Parse a token
Method SigningMethod // The signing method used or to be used
Header map[string]interface{} // The first segment of the token
Claims Claims // The second segment of the token
Signature string // The third segment of the token. Populated when you Parse a token
Valid bool // Is the token valid? Populated when you Parse/Verify a token
}
返回一个token的结构体指针
func NewWithClaims(method SigningMethod, claims Claims) *Token {
return &Token{
Header: map[string]interface{}{
"typ": "JWT",
"alg": method.Alg(),
},
Claims: claims,
Method: method,
}
}
token 签名
SignKey: 签名信息应该设置成动态从库中获取,提高 token 的安全性。
// Get the complete, signed token
func (t *Token) SignedString(key interface{}) (string, error) {
var sig, sstr string
var err error
if sstr, err = t.SigningString(); err != nil {
return "", err
}
if sig, err = t.Method.Sign(sstr, key); err != nil {
return "", err
}
return strings.Join([]string{sstr, sig}, "."), nil
}
token 解析
会对token进行解析,信息存放在claim.
func ParseWithClaims(tokenString string, claims Claims, keyFunc Keyfunc) (*Token, error) {
return new(Parser).ParseWithClaims(tokenString, claims, keyFunc)
}
token.go Structure
总结
在go中使用jwt,可以引入 github.com/dgrijalva/jwt-go
,这个包是官方推荐的也是使用比较多的。在项目中集成 jwt-go 需要注意jwt并不是完全安全的,如果劫持者获取到多个token,有可能通过规则知道你的签名,会解密得到用户信息。因此SignKey
应该设置成动态从库中获取,其次必须设置一个过期时间,在token过期的时候可以自动更新token, 当然用户是不知道的,也就是说不用重复登陆。