关于Nginx跨域配置的一些问题
首先说说跨域请求
为什么跨域请求的时候浏览器会报错,拿不到服务器返回的结果?
跨域不是请求发不出去,而是服务端正常返回结果后被浏览器拦截了返回结果。(浏览器为了防止非同源的请求 拿到服务器的返回数据结果)
什么是跨域请求?
凡是请求来源与请求目标的协议(如http和https)、域名(如a.b.com和b.b.com)、端口号不同(如a.com:80和a.com:81)的请求,都是跨域请求。
跨域请求举例:
从http://a.com 请求 https://a.com
从http://a.a.com 请求 http://b.a.com
从http://a.com:80 请求http://a.com:81
都是跨域请求。
跨域请求的类型
1、简单请求
请求方法只能是:HEAD、GET、POST
能主动自定义设置的头信息只能包括:Accept、Accept-Language、Content-Language、Last-Event-ID、Content-Type
其中Content-Type只能限定三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain
2、非简单请求
不能同时满足简单请求的两个条件的都是非简单请求。例如:
请求方法为PUT,DELETE
请求方法为POST,但是Content-Type值是application/json
请求的头信息里面自定义了属性X-Token、Authorization等
这些都是非简单请求。
3、浏览器对简单请求和非简单请求的处理方式
1、对于简单请求:浏览器会在 请求头信息里增加一个Origin字段,表明本次请求的来源,服务器返回的头部信息里需包含 Access-Control-Allow-Origin,并包含Origin值,则浏览器才能正常返回数据,否则出现跨域错误。
2、对于非简单请求:浏览器会分成2步处理:①预检(preflight)请求 + ②简单请求
①:真正请求前增加一次预检(preflight)请求,请求方法是OPTIONS,浏览器进行校验,如果返回状态码是2XX(通常是204)表示验证通过
②:预检请求通过后,浏览器向服务器发送简单请求。
通过和Response里的相对应的内容进行对比,进行预检请求校验,例如Access-Control-Allow-Origin和Origin是否匹配;返回PUT,DELETE,请求的是PUT,则校验通过。
跨域请求容易遇到的问题
对于新手来说,服务器的跨域配置,都是面向谷歌,百度编程,例如nginx跨域配置,网上一大堆,直接拿过来就用了。对于简单请求,一般不会出现什么问题。但是对于非简单请求,就容易踩坑了。因为很多配置对于复杂的需求场景,或者说对于自己的特定的需求场景,并不完整或者说是适合的。
举个例子来说明:
例如我们做API开发的场景,或者前后端分离的场景,token验证登录。
通常简单流程是:
客户端请求登录->
服务器验证用户名密码->
通过后返回一个token->
客户端每次携带token向服务器请求数据->
服务器端校验token决定是否返回数据。
那么我们假设,客户端是通过自定义header的Authorization携带token向服务器请求数据,很明显这是一个非简单请求,那么浏览器首先会向服务器发送一个预检请求(上面说了请求的方法是OPTIONS),那么服务器端在接收到这个OPTIONS请求之后,如果不做判断处理,那么请求直接会被转发到应用里面。因为预检请求是不会携带token的,那么应用校验token的时候,必然就校验失败,返回xxx错误。这时候问题就出现了,浏览器预检请求校验就失败了,从而导致跨域错误。
为了解决这个问题,通常有2个方式
1、正确的配置跨域,判断OPTIONS请求(推荐)
if ($request_method = 'OPTIONS') {
return 204;
}
2、应用里面判断OPTIONS请求(让应用更健壮)
//以PHP的TP框架为例
if($request->isOptions()) {
return json([],204);
}
Nginx正确的跨域配置
server 级
add_header Access-Control-Allow-Credentials true; #是否携带cookie
add_header Access-Control-Allow-Origin $http_origin; #如果配置所有来源会有安全性问题,所以这里可以指定来源,如:xxx.xxx.com
add_header Access-Control-Allow-Methods $http_access_control_request_method; #允许的请求类型
add_header Access-Control-Allow-Headers $http_access_control_request_headers; #允许的header
add_header Access-Control-Max-Age 1728000; #在此时间内不在发送预检请求
#判断请求类型
if ($request_method = 'OPTIONS') {
return 204;
}
location 级(引用优快云博主「懒的去其」的配置)
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Origin $http_origin;
location /file {
if ($request_method = 'OPTIONS') {
add_header Access-Control-Allow-Origin $http_origin;
add_header Access-Control-Allow-Methods $http_access_control_request_method;
add_header Access-Control-Allow-Credentials true;
add_header Access-Control-Allow-Headers $http_access_control_request_headers;
add_header Access-Control-Max-Age 1728000;
return 204;
}
}