浏览器同源策略以及跨源解决方案

666 阅读5分钟

一、前言

浏览器为什么会有同源策略(same-origin-policy),这里先记录一个原因:避免跨站请求伪造(Cross-site request forgery )。通常缩写为 CSRF 或者 XSRF。

当然,同源策略仅适用于javascript,这意味着某网站可以通过相应的 HTML标签 访问不同来源网站上的图像CSS动态加载脚本等资源。而通常意义上的跨站请求伪造就是利用同源策略不适用于HTML标签的缺陷,这里按下不表。

二、什么是同源策略

同源策略是指在Web浏览器中,允许某个网页脚本访问另一个网页的数据,但前提是这两个网页必须有相同的协议主机名端口号,一旦两个网站满足上述条件,这两个网站就被认定为具有相同来源。下图标红的代表origin,如果相同,则代表同源,否则就表示跨源。

image.png

下表列出哪些URL与URL http://www.example.com/dir/page.html 属于相同来源:

URL结果原因
http://www.example.com/dir/page2.html只有路径不同
http://www.example.com/dir2/other.html只有路径不同
http://username:password@www.example.com/dir2/other.html只有路径不同
http://www.example.com:81/dir/other.html不同端口(若未标明,http:// 默认端口号为80)
https://www.example.com/dir/other.html不同协议(https和http)
http://en.example.com/dir/other.html不同域名
http://example.com/dir/other.html否 不同域名(需要完全匹配)
http://v2.www.example.com/dir/other.html不同域名(需要完全匹配)

三、如果没有同源策略

如果 AJAX(Asynchronous JavaScript and XML")不遵守同源策略,那么发起攻击过程如下: 我们发起的每一次 HTTP 请求都会全额发送 request 地址对应的 cookie,

  1. 那么用户登录了自己的银行网站http://bank.comhttp://bank.com向客户端的cookie中添加用户标识
  2. 用户浏览了恶意页面http://evil.com。 执行了页面中的恶意 AJAX 请求代码
  3. 恶意页面向http://bank.com发起 AJAX 请求,请求的同时携带了http://bank.com对应 cookie
  4. 银行页面从 cookie 中判断用户身份无误,返回请求数据。

四、跨源解决方法:

1. CORS(cross-origin-resource-sharing)跨源资源共享

CORS 是一个系统,由一系列 HTTP 头组成,CORS 给了 Web 服务器这样的权限,即服务器可以选择,允许跨源请求访问到它们的资源。由他们决定浏览器是否阻止前端JS代码获取跨源请求的响应。

服务器在响应头中设置以下值即可控制跨源请求:

HTTP头作用备注
Access-Control-Allow-Origin指示请求的资源能共享给哪些域。服务器设置必填。
Access-Control-Allow-Credentials该响应头只能是true或者不设置!服务器设置必填。注意,当设置了 Access-Control-Allow-Credentials 为 true 时,Access-Control-Allow-Origin 不能为*。比如:在cookie中存取的是用户的登录信息,又不限制客户端的请求来源,他人获取到cookie以后则可随意发起请求,登录该用户账号,损害用户权益
Access-Control-Allow-Methods指定对预请求的响应中,哪些 HTTP 方法允许访问请求的资源。服务器设置必填。
Access-Control-Allow-Headers用在对预请求的响应中,指示实际的请求中可以使用哪些 HTTP 头。
Access-Control-Expose-Headers指示哪些 HTTP 头的名称能在响应中列出。
Access-Control-Max-Age指示预请求的结果能被缓存多久。
Access-Control-Request-Headers用于发起一个预请求,告知服务器正式请求会使用那些 HTTP 头。浏览器发送预检请求时携带
Access-Control-Request-Method用于发起一个预请求,告知服务器正式请求会使用哪一种 HTTP 请求方法。浏览器发送预检请求时携带
Origin指示获取资源的请求是从什么域发起的。

CORS 规定浏览器的请求分为简单请求和预检请求

  1. 简单请求

    同时满足以下条件:

    • 请求方法是 HEADGETPOST 三种方法之一
    • HTTP 的头信息不超出以下几种字段:Accept,Accept-Language,Last-Event-ID,Content-Language
    • Content-Type:只限于三个指定值application/x-www-form-urlencodedmultipart/form-datatext/plain
    • 请求中的任意 XMLHttpRequestUpload 对象均没有注册任何事件监听器
    • 请求中没有使用 ReadableStream 对象
  2. 预检请求 pre-flight request

    • 凡是不同时满足上面两个条件,就属于非简单请求。
    • 在发送正式 HTTP 请求前,浏览器会先发送 OPTIONS 请求,得到返回结果后在决定是否发送正式请求。
  3. 一般来说,浏览器不借助 JS 脚本能发出的请求都属于简单请求,包括 form 表单的 post 请求

2. JSONP 跨源

利用 script 标签的 src 属性不受同源策略限制的特性,可以向服务器发起 get 请求,需要和服务端协商把数据以回调函数参数的形式发送给前端。目前这种方法已经过时。

下面是一个简化版例子:

//服务端
 const responseData = { data: [{ id: 1, name: 'Brian' }, { id: 2, name: 'Peter' }] }
 const responseDataText = JSON.stringify(responseData)
app.get('/josn',(req,res)=>{
  const callback=new Url(req.url,`http://${request.headers.host}`).searchParams('cb')
  res.set('Content-Type','Application/json')
  res.send(`${callback}(${responseDataText})`)
})
//浏览器端
  function sendJsonp(url){
  return new Promise(resolve => {
   let script= document.createElement('script')
    script.src=url
    document.body.append(script)
    //jsonpCallback函数必须挂载在全局对象上
    window['_jsonpCallback']=function(data){
        resolve(data)
    }
  })
}
sendJsonp('//localhost:3000/json?cb=foo').then((data)=>{
  console.log(data) //{ data: [{ id: 1, name: 'Brian' }, { id: 2, name: 'Peter' }] }
})

3. 代理跨源

将跨源请求转发给同域服务器,由服务器转发请求给目标服务器,数据返回后再返回给浏览器。由于服务器之间没同源策略,所以通过服务器代理的方式获取数据。