package lzcwyjwt import ( "errors" "time" "git.lzcwy.cn/zhengchiliu/zt-closet-manage-backend/global" "github.com/golang-jwt/jwt/v5" ) type TokenType string const ( TokenTypeAccess TokenType = "access" TokenTypeRefresh TokenType = "refresh" ) // Claims 管理员 JWT claims(与业务服务接口风格一致,字段替换为 AdminID)。 type Claims struct { AdminID int64 `json:"admin_id"` TokenType TokenType `json:"token_type"` jwt.RegisteredClaims } var ( ErrTokenInvalid = errors.New("token invalid") ErrTokenExpired = errors.New("token expired") ErrTokenWrongType = errors.New("token type mismatch") ) func GenerateTokens(adminID int64) (accessToken, refreshToken string, err error) { now := time.Now() accessClaims := Claims{ AdminID: adminID, TokenType: TokenTypeAccess, RegisteredClaims: jwt.RegisteredClaims{ Issuer: global.JWTSetting.Issuer, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(global.JWTSetting.AccessTokenExpire) * time.Second)), NotBefore: jwt.NewNumericDate(now), }, } refreshClaims := Claims{ AdminID: adminID, TokenType: TokenTypeRefresh, RegisteredClaims: jwt.RegisteredClaims{ Issuer: global.JWTSetting.Issuer, IssuedAt: jwt.NewNumericDate(now), ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(global.JWTSetting.RefreshTokenExpire) * time.Second)), NotBefore: jwt.NewNumericDate(now), }, } accessToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, accessClaims).SignedString([]byte(global.JWTSetting.SecretKey)) if err != nil { return } refreshToken, err = jwt.NewWithClaims(jwt.SigningMethodHS256, refreshClaims).SignedString([]byte(global.JWTSetting.SecretKey)) if err != nil { return } return accessToken, refreshToken, nil } func ParseAccessToken(tokenString string) (*Claims, error) { claims, err := parseToken(tokenString) if err != nil { return nil, err } // 校验 token 类型(防止 refresh token 被当 access token 用) if claims.TokenType != TokenTypeAccess { return nil, ErrTokenWrongType } return claims, nil } func ParseRefreshToken(tokenString string) (*Claims, error) { claims, err := parseToken(tokenString) if err != nil { return nil, err } // 校验 token 类型(防止 access token 被当 refresh token 用) if claims.TokenType != TokenTypeRefresh { return nil, ErrTokenWrongType } return claims, nil } // parseToken 解析并校验 token 签名、签发方。 func parseToken(tokenString string) (*Claims, error) { token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(t *jwt.Token) (interface{}, error) { // 验证签名算法 if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok { return nil, ErrTokenInvalid } return []byte(global.JWTSetting.SecretKey), nil }) if err != nil { // 区分过期和其他错误 if errors.Is(err, jwt.ErrTokenExpired) { return nil, ErrTokenExpired } return nil, err } claims, ok := token.Claims.(*Claims) if !ok || !token.Valid { return nil, ErrTokenInvalid } // 校验签发方,防止跨服务 token 混用。 if global.JWTSetting.Issuer != "" && claims.Issuer != global.JWTSetting.Issuer { return nil, ErrTokenInvalid } return claims, nil }