同源策略


上次更新时间:3/9/2021, 11:46:55 AM 0

同源策略是一个重要的安全策略,它用于限制一个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 不同

# 限制范围

如果非同源,以下的行为受到限制:

  1. Cookie、LocalStorage 和 IndexDB 无法读取
  2. DOM 无法获得(iframe无法通信)
  3. AJAX 请求不能发送

# 跨域/窗口数据解决方案

使用以下几种方法,可以解决跨域窗口的通信问题。

# 1.document.domain

Cookie 只有同源的网页才能共享。但是浏览器允许通过设置 document.domain 指定一级域名,共享 Cookie。

# 举个栗子:

页面A: http://w1.example.com/a.html 和 页面B: http://w2.example.com/b.html,原本两个页面不可共享Cookie,但可通过以下设置使得它们可以共享。

  1. 在A、B页面设置同样的 document.domain
document.domain = 'example.com';
1

现在,页面 A 通过脚本设置一个 Cookie。页面 B 就可以读到这个 Cookie。

// 页面A
document.cookie = "test1=hello";

// 页面B
document.cookie // "test1=hello"
1
2
3
4
5
  1. 在服务端将 cookie 设置为一级域名

这样二级域名、三级域名下页面不再需要做其他设置就可以读到该 cookie

Set-Cookie: key=value; domain=.example.com; path=/
1

iframe 也是一样,如果两个网页不同源,两个窗口无法通信的,互相获取 Dom 会报错。也可以通过设置 document.domain 的方法来规避。除了 document.domain,也可以使用以下方法实现通信。

# 2.巧用URL的#

修改 URl 后的 # 的值,页面不会重新刷新,可以通过 hashChange 监听,从而获取数据。(vue-router 的hash模式也是利用这个原理)

  1. 父窗口写入信息
var src = originURL + '#' + data;
document.getElementById('myIFrame').src = src;
1
2
  1. 子窗口使用 hashChange 监听改变
window.onhashchange = checkMessage;

function checkMessage() {
  var message = window.location.hash;
  // ...
}
1
2
3
4
5
6

同样,子窗口也可以修改父窗口的 hash 值

parent.location.href= target + "#" + hash;
1

# 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);
1
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);
1
2
3
4
5
6
7
8
9
10
11
12

# 跨域请求解决方案

# 1.CORS

通过设置 CORS,解决跨域请求。CORS 需要浏览器和服务器同时支持,目前,所有浏览器都支持了。所以如果要解决跨域的话,需要在服务端设置Access-Control-Allow-OriginHTTP首部字段介绍-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 {
    # 配置
  }
}
1
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;
	}
}
1
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
    });
};
1
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': ''
          }
        }
    }
  }
};

1
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;

    }
}
1
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>
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

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)
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

参考文章:

上次更新时间: 3/9/2021, 11:46:55 AM