package com.rongyichuang.auth.filter; import com.rongyichuang.auth.util.JwtUtil; import com.rongyichuang.user.entity.User; import com.rongyichuang.user.repository.UserRepository; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.AnonymousAuthenticationToken; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.security.core.Authentication; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import org.springframework.web.util.ContentCachingRequestWrapper; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Optional; /** * JWT认证过滤器 */ @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(JwtAuthenticationFilter.class); @Autowired private JwtUtil jwtUtil; @Autowired private UserRepository userRepository; // 允许匿名访问的GraphQL查询列表 private static final List PUBLIC_GRAPHQL_QUERIES = Arrays.asList( "carouselPlayList", "activities", "hello" ); /** * 判断是否应该跳过JWT认证 */ private boolean shouldSkipAuthentication(String requestURI) { // 这些路径不需要JWT认证(已去掉context path) String[] skipPaths = { "/auth/", "/actuator/", "/test/", "/cleanup/", "/upload/", "/graphiql" // GraphiQL开发工具 // 注意:/graphql 不在跳过列表中,需要由JWT过滤器处理以区分公开和私有查询 }; for (String path : skipPaths) { if (requestURI.startsWith(path)) { return true; } } return false; } /** * 检查是否是GraphQL请求 */ private boolean isGraphQLRequest(String requestURI) { return "/graphql".equals(requestURI); } /** * 检查GraphQL请求是否为公开查询(不需要认证) * 只有明确标记为公开的查询才允许匿名访问 */ private boolean isPublicGraphQLQuery(HttpServletRequest request) { try { // 检查GET参数中的query String query = request.getParameter("query"); if (query != null && !query.trim().isEmpty()) { logger.debug("从参数获取GraphQL查询: {}", query); return containsPublicQuery(query); } // 对于POST请求,尝试读取请求体 if ("POST".equalsIgnoreCase(request.getMethod())) { try { // 使用CachedBodyHttpServletRequest来读取请求体 String body = getRequestBody(request); if (body != null && !body.trim().isEmpty()) { logger.debug("从请求体获取GraphQL查询: {}", body); // 检查Content-Type String contentType = request.getContentType(); if (contentType != null && contentType.contains("application/graphql")) { // 对于application/graphql,请求体直接是GraphQL查询 return containsPublicQuery(body); } else { // 对于application/json,简单解析JSON,查找query字段 if (body.contains("\"query\"")) { return containsPublicQuery(body); } } } } catch (Exception e) { logger.warn("读取POST请求体失败", e); } } return false; } catch (Exception e) { logger.error("解析GraphQL请求失败", e); return false; } } /** * 读取请求体内容 */ private String getRequestBody(HttpServletRequest request) { try { if (request instanceof ContentCachingRequestWrapper) { ContentCachingRequestWrapper wrapper = (ContentCachingRequestWrapper) request; byte[] content = wrapper.getContentAsByteArray(); if (content.length > 0) { String encoding = wrapper.getCharacterEncoding() != null ? wrapper.getCharacterEncoding() : "UTF-8"; return new String(content, encoding); } } // 不从原始请求流读取,避免下游组件拿不到请求体导致 400 return null; } catch (Exception e) { logger.warn("读取请求体失败", e); return null; } } /** * 检查查询字符串是否包含公开查询 */ private boolean containsPublicQuery(String queryString) { if (queryString == null || queryString.trim().isEmpty()) { return false; } // 检查是否包含公开查询 for (String publicQuery : PUBLIC_GRAPHQL_QUERIES) { if (queryString.contains(publicQuery)) { logger.debug("检测到公开GraphQL查询: {}", publicQuery); return true; } } return false; } /** * 返回权限错误响应 */ private void sendUnauthorizedResponse(HttpServletResponse response) throws IOException { response.setStatus(HttpServletResponse.SC_FORBIDDEN); response.setContentType("application/json;charset=UTF-8"); response.getWriter().write("{\"errors\":[{\"message\":\"没有权限访问,请先登录\",\"extensions\":{\"code\":\"UNAUTHORIZED\"}}]}"); } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String requestURI = request.getRequestURI(); String contextPath = request.getContextPath(); // 去掉context path,与Spring Security的行为保持一致 String pathWithoutContext = requestURI; if (contextPath != null && !contextPath.isEmpty() && requestURI.startsWith(contextPath)) { pathWithoutContext = requestURI.substring(contextPath.length()); } System.out.println("=== JWT过滤器被调用 === 原始URI: " + requestURI + ", 去掉context path后: " + pathWithoutContext); logger.debug("JWT过滤器开始处理请求: {}", pathWithoutContext); // 跳过不需要认证的路径 if (shouldSkipAuthentication(pathWithoutContext)) { logger.debug("跳过JWT认证,路径: {}", pathWithoutContext); filterChain.doFilter(request, response); return; } // 对GraphQL请求进行特殊处理 if (isGraphQLRequest(pathWithoutContext)) { logger.debug("检测到GraphQL请求"); // 为POST请求包装请求以支持重复读取请求体 HttpServletRequest wrappedRequest = request; if ("POST".equalsIgnoreCase(request.getMethod())) { wrappedRequest = new ContentCachingRequestWrapper(request); } // 先检查Authorization头,如果没有token,再检查是否为公开查询 String authHeader = request.getHeader("Authorization"); if (authHeader == null || !authHeader.startsWith("Bearer ")) { logger.debug("GraphQL请求没有Authorization头,检查是否为公开查询"); // 检查是否为公开查询 if (isPublicGraphQLQuery(wrappedRequest)) { logger.debug("检测到公开GraphQL查询,允许匿名访问"); // 设置匿名认证,让Spring Security知道这是一个已认证的匿名用户 AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken( "anonymous", "anonymous", Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) ); SecurityContextHolder.getContext().setAuthentication(anonymousAuth); logger.debug("为公开GraphQL查询设置匿名认证"); filterChain.doFilter(wrappedRequest, response); return; } logger.warn("GraphQL请求缺少有效的Authorization头且不是公开查询"); sendUnauthorizedResponse(response); return; } logger.debug("检测到需要认证的GraphQL请求,开始验证JWT"); String token = authHeader.substring(7); try { Long userId = jwtUtil.getUserIdFromToken(token); if (userId == null || !jwtUtil.validateToken(token)) { logger.warn("GraphQL请求的JWT token无效"); sendUnauthorizedResponse(response); return; } // 查找用户信息并设置认证 Optional userOpt = userRepository.findById(userId); if (userOpt.isPresent()) { User user = userOpt.get(); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( user.getId().toString(), null, new ArrayList<>() ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); logger.debug("GraphQL请求认证成功: userId={}", user.getId()); } else { logger.warn("GraphQL请求的用户不存在: userId={}", userId); sendUnauthorizedResponse(response); return; } } catch (Exception e) { logger.error("GraphQL请求JWT验证失败: {}", e.getMessage()); sendUnauthorizedResponse(response); return; } // 继续处理请求 filterChain.doFilter(wrappedRequest, response); return; } String authHeader = request.getHeader("Authorization"); String token = null; Long userId = null; logger.debug("Authorization头: {}", authHeader); // 从请求头中提取JWT token if (authHeader != null && authHeader.startsWith("Bearer ")) { token = authHeader.substring(7); logger.debug("提取到JWT token: {}", token.substring(0, Math.min(20, token.length())) + "..."); try { userId = jwtUtil.getUserIdFromToken(token); logger.debug("从token中解析到用户ID: {}", userId); } catch (Exception e) { logger.error("JWT token解析失败: {}", e.getMessage(), e); } } else { logger.debug("没有找到Authorization头或格式不正确"); } // 如果token有效且当前是匿名或无认证,则进行认证 Authentication existingAuth = SecurityContextHolder.getContext().getAuthentication(); boolean isAnonymous = (existingAuth == null) || ("anonymousUser".equals(String.valueOf(existingAuth.getPrincipal()))); if (userId != null && isAnonymous) { logger.debug("开始验证token有效性"); // 验证token是否有效 if (jwtUtil.validateToken(token)) { logger.debug("Token验证成功,查找用户信息"); // 查找用户信息 Optional userOpt = userRepository.findById(userId); if (userOpt.isPresent()) { User user = userOpt.get(); logger.debug("找到用户: userId={}, phone={}", user.getId(), user.getPhone()); // 创建认证对象 UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( user.getId().toString(), null, new ArrayList<>() // 暂时不设置权限,后续可以根据角色设置 ); authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request)); SecurityContextHolder.getContext().setAuthentication(authToken); logger.info("用户认证成功: userId={}, phone={}", user.getId(), user.getPhone()); } else { logger.warn("用户不存在: userId={}", userId); } } else { logger.warn("Token验证失败"); } } else if (userId == null) { logger.debug("没有解析到用户ID"); } else { logger.debug("已存在非匿名认证信息,跳过JWT认证"); } filterChain.doFilter(request, response); } }