1. 为什么Tomcat需要处理跨域问题?
跨域问题本质上是浏览器出于安全考虑实施的同源策略限制。当你的前端页面(比如运行在http://localhost:8081)尝试通过AJAX请求访问另一个域名或端口(比如http://localhost:8080的Tomcat服务)时,浏览器会阻止这种请求。
在实际开发中,这种场景非常常见:
- 前后端分离架构下,前端开发服务器与后端API服务往往运行在不同端口
- 微服务架构中,不同服务可能部署在不同子域名下
- 需要集成第三方API时,经常遇到跨域访问需求
Tomcat作为Java Web应用的主流容器,通常会同时承担两种角色:
- 静态资源服务器:托管前端HTML/JS/CSS文件
- 动态API服务器:处理Java Web(Spring MVC/Spring Boot)的后端请求
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. Tomcat配置跨域的三种方案对比
2.1 方案一:web.xml配置过滤器(推荐)
这是最灵活、最可控的方式,适合需要精细控制跨域规则的场景。创建一个CorsFilter:
java复制public class CorsFilter implements Filter {
@Override
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletResponse response = (HttpServletResponse) res;
response.setHeader("Access-Control-Allow-Origin", "*");
response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
response.setHeader("Access-Control-Max-Age", "3600");
response.setHeader("Access-Control-Allow-Headers", "x-requested-with, Content-Type");
chain.doFilter(req, res);
}
// 其他方法省略...
}
在web.xml中注册:
xml复制<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>com.yourpackage.CorsFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
提示:生产环境不要使用"*"作为Allow-Origin值,应该明确指定允许的域名列表
2.2 方案二:Tomcat全局配置(适用于静态文件)
修改Tomcat的conf/web.xml,在<web-app>节点内添加:
xml复制<filter>
<filter-name>CorsFilter</filter-name>
<filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
<init-param>
<param-name>cors.allowed.origins</param-name>
<param-value>*</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
这种方式的优点是:
- 对所有部署的应用生效
- 不需要修改应用代码
- 对静态文件请求也有效
缺点是不如方案一灵活,无法针对特定URL做精细控制。
2.3 方案三:Spring MVC/Spring Boot的注解配置
如果你使用的是Spring框架,可以在Controller类或方法上添加@CrossOrigin注解:
java复制@RestController
@CrossOrigin(origins = "http://localhost:8081")
public class MyController {
@GetMapping("/api/data")
public Data getData() {
// ...
}
}
或者在Spring Boot中配置全局CORS:
java复制@Configuration
public class WebConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:8081")
.allowedMethods("GET", "POST");
}
}
3. 处理OPTIONS预检请求的坑
当请求满足以下条件时,浏览器会先发送OPTIONS预检请求:
- 使用了PUT、DELETE等非简单方法
- 自定义了请求头
- Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain
常见问题及解决方案:
3.1 预检请求返回403
确保你的CORS过滤器或配置正确处理了OPTIONS方法:
java复制if ("OPTIONS".equalsIgnoreCase(request.getMethod())) {
response.setStatus(HttpServletResponse.SC_OK);
} else {
chain.doFilter(req, res);
}
3.2 认证信息无法跨域
如果需要携带Cookie或Authorization头,需要额外配置:
java复制response.setHeader("Access-Control-Allow-Credentials", "true");
并且前端也需要设置:
javascript复制fetch(url, {
credentials: 'include'
})
注意:此时Access-Control-Allow-Origin不能为"*",必须指定具体域名
4. 静态文件与API服务的差异化配置
实际项目中,我们通常希望对静态文件和API接口采用不同的CORS策略:
4.1 静态文件配置
在Tomcat的conf/web.xml中添加:
xml复制<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/static/*</url-pattern>
</filter-mapping>
4.2 API接口配置
在Spring Boot应用中:
java复制@Bean
public FilterRegistrationBean<CorsFilter> corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOrigin("http://trusted-domain.com");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/api/**", config);
FilterRegistrationBean<CorsFilter> bean = new FilterRegistrationBean<>(new CorsFilter(source));
bean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return bean;
}
5. 生产环境最佳实践
-
不要使用通配符:明确列出允许的域名
java复制// 错误做法 response.setHeader("Access-Control-Allow-Origin", "*"); // 正确做法 String origin = request.getHeader("Origin"); if (allowedOrigins.contains(origin)) { response.setHeader("Access-Control-Allow-Origin", origin); } -
限制HTTP方法:只开放必要的请求方法
java复制response.setHeader("Access-Control-Allow-Methods", "GET, POST"); -
缓存预检请求:减少OPTIONS请求次数
java复制response.setHeader("Access-Control-Max-Age", "3600"); -
安全头设置:防止滥用
java复制response.setHeader("X-Content-Type-Options", "nosniff"); response.setHeader("X-Frame-Options", "DENY"); -
监控与日志:记录异常的CORS请求
java复制logger.warn("Blocked CORS request from: " + request.getHeader("Origin"));
6. 常见问题排查指南
6.1 配置了但依然报跨域错误
检查顺序:
- 确认响应头确实被添加(通过浏览器开发者工具查看)
- 检查是否有多个过滤器互相覆盖
- 确保没有安全框架(如Spring Security)覆盖了你的CORS头
6.2 特殊场景:WebSocket跨域
WebSocket不受同源策略限制,但浏览器会在建立连接时检查Origin头。需要在Tomcat的WebSocket配置中添加:
xml复制<websocket:handlers allowed-origins="http://yourdomain.com">
6.3 静态资源跨域缓存问题
对于版本化的静态资源(如app.12345.js),可以放宽CORS限制:
xml复制<filter-mapping>
<filter-name>CorsFilter</filter-name>
<url-pattern>/*.v*.js</url-pattern>
</filter-mapping>
6.4 本地开发环境配置
开发时可以在IDE的Tomcat启动配置中添加参数:
code复制-Dcors.allow.origins=http://localhost:3000
然后在代码中读取:
java复制String allowOrigins = System.getProperty("cors.allow.origins", "");
7. 性能优化建议
- 避免过度使用CORS:同域请求不要添加CORS头
- 预检请求缓存:合理设置Access-Control-Max-Age
- CDN静态资源:将静态文件部署到CDN,减少Tomcat负担
- Nginx反向代理:在前置Nginx统一处理CORS
Nginx配置示例:
nginx复制location /api/ {
add_header 'Access-Control-Allow-Origin' 'http://yourdomain.com';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
proxy_pass http://tomcat-server:8080;
}
8. 替代方案:前后端同域部署
如果条件允许,最简单的解决方案是让前后端使用相同域名:
- 前端构建产物放到Tomcat的webapps/ROOT目录
- 使用Nginx将/api代理到Tomcat,其他请求指向静态文件
- 将前端部署为单独的Web应用,通过相对路径访问后端API
这种架构完全避免了跨域问题,但牺牲了前后端分离的一些优势。
