JavaScript中的同源策略

vegan.qian
发布于: 2021-07-09 11:18:17
同源策略是一个重要的安全策略,它用于限制一个origin的文档或者它加载的脚本如何能与另一个源的资源进行交互。它能帮助阻隔恶意文档,减少可能被攻击的媒介。

1. 概念

两个页面同源需要满足以下三个条件:

  • 协议(protocol)相同: 同为httpshttp
  • 端口相同: 如未指定,http默认为80端口,https默认为443端口
  • 域名相同
以https://www.gogoing.site/index.html为例来说明以下示例是否存在同源限制:

1. https://www.gogoing.site/articles/1.html // 同源
2. http://www.gogoing.site/index.html // 不同源,协议不同
3. https://www.gogoing.site:464/index.html  // 不同源,端口不同
4. https://api.gogoing.site/index.html // 不同源,域名不同

2. 同源策略功能

为了保护网站信息的安全,一个源下的JS脚本无法获取另外一个源下的资源。

  • CookielocalStoragesessionStorage 访问限制
  • DOM获取限制
  • AJAX请求限制(跨域问题)

3. 不受同源限制的标签

linkscriptimgiframe不受同源限制影响,可以加载其他源下的资源。

4. 跨域及解决方案

由于同源策略的限制,一个域下的JS脚本无法向另外一个域发送AJAX请求。

跨域有以前常用解决方案:

  • 通过JSONP跨域
  • 使用postMessage跨域传递消息
  • 跨域资源共享(CORS)
  • Nginx反向代理
  • WebSocket通信(允许跨域通信,但需要服务器允许)

4.1. JSONP

由于script标签不受同源限制影响,从而我们可以通过动态创建script来请求一个带参数的网址实现跨域通信。实现方法如下:

const script = document.createElement('script')

script.type = 'text/javascript'

script.src = `https://www.gogoing.site/api/auth?username=user&callback=authCallback`

document.body.appendChild(script)

function authCallback(data) {
  console.log(data)
}

通过该链接请求服务器返回回来的内容的script脚本,如以下示例内容:

authCallback({code: 200, data: {...}})

浏览器在加载该脚本后将会执行authCallback方法,从而实现跨域。

缺点:该方法只支持GET请求且需要服务器配合

4.2. postMessage

4.2.1 说明

postMessageHTML5 XMLHttpRequest Level 2中的API,且是为数步度可以跨域操作的window属性之一。它可以用于解决以下问题:

  • 页面和其打开的子窗口之间的数据传递
  • 页面与嵌套的iframe之间的消息传递
  • 多窗口(frames)之间消息传递
  • 以上三个场景的跨域数据传递

4.2.2 用法

postMessage(data, targetOrigin)方法接收两个参数:

  • data: 支持任意类型或可复制的对象。但由不部分浏览器只支持字符串,所以最好使用JSON.stringify序列化
  • targetOrigin: 通过窗口的origin属性来指定哪些窗口可以获取到消息,也可以是字符串*(表示任意窗口)或/(指定和当前窗口同源)或一个URI
  • 页面和子窗口之间的通信
// 父窗口中
const win = window.open('https://www.gogoing.site', 'postMessage', 'width=500px&height=500px')

win.postMessage('https://www.gogoing.site', 'message from parent window')

// 子窗口
window.addEventListener('message', e => console.log(e), false)
  • 页面和iframe之间通信
// html
<iframe src="https://www.gogoing.site/index.html" id="iframe"></iframe>

// js
const iframe = document.getElementById('iframe')

// 向iframe发送消息
iframe.contentWindow.postMessage('message',  'https://www.gogoing.site')

// iframe向父页面发送消息
window.parent.postMessage('message from iframe',  'https://www.gogoing.site')
  • 多窗口(frames)之间消息传递,HTML5中已经不支持frameset
<frameset cols="25%,50%,25%">
  <frame src="frame_a.htm" />
  <frame src="frame_b.htm" />
  <frame src="frame_c.htm" />
</frameset>
// 在父页面中通过`window.frames[index]`来访问子frame的window
window.frames[0].postMessage('message',  '/')

// 在子页面中,通过`window.parent`访问父页面的window
window.parent.postMessage('message',  '/')

4.3. 跨域资源共享(CORS)

CORS允许浏览器向跨源服务器发出XMLHttpRequest请求,它的基本思想就是使用自定义的HTTTP请求头让浏览器与服务器进行沟通,从而决定请求或响应是应该成功还是失败。CORS需要浏览器和服务器的同时支持,目前所有浏览器(IE浏览器不低于IE10)都支持该功能。因为,只需在服务器进行相应设置,就可以允许跨域通信。

4.3.1 前端请求配置

在跨域请求时,浏览器会先发送一次预请求(OPTIONS请求,不携带任何数据),在响应中,如果服务器端允许跨域,则发送真实的请求(GET, POST, PUT等)。如果服务器不允许跨域,则会抛出错误且不会发送真实请求。

var xhr = new XMLHttpRequest()

// withCredentials: 是否携带cookie,如果设置为true,服务器端不能将Access-Control-Allow-Origin设置为*
xhr.withCredentials = true

4.3.2 Node.js中配置

// 以express为例,配置如下

res.header('Access-Control-Allow-Origin', 'www.gogoing.site') // 设置允许跨域的域名,* 代表所有域名
res.header('Access-Control-Allow-Credentials', true) // 允许携带cookie,如果为true, Access-Control-Allow-Origin不能设置为*
res.header('Access-Control-Allow-Headers', 'X-Requested-With, CONTENT-TYPE, Authorization') // 允许的请求头
res.header('Access-Control-Allow-Methods', 'PUT,POST,GET,DELETE,OPTIONS') // 允许的请求方法

及时获取更新,了解更多动态,请关注https://www.gogoing.site

如果你觉得这篇文章对你有帮助,欢迎关注微信公众号-前端学堂,更多精彩文章等着你!

评论
fallback