1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
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<String> 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) {
                    return new String(content, wrapper.getCharacterEncoding());
                }
            }
            
            // 如果不是包装器,尝试直接读取(可能会消耗请求体)
            StringBuilder buffer = new StringBuilder();
            String line;
            java.io.BufferedReader reader = request.getReader();
            while ((line = reader.readLine()) != null) {
                buffer.append(line);
            }
            return buffer.toString();
        } 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<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;
        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<User> 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);
    }
}