xyhthink

同源策略

​ 1995年,同源政策由 Netscape 公司引入浏览器。目前,所有浏览器都实行这个政策。最初,它的含义是指,A 网页设置的 Cookie,B 网页不能打开,除非这两个网页“同源”。所谓“同源”指的是“三个相同”。即: 协议相同域名相同端口相同

​ 举例来说,http://www.example.com/dir/page.html这个网址,协议是http://,域名是www.example.com,端口是80(默认端口可以省略),它的同源情况如下。

1
2
3
4
5
http://www.example.com/dir2/other.html:同源
http://example.com/dir/other.html:不同源(域名不同)
http://v2.www.example.com/dir/other.html:不同源(域名不同)
http://www.example.com:81/dir/other.html:不同源(端口不同)
https://www.example.com/dir/page.html:不同源(协议不同)

目的

​ 同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。

​ 设想这样一种情况:A 网站是一家银行,用户登录以后,A 网站在用户的机器上设置了一个 Cookie,包含了一些隐私信息(比如存款总额)。用户离开 A 网站以后,又去访问 B 网站,如果没有同源限制,B 网站可以读取 A 网站的 Cookie,那么隐私信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。

限制范围

(1) 无法读取非同源网页的 Cookie、LocalStorage 和 IndexedDB。

(2) 无法接触非同源网页的 DOM。

(3) 无法向非同源地址发送 AJAX 请求(可以发送,但浏览器会拒绝接受响应)。

​ 虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。下面介绍如何规避上面的限制。

AJAX

​ 同源政策规定,AJAX 请求只能发给同源的网址,否则就报错。

解决方案

  1. 架设服务器代理

  2. JSONP

  3. WebSocket

  4. CORS

服务器代理

​ 服务器其实是不受同源策略限制的;

​ 浏览器发送ajax请求 => 本机的nginx服务器 => 代理请求,向其他服务器发起请求并返回数据 => 返回的数据由nginx进行转发,返回浏览器;

nginx服务器的nginx.conf配置如下:

1
2
3
4
5
6
7
http {
server {
location = /自己的代理名称 {
proxy_pass 远程服务器域名;
}
}
}

ajax的url配置如下:

1
2
3
let url = "http://服务器地址/代理名称?接口";
// 例如:
let url = "http://localhost/dt?include_fields=top_comments%2Cis_root%2Csource_link%2Citem%2Cbuyable%2Croot_id%2Cstatus%2Clike_count%2Csender%2Calbum%2Creply_count&filter_id=%E5%AE%B6%E5%B1%85%E7%94%9F%E6%B4%BB&start=24&_=1562809436168";

JSONP

​ JSONP 是服务器与客户端跨源通信的常用方法。

原理
  1. 使用script 标签发送请求,这个标签支持跨域访问

  2. 在script 标签里面给服务器端传递一个 callback回调函数

  3. callback 的值对应到页面一定要定义一个全局函数(为什么是全局?因为服务端接收到callback函数后会返回页面中的script中去找,如果不写在全局作用域中根本找不到)

  4. 服务端返回的是一个函数的调用。调用的时候会把数据作为参数包在这个函数里面。

步骤

​ 第一步,网页添加一个<script>标签,向服务器请求一个脚本,这不受同源政策限制,可以跨域请求。

1
2
<script src="http://api.foo.com?callback=bar"></script>
// callback参数(?callback=bar),用来告诉服务器,客户端的回调函数名称(bar)。

​ 第二步,服务器收到请求后,拼接一个字符串,将 JSON 数据放在函数名里面,作为字符串返回(bar({...}))。

​ 第三步,客户端会将服务器返回的字符串,作为代码解析,因为浏览器认为,这是<script>标签请求的脚本内容。这时,客户端只要定义了bar()函数,就能在该函数体内,拿到服务器返回的 JSON 数据。

缺点

​ JSONP只能解决GET方式的跨域。

前端代码:

1
2
3
4
// 前端的全局函数,等待后端字符串去调用;
function jsonpcallback(res){
// 代码段
}

利用script标签发起请求:

1
<script src="php代码路径">

后端做出响应:

1
2
3
  #数据库查询;
$data = 数据库查询结果;
echo "jsonpcallback($data)"

​ 由于<script>元素请求的脚本,直接作为代码运行。这时,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的 JSON 数据被视为 JavaScript 对象,而不是字符串,因此避免了使用JSON.parse的步骤。

JSONP封装常规版
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
function jsonp( url , cb_fild , callback , data){
var GLOBAL_CB = "foo"; // 目的是让script接收到的代码在解析时候执行这个异步函数
window[GLOBAL_CB] = function(res){
// 这是异步;
callback(res)
}
if(typeof data === "object"){ // 如果存在传输的数据,则拼接字符串
var dataStr = "";
for(var attr in data){
dataStr += (dataStr.length > 0 ? "&" : "") + attr + "=" + data[attr];
}
url += (/\?/.test(url) ? "&" : "?") + dataStr;
}
// 请求发送;
var script = document.createElement("script"); // 创建script元素
script.src = src;
script.onload = function(){ // 加载完之后删除
this.remove();
}
document.body.appendChild(script);
}
Promise版本
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
function jsonp( url , cb_fild , data){
return new Promise( function(resolve , reject){
var GLOBAL_CB = "foo";
window[GLOBAL_CB] = function(res){
// 这是异步;
resolve(res)
}
// 字段名和回调函数名拼接;
url += (/\?/.test(url) ? "&" : "?")+ cb_fild + "=" + GLOBAL_CB;
// 判定是否存在数据;数据拼接;
if(typeof data === "object"){
var dataStr = "";
for(var attr in data){
dataStr += (dataStr.length > 0 ? "&" : "") + attr + "=" + data[attr];
}
url += "&" + dataStr;
}
// 请求发送;
var script = document.createElement("script");
script.src = url;
script.onload = function(){
this.remove();
}
document.body.appendChild(script);
})
}

例如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var url = "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su";
var data = {
json:1 ,
p:3,
// sid:1423_21080_29238_28518_29099_28830_29221_22158,
req:2,
sc:"eb",
csor:0,
_:Date.now(),
wd : ""
}
jsonp( url, "cb" , data) // Promise方法调用
.then(function(res){
// 执行代码段
})
jQuery实现JSONP调用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$.ajax({
type: "get",
async: true,
url: "https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su",
dataType: "jsonp",
data: data,
jsonp: "callback",//传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"foo",//自定义的jsonp回调函数名称,默认为jQuery自动生成的随机函数名,也可以写"?",jQuery会自动为你处理数据
success: function(json){
// 执行代码段
},
error: function(){
// 执行代码段
}
});
补充
  1. ajax和jsonp这两种技术在调用方式上”看起来”很像,目的也一样,都是请求一个url,然后把服务器返回的数据进行处理,因此jQuery和ext等框架都把jsonp作为ajax的一种形式进行了封装。
  2. ajax和jsonp其实本质上是不同的东西。ajax的核心是通过XMLHttpRequest获取非本页内容,而jsonp的核心则是动态添加。

WebSocket

​ WebSocket 是HTML5的一种新的通信协议,使用ws://(非加密)和wss://(加密)作为协议前缀。该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信。

下面是一个例子,浏览器发出的 WebSocket 请求的头信息:

1
2
3
4
5
6
7
8
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://example.com

​ 上面代码中,有一个字段是Origin,表示该请求的请求源(origin),即发自哪个域名。

​ 正是因为有了Origin这个字段,所以 WebSocket 才没有实行同源政策。因为服务器可以根据这个字段,判断是否许可本次通信。如果该域名在白名单内,服务器就会做出如下回应。

1
2
3
4
5
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=
Sec-WebSocket-Protocol: chat

CORS

原理

​ CORS 是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是 W3C 标准,属于跨源 AJAX 请求的根本解决方法。相比 JSONP 只能发GET请求,CORS 允许任何类型的请求,从而克服了 AJAX 只能同源使用的限制。

​ 整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说,CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

​ 因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。

服务器端配置
1
2
3
4
<?php
header("Access-Control-Allow-Origin:http://localhost");
// 代码区...
?>

前端正常的ajax请求即可。

参考文献

 评论


Power by Yuhangxie , 总访问量为 次 。
载入天数...载入时分秒...
京ICP备19024986号