jwt v4 项目代码实例
安装jwt包go get -u github.com/golang-jwt/jwt/v4
pkg/jwt/jwt.go
package jwt
import (
"errors"
"github.com/gin-gonic/gin"
jwtpkg "github.com/golang-jwt/jwt/v4"
"github.com/sreio/gohub/pkg/app"
"github.com/sreio/gohub/pkg/config"
"github.com/sreio/gohub/pkg/logger"
"strings"
"time"
)
var (
ErrTokenExpired = errors.New("令牌已过期")
ErrTokenExpiredMaxRefresh = errors.New("令牌已过最大刷新时间")
ErrTokenMalformed = errors.New("请求令牌格式有误")
ErrTokenInvalid = errors.New("请求令牌无效")
ErrHeaderEmpty = errors.New("需要认证才能访问!")
ErrHeaderMalformed = errors.New("请求头中 Authorization 格式有误")
)
type JWT struct {
SignKey []byte // 秘钥
MaxRefresh time.Duration // 刷新token的最大过期时间
}
// UserInfo 自定义用户信息
type UserInfo struct {
UserID string `json:"user_id"`
UserName string `json:"user_name"`
}
// CustomJWTClaims 自定义Payload信息
type CustomJWTClaims struct {
UserInfo
ExpireAtTime int64 `json:"expire_time"` // 过期时间
// StandardClaims 结构体实现了 Claims 接口继承了 Valid() 方法
// JWT 规定了7个官方字段,提供使用:
// - iss (issuer):发布者
// - sub (subject):主题
// - iat (Issued At):生成签名的时间
// - exp (expiration time):签名过期时间
// - aud (audience):观众,相当于接受者
// - nbf (Not Before):生效时间
// - jti (JWT ID):编号
jwtpkg.RegisteredClaims
}
func NewJWT() *JWT {
return &JWT{
SignKey: []byte(config.Get("app.key")),
MaxRefresh: time.Duration(config.GetInt64("jwt.max_refresh_time")) * time.Minute,
}
}
// ParserToken 解析 Token,中间件中调用
func (j *JWT) ParserToken(ctx *gin.Context) (*CustomJWTClaims, error) {
// 从header获取token
tokenString, err := j.getTokenFromHeader(ctx)
if err != nil {
return nil, err
}
// 解析token
token, err := j.parseTokenString(tokenString)
if err != nil {
validationErr, ok := err.(*jwtpkg.ValidationError)
if ok {
if validationErr.Errors == jwtpkg.ValidationErrorMalformed {
return nil, ErrTokenMalformed
} else if validationErr.Errors == jwtpkg.ValidationErrorExpired {
return nil, ErrTokenExpired
}
}
return nil, ErrTokenInvalid
}
if claims, ok := token.Claims.(*CustomJWTClaims); ok && token.Valid {
return claims, nil
}
return nil, ErrTokenInvalid
}
// RefreshToken 更新 Token,用以提供 refresh token 接口
func (j *JWT) RefreshToken(ctx *gin.Context) (string, error) {
// 1. 从 Header 里获取 token
tokenString, parseErr := j.getTokenFromHeader(ctx)
if parseErr != nil {
return "", parseErr
}
// 2. 调用 jwt 库解析用户传参的 Token
token, err := j.parseTokenString(tokenString)
// 3. 解析出错,未报错证明是合法的 Token(甚至未到过期时间)
if err != nil {
validationErr, ok := err.(*jwtpkg.ValidationError)
// 满足 refresh 的条件:只是单一的报错 ValidationErrorExpired
if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired {
return "", err
}
}
// 4. 解析 JWTCustomClaims 的数据
claims := token.Claims.(*CustomJWTClaims)
// 5. 检查是否过了『最大允许刷新的时间』
t := app.TimeNowInTimezone().Add(-j.MaxRefresh).Unix()
// 首次签名时间 > (当前时间 - 最大允许刷新时间)
if claims.IssuedAt.Unix() > t {
claims.RegisteredClaims.ExpiresAt = jwtpkg.NewNumericDate(j.expireAtTime())
return j.createToken(*claims)
}
return "", ErrTokenExpiredMaxRefresh
}
// IssueToken 生成 Token,在登录成功时调用
func (j *JWT) IssueToken(info UserInfo) string {
// 构造自定义Payload信息
expireTime := j.expireAtTime()
claims := CustomJWTClaims{
// 用户信息
UserInfo: UserInfo{
UserID: info.UserID,
UserName: info.UserName,
},
// 过期时间
ExpireAtTime: expireTime.Unix(),
RegisteredClaims: jwtpkg.RegisteredClaims{
NotBefore: jwtpkg.NewNumericDate(app.TimeNowInTimezone()), // 签名生效时间
IssuedAt: jwtpkg.NewNumericDate(app.TimeNowInTimezone()), // 首次签名时间(后续刷新 Token 不会更新)
ExpiresAt: jwtpkg.NewNumericDate(expireTime), // 签名过期时间
Issuer: config.GetString("app.name"), // 签名颁发者
},
}
// 根据 claims 生成token对象
token, err := j.createToken(claims)
if err != nil {
logger.LogIf(err)
return ""
}
return token
}
// createToken 创建 Token,内部使用,外部请调用 IssueToken
func (j *JWT) createToken(claims CustomJWTClaims) (string, error) {
// 使用HS256算法进行token生成
t := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
return t.SignedString(j.SignKey)
}
// token过期时间
func (j *JWT) expireAtTime() time.Time {
timezone := app.TimeNowInTimezone()
var expireTime int64
if config.GetBool("app.debug") {
expireTime = config.GetInt64("jwt.debug_expire_time")
} else {
expireTime = config.GetInt64("jwt.expire_time")
}
expire := time.Duration(expireTime) * time.Minute
return timezone.Add(expire)
}
// getTokenFromHeader 使用 jwtpkg.ParseWithClaims 解析 Token
// Authorization:Bearer xxxxx
func (j *JWT) getTokenFromHeader(ctx *gin.Context) (string, error) {
authHeader := ctx.Request.Header.Get("Authorization")
if authHeader == "" {
return "", ErrHeaderEmpty
}
parts := strings.SplitN(authHeader, " ", 2)
if len(parts) != 2 || parts[0] != "Bearer" {
return "", ErrHeaderMalformed
}
return parts[1], nil
}
// parseTokenString 解析token
func (j *JWT) parseTokenString(token string) (*jwtpkg.Token, error) {
return jwtpkg.ParseWithClaims(token, &CustomJWTClaims{}, func(token *jwtpkg.Token) (interface{}, error) {
return j.SignKey, nil
})
}
使用方法
token := jwt.NewJWT().IssueToken(jwt.UserInfo{
UserID: userModel.GetIDString(),
UserName: userModel.Name,
})
本帖已被设为精华帖!
本帖由系统于 2年前 自动加精
感觉这章refreshToken根本就是做样子的,没有一点作用,一般正常流程都是登录成功后生成双 token,一个refreshToken 和 access token,进行请求的时候携带access token,access token过期后使用 refreshToken 获取新的 access token,因为access token时间短,被恶意获取后,损失不会很大,而 refreshToken 不需要频繁的在网络中传递保证了安全性,而这里却没有任何体现,不如直接将 access token 的过期时间变长没有如何区块,没必要使用 refreshToken