package auth import ( "errors" "time" "perms-system-server/internal/consts" "perms-system-server/internal/middleware" "github.com/golang-jwt/jwt/v4" ) var ErrTokenTypeMismatch = errors.New("token type mismatch") // ParseWithHMAC 转发到 middleware 包的统一 JWT 解析入口。保留本别名以兼容旧调用方(测试与 // 历史 logic 代码);真实实现已经上移到 middleware 层,避免 middleware↔auth 循环依赖, // 同时把"算法混淆防御"的审计覆盖收敛到唯一一个函数(见审计 L-N1)。 func ParseWithHMAC(tokenStr, secret string, claims jwt.Claims) (*jwt.Token, error) { return middleware.ParseWithHMAC(tokenStr, secret, claims) } type RefreshClaims struct { TokenType string `json:"tokenType"` UserId int64 `json:"userId"` ProductCode string `json:"productCode"` TokenVersion int64 `json:"tokenVersion"` jwt.RegisteredClaims } func GenerateAccessToken(secret string, expireSeconds int64, userId int64, username, productCode, memberType string, tokenVersion int64) (string, error) { now := time.Now() claims := middleware.Claims{ TokenType: consts.TokenTypeAccess, UserId: userId, Username: username, ProductCode: productCode, MemberType: memberType, TokenVersion: tokenVersion, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expireSeconds) * time.Second)), IssuedAt: jwt.NewNumericDate(now), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret)) } func GenerateRefreshToken(secret string, expireSeconds int64, userId int64, productCode string, tokenVersion int64) (string, error) { now := time.Now() claims := RefreshClaims{ TokenType: consts.TokenTypeRefresh, UserId: userId, ProductCode: productCode, TokenVersion: tokenVersion, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expireSeconds) * time.Second)), IssuedAt: jwt.NewNumericDate(now), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret)) } // GenerateRefreshTokenWithExpiry 签发 refreshToken,使用绝对过期时间(用于 token 轮转场景)。 func GenerateRefreshTokenWithExpiry(secret string, expiresAt time.Time, userId int64, productCode string, tokenVersion int64) (string, error) { now := time.Now() if !expiresAt.After(now) { return "", errors.New("refresh token has expired") } claims := RefreshClaims{ TokenType: consts.TokenTypeRefresh, UserId: userId, ProductCode: productCode, TokenVersion: tokenVersion, RegisteredClaims: jwt.RegisteredClaims{ ExpiresAt: jwt.NewNumericDate(expiresAt), IssuedAt: jwt.NewNumericDate(now), }, } token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims) return token.SignedString([]byte(secret)) } func ParseRefreshToken(tokenStr, secret string) (*RefreshClaims, error) { token, err := ParseWithHMAC(tokenStr, secret, &RefreshClaims{}) if err != nil { return nil, err } claims, ok := token.Claims.(*RefreshClaims) if !ok || !token.Valid { return nil, jwt.ErrSignatureInvalid } if claims.TokenType != consts.TokenTypeRefresh { return nil, ErrTokenTypeMismatch } return claims, nil }