本文首发于我的微信公众号《andyqian》,期待你的关注~javascript
前言前端
系统一般都是由单体应用逐渐演化而来,演化成为先后端分离的分布式应用。在享受分布式系统带来的诸多好处之时,随之而来的也有很多新的问题。其中跨域问题就成了第一只拦路虎。今天咱们就来揭露一下这只老虎的真面目!java
什么是跨域?nginx
在解决问题前,咱们首先得先了解什么是跨域?其实咱们能够简单的理解跨域就是跨不一样的”域名”。但这个域名比咱们一般理解中的域名范围更普遍一些。在这里用 “非同源访问” 可能更合适一些。那么同源又是什么呢?json
同源为: 相同协议,相同域名,相同端口。后端
早在1995年,同源政策由 Netscape 公司引入浏览器。目前,全部浏览器都实行这个政策。其引入的目的是为了保证用户信息的安全,防止恶意的网站窃取数据。跨域
例如:http://www.andyqian.com 其中:浏览器
http 为协议,经常使用的有http和https协议。安全
www.andyqian.com为域名。服务器
端口为80。(默认端口省略)。
基于上面的定义。咱们来判断一下下面表格中,哪些属于同源,哪些不属于同源。如下述连接为例:
http://www.andyqian.com
URL | 是否同源 | 缘由 |
---|---|---|
https://www.andyqian.com | 否 | 协议不一样 |
http://tech.andyqian.com | 否 | 域名不一样 |
http://www.andyqian.com:8080 | 否 | 端口 |
http://www.andyqian.com/a/ | 是 | 协议,域名,端口均一致 |
其交互方式,以下图所示:
CORS中的两种请求
经过上面的介绍,咱们已经知道同源的概念,以及跨域是怎么回事。如今咱们继续说说,如何解决跨域问题。其实根据同源政策规定,AJAX请求只能发给同源的网址,不然就报错。在平常中,一般咱们经过代理服务器以及CORS(Cross-Origin Resource Sharing)跨源资源分享来解决。浏览器中一般将CORS分为简单请求以及复杂请求。
简单请求:
简单请求只支持: GET,POST,PUT
请求方法。
除了用户设置的代理请求头外(Content,User-Agent
),仅支持如下请求头:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-type
其中Content-Type也仅支持如下几种类型:
application/x-www-form-urlencoded
multipart/form-data
text/plain
使用简单请求在发送请求时,浏览器一般会在Request Header中自动添加一个名为Origin
的字段,用来表示请求来源。如如下请求信息为例:
Accept: application/json, text/javascript, */*; q=0.01
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8
Connection: keep-alive
Content-Length: 2116
Content-Type: application/x-www-form-urlencoded; charset=UTF-8
Cookie: QC005=7edda8574ca744e800667e65ba85e01d; QP001=1;
Host: apollo.iqiyi.com
Origin: https://www.iqiyi.com
User-Agent: Mozilla/5.0
其中Origin
中的https://www.iqiyi.com
表示请求来源。若是服务端容许该Origin
进行访问。其Response Header返回头信息以下:
Accept-Charset: utf
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Allow-Origin: https://www.iqiyi.com
Connection: keep-alive
Content-Encoding: gzip
Content-Type: application/json;charset=UTF-8
Date: Mon, 16 Jul 2018 15:47:22 GMT
Server: openresty/1.11.2.2
Transfer-Encoding: chunked
Vary: Accept-Encoding
其中以Access-Control-Allow
开头字段均与CORS请求相关。(会在下面章节作详细介绍)。
其交互方式,如图所示:
备注: 为了更好的理解。以上例子为爱奇艺登陆界面抓包(https://apollo.iqiyi.com/validate)接口的实际Request头信息,Response头信息。有兴趣的童鞋也能够本身抓包看看。
复杂请求:不知足简单请求的,均为复杂请求。
例如:请求方法为DELETE
方法。
Content-Type
为 applicatioin/json
或者 application/xml
。
还须要特别注意的是,复杂请求在正式通讯以前,浏览器会自动增长一次Request Mehotd方法为: OPTIONS 的HTTP请求,咱们一般称之为”预检”请求。
主要用来检查当前请求的Origin
以及Header
,Method
是否可以被服务端容许。
如下列请求例子:
Accept: */*
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,en;q=0.
Access-Control-Request-Headers: auhorization,content-type
Access-Control-Request-Method: POST
Connection: keep-alive
Origin: https://www.iqiyi.com
其中:
Access-Control-Request-Headers
:表示这次复杂请求额外携带的请求头信息。上例中分别为:auhorization
和 content-type
。
Access-Control-Request-Method
: 则表示这次复杂请求的方法。上例为: POST方法。
收到预检请求后。服务端会将Access-Control-Request
为前缀的请求头信息进行校验。若是校验经过。则表示服务端容许这次跨域请求。
其返回Response Header信息中则会返回以下信息所示:
Access-Control-Allow-Credentials: true
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: GET, POST, OPTION
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers:authorization
Connection: close
Content-Type: text/plain
则表示服务端容许这次复杂请求。
其交互方式,以下图所示:
CORS中支持全部属性
CORS中服务端能够控制的全部header头属性有:
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers:authorization
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Max-Age: 1024
其中:
Access-Control-Allow-Origin: (必选参数)表示容许接受请求源。若是为*,则表示接受任意域名的请求。若是已知来源,咱们能够设置为特定的值。例如:
Access-Control-Allow-Origin: https://www.andyqian.com
指定特定的请求源后,其余请求源的请求过来后均会被拒绝
Access-Control-Allow-Credentials:(可选参数) 表示服务端是否容许携带cookie至服务端。它的值是一个boolean类型的值。为true时则表示服务端明确接收容许携带cookie信
Access-Control-Expose-Headers: (可选参数) 表示请求中的Response Headers中容许返回的自定义Header。若是有多个。则使用,
符号进行分割。
在CORS请求中,使用XMLHttpRequest对象的getResponseHeader()方法只能拿到如下标准字段:
Cache-Control,Connect,Content-Type,Date,Server,Content-Language,Expires,Last-Modified,Transfer-Encoding,Vary等标准字段。当咱们须要使用自定义header头时。咱们须要在此处进行填写对应的key值。
例如设置为以下:
Access-Control-Expose-Headers: mysign
咱们就能够经过getResponseHeader("mysign")
获取到对应的值。
Access-Control-Allow-Headers : (可选参数) 表示容许的Request中的header信息。使用 *
表示全部请求头信息。
Access-Control-Allow-Methods: (可选参数) 表示容许跨域的请求方法,若是请求方法不在此列表中。则表示该方法不容许跨域。在校验时,会认为是一个不容许的请求而被拦截。
Access-Control-Max-Age : (可选参数) 表示本次预检请求的有效期,单位为秒。在有效期内,再也不发起预检请求。 (建议设置该值,不然每次都会进行一次预检请求)。
如何解决跨域?
经过上面的了解,咱们知道解决跨域问题时,须要在服务端进行配置。如何配置呢?咱们以Nginx为例。一般咱们在Nginx中的nginx.conf文件中对应location路径下添加以下配置便可:
跨域名
Access-Control-Allow-Origin: *;
Access-Control-Allow-Credentials: true;
Access-Control-Allow-Methods: POST,PUT,GET,HEAD,OPTIONS
Access-Control-Allow-Headers: *
Access-Control-Max-Age: 21600
Access-Control-Expose-Headers: sign
上述预检请求的时间有效期为6小时,以及其余对应的值。都可根据实际状况进行修改使用。
对于OPTIONS预检查请求,咱们可使用下述方法进行返回处理:
if ($request_method = OPTIONS ) {
add_header Content-Length 0;
add_header Content-Type text/plain;
return 204;
}
这样作的好处,是在nginx层作出返回结果。也能够看做是对业务层的一种保护。(状态码不必定为204,只要为成功的状态便可,200也行。)
备注: 建议将内容新建为一个后缀为.conf的文件,在使用时导入便可。
最后
上面简单记录了什么是跨域以及如何解决跨域。一般咱们在开发中,上面这个问题也确确实实的存在。但愿在遇到如下问题时,我以为应该能够优先考虑一下是不是跨域在做祟。
后端接口在Postman中访问是正常的,在与前端工程师联调时,死活都不通。甚至在测试环境是正常的。一上线到生产环境时。连后端接口请求都访问不了。 (跨域问题)
后端接口中莫名出现类型转换异常。(OPTIONS方法)
话外音:以前就吃过上面的亏,踩了上面的坑,大家可别再掉下去了。若是已经掉下去了,那就赶忙爬上来。前面还有若干坑等着你呢!(手动微笑)
相关阅读:
《谈谈用户隐私》
《说说Java日志》
《说说Java注释》
扫码关注,一块儿进步
我的博客: http://www.andyqian.com