跨域

同源策略:

只允许与本域下的接口进行交互

不同源客户端脚本在没有对方许可情况下不能访问对方的资源

前端跨域策略

1. w3c标准—cors(跨域资源共享)—【需要服务端配合】

浏览器发现是一个跨域的请求后会做一些事情,需要服务端的支持,前端不用做额外的工作

简单请求

过程:

如果是一个简单请求,浏览器自动请求的头信息上会加上“Origin”的字段,表示请求来自哪个源,(协议+域名:端口)服务端可以拿到这个值,判断是否同意这次请求,并返回

1
2
3
4
5
6
7
8
// 请求

GET /cors HTTP/1.1
Origin: https://api.qiutc.me
Host: api.alice.com
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

情况一:服务端允许这次跨域请求

返回的信息

1
2
3
4
5
// 返回
Access-Control-Allow-Origin: https://api.qiutc.me
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: Info
Content-Type: text/html; charset=utf-8

这三个字段的意思是:

1
Access-Control-Allow-Origin:

他的值是: 请求时Origin字段的值或者 *

*: 表示允许任意源的请求

1
ccess-Control-Allow-Credentials: true

Boolean:

表示是否允许发送Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,Cookie可以包含在请求中,一起发给服务器,

再发送cookie的时候还要在Ajax请求中打开withCredentials 属性

1
2
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

如果要发送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 字段,浏览器发现这个跨域请求的返回头信息没有该字段,就会抛出一个错误,会被 XMLHttpRequestonerror 回调捕获到。

这种错误无法通过 HTTP 状态码判断,因为回应的状态码有可能是200

非简单请求

对服务器有特殊的要求,请求访求方法:

PUT或DELETE

Content-Type字段的类型是 application/json

过程:

请求过程分为2个部分

1.预检请求

在正式请求之前,增加一次查询,称为预检请求(preflight)

请求方法是 OPTIONS —该请求是用来询问的

询问:

网页当前的域名是否在服务器的许可名单之内,

可以选用的请求方法

可以使用的头信息

具体的请求头:

1
2
3
4
5
6
7
OPTIONS: /cors HTTP/1.1   
//options 表示该请求时用来询问的
Origin: 请求来源
Access-Control-Request-Method: Put
Access-Control-Request-Headers:
Host:
Accept-Lanuage: en-US
  • Access-Control-Request-Method: put

浏览器可能使用的cors请求的方法

  • Access-Control-Request-Headers

指定浏览器CORS请求会额外发送的头信息字段,用 , 分割

返回

1
2
3
4
5
6
7
8
9
10
11
12
HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: https://api.qiutc.me
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain
  • 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
2
3
4
5
6
7
function resolveJosn(result) {
console.log(result.name);
}
var jsonpScript= document.createElement("script");
jsonpScript.type = "text/javascript";
jsonpScript.src = "https://www.qiute.com?callbackName=resolveJson";
document.getElementsByTagName("head")[0].appendChild(jsonpScript);

动态生成一个 script 标签,src 为:请求资源的地址+获取函数的字段名+回调函数名称,这里的获取函数的字段名是要和服务端约定好的,是为了让服务端拿到回调函数名称。

服务端

在接受到浏览器端 script 的请求之后,从url的query的callbackName获取到回调函数的名字,例子中是resolveJson。然后动态生成一段javascript片段去给这个函数传入参数执行这个函数。比如

1
resolveJson({name: 'qiutc'});
例子:

客户端代码:

1
2
3
4
5
6
7
8
9
10
11
<script>
$('.change').addEventListener('click',function (){
var script = document.createElement('script');
script.src = 'http://a.xx.com:8080/getNews?callback=appendHtml';
document.head.appendChild(script);
document.head.removeChild(script);
})
function appendHtml(news) {
console.log(news);
}
</script>

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
app.get('/getNews',function(req,res) {
var news = [
"a:asdhakj",
"b:吉萨大环境宽松",
"c: 'sda'"
];
var data = [];
for(let i = 0;i<3;i++) {
let index = parseInt(Math.random()*news.length);
data.push(news[index]);
news.splice(index,1);
}
var cb = req.query.callback;
if(cb) {
res.send(cb+'('+JSON.stringify(data)+')');
} else {
res.send(data);
}

})

实际上是去加载 http://a.xx.com:8080/getNews?callback=appendHtml,这个文件,然后当作 js 执行

jsonp的形式必须要后端配合才能完成

3.window.postMessage
1
2
3
4
5
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>

<iframe src="http://localhost:8080/b.html" frameborder="0" ></iframe>
1
2
3
4
5
6
7
8
$('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].postMessage(this.value,'*');
})
window.addEventListener('message',function(e) {
$('.main input').value = e.data
console.log(e.data);
});
1
<input id="input" type="text"  placeholder="http://b.jrg.com:8080/b.html">
1
2
3
4
5
6
7
$('#input').addEventListener('input', function(){
window.parent.postMessage(this.value, '*');
})
window.addEventListener('message',function(e) {
$('#input').value = e.data
console.log(e.data);
});

只能是window的方法,场景:

当前页面嵌入 iframe 需要 各个iframe 之间通信。

4.降域
1
2
3
4
5
<div class="main">
<input type="text" placeholder="http://a.jrg.com:8080/a.html">
</div>

<iframe src="http://b.jrg.com:8080/b.html" frameborder="0" ></iframe>
1
2
3
4
5
document.querySelector('.main input').addEventListener('input', function(){
console.log(this.value);
window.frames[0].document.querySelector('input').value = this.value;
})
document.domain = "jrg.com"

降域名 降到具有相同域名的部分,两方同时降域

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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
}

nginx 通过转发实现跨域(API 代理转发)

1
2
3
4
5
6
7
8
9
10
11
12
server {
listen 80;
server_name 127.0.0.1;

location / {
proxy_pass http://127.0.0.1:3000;
}

location ~ /api/ {
proxy_pass http://172.30.1.123:8081;
}
}

含义

1
2
监听80端口(Nginx默认启动了80端口),将http://127.0.0.1的所有请求服务转发到127.0.0.1端口为3000;
将http://127.0.0.1/api/或者http://127.0.0.1/api/getList请求转发到http://172.30.1.123:8081

nginx

反向代理