使用EventSource建立SSE连接时遭遇403错误的解决求助
问题描述
使用SSE协议实现后端向前端推送数据时遭遇403错误,已确认JWT Token有效,相关代码如下:
后端(Spring Security JWT)
@CrossOrigin(origins = "http://localhost:4444", allowCredentials = "true") @GetMapping("/") public Flux<ServerSentEvent<List<UserResponse>>> getAll() throws CancelQueryException { return Flux.interval(Duration.ofSeconds(3)) .flatMap(sequence -> { List<UserResponse> data = null; try { data = userService.getAllUsers(); System.out.println("data: " + data); } catch (CancelQueryException e) { throw new RuntimeException(e); } return Mono.just(ServerSentEvent.<List<UserResponse>>builder() .id(String.valueOf(sequence)) .event("update") .data(data) .build()); }); }
前端(React)
useEffect(() => { const token = "eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJld2ZhZXJnZSIsImlhdCI6MTY4ODEwNTg4MCwiZXhwIjoxNjg4NzEwNjgwfQ.V3d_1n7QElQZpPshH_UkkR707sLJlxqz7fYmqSJ3V-4"; const config = { withCredentials: true, headers: { Authorization: `Bearer ${token}`, }, }; const url = "http://localhost:8558/monitoring-location/api/v1/admin/user/"; try { const eventSource = new EventSource(url, config); eventSource.onmessage = (event) => { const receivedEvent = JSON.parse(event.data); console.log(receivedEvent); }; console.log("running"); return () => { console.log("close connecttion"); eventSource.close(); // Đóng kết nối SSE khi component bị hủy }; } catch (error) { console.log("error ----", error); } }, []);
排查与解决步骤
1. 修复EventSource自定义请求头问题
原生EventSourceAPI 不支持携带自定义请求头,你代码里的Authorization头不会被发送到后端,这是导致403的核心原因——Spring Security没收到Token,直接拦截请求。
有两种解决方案:
- 方案一:URL参数携带Token
修改前端URL:
const url = `http://localhost:8558/monitoring-location/api/v1/admin/user/?token=${token}`;
后端配置Spring Security支持从请求参数提取Token,比如在JWT过滤器中添加参数提取逻辑:
// 示例:在JwtAuthenticationFilter中添加参数提取 String token = request.getParameter("token"); if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); } // 后续验证token逻辑
- 方案二:使用Polyfill支持自定义头
安装eventsource-polyfill依赖:
npm install eventsource-polyfill
修改前端代码:
import EventSource from 'eventsource-polyfill'; // ... 原有代码 const eventSource = new EventSource(url, { headers: { 'Authorization': `Bearer ${token}` }, withCredentials: true });
2. 验证Spring Security端点权限配置
确保SSE端点的权限规则和Token中的权限匹配:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.authorizeRequests() // 匹配你的SSE端点路径 .antMatchers("/monitoring-location/api/v1/admin/user/**") // 确保Token包含对应角色/权限 .hasAuthority("ROLE_ADMIN") .and() .oauth2ResourceServer() .jwt(); // 其他配置... } }
用JWT解析工具检查Token的authorities字段,确认包含端点要求的权限。
3. 统一CORS配置避免冲突
后端的@CrossOrigin注解可能和Spring Security的CORS配置冲突,建议使用全局CORS配置:
@Configuration public class SecurityConfig extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity http) throws Exception { http.cors().and() // 启用全局CORS .authorizeRequests() // ... 其他规则 } @Bean public CorsConfigurationSource corsConfigurationSource() { CorsConfiguration config = new CorsConfiguration(); config.setAllowedOrigins(Arrays.asList("http://localhost:4444")); config.setAllowedMethods(Arrays.asList("GET", "OPTIONS")); config.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type")); config.setAllowCredentials(true); UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); source.registerCorsConfiguration("/**", config); return source; } }
配置后可以去掉接口上的@CrossOrigin注解。
4. 再次验证Token有效性
用JWT解析工具检查:
- Token的
exp字段是否未过期 - Token的签名密钥和后端配置的一致
内容的提问来源于stack exchange,提问作者Sơn Giang Lê Quốc
相关产品推荐
相关产品推荐

