一、跨域问题的本质是什么
每次看到前端小朋友和后端开发因为接口调不通吵架,我就想起自己刚入行时被跨域支配的恐惧。其实跨域问题就像两个小区之间的围墙 - 前端住在A小区,后端住在B小区,浏览器这个尽职的保安严格执行着"同源策略"的规定。
所谓同源策略,就是要求协议、域名、端口三者必须完全相同。比如:
- http://a.com 和 https://a.com (协议不同)
- http://a.com 和 http://b.com (域名不同)
- http://a.com:80 和 http://a.com:8080 (端口不同)
这三种情况都会触发跨域限制。而在现代前后端分离架构中,前端框架(如Vue/React)运行在开发服务器的3000端口,后端API运行在5000端口,跨域问题就不可避免了。
二、DotNetCore的解决方案全家桶
1. CORS中间件 - 官方推荐方案
DotNetCore提供了非常优雅的CORS支持,只需要几行代码就能搞定。我们来看一个完整的示例:
// Startup.cs
public void ConfigureServices(IServiceCollection services)
{
// 添加CORS服务
services.AddCors(options =>
{
options.AddPolicy("MyPolicy", builder =>
{
builder.WithOrigins("http://localhost:3000") // 允许的前端地址
.AllowAnyHeader() // 允许所有头
.AllowAnyMethod() // 允许所有HTTP方法
.AllowCredentials(); // 允许携带凭证
});
});
services.AddControllers();
}
public void Configure(IApplicationBuilder app)
{
// 使用CORS中间件
app.UseCors("MyPolicy");
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
}
这个方案有几个关键点需要注意:
WithOrigins可以配置多个域名,用逗号分隔- 生产环境一定要替换掉localhost为真实域名
- 如果前端需要发送cookie,必须设置
AllowCredentials
2. 代理方案 - 开发环境的最佳实践
有时候我们不想在前端代码里写完整URL,这时候可以配置开发服务器代理。以Vue CLI为例:
// vue.config.js
module.exports = {
devServer: {
proxy: {
'/api': {
target: 'http://localhost:5000', // 后端地址
changeOrigin: true,
pathRewrite: {
'^/api': ''
}
}
}
}
}
这样前端代码里只需要写/api/users,请求会被自动转发到后端,完全避免了跨域问题。这个方案特别适合开发阶段使用。
三、那些年我们踩过的坑
1. 预检请求(OPTIONS)的坑
当请求满足以下条件时,浏览器会先发送OPTIONS预检请求:
- 使用了PUT、DELETE等非简单方法
- 自定义了请求头
- Content-Type不是application/x-www-form-urlencoded、multipart/form-data或text/plain
很多同学配置了CORS但还是报错,往往是因为没处理好OPTIONS请求。DotNetCore的CORS中间件已经帮我们处理了这个问题,但如果用Nginx做反向代理就需要额外配置:
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,Content-Type';
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
}
2. 带凭证的请求要特别注意
当请求需要携带cookie或认证头时,必须满足三个条件:
- 后端必须设置
AllowCredentials Access-Control-Allow-Origin不能是通配符*- 前端需要设置
withCredentials
// 前端axios示例
axios.get('http://api.example.com/data', {
withCredentials: true
});
// 后端DotNetCore配置
builder.WithOrigins("http://frontend.example.com")
.AllowCredentials();
四、进阶场景与最佳实践
1. 动态允许源
有时候我们需要根据请求动态判断是否允许跨域,比如多租户系统。这时可以自定义一个策略:
services.AddCors(options =>
{
options.AddPolicy("DynamicPolicy", builder =>
{
builder.AllowAnyHeader()
.AllowAnyMethod()
.SetIsOriginAllowed(origin =>
{
// 这里可以写自定义逻辑
return origin.EndsWith(".example.com")
|| origin == "http://localhost:3000";
})
.AllowCredentials();
});
});
2. 生产环境配置建议
生产环境的CORS配置应该更加严格:
- 明确指定允许的域名,不要使用通配符
- 限制允许的HTTP方法
- 设置适当的缓存时间
- 配合HTTPS使用
services.AddCors(options =>
{
options.AddPolicy("ProductionPolicy", builder =>
{
builder.WithOrigins("https://www.myapp.com")
.WithMethods("GET", "POST", "PUT")
.WithHeaders("Content-Type", "Authorization")
.SetPreflightMaxAge(TimeSpan.FromHours(1));
});
});
五、总结与选型建议
经过这么多年的实践,我的建议是:
- 开发环境使用代理方案最方便
- 生产环境一定要用CORS中间件
- 简单项目用基本配置即可
- 复杂项目考虑动态源配置
记住,跨域安全策略是为了保护用户,不要为了方便而完全禁用安全限制。正确的做法是根据业务需求找到最合适的平衡点。
评论