背景

最近在写项目的时候,发现如果后端没有处理CORS的问题,前端使用Axios请求数据的时候,会直接报Axios Network Error。影响了开发。

解决

一般情况下后端服务都会有CORS middleware。能够直接解决CORS的问题。但是如果每个服务都这样,我觉得麻烦。所以尝试使用Nginx来同样处理。

跨域相关的响应头

  • Access-Control-Allow-Origin: Used to set the source address that allows cross-domain requests (preflight requests and formal requests will be verified when cross-domain)
  • Access-Control-Allow-Headers: Special header information fields allowed to be carried across domains (only verified in preflight requests)
  • Access-Control-Allow-Methods: Cross-domain allowed request methods or HTTP verbs (only in preflight request verification)
  • Access-Control-Allow-Credentials: Whether to allow the use of cookies across domains. If you want to use cookies across domains, you can add this request response header and set the value to true (setting it or not setting it will not affect the sending of the request, but will only affect whether cookies should be carried across domains. , but if set, both preflight requests and formal requests need to be set). However, it is not recommended to use it cross-domain (it has been used in the project, but it is unstable and cannot be carried by some browsers) unless necessary, because there are many alternatives.

如果出现跨域,直接在前端请求会报错

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.

跨域机制会让浏览器发送一个OPTIONS的preflight请求,只有这个请求成功后,后续才会发送正常的请求。

从报错信息种可以看出 preflight 请求种缺少’Access-Control-Allow-Origin’这个header。我们在Nginx中添加上这个header

location / {
  add_header Access-Control-Allow-Origin $http_origin;
}

但是加上后仍然报相同的错误。

参考add_header Directives的文档。只有返回对应的http status code的时候,才会执行add_header指令。如果想直接添加需要添加always

location / {
  add_header Access-Control-Allow-Origin $http_origin always;
}

这次错误不再是之前的错误了,但是出现了新的错误

Access to XMLHttpRequest at 'http://api.admin.collie.io/v1/vocabulary/search?keyword=' from origin 'http://localhost:3000' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: It does not have HTTP ok status.

从错误信息种可以知道preflight请求没有收到HTTP ok status. 所以修改一下配置

location / {
  add_header Access-Control-Allow-Origin $http_origin always;
  if ($request_method = 'OPTIONS') {
    return 204;
  }
}

加上之后,错误又发生变化了。authorization这个header没有添加到Access-Control-Allow-Headers里面。这个是跨域机制保证安全,在preflight请求中收到的Access-Control-Allow-Headers中设置的header才能后续正常的请求中发送给服务器。

ccess to XMLHttpRequest at 'http://api.admin.collie.io/v1/vocabulary/search?keyword=' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field authorization is not allowed by Access-Control-Allow-Headers in preflight response.

继续修改一下配置

location / {
  add_header Access-Control-Allow-Origin $http_origin always;
  if ($request_method = 'OPTIONS') {
    add_header Access-Control-Allow-Headers '*'; #为什么写在if里面而不是接着Access-Control-Allow-Origin往下写?因为这里只有预检请求才会检查  
    return 204;
  }
}

如果这么配置你会发现,有会出现最开始的那个错误

Access to XMLHttpRequest at 'http://api.admin.collie.io/v1/vocabulary/search?keyword=' from origin 'http://localhost:3000' 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.

在Nginx的文档中是这么描述的

There could be several add_header directives. These directives are inherited from the previous configuration level if and only if there are no add_header directives defined on the current level.

所以如果在同一级下有了add_header指令,就不会继承上一级的add_header指令了,也就是说,如果按照上面这样配置Access-Control-Allow-Origin就会没有,出现最开始的这个错误。

后续还是会出现其他错误,基本上就是添加Access-Control-Allow-MethodsAccess-Control-Allow-Headers这些问题就不再继续下去了。

最后就是我最后的Nginx的配置

location / {
  add_header Access-Control-Allow-Origin $http_origin always;
  add_header Access-Control-Allow-Credentials 'true';
  add_header Access-Control-Allow-Methods '*';
  add_header Access-Control-Allow-Headers '*';
  if ($request_method = 'OPTIONS') {
    return 204;
  }
}

参考文档