在跨域情况下(源域名与目的域名不一样),浏览器可能会发起一个HTTP OPTIONS
的请求预检(preflighting
)服务端是否支持CORS
。如果服务端允许当前域名(源域名)CORS
,则浏览器会再发起真正的请求。
跨域请求
浏览器将CORS
请求分成两类:简单请求(simple request
)和非简单请求(not-so-simple request
)。只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
HEAD,GET,POST
HTTP
的头信息不超出以下几种字段:Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type
(只限于三个值application/x-www-form-urlencoded
、multipart/form-data
、text/plain
)
凡是不同时满足上面两个条件,就属于非简单请求。
简单请求
对于简单请求,浏览器直接发出CORS
请求。浏览器会自动在头信息(Request Headers
)中,添加一个Origin
字段,来表明本次请求来自哪个域。
- 如果
Origin
不在许可范围内,会报错:
|
|
- 如果
Origin
指定的域名在许可范围内(必须是跨域的),Response Headers
中会多出几个头信息字段:
|
|
这几个字端要么在服务器端直接返回,要么配置在nginx
中,具体实现看下文
withCredentials
属性
CORS
默认不发送cookie
和http
认证,如果要把Cookie
发到服务器,就要指定Access-Control-Allow-Credentials:true;
另外ajax
中也要打开withCredentials
属性
|
|
非简单请求
比如:请求方法是PUT
或DELETE
,或者Content-Type
字段的类型是application/json
结果发现浏览器连续向同一地址请求了两次,而第一次请求什么值也没拿到
- 第一次请求信息
|
|
- 第二次请求信息
|
|
这是因为浏览器发现,这是一个非简单请求,就自动发出一个”预检”请求,要求服务器确认可以这样请求。”预检”请求用的请求方法是OPTIONS
,表示这个请求是用来询问的。”预检”请求之后,浏览器球会进行正常CORS
请求。
案例
背景说明
前端H5通过ajax
调用后端restful api
,并且页面访问域名与api
访问域名(mobile.test.ximalaya.com
)不同,导致访问报错:
出现这种情况的原因如下:
- 本次
ajax
请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS) - 服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址
原因分析
- 前端
ajax
请求代码
|
|
这里请求类型使用post
,数据类型是json
http
请求信息
从request header
信息中可以发现,实际请求类型是OPTIONS
,注意:
Access-Control-Request-Method
:在发出预检请求时带有这个头信息, 告诉服务器在实际请求时会使用的请求方式Access-Control-Request-Headers
:在发出预检请求时带有这个头信息, 告诉服务器在实际请求时会携带的自定义头信息. 如有多个, 可以用逗号分开.Origin
:表明发送请求或者预请求的域
- 失败分析
Status Code:401
这里的http
返回状态码401
(未授权),是因为服务端这边设置了登录验证过滤器,服务端返回结果如下:
- 跨域
|
|
从控制台错误信息中,可以发现浏览器发送的OPTIONS
预检请求不允许访问服务器,也就是不允许跨域访问
- 第一次尝试解决
Nginx
添加如下配置:
|
|
同时用户先登录,保留Cookie
,再次访问目的地址,结果如下:
结果没有任何变化,跨域访问没有成功,服务端还是没有拿到Cookie
信息
- 第二次尝试解决
如果http
请求需要带上Cookie
,必须设置Access-Control-Allow-Credentials
为true
,同时不能设置Access-Control-Allow-Origin
为*
重新配置Nginx
:
|
|
再次访问,但是结果没有任何变化
- 第三次尝试解决
重新观察请求头信息,浏览器发送OPTIONS
请求并没有携带Cookie
,该请求进入服务器后被判断未登录,所以一直返回401
错误,首先需要解决的是处理OPTIONS
请求
重新配置Nginx
:
|
|
再次跨域访问,结果如下:
跨域的问题已经解决,但是由于我的请求头中设置了'Content-Type':'application/json'
,需要服务端支持该类型
- 第三次尝试解决: 终极方案
重新配置Nginx
:
|
|
Access-Control-Allow-Headers
:指明了实际请求中允许携带的首部字段。
结果如下:
到此,跨域问题终于解决了
总结
Ajax
跨域可以用2种方式解决:Nginx
跨域配置,Java
服务端配置
Nginx
跨域配置(推荐)
- 简单跨域请求
如果需要跨域的服务接口提供简单的http请求,可以在Nginx
中配置如下:
|
|
注意:如果http
请求需要带上Cookie
,必须设置Access-Control-Allow-Credentials
为true
,同时不能设置Access-Control-Allow-Origin
为*
跨域发送Cookie
还要求Access-Control-Allow-Origin
不允许使用通配符。 事实上不仅不允许通配符,而且只能指定单一域名:
If the credentials flag is true and the response includes zero or more than one Access-Control-Allow-Credentials header values return fail and terminate this algorithm. –W3C Cross-Origin Resource Sharing
修改如下:
|
|
- 非简单跨域请求
项目中常见的非简单跨域请求一般是:post
请求,以及json
数据;Nginx
配置如下:
|
|
关键的地方在于先判断请求类型如果是OPTIONS
,设置Allow
的响应头,重新处理这次请求。
- 过滤请求域名
如果需要指定一些域名跨域访问服务,可以如下配置:
|
|