Просмотр исходного кода

fix(backend): 生成海报二维码必须登录 — 拦截 anonymous 流量反复触发 access_token 'not latest'

- JwtInterceptor 新增 LOGIN_REQUIRED_PATHS 白名单:未登录请求直接返回 401,不再降级为 anonymous
- /api/share/qrcode 加入必登录清单,根因解决 anonymous 反复调 cgi-bin/token 导致全实例 token 失效
- ShareController.getQrCode 同步加 userId<=0 兜底校验(双保险)
Xiaogang Liao 3 месяцев назад
Родитель
Сommit
6fe0d34c64

+ 33 - 3
cfc-backend/src/main/java/com/etotem/cfc/config/JwtInterceptor.java

@@ -59,6 +59,11 @@ public class JwtInterceptor implements HandlerInterceptor {
         "/api/mind/traditional/compatibility"
     };
 
+    // 必须登录才能访问的路径:anonymous 访问直接 401(这些接口消耗微信配额/写数据/扣积分)
+    private static final String[] LOGIN_REQUIRED_PATHS = {
+        "/api/share/qrcode"     // 生成海报小程序码:anonymous 会反复触发微信 access_token 失效("not latest")
+    };
+
     @Override
     public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
         // 放 OPTIONS 请求
@@ -75,14 +80,28 @@ public class JwtInterceptor implements HandlerInterceptor {
 
         // 获取token
         String token = request.getHeader("Authorization");
-        
+
         if (!StringUtils.hasText(token)) {
             // 尝试从参数中获取
             token = request.getParameter("token");
         }
 
-if (!StringUtils.hasText(token)) {
-            log.debug("请求 {} 无Token,设为匿名用户", request.getRequestURI());
+        boolean hasToken = StringUtils.hasText(token);
+        boolean loginRequired = false;
+        for (String requiredPath : LOGIN_REQUIRED_PATHS) {
+            if (path.equals(requiredPath) || path.startsWith(requiredPath)) {
+                loginRequired = true;
+                break;
+            }
+        }
+
+        if (!hasToken) {
+            if (loginRequired) {
+                log.debug("请求 {} 需登录但无Token,拒绝", path);
+                writeUnauthorized(response);
+                return false;
+            }
+            log.debug("请求 {} 无Token,设为匿名用户", path);
             setAnonymousUser(request);
             return true;
         }
@@ -122,12 +141,23 @@ if (!StringUtils.hasText(token)) {
 
             return true;
         } catch (Exception e) {
+            if (loginRequired) {
+                log.debug("请求 {} 需登录但Token解析失败,拒绝: {}", path, e.getMessage());
+                writeUnauthorized(response);
+                return false;
+            }
             log.warn("Token解析失败: {}, 设为匿名用户", e.getMessage());
             setAnonymousUser(request);
             return true;
         }
     }
 
+    private void writeUnauthorized(HttpServletResponse response) throws java.io.IOException {
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json;charset=UTF-8");
+        response.getWriter().write("{\"code\":401,\"message\":\"请先登录\",\"data\":null}");
+    }
+
     private void setAnonymousUser(HttpServletRequest request) {
         request.setAttribute("userId", -1L);
         request.setAttribute("role", "anonymous");

+ 6 - 1
cfc-backend/src/main/java/com/etotem/cfc/controller/ShareController.java

@@ -120,7 +120,12 @@ public class ShareController {
      */
     @Operation(summary = "生成分享小程序码")
     @PostMapping("/qrcode")
-    public Result<Map<String, Object>> getQrCode(@RequestBody Map<String, String> params) {
+    public Result<Map<String, Object>> getQrCode(@RequestAttribute("userId") Long userId,
+                                                 @RequestBody Map<String, String> params) {
+        // 登录校验(双保险:JwtInterceptor.LOGIN_REQUIRED_PATHS 已先拦截,此处兜底)
+        if (userId == null || userId <= 0) {
+            return Result.error("请先登录");
+        }
         String page = params.get("page");
         String scene = params.get("scene");
         if (page == null || page.isEmpty()) {