背景
最近来个一个新同事,在开发功能时遇到sj跨域请求的问题,他调整了一天都没有搞好,看来有必要记录一下。在前端开发中,跨域请求(Cross-Origin Request) 是一个非常常见,但又让人头疼的问题。很多初学者在使用 AJAX 调用后端接口时,经常会遇到。跨域是一个比较老的问题了,解决方案也很多。对于.net 开发来说,一般都是修改webconfig。
跨域问题错误的表现
我们在前端页面访问一个后端的服务的api接口(/api/snappic/matting/ping接口正常应该返回字符串“Pong”),但请求没有获取到数据错误如下图。
但我们直接使用浏览器访问这个接口是可以正常返回数据的,证明这个api接口是正常的,没有问题。上面的错误就是跨域了。
一、什么是“跨域”?
在浏览器中,只要你通过 JavaScript 发起 HTTP 请求,如果请求的 协议、域名、端口 任何一个与当前页面的地址不同,就会被浏览器认为是“跨域”。通俗一点将就是用一个网站去访问另一个网站。
例如:上面的例子,网站的地址是:http://localhost:8066/ ,但去请求http://192.168.100.40:8033,这两个不是同一个网站。
二、为什么浏览器要限制跨域?
这是出于 浏览器的安全机制 —— 同源策略(Same-Origin Policy)。
同源策略是浏览器的一项重要安全策略,它规定了不同源之间的脚本是无法相互访问数据的,以防止恶意网站窃取用户数据。
三、CORS 是什么?
CORS,全称 Cross-Origin Resource Sharing(跨源资源共享),是 W3C 标准,允许浏览器向跨源服务器发出 XMLHttpRequest 或 Fetch 请求的一种机制。
它不是浏览器能“直接打开”的功能,而是需要 服务器端返回正确的 HTTP 响应头 来授权浏览器访问。
四、CORS 的工作原理
当浏览器发出一个跨域请求时,会按照以下流程工作:
1. 简单请求
比如使用 GET 或 POST,但不携带特殊头部时,浏览器会直接发起请求。
服务器若允许该请求,需返回如下 HTTP 响应头:
Access-Control-Allow-Origin: https://www.example.com
说明:如果服务端设置为 *,表示允许所有来源访问。
2.复杂请求(Preflight 预检请求)
如果你使用了以下任意一种情况:
- 请求方法是 PUT、DELETE、PATCH
- 自定义头部(如 Authorization)
- Content-Type 是 application/json
浏览器会 先发送一个 OPTIONS 请求,称为“预检请求(Preflight Request)”。
服务器必须返回如下头部,浏览器才会继续发起实际请求:
Access-Control-Allow-Origin: https://www.example.com
Access-Control-Allow-Methods: POST, PUT, DELETE
Access-Control-Allow-Headers: Content-Type, Authorization
五、前端如何发起 CORS 请求?
这里首先明确一个问题,前端不能并不能设置让某一个指定API接收跨域请求,跨域是需要配置服务端配置的。下面就是.net web项目来做例子。
后端api接口代码(APP1 应用):
[Route("api/snappic/matting")]
[ApiController]
public class MattingController : ControllerBase
{
[HttpGet]
[Route("ping")]
public async Task<string> Ping()
{
return "Pong";
}
}
前端js请求代码(APP2 应用)
$.ajax({
url: ‘http://192.168.100.40:8033/api/snappic/matting/ping’, // 请求 URL
type: 'GET', // 请求方式 GET 或 POST
success: function (response) {
console.log('成功响应数据:', response);
// 你可以在这里处理返回的数据
}
});
使用默认配置,我将两个应用都发布到iis,APP1 应用的地址是http://192.168.100.40:8033/api/snappic/matting/ping ,APP2 应用地址http://localhost:8066。运行起来结果就如开始的截图所示。
可以看到虽然返回状态是200,但是请求已经被拦截,获取不到任何数据。
新手在这个时候可能会不停找APP2 应用的问题,但是问题是APP1 禁止了APP2 的请求,如果要正常访问就需要指定APP1 允许APP2访问。配置方法的话,只需要在APP2 的webconfig中添加允许跨域的配置即可。不管什么语言开发的web应用都是同一原理。
后端配置
只需webconfig修改成一下配置即可。
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<!-- 配置当前站点 -->
<location path="." inheritInChildApplications="false">
<system.webServer>
<!-- ASP.NET Core 配置 -->
<handlers>
<add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
<!-- 允许处理 OPTIONS 请求(预检请求) -->
<add name="OPTIONS" path="*" verb="OPTIONS" type="System.Web.HttpNotFoundHandler" resourceType="Unspecified" />
</handlers>
<aspNetCore processPath="dotnet" arguments=".\Snappic.dll" stdoutLogEnabled="false" stdoutLogFile=".\logs\stdout" hostingModel="inprocess" />
<!-- CORS 配置 -->
<httpProtocol>
<customHeaders>
<!-- 允许所有域进行跨域请求 -->
<add name="Access-Control-Allow-Origin" value="*" />
<!-- 允许的 HTTP 请求方法 -->
<add name="Access-Control-Allow-Methods" value="GET, POST, OPTIONS" />
<!-- 允许的请求头 -->
<add name="Access-Control-Allow-Headers" value="Content-Type, Authorization, X-Requested-With" />
<!-- 如果需要携带认证信息(如 Cookies),则添加此项 -->
<add name="Access-Control-Allow-Credentials" value="false" />
</customHeaders>
</httpProtocol>
</system.webServer>
</location>
</configuration>
这里有一个点需要注意:
Access-Control-Allow-Credentials 设置为false表示不带身份信息,Access-Control-Allow-Origin 可以设置成 * ,表示允许所有域进行跨域请求。
Access-Control-Allow-Credentials 设置为true表示带身份信息,Access-Control-Allow-Origin 必须写出指定的网址地址(域名或者IP) ,表示允许指定的网站访问。同时前端需要带上 withCredentials,反过来也是一样,如果ajax请求带上了withCredentials,则webconfig中必须Access-Control-Allow-Credentials 设置为true,否则就会报错。
$.ajax({
url: "https://api.example.com/data",
method: "GET",
xhrFields: {
withCredentials: true // 启用 cookie 发送
},
success: function (data) {
console.log("数据:", data);
},
error: function (xhr, status, err) {
console.error("错误:", err);
}
});
做好配置,我们在发布一个APP3 应用地址是http://192.168.100.40:8044,Api代码不做任何改变。我测试一下两个请求。
APP3 应用已经能正常访问,响应头部也添加了允许跨域的信息。(.net除了webconfig也可以在后端代码中做相同配置,可去网上搜索一下)
好了就这些,加油!