通过我们关于 Nginx 跨源解决方案的综合指南提升您的前端开发,解决并克服跨源问题的挑战,确保安全、无缝的用户体验。
遇到跨域问题时,不要急于复制尝试。在继续之前,请仔细阅读整篇文章。我相信它可以为您提供宝贵的帮助。
在开始之前,首先确保服务器端没有处理跨域问题,并且服务器接口可以访问,没有任何异常。
- 前端网址:http://localhost:8080。
- 服务器端 URL:http://localhost:59200。
当该端口的网站8080尝试访问服务器端接口时,会触发跨域问题。
那么,我们该如何解决这个问题呢?
我现在列举一下跨域情况下遇到的各种场景,通过Nginx代理来解决(只要掌握了底层原理,后端服务解析也是一样的原理)。
四个响应标头
Access-Control-Allow-Origin:用于建立跨域请求允许的来源(在预检请求和跨域场景中的实际请求期间进行验证)。
Access-Control-Allow-Headers:允许在跨源请求期间包含特定标头信息字段(仅在预检请求期间进行验证)。
Access-Control-Allow-Methods指定跨源请求允许的请求方法或 HTTP 动词(仅在预检请求期间验证)。
Access-Control-Allow-Credentials确定是否允许跨源使用 cookie。如果要跨源使用 cookie,您可以包含此响应标头,并将值设置为 true。设置与否不影响请求的发送;只影响跨域场景下是否携带cookie。但是,如果设置,则预检和实际请求都需要配置此标头。
很多网上文章建议直接在 Nginx 中添加这些响应头来解决跨域问题,并且在大多数情况下,这种方法是有效的。但是,我相信在某些情况下,即使配置正确,跨域问题仍然存在。
在开始之前,了解什么是预检请求至关重要。
当出现跨域情况时,浏览器首先向服务器查询当前网页的域名是否在服务器的白名单中。它还查询允许的 HTTP 动词和标头信息字段。
只有收到肯定响应后,浏览器才会继续发送实际的XMLHttpRequest;否则报错。如下图所示:
开始具体实施
Nginx代理端口:22222配置如下:
server {
listen 22222;
server_name localhost;
location / {
proxy_pass http://localhost:59200;
}
}
通过Nginx代理端口访问该界面,验证22222代理是否成功。
确保后端服务可以正常访问。
接下来,通过Nginx代理地址访问端口为8080网站的界面时,错误场景如下。
错误1:
Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
错误消息清楚地表明了问题。Preflight 表示它是预请求,作为 CORS 机制的一部分,其中跨源请求首先经历预检(OPTIONS 请求)。只有成功完成此预检请求后,才会发送实际请求。
实现此设计是为了确保服务器能够感知 CORS,从而保护不支持 CORS 的旧服务器。
根据错误消息,预检请求似乎缺少Access-Control-Allow-Origin响应标头。
修改Nginx配置如下:
server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080';
proxy_pass http://localhost:59200;
}
}
继续接口请求,似乎同样的错误依然存在。
如果你希望每个响应中都携带 header 字段,则需要在末尾添加 'always' (根据我的测试,只有 headerAccess-Control-Allow-Origin需要 'always',而其他的则不需要携带)。
该add_header指令用于添加响应头字段,并且仅当状态代码与图中列出的代码匹配时才有效。
如果你希望每个响应中都携带 header 字段,则需要在末尾添加 'always' (根据我的测试,只有 headerAccess-Control-Allow-Origin需要 'always',而其他的则不需要携带)。
server{
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
proxy_pass http://localhost:59200;
}
}
修改配置后,看起来已经生效了。但需要注意的是,解决这个问题并不一定意味着解决了跨域问题。
错误已更改,因为我们已经解决了前面提到的具体问题。
错误2:
Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.
根据错误消息,很明显,预检(OPTIONS)请求(跨域场景下浏览器的默认行为)没有收到 OK 状态代码。
要解决此问题,请修改配置以在请求是 OPTIONS 请求时提供状态代码(通常为 204)。
server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://localhost:59200;
}
}
完成配置后,错误变了。
错误3:
Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.
这意味着预检响应标头 Access-Control-Allow-Headers 缺少“授权”标头信息。
在各种跨域情况下,跨域事件发生后,不允许添加自定义标头,除非明确添加到响应标头中的 Access-Control-Allow-Headers 中。
这可确保浏览器识别服务器合法包含这些标头。
这种情况下包括授权,但在其他情况下,它可能是令牌或类似的。
现在我们知道了问题所在,让我们修改配置文件,添加缺少的部分,然后再试一次。
server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Headers 'authorization';
return 204;
}
proxy_pass http://localhost:59200;
}
}
至此,错误问题似乎又回到了异常1。 经过测试和验证,如果 add_header 包含在 if ($request_method = 'OPTIONS') 块中,则在预检请求期间外部配置将无效。 官方文档确实提到了这种行为。
可能有多个 add_header 指令。当且仅当当前级别上没有定义 add_header 指令时,这些指令才会从上一级继承。
我们再次修改配置如下:
server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin 'http://localhost:8080';
add_header Access-Control-Allow-Headers 'authorization';
return 204;
}
proxy_pass http://localhost:59200;
}
}
进行这些更改后,跨域问题似乎已得到解决。
虽然上述更改解决了跨域问题,但需要考虑 Nginx 版本中的潜在更新。
未来该规则是否会修改尚不确定。此外,当前配置可能会导致携带两个 Access-Control-Allow-Origin 标头,这也是不允许的。 为了解决这些问题,建议对配置进行轻微修改,如下所示:
server {
listen 22222;
server_name localhost;
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin 'http://localhost:8080';
add_header Access-Control-Allow-Headers 'authorization';
return 204;
}
if ($request_method != 'OPTIONS') {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
}
proxy_pass http://localhost:59200;
}
}
错误4:
跨域场景下的Access-Control-Allow-Methods响应头默认只支持POST和GET。
当出现其他请求类型时,可能会导致跨域异常。 例如,如果我将API接口的请求方式从原来的GET改为PUT,然后发出请求,控制台就会抛出错误:
Access to XMLHttpRequest at 'http://localhost:22222/api/Login/TestGet' from origin 'http://localhost:8080' has been blocked by CORS policy: Method PUT is not allowed by Access-Control-Allow-Methods in preflight response.
错误消息明确表明在此预检请求期间跨域场景中不允许使用 PUT 方法。
我们需要调整 Access-Control-Allow-Methods 的配置(添加任何缺少的方法;这里我添加了 PUT,但您可以根据需要添加其他方法)以通知浏览器服务器允许它们。
server {
listen 22222;
server_name localhost;
location / {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin 'http://localhost:8080';
add_header Access-Control-Allow-Headers 'content-type,authorization';
add_header Access-Control-Allow-Methods 'PUT';
return 204;
}
if ($request_method != 'OPTIONS') {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
}
proxy_pass http://localhost:59200;
}
}
这里值得注意的是,当您切换到 PUT 方法时,Access-Control-Allow-Headers 响应标头将自动验证 content-type 标头,类似于例外 3。
解决方案保持不变 - 包括缺少的内容。 如果不带content-type,会报类似下面的错误。
简化一下,您可以将 Access-Control-Allow-Headers 和 Access-Control-Allow-Methods 设置为 * 以匹配所有内容。 但是,出于安全原因,不建议将 Access-Control-Allow-Origin 设置为 *,建议将其限制为特定域。
server {
listen 22222;
server_name localhost;
location / {
add_header Access-Control-Allow-Origin 'http://localhost:8080' always;
add_header Access-Control-Allow-Headers '*';
add_header Access-Control-Allow-Methods '*';
add_header Access-Control-Allow-Credentials 'true';
if ($request_method = 'OPTIONS') {
return 204;
}
proxy_pass http://localhost:59200;
}
}
本文暂时没有评论,来添加一个吧(●'◡'●)