同源策略
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介
。
# 同源的定义
协议://地址:端口
三者都相同,才算同源,否则报跨域错误。
举个栗子:
URL1 | URL2 | 结 果 | 原因 |
---|---|---|---|
http://store.com/page.html | http://store.com/demo.html | 同源 | 只有路径不同 |
http://store.com/page.html | https://store.com/demo.html | 失败 | 协议不同 |
http://store.com/page.html | http://store.com:81/demo.html | 失败 | 端口不同 ( http:// 默认端口是80) |
http://store.com/page.html | http://news.com:81/demo.html | 失败 | host 不同 |
# 限制范围
如果非同源,以下的行为受到限制:
- Cookie、LocalStorage 和 IndexDB 无法读取
- DOM 无法获得(iframe无法通信)
- AJAX 请求不能发送
# 跨域/窗口数据解决方案
使用以下几种方法,可以解决跨域窗口的通信问题。
# 1.document.domain
Cookie 只有同源的网页才能共享。但是浏览器允许通过设置 document.domain
指定一级域名,共享 Cookie。
# 举个栗子:
页面A: http://w1.example.com/a.html
和 页面B: http://w2.example.com/b.html
,原本两个页面不可共享Cookie,但可通过以下设置使得它们可以共享。
- 在A、B页面设置同样的 document.domain
document.domain = 'example.com';
现在,页面 A 通过脚本设置一个 Cookie。页面 B 就可以读到这个 Cookie。
// 页面A
document.cookie = "test1=hello";
// 页面B
document.cookie // "test1=hello"
2
3
4
5
- 在服务端将 cookie 设置为一级域名
这样二级域名、三级域名下页面不再需要做其他设置就可以读到该 cookie
Set-Cookie: key=value; domain=.example.com; path=/
iframe 也是一样,如果两个网页不同源,两个窗口无法通信的,互相获取 Dom 会报错。也可以通过设置 document.domain 的方法来规避。除了 document.domain,也可以使用以下方法实现通信。
# 2.巧用URL的#
修改 URl 后的 #
的值,页面不会重新刷新,可以通过 hashChange
监听,从而获取数据。(vue-router 的hash模式也是利用这个原理)
- 父窗口写入信息
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
2
- 子窗口使用
hashChange
监听改变
window.onhashchange = checkMessage;
function checkMessage() {
var message = window.location.hash;
// ...
}
2
3
4
5
6
同样,子窗口也可以修改父窗口的 hash 值
parent.location.href= target + "#" + hash;
# 3.window.postMessage
window.postMessage 是 html5 新增的 api,该方法可以在不同窗口/iframe之间通信。
- 发送:
otherWindow.postMessage(message, targetOrigin, [transfer对象])
; - 接收:
window.addEventListener("message", receiveMsgFunction, false)
;
# 注意
- 任何窗口可以在任何其他窗口访问此方法,在任何时间,无论文档在窗口中的位置,向其发送消息。所以用于接收消息的任何事件监听器必须首先使用 origin 和 source 属性来检查
- postMessage的调用者检测不到postMessage发送时抛出的异常
- 分派事件的origin属性的值不受调用窗口中document.domain的当前值的影响
- 当发送窗口包含 javascript: 或 data: URL时,origin属性的值是加载URL的脚本的
举个栗子
/*
* A窗口的域名是<http://example.com:8080>,以下是A窗口的script标签下的代码:
*/
var popup = window.open(...popup details...);
// 这行语句没有发送信息出去,因为targetOrigin设置不对
popup.postMessage("The user is 'bob' and the password is 'secret'", "https://secure.example.net");
// 添加message到发送队列中去
popup.postMessage("hello there!", "http://example.org");
function receiveMessage(event){
// 安全检测
if (event.origin !== "http://example.org")
return;
}
window.addEventListener("message", receiveMessage, false);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/*
* 弹出页 popup 域名是<http://example.org>,以下是script标签中的代码:
*/
//当 A 页面postMessage被调用后,这个function被addEventListener调用
function receiveMessage(event){
if (event.origin !== "http://example.com:8080")
return;
event.source.postMessage("hi there yourself! the secret response " + "is: rheeeeet!", event.origin);
}
window.addEventListener("message", receiveMessage, false);
2
3
4
5
6
7
8
9
10
11
12
# 跨域请求解决方案
# 1.CORS
通过设置 CORS,解决跨域请求。CORS 需要浏览器和服务器同时支持,目前,所有浏览器都支持了。所以如果要解决跨域的话,需要在服务端设置Access-Control-Allow-Origin
(HTTP首部字段介绍-accept首部)。如:
在 nginx
中配置
server {
listen 80;
server_name localhost;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Headers "token, Content-Type";
location /web1 {
# 配置
}
}
2
3
4
5
6
7
8
9
10
11
在 Java
中配置
public class CommonInterceptor extends HandlerInterceptorAdapter {
private boolean debugFlag;
public CommonInterceptor(boolean debugFlag){
this.debugFlag = debugFlag;
}
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
if( debugFlag ){ // 测试环境支持跨域
response.addHeader("Access-Control-Allow-Origin", "*");
response.addHeader("Access-Control-Allow-Methods", "POST,GET");
response.addHeader("Access-Control-Allow-Credentials", "true");
}
response.addHeader("Access-Control-Allow-Headers", "Content-type,token,mid,openId");
return true;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 2. 代理
除了直接设置 Access-Control-Allow-Origin
,也可以使用 nginx 进行代理,由 nginx 去请求服务,就不会触发浏览器的同源策略了。
前端接口请求加个前缀/apis
export function startGame(params) {
return http({
url: '/apis/doubleElevenPreHigh/clickBegin',
method: 'post',
data: params
});
};
2
3
4
5
6
7
本地开发需要在webpack.config.js
中设置devServer
,它的功能跟下面的 nginx
是一样的。《devServer.proxy》
mmodule.exports = {
//...
devServer: {
proxy: {
'/apis': {
target: 'http://172.25.62.112:81',
changeOrigin: true,
pathRewrite: {
'^/apis': ''
}
}
}
}
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
在配置前端页面的 nginx
,添加对/apis
的转发
server {
listen 3000;
server_name localhost;
charset utf-8;
location /blog {
root /home/blog/dist;
}
location /apis {
proxy_pass http://172.25.245.26;
add_header Access-Control-Allow-Origin *;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header REMOTE-HOST $remote_addr;
proxy_set_header Host $host:$server_port;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
# 3.JSONP
JOSNP 其实用的比较少,但可以了解一下。
# 原理
JOSNP 原理简单的说,就是动态创建<script
>标签,然后利用<script>
的src 不受同源策略约束来跨域获取数据,然后通过回调函数
接收数据。
# 优点
- 可以跨域
- 兼容性好
# 缺点
- 仅支持
get
请求 - 拓展性差,不能解决不同域页面间js调用
- 调用失败不会返回HTTP状态码,排查问题比较困难
- 安全性差
# 举个例子
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>JSONP实现跨域2</title>
</head>
<body>
<div id="mydiv">
<button id="btn">点击</button>
</div>
</body>
<script type="text/javascript">
function handleResponse(response){
// 拿到接口返回的数据
console.log(response);
}
</script>
<script type="text/javascript">
window.onload = function() {
var oBtn = document.getElementById('btn');
oBtn.onclick = function() {
var script = document.createElement("script");
script.src = "https://api.douban.com/v2/book/search?q=javascript&count=1&callback=handleResponse";
document.body.insertBefore(script, document.body.firstChild);
};
};
</script>
</html>
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
JQuery 中有提供对应 Api
// $.ajax
$.ajax({
url:'http://localhost:8080/g',
type:'post',
dataType: "jsonp",
jsonp: "callback",// 传递给请求处理程序或页面的,用以获得jsonp回调函数名的参数名(一般默认为:callback)
jsonpCallback:"showData",// 自定义的jsonp回调函数名称
success: function(data){
console.log(data);
}
});
function showData (result) {
var data = JSON.stringify(result); // json对象转成字符串
console.log(data);
}
// $.getJSON
$.getJSON("https://www.runoob.com/try/ajax/jsonp.php?jsoncallback=?", function(data) {
console.log('响应数据', data)
});
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
参考文章: