| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108 |
- package auth
- import (
- "errors"
- "fmt"
- "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 统一的 keyfunc 断言入口。所有 JWT 解析点(HTTP 中间件 / gRPC VerifyToken / RefreshToken)
- // 都必须走这里,而不是直接 jwt.ParseWithClaims(... func() {return []byte(secret)}) ——
- // 必须显式断言 token.Method 是 *jwt.SigningMethodHMAC,避免未来迁移到 RSA/ECDSA 非对称密钥
- // 时,攻击者把公钥当成 HMAC 共享密钥伪造 token(jwt-go 历史上 CVE-2016-10555 同类问题,
- // OWASP JWT Cheat Sheet / RFC 8725 均强制要求 alg 白名单,见审计 H-4)。
- //
- // alg=none 在 jwt-go v4 早已默认拒绝,但显式 method 断言仍是深度防御的必要一步:
- // 即使未来有人误签出 alg=HS512 的 token,这里也会直接报错而不是当成 HS256 尝试解析。
- func ParseWithHMAC(tokenStr, secret string, claims jwt.Claims) (*jwt.Token, error) {
- return jwt.ParseWithClaims(tokenStr, claims, func(token *jwt.Token) (interface{}, error) {
- if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
- return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"])
- }
- return []byte(secret), nil
- })
- }
- 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
- }
|