|
|
@@ -1,222 +1,222 @@
|
|
|
package jwt
|
|
|
|
|
|
import (
|
|
|
- "errors"
|
|
|
- "strings"
|
|
|
- "time"
|
|
|
+ "errors"
|
|
|
+ "strings"
|
|
|
+ "time"
|
|
|
|
|
|
- "github.com/gin-gonic/gin"
|
|
|
- jwtpkg "github.com/golang-jwt/jwt/v5"
|
|
|
+ "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"
|
|
|
+ "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("请求头格式错误")
|
|
|
+ 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
|
|
|
+ // 签名密钥,用于生成和验证 JWT 签名,读取 app.key
|
|
|
+ SigningKey []byte
|
|
|
|
|
|
- // 刷新 Token 的最大过期时间
|
|
|
- MaxRefresh time.Duration
|
|
|
+ // 刷新 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
|
|
|
+ // 用户 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,
|
|
|
- }
|
|
|
+ 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
|
|
|
+ // 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
|
|
|
+ // 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
|
|
|
+ // 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)
|
|
|
+ // 创建一个新的 Token 对象,使用 HMAC SHA256 签名方法
|
|
|
+ token := jwtpkg.NewWithClaims(jwtpkg.SigningMethodHS256, claims)
|
|
|
|
|
|
- return token.SignedString(j.SigningKey)
|
|
|
+ return token.SignedString(j.SigningKey)
|
|
|
}
|
|
|
|
|
|
// expireAtTime 计算过期时间
|
|
|
func (j *JWT) expireAtTime() time.Time {
|
|
|
- timeNow := app.TimenowInTimezone()
|
|
|
+ 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")
|
|
|
- }
|
|
|
+ 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
|
|
|
+ expire := time.Duration(expireTime) * time.Minute
|
|
|
|
|
|
- return timeNow.Add(expire)
|
|
|
+ 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
|
|
|
+ // 获取请求头中的 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
|
|
|
- },
|
|
|
- )
|
|
|
+ return jwtpkg.ParseWithClaims(
|
|
|
+ tokenString,
|
|
|
+ &CustomClaims{},
|
|
|
+ func(token *jwtpkg.Token) (any, error) {
|
|
|
+ return j.SigningKey, nil
|
|
|
+ },
|
|
|
+ )
|
|
|
}
|