在跨域情况下(源域名与目的域名不一样),浏览器可能会发起一个HTTP OPTIONS 的请求预检(preflighting)服务端是否支持CORS。如果服务端允许当前域名(源域名)CORS,则浏览器会再发起真正的请求。
跨域请求
浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。只要同时满足以下两大条件,就属于简单请求。
- 请求方法是以下三种方法之一:
HEAD,GET,POST HTTP的头信息不超出以下几种字段:AcceptAccept-LanguageContent-LanguageLast-Event-IDContent-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的响应头,重新处理这次请求。
- 过滤请求域名
如果需要指定一些域名跨域访问服务,可以如下配置:
|
|