refreshTokenLogic_test.go 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381
  1. package pub
  2. import (
  3. "context"
  4. "database/sql"
  5. "errors"
  6. "testing"
  7. "time"
  8. authHelper "perms-system-server/internal/logic/auth"
  9. permModel "perms-system-server/internal/model/perm"
  10. productmemberModel "perms-system-server/internal/model/productmember"
  11. userModel "perms-system-server/internal/model/user"
  12. "perms-system-server/internal/response"
  13. "perms-system-server/internal/testutil"
  14. "perms-system-server/internal/types"
  15. "github.com/stretchr/testify/assert"
  16. "github.com/stretchr/testify/require"
  17. )
  18. func insertRefreshTestUser(t *testing.T, ctx context.Context, username, password string, status, isSuperAdmin int64) (int64, func()) {
  19. t.Helper()
  20. svcCtx := newTestSvcCtx()
  21. conn := testutil.GetTestSqlConn()
  22. now := time.Now().Unix()
  23. hashed := testutil.HashPassword(password)
  24. res, err := svcCtx.SysUserModel.Insert(ctx, &userModel.SysUser{
  25. Username: username,
  26. Password: hashed,
  27. Nickname: username,
  28. Avatar: sql.NullString{},
  29. Email: username + "@test.com",
  30. Phone: "13800000000",
  31. Remark: "",
  32. DeptId: 0,
  33. IsSuperAdmin: isSuperAdmin,
  34. MustChangePassword: 2,
  35. Status: status,
  36. CreateTime: now,
  37. UpdateTime: now,
  38. })
  39. require.NoError(t, err)
  40. id, _ := res.LastInsertId()
  41. cleanup := func() {
  42. testutil.CleanTable(ctx, conn, "`sys_user`", id)
  43. }
  44. return id, cleanup
  45. }
  46. // TC-0026: 正常刷新(refreshToken从header获取,原样返回不重新生成)
  47. func TestRefreshToken_Normal(t *testing.T) {
  48. ctx := context.Background()
  49. svcCtx := newTestSvcCtx()
  50. username := testutil.UniqueId()
  51. password := "TestPass123"
  52. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  53. t.Cleanup(cleanUser)
  54. refreshToken, err := authHelper.GenerateRefreshToken(
  55. svcCtx.Config.Auth.RefreshSecret,
  56. svcCtx.Config.Auth.RefreshExpire,
  57. userId, "", 0,
  58. )
  59. require.NoError(t, err)
  60. logic := NewRefreshTokenLogic(ctx, svcCtx)
  61. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  62. Authorization: "Bearer " + refreshToken,
  63. })
  64. require.NoError(t, err)
  65. require.NotNil(t, resp)
  66. assert.NotEmpty(t, resp.AccessToken)
  67. assert.NotEmpty(t, resp.RefreshToken, "应返回新的refreshToken")
  68. assert.NotEqual(t, resp.AccessToken, resp.RefreshToken, "accessToken和refreshToken应不同")
  69. assert.True(t, resp.Expires > time.Now().Unix(), "expires应为未来的unix时间戳")
  70. assert.Equal(t, userId, resp.UserInfo.UserId)
  71. assert.Equal(t, username, resp.UserInfo.Username)
  72. }
  73. // TC-0027: 不带productCode(回退)
  74. func TestRefreshToken_FallbackToClaimsProductCode(t *testing.T) {
  75. ctx := context.Background()
  76. svcCtx := newTestSvcCtx()
  77. conn := testutil.GetTestSqlConn()
  78. username := testutil.UniqueId()
  79. password := "TestPass123"
  80. pc := testutil.UniqueId()
  81. now := time.Now().Unix()
  82. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  83. t.Cleanup(cleanUser)
  84. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  85. t.Cleanup(cleanProduct)
  86. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  87. ProductCode: pc, UserId: userId, MemberType: "ADMIN", Status: 1, CreateTime: now, UpdateTime: now,
  88. })
  89. require.NoError(t, err)
  90. pmId, _ := pmRes.LastInsertId()
  91. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId) })
  92. permCode := testutil.UniqueId()
  93. permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  94. ProductCode: pc, Name: "refresh_perm", Code: permCode, Status: 1, CreateTime: now, UpdateTime: now,
  95. })
  96. require.NoError(t, err)
  97. permId, _ := permRes.LastInsertId()
  98. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permId) })
  99. refreshToken, err := authHelper.GenerateRefreshToken(
  100. svcCtx.Config.Auth.RefreshSecret,
  101. svcCtx.Config.Auth.RefreshExpire,
  102. userId, pc, 0,
  103. )
  104. require.NoError(t, err)
  105. logic := NewRefreshTokenLogic(ctx, svcCtx)
  106. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  107. Authorization: "Bearer " + refreshToken,
  108. })
  109. require.NoError(t, err)
  110. require.NotNil(t, resp)
  111. assert.Equal(t, "ADMIN", resp.UserInfo.MemberType)
  112. assert.Contains(t, resp.UserInfo.Perms, permCode)
  113. }
  114. // TC-0028: token无效
  115. func TestRefreshToken_InvalidToken(t *testing.T) {
  116. ctx := context.Background()
  117. svcCtx := newTestSvcCtx()
  118. logic := NewRefreshTokenLogic(ctx, svcCtx)
  119. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  120. Authorization: "Bearer invalid.token.string",
  121. })
  122. require.Nil(t, resp)
  123. require.Error(t, err)
  124. var codeErr *response.CodeError
  125. require.True(t, errors.As(err, &codeErr))
  126. assert.Equal(t, 401, codeErr.Code())
  127. assert.Equal(t, "refreshToken无效或已过期", codeErr.Error())
  128. }
  129. // TC-0029: 用户已删除(UserDetailsLoader 返回 Status=0 → 403 账号已被冻结)
  130. func TestRefreshToken_UserDeleted(t *testing.T) {
  131. ctx := context.Background()
  132. svcCtx := newTestSvcCtx()
  133. nonExistentUserId := int64(999999999)
  134. refreshToken, err := authHelper.GenerateRefreshToken(
  135. svcCtx.Config.Auth.RefreshSecret,
  136. svcCtx.Config.Auth.RefreshExpire,
  137. nonExistentUserId, "", 0,
  138. )
  139. require.NoError(t, err)
  140. logic := NewRefreshTokenLogic(ctx, svcCtx)
  141. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  142. Authorization: "Bearer " + refreshToken,
  143. })
  144. require.Nil(t, resp)
  145. require.Error(t, err)
  146. var codeErr *response.CodeError
  147. require.True(t, errors.As(err, &codeErr))
  148. assert.Equal(t, 403, codeErr.Code())
  149. assert.Equal(t, "账号已被冻结", codeErr.Error())
  150. }
  151. // TC-0030: 账号冻结
  152. func TestRefreshToken_AccountFrozen(t *testing.T) {
  153. ctx := context.Background()
  154. svcCtx := newTestSvcCtx()
  155. username := testutil.UniqueId()
  156. password := "TestPass123"
  157. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 2, 2)
  158. t.Cleanup(cleanUser)
  159. refreshToken, err := authHelper.GenerateRefreshToken(
  160. svcCtx.Config.Auth.RefreshSecret,
  161. svcCtx.Config.Auth.RefreshExpire,
  162. userId, "", 0,
  163. )
  164. require.NoError(t, err)
  165. logic := NewRefreshTokenLogic(ctx, svcCtx)
  166. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  167. Authorization: "Bearer " + refreshToken,
  168. })
  169. require.Nil(t, resp)
  170. require.Error(t, err)
  171. var codeErr *response.CodeError
  172. require.True(t, errors.As(err, &codeErr))
  173. assert.Equal(t, 403, codeErr.Code())
  174. assert.Equal(t, "账号已被冻结", codeErr.Error())
  175. }
  176. // TC-0032: 尝试切换产品被拒绝
  177. func TestRefreshToken_ProductCodeSwitchRejected(t *testing.T) {
  178. ctx := context.Background()
  179. svcCtx := newTestSvcCtx()
  180. username := testutil.UniqueId()
  181. password := "TestPass123"
  182. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  183. t.Cleanup(cleanUser)
  184. refreshToken, err := authHelper.GenerateRefreshToken(
  185. svcCtx.Config.Auth.RefreshSecret,
  186. svcCtx.Config.Auth.RefreshExpire,
  187. userId, "product_a", 0,
  188. )
  189. require.NoError(t, err)
  190. logic := NewRefreshTokenLogic(ctx, svcCtx)
  191. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  192. Authorization: "Bearer " + refreshToken,
  193. ProductCode: "product_b",
  194. })
  195. require.Nil(t, resp)
  196. require.Error(t, err)
  197. var codeErr *response.CodeError
  198. require.True(t, errors.As(err, &codeErr))
  199. assert.Equal(t, 400, codeErr.Code())
  200. assert.Equal(t, "刷新令牌不允许切换产品", codeErr.Error())
  201. }
  202. // TC-0033: TokenVersion不匹配时拒绝刷新
  203. func TestRefreshToken_TokenVersionMismatch(t *testing.T) {
  204. ctx := context.Background()
  205. svcCtx := newTestSvcCtx()
  206. username := testutil.UniqueId()
  207. password := "TestPass123"
  208. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  209. t.Cleanup(cleanUser)
  210. refreshToken, err := authHelper.GenerateRefreshToken(
  211. svcCtx.Config.Auth.RefreshSecret,
  212. svcCtx.Config.Auth.RefreshExpire,
  213. userId, "", 999,
  214. )
  215. require.NoError(t, err)
  216. logic := NewRefreshTokenLogic(ctx, svcCtx)
  217. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  218. Authorization: "Bearer " + refreshToken,
  219. })
  220. require.Nil(t, resp)
  221. require.Error(t, err)
  222. var codeErr *response.CodeError
  223. require.True(t, errors.As(err, &codeErr))
  224. assert.Equal(t, 401, codeErr.Code())
  225. assert.Equal(t, "登录状态已失效,请重新登录", codeErr.Error())
  226. }
  227. // TC-0034: 使用accessToken作为refreshToken被拒绝
  228. func TestRefreshToken_AccessTokenRejected(t *testing.T) {
  229. ctx := context.Background()
  230. svcCtx := newTestSvcCtx()
  231. username := testutil.UniqueId()
  232. password := "TestPass123"
  233. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  234. t.Cleanup(cleanUser)
  235. accessToken, err := authHelper.GenerateAccessToken(
  236. svcCtx.Config.Auth.RefreshSecret,
  237. svcCtx.Config.Auth.AccessExpire,
  238. userId, username, "", "", 0,
  239. )
  240. require.NoError(t, err)
  241. logic := NewRefreshTokenLogic(ctx, svcCtx)
  242. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  243. Authorization: "Bearer " + accessToken,
  244. })
  245. require.Nil(t, resp)
  246. require.Error(t, err)
  247. var codeErr *response.CodeError
  248. require.True(t, errors.As(err, &codeErr))
  249. assert.Equal(t, 401, codeErr.Code())
  250. assert.Equal(t, "refreshToken无效或已过期", codeErr.Error())
  251. }
  252. // TC-0035: 产品成员已移除时拒绝刷新
  253. func TestRefreshToken_MemberRemovedRejected(t *testing.T) {
  254. ctx := context.Background()
  255. svcCtx := newTestSvcCtx()
  256. conn := testutil.GetTestSqlConn()
  257. username := testutil.UniqueId()
  258. password := "TestPass123"
  259. pc := testutil.UniqueId()
  260. now := time.Now().Unix()
  261. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 2)
  262. t.Cleanup(cleanUser)
  263. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  264. t.Cleanup(cleanProduct)
  265. pmRes, err := svcCtx.SysProductMemberModel.Insert(ctx, &productmemberModel.SysProductMember{
  266. ProductCode: pc, UserId: userId, MemberType: "MEMBER", Status: 1, CreateTime: now, UpdateTime: now,
  267. })
  268. require.NoError(t, err)
  269. pmId, _ := pmRes.LastInsertId()
  270. refreshToken, err := authHelper.GenerateRefreshToken(
  271. svcCtx.Config.Auth.RefreshSecret,
  272. svcCtx.Config.Auth.RefreshExpire,
  273. userId, pc, 0,
  274. )
  275. require.NoError(t, err)
  276. testutil.CleanTable(ctx, conn, "`sys_product_member`", pmId)
  277. logic := NewRefreshTokenLogic(ctx, svcCtx)
  278. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  279. Authorization: "Bearer " + refreshToken,
  280. })
  281. require.Nil(t, resp)
  282. require.Error(t, err)
  283. var codeErr *response.CodeError
  284. require.True(t, errors.As(err, &codeErr))
  285. assert.Equal(t, 403, codeErr.Code())
  286. assert.Equal(t, "您已不是该产品的成员", codeErr.Error())
  287. }
  288. // TC-0031: 超管+productCode(refreshToken原样返回)
  289. func TestRefreshToken_SuperAdminWithProductCode(t *testing.T) {
  290. ctx := context.Background()
  291. svcCtx := newTestSvcCtx()
  292. conn := testutil.GetTestSqlConn()
  293. username := testutil.UniqueId()
  294. password := "TestPass123"
  295. pc := testutil.UniqueId()
  296. now := time.Now().Unix()
  297. userId, cleanUser := insertRefreshTestUser(t, ctx, username, password, 1, 1)
  298. t.Cleanup(cleanUser)
  299. _, cleanProduct := insertTestProduct(t, ctx, svcCtx, pc, testutil.UniqueId(), "secret")
  300. t.Cleanup(cleanProduct)
  301. permCode := testutil.UniqueId()
  302. permRes, err := svcCtx.SysPermModel.Insert(ctx, &permModel.SysPerm{
  303. ProductCode: pc, Name: "sa_refresh_perm", Code: permCode, Status: 1, CreateTime: now, UpdateTime: now,
  304. })
  305. require.NoError(t, err)
  306. permId, _ := permRes.LastInsertId()
  307. t.Cleanup(func() { testutil.CleanTable(ctx, conn, "`sys_perm`", permId) })
  308. refreshToken, err := authHelper.GenerateRefreshToken(
  309. svcCtx.Config.Auth.RefreshSecret,
  310. svcCtx.Config.Auth.RefreshExpire,
  311. userId, pc, 0,
  312. )
  313. require.NoError(t, err)
  314. logic := NewRefreshTokenLogic(ctx, svcCtx)
  315. resp, err := logic.RefreshToken(&types.RefreshTokenReq{
  316. Authorization: "Bearer " + refreshToken,
  317. ProductCode: pc,
  318. })
  319. require.NoError(t, err)
  320. require.NotNil(t, resp)
  321. assert.NotEmpty(t, resp.RefreshToken, "应返回新的refreshToken")
  322. assert.Equal(t, "SUPER_ADMIN", resp.UserInfo.MemberType)
  323. assert.Contains(t, resp.UserInfo.Perms, permCode)
  324. assert.Equal(t, int64(1), resp.UserInfo.IsSuperAdmin)
  325. }