同源策略:
只允许与本域下的接口进行交互
不同源客户端脚本在没有对方许可情况下不能访问对方的资源
前端跨域策略
1. w3c标准—cors(跨域资源共享)—【需要服务端配合】
浏览器发现是一个跨域的请求后会做一些事情,需要服务端的支持,前端不用做额外的工作
简单请求
过程:
如果是一个简单请求,浏览器自动请求的头信息上会加上“Origin”的字段,表示请求来自哪个源,(协议+域名:端口)服务端可以拿到这个值,判断是否同意这次请求,并返回
1 | // 请求 |
情况一:服务端允许这次跨域请求
返回的信息
1 | // 返回 |
这三个字段的意思是:
1 | Access-Control-Allow-Origin: |
他的值是: 请求时Origin字段的值或者 *
*: 表示允许任意源的请求
1 | ccess-Control-Allow-Credentials: true |
Boolean:
表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器,
再发送cookie的时候还要在Ajax请求中打开withCredentials 属性
1 | var xhr = new XMLHttpRequest(); |
如果要发送Cookie,Access-Control-Allow-Origin就不能设为*
,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且原网页代码中的document.cookie
也无法读取服务器域名下的Cookie。
1 | Access-Control-Expose-Headers: |
CORS请求时,XMLHttpRequest对象的getResponseHeader()
方法只能拿到6个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在Access-Control-Expose-Headers里面指定。上面的例子指定,getResponseHeader('Info')
可以返回Info字段的值.
情况二:服务端拒绝
当然我们为了防止接口被乱调用,需要限制源,对于不允许的源,服务端还是会返回一个正常的HTTP回应,但是不会带上 Access-Control-Allow-Origin
字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequest
的 onerror
回调捕获到。
这种错误无法通过 HTTP 状态码判断,因为回应的状态码有可能是200
非简单请求
对服务器有特殊的要求,请求访求方法:
PUT或DELETE
Content-Type字段的类型是 application/json
过程:
请求过程分为2个部分
1.预检请求
在正式请求之前,增加一次查询,称为预检请求(preflight)
请求方法是 OPTIONS —该请求是用来询问的
询问:
网页当前的域名是否在服务器的许可名单之内,
可以选用的请求方法
可以使用的头信息
具体的请求头:
1 | OPTIONS: /cors HTTP/1.1 |
- Access-Control-Request-Method: put
浏览器可能使用的cors请求的方法
- Access-Control-Request-Headers
指定浏览器CORS请求会额外发送的头信息字段,用 , 分割
返回
1 | HTTP/1.1 200 OK |
- Access-Control-Allow-Methods:
返回服务端支持的跨域请求的方法,不限于浏览器发过去的那些
- Access-Control-Allow-Headers
同上
- Access-Control-Max-Age
预检请求的有效期(m)在期限内不用再发出预检请求
使用cors 的写法就与普通的ajax的请求方式一样
服务端的区别:
1 | res.setHeader('Access-Control-Allow-Origin': '*'); |
cors: ie 11支持
2.jsonp
写法丑陋,兼容性好,只能发送 get请求
客户端
1 | function resolveJosn(result) { |
动态生成一个 script 标签,src 为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是要和服务端约定好的,是为了让服务端拿到回调函数名称。
服务端
在接受到浏览器端 script 的请求之后,从url的query的callbackName获取到回调函数的名字,例子中是resolveJson
。然后动态生成一段javascript片段去给这个函数传入参数执行这个函数。比如
1 | resolveJson({name: 'qiutc'}); |
例子:
客户端代码:
1 | <script> |
服务端代码:
1 | app.get('/getNews',function(req,res) { |
实际上是去加载 http://a.xx.com:8080/getNews?callback=appendHtml,这个文件,然后当作 js 执行
jsonp的形式必须要后端配合才能完成
3.window.postMessage
1 | <div class="main"> |
1 | $('.main input').addEventListener('input', function(){ |
1 | <input id="input" type="text" placeholder="http://b.jrg.com:8080/b.html"> |
1 | $('#input').addEventListener('input', function(){ |
只能是window的方法,场景:
当前页面嵌入 iframe 需要 各个iframe 之间通信。
4.降域
1 | <div class="main"> |
1 | document.querySelector('.main input').addEventListener('input', function(){ |
降域名 降到具有相同域名的部分,两方同时降域
1 | window.parent.document.querySelector('input').value = this.value |
前提: 应该是有相同的部分的域名
5.Node.js跨域
6.nginx跨域
【1】然而nginx是实现跨域的原理是什么呢?
【2】是做代理接口,它去请求实际服务器,在将数据返回给我们吗?
【3】还是说它修改了发送方的header值,让请求的header与目标域名一致?
【4】它具体的运行流程是怎么样的呢?
【1】首先,直接在浏览器地址栏中,输入某接口地址。是不会产生跨域问题的。
只有当在某域名的页面中,由该页面发起的接口请求。才可能会跨域。
nginx就类似于这个浏览器,它接收到外部对它的请求( 注意,nginx只会接收别人对它的请求,而不会拦截浏览器的请求 ),再类似浏览器地址栏一样去请求某个接口。最后将请求到的内容返回回去
【2】是的
【3】不一定会修改,可以进行修改。
【4】前端利用host结合nginx实现跨域的运行流程:
Brower =》 host =》 nginx =》 目标地址
服务器数据 =》 nginx =》 Brower
也就是说,nginx并不是通过监听brower的请求。
而是作为一个服务器,接收外部对本机的请求。
所以是先通过host,让请求指向本机,才会经过nginx。才能进行转发
cors on nginx
1 | # |
nginx 通过转发实现跨域(API 代理转发)
1 | server { |
含义
1 | 监听80端口(Nginx默认启动了80端口),将http://127.0.0.1的所有请求服务转发到127.0.0.1端口为3000; |