feat: 新增全局CORS跨域过滤器,支持设备后台跨域访问网关

- 新增 CorsFilter(@Component(index=-200)最高优先级)
- OPTIONS预检请求:动态回显 Access-Control-Request-Headers,直接短路返回200不透传源服务器
- 其他请求:响应头补充 Access-Control-Allow-Origin 等CORS头
- 解决jeecg vue3前端自定义头(x-version等)被CORS拦截问题

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
2026-08-05 18:23:13 +08:00
co-authored by Claude
parent 020f112124
commit a7937b76d6
2 changed files with 53 additions and 0 deletions
+1
View File
@@ -14,6 +14,7 @@
- `/sys/watchUserLogin`**本地处理**:解密验证设备密码(防伪造)+ 单飞换 token + Redis 缓存 - `/sys/watchUserLogin`**本地处理**:解密验证设备密码(防伪造)+ 单飞换 token + Redis 缓存
- 其余业务接口 → **手动 HttpUtils 透传**至源服务器 `http://10.10.10.228:29999` - 其余业务接口 → **手动 HttpUtils 透传**至源服务器 `http://10.10.10.228:29999`
- token 失效三重自愈:10秒主动探活 + 预过期刷新 + 业务接口 401 被动兜底 - token 失效三重自愈:10秒主动探活 + 预过期刷新 + 业务接口 401 被动兜底
- **全局 CORS**CorsFilter 处理跨域(设备后台跨域访问网关),OPTIONS 预检直接短路返回
## 技术栈 ## 技术栈
@@ -0,0 +1,52 @@
package com.renkang.gateway.filter;
import org.noear.solon.annotation.Component;
import org.noear.solon.core.handle.Context;
import org.noear.solon.core.handle.Filter;
import org.noear.solon.core.handle.FilterChain;
/**
* 全局 CORS 跨域过滤器。
* <p>
* 健康设备后台(如 http://10.10.10.228:23001)跨域访问本网关时,浏览器要求响应带
* Access-Control-Allow-Origin 等头,否则被拦截。
* <p>
* 处理策略(CORS 最佳实践,避免逐个枚举允许的头):
* <ul>
* <li>OPTIONS 预检请求:读取客户端请求的 Access-Control-Request-Headers
* 原样回显到 Access-Control-Allow-Headers,并返回 200,不透传源服务器</li>
* <li>其他请求:放行到后续 Controller,响应头补充 CORS 头</li>
* </ul>
* 这样无论前端(jeecg vue3 等)加什么自定义头(X-Version、X-TIMESTAMP、X-Sign 等)都不会被拦。
*
* @author renkang
*/
@Component(index = -200) // index 越小越先执行,确保 CORS 在业务透传前处理
public class CorsFilter implements Filter {
@Override
public void doFilter(Context ctx, FilterChain chain) throws Throwable {
// 统一设置基础 CORS 响应头(OPTIONS 和普通请求都需要)
ctx.headerSet("Access-Control-Allow-Origin", "*");
ctx.headerSet("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS, PATCH");
// 动态回显客户端预检请求的头,避免逐个枚举(最佳实践)
String reqHeaders = ctx.header("Access-Control-Request-Headers");
if (reqHeaders != null && !reqHeaders.isEmpty()) {
ctx.headerSet("Access-Control-Allow-Headers", reqHeaders);
} else {
// 无预检头时给一个兜底白名单(含 jeecg vue3 常用头)
ctx.headerSet("Access-Control-Allow-Headers",
"Content-Type, Authorization, X-Access-Token, X-Requested-With, X-Version, X-TIMESTAMP, X-Sign, token, Accept, Origin");
}
ctx.headerSet("Access-Control-Max-Age", "3600");
// OPTIONS 预检请求直接短路返回,不透传源服务器(降低源服务器压力)
if ("OPTIONS".equalsIgnoreCase(ctx.method())) {
ctx.status(200);
return; // 不进入后续过滤器/Controller
}
// 其他请求放行到后续处理(GatewayController 透传 / TokenController
chain.doFilter(ctx);
}
}