jwt_test.go 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195
  1. package auth
  2. import (
  3. "encoding/base64"
  4. "encoding/json"
  5. "strings"
  6. "testing"
  7. "time"
  8. "perms-system-server/internal/middleware"
  9. "github.com/golang-jwt/jwt/v4"
  10. "github.com/stretchr/testify/assert"
  11. "github.com/stretchr/testify/require"
  12. )
  13. const testSecret = "test-jwt-secret-key"
  14. // TC-0292: secret="s", expire=3600, userId=1, username="u", productCode="p", memberType="M"
  15. func TestGenerateAccessToken(t *testing.T) {
  16. tests := []struct {
  17. name string
  18. secret string
  19. expire int64
  20. userId int64
  21. username string
  22. productCode string
  23. memberType string
  24. tokenVersion int64
  25. }{
  26. {
  27. name: "normal generation",
  28. secret: testSecret,
  29. expire: 3600,
  30. userId: 1,
  31. username: "admin",
  32. productCode: "p1",
  33. memberType: "ADMIN",
  34. },
  35. {
  36. name: "empty productCode",
  37. secret: testSecret,
  38. expire: 3600,
  39. userId: 3,
  40. username: "user2",
  41. productCode: "",
  42. memberType: "",
  43. },
  44. {
  45. name: "super admin with tokenVersion",
  46. secret: testSecret,
  47. expire: 7200,
  48. userId: 100,
  49. username: "super",
  50. productCode: "p1",
  51. memberType: "SUPER_ADMIN",
  52. tokenVersion: 5,
  53. },
  54. }
  55. for _, tt := range tests {
  56. t.Run(tt.name, func(t *testing.T) {
  57. tokenStr, err := GenerateAccessToken(tt.secret, tt.expire, tt.userId, tt.username, tt.productCode, tt.memberType, tt.tokenVersion)
  58. require.NoError(t, err)
  59. assert.NotEmpty(t, tokenStr)
  60. token, err := jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) {
  61. return []byte(tt.secret), nil
  62. })
  63. require.NoError(t, err)
  64. assert.True(t, token.Valid)
  65. claims, ok := token.Claims.(*middleware.Claims)
  66. require.True(t, ok)
  67. assert.Equal(t, tt.userId, claims.UserId)
  68. assert.Equal(t, tt.username, claims.Username)
  69. assert.Equal(t, tt.productCode, claims.ProductCode)
  70. assert.Equal(t, tt.memberType, claims.MemberType)
  71. assert.Equal(t, tt.tokenVersion, claims.TokenVersion)
  72. // 审计修复项 M-6:`perms` 字段已从 Claims 结构体中移除。
  73. // 解析原始 JWT payload,确保 token JSON 中不存在 "perms" key。
  74. segments := strings.Split(tokenStr, ".")
  75. require.Len(t, segments, 3, "jwt must have 3 segments")
  76. payloadBytes, err := base64.RawURLEncoding.DecodeString(segments[1])
  77. require.NoError(t, err)
  78. var raw map[string]interface{}
  79. require.NoError(t, json.Unmarshal(payloadBytes, &raw))
  80. _, hasPerms := raw["perms"]
  81. assert.False(t, hasPerms, "access token payload must NOT contain perms field (audit M-6)")
  82. })
  83. }
  84. }
  85. // TC-0296: expireSeconds=1, sleep 2s
  86. func TestGenerateAccessToken_Expiry(t *testing.T) {
  87. tokenStr, err := GenerateAccessToken(testSecret, 1, 1, "u", "", "", 0)
  88. require.NoError(t, err)
  89. time.Sleep(2 * time.Second)
  90. _, err = jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) {
  91. return []byte(testSecret), nil
  92. })
  93. assert.Error(t, err)
  94. assert.Contains(t, err.Error(), "token is expired")
  95. }
  96. // TC-0297: secret="s", expire=86400, userId=1, productCode="p"
  97. func TestGenerateRefreshToken(t *testing.T) {
  98. tests := []struct {
  99. name string
  100. secret string
  101. expire int64
  102. userId int64
  103. productCode string
  104. }{
  105. {"normal", testSecret, 86400, 1, "p1"},
  106. {"empty productCode", testSecret, 86400, 2, ""},
  107. }
  108. for _, tt := range tests {
  109. t.Run(tt.name, func(t *testing.T) {
  110. tokenStr, err := GenerateRefreshToken(tt.secret, tt.expire, tt.userId, tt.productCode, 0)
  111. require.NoError(t, err)
  112. assert.NotEmpty(t, tokenStr)
  113. claims, err := ParseRefreshToken(tokenStr, tt.secret)
  114. require.NoError(t, err)
  115. assert.Equal(t, tt.userId, claims.UserId)
  116. assert.Equal(t, tt.productCode, claims.ProductCode)
  117. })
  118. }
  119. }
  120. // TC-0300: 有效token+正确secret
  121. func TestParseRefreshToken(t *testing.T) {
  122. validToken, err := GenerateRefreshToken(testSecret, 3600, 42, "prod", 0)
  123. require.NoError(t, err)
  124. t.Run("valid token", func(t *testing.T) {
  125. claims, err := ParseRefreshToken(validToken, testSecret)
  126. require.NoError(t, err)
  127. assert.Equal(t, int64(42), claims.UserId)
  128. assert.Equal(t, "prod", claims.ProductCode)
  129. })
  130. t.Run("wrong secret", func(t *testing.T) {
  131. _, err := ParseRefreshToken(validToken, "wrong-secret")
  132. assert.Error(t, err)
  133. })
  134. t.Run("invalid token string", func(t *testing.T) {
  135. _, err := ParseRefreshToken("not-a-valid-token", testSecret)
  136. assert.Error(t, err)
  137. })
  138. t.Run("empty token", func(t *testing.T) {
  139. _, err := ParseRefreshToken("", testSecret)
  140. assert.Error(t, err)
  141. })
  142. t.Run("expired token", func(t *testing.T) {
  143. expiredToken, err := GenerateRefreshToken(testSecret, 1, 1, "p", 0)
  144. require.NoError(t, err)
  145. time.Sleep(2 * time.Second)
  146. _, err = ParseRefreshToken(expiredToken, testSecret)
  147. assert.Error(t, err)
  148. })
  149. // TC-0305: AccessToken误用 — TokenType校验拒绝
  150. t.Run("access token used as refresh - should be rejected", func(t *testing.T) {
  151. accessToken, err := GenerateAccessToken(testSecret, 3600, 1, "u", "p", "M", 0)
  152. require.NoError(t, err)
  153. _, err = ParseRefreshToken(accessToken, testSecret)
  154. assert.Error(t, err, "BUG-002: access token 不应被 ParseRefreshToken 接受,应通过 TokenType 字段区分")
  155. })
  156. }
  157. // TC-0294: secret=""
  158. func TestGenerateAccessToken_EmptySecret(t *testing.T) {
  159. tokenStr, err := GenerateAccessToken("", 3600, 1, "u", "p", "M", 0)
  160. require.NoError(t, err)
  161. assert.NotEmpty(t, tokenStr)
  162. token, err := jwt.ParseWithClaims(tokenStr, &middleware.Claims{}, func(token *jwt.Token) (interface{}, error) {
  163. return []byte(""), nil
  164. })
  165. require.NoError(t, err)
  166. assert.True(t, token.Valid)
  167. claims, ok := token.Claims.(*middleware.Claims)
  168. require.True(t, ok)
  169. assert.Equal(t, int64(1), claims.UserId)
  170. }