jwt.go 3.2 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. package auth
  2. import (
  3. "errors"
  4. "time"
  5. "perms-system-server/internal/consts"
  6. "perms-system-server/internal/middleware"
  7. "github.com/golang-jwt/jwt/v4"
  8. )
  9. var ErrTokenTypeMismatch = errors.New("token type mismatch")
  10. // ParseWithHMAC 转发到 middleware 包的统一 JWT 解析入口。保留本别名以兼容旧调用方(测试与
  11. // 历史 logic 代码);真实实现已经上移到 middleware 层,避免 middleware↔auth 循环依赖,
  12. // 同时把"算法混淆防御"的审计覆盖收敛到唯一一个函数(见审计 L-N1)。
  13. func ParseWithHMAC(tokenStr, secret string, claims jwt.Claims) (*jwt.Token, error) {
  14. return middleware.ParseWithHMAC(tokenStr, secret, claims)
  15. }
  16. type RefreshClaims struct {
  17. TokenType string `json:"tokenType"`
  18. UserId int64 `json:"userId"`
  19. ProductCode string `json:"productCode"`
  20. TokenVersion int64 `json:"tokenVersion"`
  21. jwt.RegisteredClaims
  22. }
  23. func GenerateAccessToken(secret string, expireSeconds int64, userId int64, username, productCode, memberType string, tokenVersion int64) (string, error) {
  24. now := time.Now()
  25. claims := middleware.Claims{
  26. TokenType: consts.TokenTypeAccess,
  27. UserId: userId,
  28. Username: username,
  29. ProductCode: productCode,
  30. MemberType: memberType,
  31. TokenVersion: tokenVersion,
  32. RegisteredClaims: jwt.RegisteredClaims{
  33. ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expireSeconds) * time.Second)),
  34. IssuedAt: jwt.NewNumericDate(now),
  35. },
  36. }
  37. token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  38. return token.SignedString([]byte(secret))
  39. }
  40. func GenerateRefreshToken(secret string, expireSeconds int64, userId int64, productCode string, tokenVersion int64) (string, error) {
  41. now := time.Now()
  42. claims := RefreshClaims{
  43. TokenType: consts.TokenTypeRefresh,
  44. UserId: userId,
  45. ProductCode: productCode,
  46. TokenVersion: tokenVersion,
  47. RegisteredClaims: jwt.RegisteredClaims{
  48. ExpiresAt: jwt.NewNumericDate(now.Add(time.Duration(expireSeconds) * time.Second)),
  49. IssuedAt: jwt.NewNumericDate(now),
  50. },
  51. }
  52. token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  53. return token.SignedString([]byte(secret))
  54. }
  55. // GenerateRefreshTokenWithExpiry 签发 refreshToken,使用绝对过期时间(用于 token 轮转场景)。
  56. func GenerateRefreshTokenWithExpiry(secret string, expiresAt time.Time, userId int64, productCode string, tokenVersion int64) (string, error) {
  57. now := time.Now()
  58. if !expiresAt.After(now) {
  59. return "", errors.New("refresh token has expired")
  60. }
  61. claims := RefreshClaims{
  62. TokenType: consts.TokenTypeRefresh,
  63. UserId: userId,
  64. ProductCode: productCode,
  65. TokenVersion: tokenVersion,
  66. RegisteredClaims: jwt.RegisteredClaims{
  67. ExpiresAt: jwt.NewNumericDate(expiresAt),
  68. IssuedAt: jwt.NewNumericDate(now),
  69. },
  70. }
  71. token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
  72. return token.SignedString([]byte(secret))
  73. }
  74. func ParseRefreshToken(tokenStr, secret string) (*RefreshClaims, error) {
  75. token, err := ParseWithHMAC(tokenStr, secret, &RefreshClaims{})
  76. if err != nil {
  77. return nil, err
  78. }
  79. claims, ok := token.Claims.(*RefreshClaims)
  80. if !ok || !token.Valid {
  81. return nil, jwt.ErrSignatureInvalid
  82. }
  83. if claims.TokenType != consts.TokenTypeRefresh {
  84. return nil, ErrTokenTypeMismatch
  85. }
  86. return claims, nil
  87. }