소스 검색

fix: 会话缺失时仅拦商家侧账号,普通用户与骑手放行

qmj 7 시간 전
부모
커밋
edb7fc83f6

+ 23 - 2
ruoyi-system/src/main/java/com/ruoyi/system/utils/AuthAspect.java

@@ -4,6 +4,8 @@ import com.ruoyi.common.core.domain.AjaxResult;
 import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.system.domain.InfoUser;
+import com.ruoyi.system.mapper.InfoUserMapper;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.annotation.Around;
 import org.aspectj.lang.annotation.Aspect;
@@ -29,9 +31,11 @@ public class AuthAspect {
 
     private HttpServletRequest request;
     private final RedisCache redisCache;
+    private final InfoUserMapper infoUserMapper;
 
-    public AuthAspect(RedisCache redisCache) {
+    public AuthAspect(RedisCache redisCache, InfoUserMapper infoUserMapper) {
         this.redisCache = redisCache;
+        this.infoUserMapper = infoUserMapper;
     }
 
     @Resource
@@ -64,7 +68,11 @@ public class AuthAspect {
             String jti = jtiValue instanceof String ? (String) jtiValue : null;
             if (auth.session() && (userId == null || jti == null || jti.isBlank()
                     || !Boolean.TRUE.equals(redisCache.hasKey(jti)))) {
-                throw new ServiceException(MessageUtils.message("merchant.session.invalid"));
+                // Redis 会话校验只服务商家侧账号的即时撤销;普通用户和骑手没有会话撤销用途,
+                // 旧 token(jti 从未写入 Redis)放行,凭已验签的 JWT 通过即可。
+                if (!isConsumerUser(userId)) {
+                    throw new ServiceException(MessageUtils.message("merchant.session.invalid"));
+                }
             }
 
             request.setAttribute(AuthContext.USER_ID_ATTRIBUTE, userId);
@@ -73,6 +81,19 @@ public class AuthAspect {
         return joinPoint.proceed();
     }
 
+    /**
+     * 会话缺失时判断是否为无需商家会话的账号类型:0=普通用户、2=骑手。
+     * 商家侧类型(1/3/4/5)、用户不存在或类型未知时一律不放行。
+     */
+    private boolean isConsumerUser(Long userId) {
+        if (userId == null) {
+            return false;
+        }
+        InfoUser user = infoUserMapper.selectById(userId);
+        String userType = user == null ? null : user.getUserType();
+        return "0".equals(userType) || "2".equals(userType);
+    }
+
     private Long parseUserId(Object claim) {
         if (!(claim instanceof String value) || value.isBlank() || "null".equals(value)) {
             return null;

+ 63 - 1
ruoyi-system/src/test/java/com/ruoyi/system/utils/AuthAspectTest.java

@@ -5,6 +5,8 @@ import com.auth0.jwt.algorithms.Algorithm;
 import com.ruoyi.common.core.redis.RedisCache;
 import com.ruoyi.common.exception.ServiceException;
 import com.ruoyi.common.utils.MessageUtils;
+import com.ruoyi.system.domain.InfoUser;
+import com.ruoyi.system.mapper.InfoUserMapper;
 import jakarta.servlet.http.HttpServletRequest;
 import org.aspectj.lang.ProceedingJoinPoint;
 import org.aspectj.lang.reflect.MethodSignature;
@@ -29,6 +31,7 @@ import static org.mockito.Mockito.when;
 class AuthAspectTest {
 
     private RedisCache redisCache;
+    private InfoUserMapper infoUserMapper;
     private HttpServletRequest request;
     private ProceedingJoinPoint joinPoint;
     private MethodSignature signature;
@@ -37,10 +40,11 @@ class AuthAspectTest {
     @BeforeEach
     void setUp() {
         redisCache = mock(RedisCache.class);
+        infoUserMapper = mock(InfoUserMapper.class);
         request = mock(HttpServletRequest.class);
         joinPoint = mock(ProceedingJoinPoint.class);
         signature = mock(MethodSignature.class);
-        aspect = new AuthAspect(redisCache);
+        aspect = new AuthAspect(redisCache, infoUserMapper);
         aspect.setHttpServletRequest(request);
         when(joinPoint.getSignature()).thenReturn(signature);
     }
@@ -75,11 +79,62 @@ class AuthAspectTest {
         verify(request).setAttribute(AuthContext.JTI_ATTRIBUTE, jti);
     }
 
+    @Test
+    void merchantSessionAuthAllowsOrdinaryUserWithoutRedisSession() throws Throwable {
+        String token = JwtUtil.token("936", "user");
+        prepareInvocation("merchantSession", token);
+        loginUser("0");
+        when(redisCache.hasKey(anyString())).thenReturn(false);
+        Object expected = new Object();
+        when(joinPoint.proceed()).thenReturn(expected);
+
+        Object actual = aspect.around(joinPoint);
+
+        assertSame(expected, actual);
+        verify(request).setAttribute(AuthContext.USER_ID_ATTRIBUTE, 936L);
+    }
+
+    @Test
+    void merchantSessionAuthAllowsRiderWithoutRedisSession() throws Throwable {
+        String token = JwtUtil.token("936", "rider");
+        prepareInvocation("merchantSession", token);
+        loginUser("2");
+        when(redisCache.hasKey(anyString())).thenReturn(false);
+        Object expected = new Object();
+        when(joinPoint.proceed()).thenReturn(expected);
+
+        Object actual = aspect.around(joinPoint);
+
+        assertSame(expected, actual);
+        verify(request).setAttribute(AuthContext.USER_ID_ATTRIBUTE, 936L);
+    }
+
     @Test
     void merchantSessionAuthRejectsMissingRedisSessionBeforeController() throws Throwable {
         String token = JwtUtil.token("936", "merchant");
         String jti = tokenJti(token);
         prepareInvocation("merchantSession", token);
+        loginUser("1");
+        when(redisCache.hasKey(jti)).thenReturn(false);
+
+        try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
+            messages.when(() -> MessageUtils.message("merchant.session.invalid"))
+                    .thenReturn("session invalid");
+
+            ServiceException exception = assertThrows(ServiceException.class,
+                    () -> aspect.around(joinPoint));
+
+            assertEquals("session invalid", exception.getMessage());
+            verify(joinPoint, never()).proceed();
+        }
+    }
+
+    @Test
+    void merchantSessionAuthRejectsUnknownUserWithoutRedisSession() throws Throwable {
+        String token = JwtUtil.token("936", "stranger");
+        String jti = tokenJti(token);
+        prepareInvocation("merchantSession", token);
+        when(infoUserMapper.selectById(936L)).thenReturn(null);
         when(redisCache.hasKey(jti)).thenReturn(false);
 
         try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
@@ -101,6 +156,7 @@ class AuthAspectTest {
                 .withExpiresAt(new Date(System.currentTimeMillis() + 60_000))
                 .sign(Algorithm.HMAC256("TEST-AUTH-TOKEN"));
         prepareInvocation("merchantSession", token);
+        loginUser("1");
 
         try (MockedStatic<MessageUtils> messages = mockStatic(MessageUtils.class)) {
             messages.when(() -> MessageUtils.message("merchant.session.invalid"))
@@ -121,6 +177,12 @@ class AuthAspectTest {
         when(request.getHeader("token")).thenReturn(token);
     }
 
+    private void loginUser(String userType) {
+        InfoUser user = new InfoUser();
+        user.setUserType(userType);
+        when(infoUserMapper.selectById(936L)).thenReturn(user);
+    }
+
     private String tokenJti(String token) {
         Map<String, Object> claims = JwtUtil.verifyToken(token);
         return (String) claims.get("jti");