From ba94ceae1315174798ae1967ef62268c6d16cd5b Mon Sep 17 00:00:00 2001 From: Codex Assistant <codex@example.com> Date: 星期一, 06 十月 2025 22:07:06 +0800 Subject: [PATCH] feat: 评审与活动相关改动 - backend(GraphQL): Activity schema 增加 updateActivityState(id, state);实现 resolver/service 仅更新 state=2 作为逻辑删除 - backend(GraphQL): region.graphqls 新增 Query leafRegions - backend(GraphQL): player.graphqls 的 projectReviewApplications 增加可选参数 regionId - backend(Service): listProjectReviewApplications 绑定 regionId 参数,修复 QueryParameterException - frontend(web): 新增 api/activity.js 的 updateActivityState 并接入 activity-list 删除逻辑 - frontend(web): review-list.vue 权限仅校验登录,移除角色限制;查询参数修正为 name/regionId - frontend(web): 删除未引用的 ActivityList.vue - frontend(web): projectReviewNew.js GraphQL 查询增加 name 参数 --- backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java | 237 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 files changed, 235 insertions(+), 2 deletions(-) diff --git a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java index 4fef1a3..c0f9452 100644 --- a/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java +++ b/backend/src/main/java/com/rongyichuang/auth/filter/JwtAuthenticationFilter.java @@ -10,7 +10,9 @@ 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; @@ -18,9 +20,12 @@ 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; /** @@ -36,12 +41,240 @@ @Autowired private UserRepository userRepository; + + // 鍏佽鍖垮悕璁块棶鐨凣raphQL鏌ヨ鍒楄〃 + private static final List<String> PUBLIC_GRAPHQL_QUERIES = Arrays.asList( + "carouselPlayList", + "activities", + "hello" + ); + + /** + * 鍒ゆ柇鏄惁搴旇璺宠繃JWT璁よ瘉 + */ + private boolean shouldSkipAuthentication(String requestURI) { + // 杩欎簺璺緞涓嶉渶瑕丣WT璁よ瘉锛堝凡鍘绘帀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); + } + + /** + * 妫�鏌raphQL璇锋眰鏄惁涓哄叕寮�鏌ヨ锛堜笉闇�瑕佽璇侊級 + * 鍙湁鏄庣‘鏍囪涓哄叕寮�鐨勬煡璇㈡墠鍏佽鍖垮悕璁块棶 + */ + private boolean isPublicGraphQLQuery(HttpServletRequest request) { + try { + // 妫�鏌ET鍙傛暟涓殑query + String query = request.getParameter("query"); + if (query != null && !query.trim().isEmpty()) { + logger.debug("浠庡弬鏁拌幏鍙朑raphQL鏌ヨ: {}", 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); + + // 妫�鏌ontent-Type + String contentType = request.getContentType(); + if (contentType != null && contentType.contains("application/graphql")) { + // 瀵逛簬application/graphql锛岃姹備綋鐩存帴鏄疓raphQL鏌ヨ + return containsPublicQuery(body); + } else { + // 瀵逛簬application/json锛岀畝鍗曡В鏋怞SON锛屾煡鎵緌uery瀛楁 + if (body.contains("\"query\"")) { + return containsPublicQuery(body); + } + } + } + } catch (Exception e) { + logger.warn("璇诲彇POST璇锋眰浣撳け璐�", e); + } + } + + return false; + } catch (Exception e) { + logger.error("瑙f瀽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_UNAUTHORIZED); + 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 { - System.out.println("=== JWT杩囨护鍣ㄨ璋冪敤 === URI: " + request.getRequestURI()); - logger.debug("JWT杩囨护鍣ㄥ紑濮嬪鐞嗚姹�: {}", request.getRequestURI()); + 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; + } + + // 瀵笹raphQL璇锋眰杩涜鐗规畩澶勭悊 + if (isGraphQLRequest(pathWithoutContext)) { + logger.debug("妫�娴嬪埌GraphQL璇锋眰"); + + // 涓篜OST璇锋眰鍖呰璇锋眰浠ユ敮鎸侀噸澶嶈鍙栬姹備綋 + HttpServletRequest wrappedRequest = request; + if ("POST".equalsIgnoreCase(request.getMethod())) { + wrappedRequest = new ContentCachingRequestWrapper(request); + } + + // 鍏堟鏌uthorization澶达紝濡傛灉娌℃湁token锛屽啀妫�鏌ユ槸鍚︿负鍏紑鏌ヨ + String authHeader = request.getHeader("Authorization"); + if (authHeader == null || !authHeader.startsWith("Bearer ")) { + logger.debug("GraphQL璇锋眰娌℃湁Authorization澶达紝灏濊瘯鍒ゅ畾鏄惁涓哄叕寮�鏌ヨ"); + + // 灏濊瘯鍒ゅ畾鍏紑鏌ヨ锛涘鏋滆兘纭畾鏄叕寮�鏌ヨ鍒欐斁琛� + if (isPublicGraphQLQuery(wrappedRequest)) { + logger.debug("妫�娴嬪埌鍏紑GraphQL鏌ヨ锛屽厑璁稿尶鍚嶈闂�"); + AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken( + "anonymous", + "anonymous", + Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) + ); + SecurityContextHolder.getContext().setAuthentication(anonymousAuth); + filterChain.doFilter(wrappedRequest, response); + return; + } + + // 鏃犳硶鍙潬璇诲彇/鍒ゅ畾璇锋眰浣撴椂锛岄粯璁や互鍖垮悕韬唤鏀捐鍒癎raphQL灞傦紝鐢卞悇Resolver鑷杩涜鏉冮檺鏍¢獙 + logger.debug("鏃犳硶鍙潬鍒ゅ畾鏄惁涓哄叕寮�鏌ヨ锛岃缃尶鍚嶈璇佸苟浜ょ敱GraphQL灞傚鐞�"); + AnonymousAuthenticationToken anonymousAuth = new AnonymousAuthenticationToken( + "anonymous", + "anonymous", + Arrays.asList(new SimpleGrantedAuthority("ROLE_ANONYMOUS")) + ); + SecurityContextHolder.getContext().setAuthentication(anonymousAuth); + filterChain.doFilter(wrappedRequest, response); + return; + } + + logger.debug("妫�娴嬪埌闇�瑕佽璇佺殑GraphQL璇锋眰锛屽紑濮嬮獙璇丣WT"); + + String token = authHeader.substring(7); + try { + Long userId = jwtUtil.getUserIdFromToken(token); + if (userId == null || !jwtUtil.validateToken(token)) { + logger.warn("GraphQL璇锋眰鐨凧WT token鏃犳晥"); + sendUnauthorizedResponse(response); + return; + } + + // 鏌ユ壘鐢ㄦ埛淇℃伅骞惰缃璇� + Optional<User> 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; -- Gitblit v1.8.0