1. 首页
  2. >
  3. 服务器技术
  4. >
  5. Nginx

利用nginx解决跨域问题

跨域是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScript实施的安全限制。

同源策略限制了一下行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax请求发送不出去

那什么是同源呢?所谓的同源是指,域名、协议、端口均为相同。出现跨域问题时,通过可以在console中看到以下错误。

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost/users-management/login. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).

以下是firefox开发者中心对该错误的解释,这个解释已经相当明确的。

The response to the CORS request is missing the required Access-Control-Allow-Origin header, which is used to determine whether or not the resource can be accessed by content operating within the current origin.

If the server is under your control, add the origin of the requesting site to the set of domains permitted access by adding it to the Access-Control-Allow-Origin header's value.

For example, to allow a site at https://amazing.site to access the resource using CORS, the header should be:

Access-Control-Allow-Origin: https://amazing.site

You can also configure a site to allow any site to access it by using the * wildcard. You should only use this for public APIs. Private APIs should never use *, and should instead have a specific domain or domains set. In addition, the wildcard only works for requests made with the crossorigin attribute set to anonymous, and it prevents sending credentials like cookies in requests.

Access-Control-Allow-Origin: *

Warning: Using the wildcard to allow all sites to access a private API is a bad idea.

To allow any site to make CORS requests without using the * wildcard (for example, to enable credentials), your server must read the value of the request's Origin header and use that value to set Access-Control-Allow-Origin, and must also set a Vary: Origin header to indicate that some headers are being set dynamically depending on the origin.

既然找到了问题所在,那么我们就开始设置nginx配置,来增加Header "Access-Control-Allow-Origin"吧。打开nginx的配置文件,增加如下节点:

location ~ ^/users-management/([a-zA-Z0-9_-]+) {
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header Host $host;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
proxy_pass http://localhost:8081;
}

以上配置的功能是将users-management相关的请求转给后端的http://localhost:8081这个服务来处理,并在响应头中增加以上设置的'Access-Control-Allow-Origin'等http头。

那么这样设置之后,跨域问题是否解决了呢?我们使用vue.js+axios来写一个程序验证一下。结果发现,users-management相关的GET请求是可以正常访问的。但是POST请求却还是报前面提到的跨域错误。为什么POST请求会出现错误呢?从网络交互中可以看到,在POST通讯之前,会先axios会先发送一个OPTIONS请求来Say Hello, 所以我们还要在nginx中对OPTIONS作一些配置,允许在收到OPTIONS请求时,在响应头中也增加 'Access-Control-Allow-Origin'等http头,并且顺便告诉客户端不要每次发送POST请求时都先发送OPTIONS请求了。

具体设置如下:

location ~ ^/users-management/([a-zA-Z0-9_-]+) {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain charset=UTF-8';
add_header 'Content-Length' 0;
return 204;
}
proxy_set_header X-Forwarded-For $remote_addr;
proxy_set_header X-Forwarded-Host $server_name;
proxy_set_header Host $host;
add_header 'Access-Control-Allow-Origin' $http_origin;
add_header 'Access-Control-Allow-Credentials' 'true';
add_header 'Access-Control-Allow-Methods' 'GET,POST,OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,X-CustomHeader,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type';
proxy_pass http://localhost:8081;
}

设置完成之后,你会发现可以通过axios来正常调用POST请求了。