AuthAspectTest.java 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129
  1. package com.ruoyi.system.utils;
  2. import com.auth0.jwt.JWT;
  3. import com.auth0.jwt.algorithms.Algorithm;
  4. import com.ruoyi.common.constant.CacheConstants;
  5. import com.ruoyi.common.core.domain.AjaxResult;
  6. import com.ruoyi.common.core.redis.RedisCache;
  7. import jakarta.servlet.http.HttpServletRequest;
  8. import org.aspectj.lang.ProceedingJoinPoint;
  9. import org.aspectj.lang.reflect.MethodSignature;
  10. import org.junit.jupiter.api.BeforeEach;
  11. import org.junit.jupiter.api.Test;
  12. import java.lang.reflect.Method;
  13. import java.util.Date;
  14. import static org.junit.jupiter.api.Assertions.assertEquals;
  15. import static org.junit.jupiter.api.Assertions.assertSame;
  16. import static org.mockito.ArgumentMatchers.anyString;
  17. import static org.mockito.Mockito.mock;
  18. import static org.mockito.Mockito.never;
  19. import static org.mockito.Mockito.verify;
  20. import static org.mockito.Mockito.when;
  21. /**
  22. * 单设备登录(2026-09-22)会话校验切面测试:
  23. * 新格式会话 token(jti 含冒号)必须存在 Redis 会话,被顶号/登出删除后即 401;
  24. * 无 Redis 会话的旧 token 和无 jti token 均拒绝。
  25. */
  26. class AuthAspectTest {
  27. private RedisCache redisCache;
  28. private HttpServletRequest request;
  29. private ProceedingJoinPoint joinPoint;
  30. private MethodSignature signature;
  31. private AuthAspect aspect;
  32. @BeforeEach
  33. void setUp() {
  34. redisCache = mock(RedisCache.class);
  35. request = mock(HttpServletRequest.class);
  36. joinPoint = mock(ProceedingJoinPoint.class);
  37. signature = mock(MethodSignature.class);
  38. aspect = new AuthAspect(redisCache);
  39. aspect.setHttpServletRequest(request);
  40. when(joinPoint.getSignature()).thenReturn(signature);
  41. }
  42. /** 手工签发新格式会话 token:jti 含冒号但不落 Redis,会话存在性由 redisCache 桩控制 */
  43. private String colonJtiToken(String jti) {
  44. return JWT.create()
  45. .withClaim("id", "936")
  46. .withExpiresAt(new Date(System.currentTimeMillis() + 60_000))
  47. .withJWTId(jti)
  48. .sign(Algorithm.HMAC256("TEST-AUTH-TOKEN"));
  49. }
  50. @Test
  51. void deletedSessionIsRejectedBeforeController() throws Throwable {
  52. String jti = CacheConstants.USER_TOKEN_KEY + "936:deleted-session";
  53. prepareInvocation("endpoint", colonJtiToken(jti));
  54. when(redisCache.hasKey(jti)).thenReturn(false);
  55. Object result = aspect.around(joinPoint);
  56. assertEquals(401, ((AjaxResult) result).get("code"), "会话被删(被顶号/登出)应返回 401");
  57. verify(joinPoint, never()).proceed();
  58. }
  59. @Test
  60. void liveSessionProceedsAndExposesTrustedIdentity() throws Throwable {
  61. String jti = CacheConstants.USER_TOKEN_KEY + "936:live-session";
  62. prepareInvocation("endpoint", colonJtiToken(jti));
  63. when(redisCache.hasKey(jti)).thenReturn(true);
  64. Object expected = new Object();
  65. when(joinPoint.proceed()).thenReturn(expected);
  66. Object actual = aspect.around(joinPoint);
  67. assertSame(expected, actual);
  68. verify(request).setAttribute(AuthContext.USER_ID_ATTRIBUTE, 936L);
  69. verify(request).setAttribute(AuthContext.JTI_ATTRIBUTE, jti);
  70. }
  71. @Test
  72. void legacyNoColonJtiWithoutSessionIsRejected() throws Throwable {
  73. // 历史无会话重载 JwtUtil.token(id, userName) 签发:jti 为纯 UUID,不含冒号
  74. prepareInvocation("endpoint", JwtUtil.token("936", "user"));
  75. Object actual = aspect.around(joinPoint);
  76. assertEquals(401, ((AjaxResult) actual).get("code"));
  77. verify(joinPoint, never()).proceed();
  78. }
  79. @Test
  80. void tokenWithoutJtiIsRejected() throws Throwable {
  81. String token = JWT.create()
  82. .withClaim("id", "936")
  83. .withExpiresAt(new Date(System.currentTimeMillis() + 60_000))
  84. .sign(Algorithm.HMAC256("TEST-AUTH-TOKEN"));
  85. prepareInvocation("endpoint", token);
  86. Object actual = aspect.around(joinPoint);
  87. assertEquals(401, ((AjaxResult) actual).get("code"));
  88. verify(redisCache, never()).hasKey(anyString());
  89. verify(joinPoint, never()).proceed();
  90. }
  91. @Test
  92. void invalidSignatureRejectedBeforeSessionLookup() throws Throwable {
  93. prepareInvocation("endpoint", "not-a-jwt");
  94. Object result = aspect.around(joinPoint);
  95. assertEquals(401, ((AjaxResult) result).get("code"));
  96. verify(redisCache, never()).hasKey(anyString());
  97. verify(joinPoint, never()).proceed();
  98. }
  99. private void prepareInvocation(String methodName, String token) throws Exception {
  100. Method method = SecuredMethods.class.getDeclaredMethod(methodName);
  101. when(signature.getMethod()).thenReturn(method);
  102. when(request.getHeader("token")).thenReturn(token);
  103. }
  104. static class SecuredMethods {
  105. @Auth
  106. public void endpoint() {
  107. }
  108. }
  109. }