| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223 |
- package jwt
- import (
- "errors"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- jwtpkg "github.com/golang-jwt/jwt/v5"
- "github.com/runningwater/gohub/pkg/app"
- "github.com/runningwater/gohub/pkg/config"
- "github.com/runningwater/gohub/pkg/logger"
- )
- var (
- ErrTokenExpired error = errors.New("令牌已过期")
- ErrTokenNotValidYet error = errors.New("令牌尚未生效")
- ErrTokenMalformed error = errors.New("令牌格式错误")
- ErrTokenInvalid error = errors.New("令牌无效")
- ErrTokenExpiredMaxRefresh error = errors.New("令牌已过期,无法刷新")
- ErrHeaderEmpty error = errors.New("需要认证才能访问!")
- ErrHeaderMalformed error = errors.New("请求头格式错误")
- )
- // JWT 结构体,包含签名密钥和刷新 Token 的最大过期时间
- type JWT struct {
- // 签名密钥,用于生成和验证 JWT 签名,读取 app.key
- SigningKey []byte
- // 刷新 Token 的最大过期时间
- MaxRefresh time.Duration
- }
- // CustomClaims 自定义的 JWT 载荷结构体,包含用户 ID、用户名 和过期时间
- type CustomClaims struct {
- // 用户 ID
- UserID string `json:"user_id"`
- // 用户名
- UserName string `json:"user_name"`
- // 过期时间
- ExpireTime int64 `json:"expire_time"`
- // 标准的 JWT 载荷字段
- // Audience aud : 受众
- // Issuer iss : 签发者
- // Not Before nbf : 生效时间
- // Issued At iat : 签发时间
- // JWT ID jti : JWT ID 编号
- // Subject sub : 主题
- // Expires At exp : 过期时间
- // 标准的 JWT 载荷字段
- jwtpkg.RegisteredClaims
- }
- func NewJWT() *JWT {
- return &JWT{
- SigningKey: []byte(config.GetString("app.key")),
- MaxRefresh: time.Duration(config.GetInt64("jwt.max_refresh_time")) * time.Minute,
- }
- }
- // ParseToken 解析 Token
- func (j *JWT) ParseToken(c *gin.Context) (*CustomClaims, error) {
- // 1. 获取请求头中的 Token
- tokenString, err := j.getTokenFromHeader(c)
- if err != nil {
- return nil, err
- }
- // 2. 调用 jwt 库解析用户传入的 token
- token, err := j.parseTokenString(tokenString)
- // 3. 解析出错, 未报错证明是合法的 token
- if err != nil {
- return nil, err
- // validationErr, ok := err.(*jwtpkg.ValidationError)
- // if ok {
- // switch validationErr.Errors {
- // case jwtpkg.ValidationErrorMalformed:
- // return nil, ErrTokenMalformed
- // case jwtpkg.ValidationErrorExpired:
- // return nil, ErrTokenExpired
- // default:
- // return nil, ErrTokenInvalid
- // }
- // }
- // }
- // return nil, ErrTokenInvalid
- }
- if claims, ok := token.Claims.(*CustomClaims); ok && token.Valid {
- return claims, nil
- }
- return nil, ErrTokenInvalid
- }
- // IssueToken 签发 Token
- func (j *JWT) IssueToken(userID, userName string) string {
- // 1. 构造用户 claims 信息(负荷)
- expireTime := j.expireAtTime()
- claims := CustomClaims{
- userID,
- userName,
- expireTime.Unix(),
- jwtpkg.RegisteredClaims{
- NotBefore: jwtpkg.NewNumericDate(app.TimenowInTimezone()), // 生效时间
- IssuedAt: jwtpkg.NewNumericDate(app.TimenowInTimezone()), // 签发时间
- ExpiresAt: jwtpkg.NewNumericDate(expireTime), // 过期时间
- Issuer: config.GetString("app.name"), // 签发者
- Subject: userID, // 主题
- Audience: []string{userName}, // 受众
- },
- }
- // 2. 根据 claims 生成 Token
- token, err := j.generateToken(claims)
- if err != nil {
- logger.LogIf(err)
- return ""
- }
- return token
- }
- // RefreshToken 刷新 Token
- func (j *JWT) RefreshToken(c *gin.Context) (string, error) {
- // 1. 从 Header 里获取 token
- tokenString, err := j.getTokenFromHeader(c)
- if err != nil {
- return "", err
- }
- // 2. 调用 jwt 库解析用户传入的 token
- token, err := j.parseTokenString(tokenString)
- // 3. 解析出错, 未报错证明是合法的 token
- if err != nil {
- // validationErr, ok := err.(*jwtpkg.ValidationError)
- // // 满足刷新条件:只是单一的报错 ValidationErrorExpired
- // if !ok || validationErr.Errors != jwtpkg.ValidationErrorExpired {
- // return "", err
- // }
- if errors.Is(err, jwtpkg.ErrTokenExpired) {
- // 过期了,返回错误
- return "", err
- }
- }
- // 4. 解析 claims
- claims := token.Claims.(*CustomClaims)
- // 5. 判断是否过了最大允许刷新的时间
- x := app.TimenowInTimezone().Add(-j.MaxRefresh).Unix()
- if claims.IssuedAt.Unix() > x {
- // 修改过期时间
- claims.ExpiresAt = jwtpkg.NewNumericDate(j.expireAtTime())
- return j.generateToken(*claims)
- }
- // 6. 超过最大允许刷新的时间,返回错误
- return "", ErrTokenExpiredMaxRefresh
- }
- // generateToken 生成 Token, 内部使用,外部请调用 IssueToken
- func (j *JWT) generateToken(claims CustomClaims) (string, error) {
- // 创建一个新的 Token 对象,使用 HMAC SHA256 签名方法
- token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
- return token.SignedString(j.SigningKey)
- }
- // expireAtTime 计算过期时间
- func (j *JWT) expireAtTime() time.Time {
- timeNow := 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 timeNow.Add(expire)
- }
- // getTokenFromHeader 获取请求头中的 Token
- // Authorization: Bearer Token
- // 从请求头中获取 Token,格式为 "Bearer Token"
- func (j *JWT) getTokenFromHeader(c *gin.Context) (string, error) {
- // 获取请求头中的 Token
- authHeader := c.Request.Header.Get("Authorization")
- if authHeader == "" {
- return "", ErrHeaderEmpty
- }
- // 检查请求头格式是否正确
- // Bearer Token
- // Bearer 后面有一个空格
- parts := strings.SplitN(authHeader, " ", 2)
- if len(parts) < 2 || parts[0] != "Bearer" {
- return "", ErrHeaderMalformed
- }
- // 返回 Token 字符串
- return parts[1], nil
- }
- // parseTokenString 解析 Token 字符串
- func (j *JWT) parseTokenString(tokenString string) (*jwtpkg.Token, error) {
- return jwtpkg.ParseWithClaims(
- tokenString,
- &CustomClaims{},
- func(token *jwtpkg.Token) (any, error) {
- return j.SigningKey, nil
- },
- )
- }
|