From 78b4700c58b26697fcfcdd39e99810ddcab2289b Mon Sep 17 00:00:00 2001 From: a19920714liou <562951158@qq.com> Date: Thu, 11 Jun 2026 11:40:29 +0800 Subject: [PATCH] =?UTF-8?q?fix(security):=20=E4=BC=98=E5=8C=96=20CsrfToken?= =?UTF-8?q?Interceptor=20=E6=A0=A1=E9=AA=8C=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 未登录用户(无 sessionId)直接放行,由 LoginInterceptor 处理 - 服务重启后旧 session(内存无 CSRF token)放行,避免误拦 - token 不匹配返回 401 替代 403,统一由前端 401 处理跳转登录页 Co-Authored-By: Claude Opus 4.8 --- .../api/interceptor/CsrfTokenInterceptor.java | 42 +++++++++---------- 1 file changed, 20 insertions(+), 22 deletions(-) diff --git a/datasophon-api/src/main/java/com/datasophon/api/interceptor/CsrfTokenInterceptor.java b/datasophon-api/src/main/java/com/datasophon/api/interceptor/CsrfTokenInterceptor.java index 74b516c1..b3421bd5 100644 --- a/datasophon-api/src/main/java/com/datasophon/api/interceptor/CsrfTokenInterceptor.java +++ b/datasophon-api/src/main/java/com/datasophon/api/interceptor/CsrfTokenInterceptor.java @@ -23,35 +23,35 @@ @Component public class CsrfTokenInterceptor implements HandlerInterceptor { - + private static final Logger logger = LoggerFactory.getLogger(CsrfTokenInterceptor.class); - + private static final ConcurrentHashMap CSRF_TOKEN_STORE = new ConcurrentHashMap<>(); - + private static final Set SAFE_METHODS = new HashSet<>(Arrays.asList("GET", "HEAD", "OPTIONS")); - + public static String generateToken(String sessionId) { String token = UUID.randomUUID().toString().replace("-", ""); CSRF_TOKEN_STORE.put(sessionId, token); return token; } - + public static void removeToken(String sessionId) { CSRF_TOKEN_STORE.remove(sessionId); } - + @Override public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServletResponse response, @NonNull Object handler) { if (SAFE_METHODS.contains(request.getMethod())) { return true; } - + // API token auth is inherently CSRF-safe (browser won't auto-attach custom headers) String apiToken = request.getHeader("token"); if (StringUtils.isNotBlank(apiToken)) { return true; } - + String sessionId = request.getHeader(Constants.SESSION_ID); if (StringUtils.isBlank(sessionId)) { Cookie cookie = WebUtils.getCookie(request, Constants.SESSION_ID); @@ -60,25 +60,23 @@ public boolean preHandle(@NonNull HttpServletRequest request, @NonNull HttpServl } } if (StringUtils.isBlank(sessionId)) { - logger.warn("CSRF check failed: missing sessionId"); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - return false; - } - - String clientToken = request.getHeader(Constants.CSRF_HEADER); - if (StringUtils.isBlank(clientToken)) { - logger.warn("CSRF check failed: missing {} header", Constants.CSRF_HEADER); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); - return false; + // 未登录用户,无需 CSRF 防护 + return true; } - + String serverToken = CSRF_TOKEN_STORE.get(sessionId); - if (serverToken == null || !serverToken.equals(clientToken)) { + if (serverToken == null) { + // 内存中无 CSRF token(服务重启后旧 session),放行由 LoginInterceptor 判断 session 有效性 + return true; + } + + String clientToken = request.getHeader(Constants.CSRF_HEADER); + if (StringUtils.isBlank(clientToken) || !serverToken.equals(clientToken)) { logger.warn("CSRF check failed: token mismatch for session {}", sessionId); - response.setStatus(HttpServletResponse.SC_FORBIDDEN); + response.setStatus(javax.servlet.http.HttpServletResponse.SC_UNAUTHORIZED); return false; } - + return true; } }