美文网首页
同源政策于跨源通信

同源政策于跨源通信

作者: MajorDong | 来源:发表于2019-08-06 18:03 被阅读0次

    浏览器安全的基石是同源政策(same-origin policy)。

    一、概述

    1.1含义和目的

    1995年,同源政策由Netscape公司引入浏览器。目前,所有浏览器都实行这个政策。

    最初的含义是指,A网页设置的cookie,B网页不能打开,除非这两个网页同源

    同源指的是“三个相同”

    • 协议相同
    • 域名相同
    • 端口相同

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

    如果cookie包含隐私,这些信息就会泄漏。

    cookie往往用来保存用户的登录状态,如果用户没有退出登陆,其他网站就可以冒充用户。

    因为浏览器同时还规定,提交表单不受同源政策的限制。

    “同源政策”是必须的,否则cookie可以共享,互联网就毫无安全可言了。

    1.2限制范围

    目前,如果非同源,共有三种行为受到限制

    • Cookie、LocalStorage 和 IndexDB 无法读取。
    • DOM无法获得
    • AJAX请求不能发送

    二、同源规避方法

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

    除了架设服务器代理(浏览器请求同源服务器,再由后者请求外部服务),由三种方法规避这个限制

    • JSONP
    • WebSocket
    • CORS

    2.1 JSONP

    可跨域HTML元素

    用form可以发送post请求,但是会刷新页面或新开页面。可跨域但拿不到响应

    用a可以发GET请求,但是也会刷新页面或新开页面。可跨域

    用img可以发GET请求,不用放在body标签里,但是只能以css、favicon的形式展示。可跨域

    button.addEventListener('click', (e)=>{
      let image = document.createElement('img')
      image.src = '/pay'
      image.onload = function(){
        alert('成功')
        }
      image.onerror = function(){ // 状态码大于等于 400 则表示失败
            alert('失败')
      }
    })
    

    用script可以发GET请求,要放在body标签里但是只能以脚本的形式运行,完成后清楚script标签,可跨越

    button.addEventListener('click', (e)=>{
        let script = document.createElement('script')
        script.src = '/pay'
        document.body.appendChild(script)
        script.onload = function(e){ // 状态码是 200~299 则表示成功
            e.currentTarget.remove()
        }
        script.onerror = function(e){ // 状态码大于等于 400 则表示失败
            e.currentTarget.remove()
        }
        
        //后端代码
    ...
    if (path === '/pay'){
        let amount = fs.readFileSync('./db', 'utf8')
        amount -= 1
        fs.writeFileSync('./db', amount)
        response.setHeader('Content-Type', 'application/javascript')
        response.write('amount.innerText = ' + amount)
        response.end()
    }
    ...
    

    JSONP是服务器与客服端跨源通信的常用方法,最大特点就是简单实用,老式浏览器全部支持,服务器改造非常小。

    JSOP原理(发源自SRJ-Server Rendered JavaScript)

    网页通过添加一个<script>元素,向服务器请求JSON数据

    这种做法不受同源政策限制,服务器收到请求后,将数据放在一个指定名字的回调函数里传回来。

    1. 动态添加<script>元素。

    2. 向服务器发送请求,该请求的查询字符串有一个callback参数,用来制定回调函数的名字,这对于JSONP是必须的

      window.onload = function () {
        addScriptTag('http://example.com/ip?callback=foo');
      }
      
    3. 服务器收到这个请求后,会将数据放在回调函数的参数位置返回。

    foo.call(undefined,{
      "ip": "8.8.8.8"
    });
    
    1. <script>元素请求的脚本直接作为代码运行,只要浏览器定义了foo函数,该函数就会立即调用。作为参数的JSON数据被视为JavaScript对象,而不是字符串。避免了JSON.parse的使用

    JSON+padding = JSONP

    JSONP的具体实现

    请求方:frank.com的前端页面(浏览器)

    响应方:jack,com的后端(服务器)

    1. 请求方动态创建script标签,src指向响应方,同时传一个一查询参数?callback=xxx

    2. 响应方根据请求方的查询参数?callback=xxx,定制callback函数的名字,这对JSONP是必须的

    • xxx.call('undefined', {请求的数据})

    • xxx.({请求的数据})数据会放在回调函数的参数位置返回

    响应方构造如上响应。

    1. 浏览器接收到响应后,就会执行xxx.call('undefined','请求的数据')

    2. 请求方就获得了想要请求的数据。

    注意:后台都是返回一个符合对象规则的字符串,后台是没有办法返回一个对象给前端的。

    问题:为什么JSONP不支持POST?

    1. 因为JSONP是通过动态创建script标签实现对跨源的跨源的服务器进行通信的。
    2. 动态创建的script标签只能用GET,不支持POST。

    JSONP的具体实现

    button.addEventListener('click',(e)=>{
      let script = document.createElement('script')
      let fucntionName = 'dong' + parseInt(Math.random()*100000,10) // 使用随机数做函数名避免污染全局变量
      window[functionName] = function (result){
        if(result === 'success'){
        amount.innerText = amount.innerText - 1//string转化为number
        }
      }
      script.src = '/pay?callback=' + functionName
      document.body.appendChlid(script)
      script.onload = function(e){ // 状态码是 200~299 则表示成功
            e.currentTarget.remove()
            delete window[functionName] 
        }
      script.onerror = function(e){ // 状态码大于400表示失败
            e.currentTarget.remove()
            delete window[functionName] // 请求完了就干掉这个随机函数
        }
    })
    
    //后端代码
    if(path === '/pay'){r
      fs.writeFlieSync('./db','utf8')
      let callbackName = query.callback
      response.setHeader('Content-type','application/javascript')
      response.write(`${callbackName}.call(undefined,’success‘)
      response.end()
    }
    

    使用jQuery实现JSONP

    $.ajax({//和ajax无关系只是JSONP
      url: "http://dong.com:80/pay",
      dataType: "jsonp",
      success: function(res) {
        if (response === "success"){
          amount.innerText = amount.innerText - 1
        }
      }  
    })
    

    2.2 WebSocket

    websocket是一种通信协议,实用ws:// (非加密) wss:// (加密)作为协议前缀

    该协议不实行同源政策,只要服务器支持,就可以通过它进行跨源通信

    2.3 CORS

    CORS是跨源资源分享(Cross-Origin Resource Sharing)的缩写。它是W3C标准,是跨源AJAX请求的根本解决方法。相比JSONP只能发GET请求,CORS允许任何类型的请求。

    aButton.addEventListener('click',(e)=> { //dongdong前端
      let request = new XMLHttpRequest
      request.open('get',   'http://dongdong.com:8001')//配置。open(method, url, asyns, user, password)后3个参数可选
      request.onreadystatechange = () => {
        if(request.readyState === 4){
          if(request.status >=200 && request.status < 300){
            let string = request.responseText
            // 把符合JSON语法的字符串转换为JavaScript对应的值
            let object = window.JSON.parse(string) 
            console.log(object.note)
          }else if(request.status >= 400){
            console.log('请求失败')
          }
        }
      }
      request.send()
    })
    
    // 后端代码 jack
    }else if(path === '/xxx'){
      response.statusCode = 200
        response.setHeader('Content-Type', 'text/json;charset=utf-8')
        response.setHeader('Access-Control-Allow-Origin', 'http://dongdong.com:8001')//CORS
        response.setHeader('Access-Control-Allow-Origin', 'http://123456.com:8001')//CORS 允许多个域名跨源访问服务器。
        response.wtite(`{
          "note": {
            "to": "dongdong"
            "from": "小瓜太郎"
            "content": "hi"  
          }
        }`)
        response.end()
    }
    

    三、AJAX

    AJAX = async + javascript + and + xml(现在为JSON)

    在AJAX出现以前(SRJ以前只能通过特定的HTML标签发起有限制的HTTP求情)

    用form可以发送post请求,但是会刷新页面或新开页面。

    用a可以发GET请求,但是也会刷新页面或新开页面。

    用img可以发GET请求,不用放在body标签里,但是只能以css、favicon的形式展示。

    用script可以发GET请求,要放在body标签里但是只能以脚本的形式运行,完成后清楚script标签

    有什么方式可以实现以下要求:

    1. GET 、POST、PUT、DELETE请求都行。
    2. 想以什么形式展示就以什么形式展示。

    微软的突破

    以前只能通过特定的HTML标签发起有限制的HTTP求情

    IE5开始在js中引入activeX对象(API),让JS可以直接发起HTTP请求

    随后Mozilla、Safari、Opera跟进,取名XMLHttpRequest纳入W3C规范。

    AJAX

    1. 使用XMLHttpRequest发送请求
    2. 服务器返回XML格式的字符串
    3. JS解析XML,并更新局部页面

    使用XMLHttpRequest

    aButton.addEventListener('click',(e)=> {
      let request = new XMLHttpRequest
      request.open('get','/xxx')//配置。open(method, url, asyns, user, password)后3个参数可选
      request.onreadystatechange = () => {
        if(request.readyState === 4){ //0unset 1opened 2headers_received(send) 3loading 4done 
          if(request.status >=200 && request.status < 300){
            console.log('请求成功')
            console.log(typeof request.responseText)
            console.log(request.responseText)
            let string = request.responseText
            // 把符合JSON语法的字符串转换为JavaScript对应的值
            let object = window.JSON.parse(string)
            console.log(typeof object)
            console.log(object)
            console.log(object.note)
          }else if(request.status >= 400){
            console.log('请求失败')
          }
        }
      }
      request.send()
    })
    
    // 后端代码
    }else if(path === '/xxx'){
      response.statusCode = 200
        response.setHeader('Content-Type', 'text/json;charset=utf-8')
        response.setHeader('Access-Control-Allow-Origin', 'http://frank.com:8001')
        response.wtite(`{
          "note": {
            "to": "dongdong"
            "from": "小瓜太郎"
            "content": "hi"  
          }
        }`)
        response.end()
    }
    

    四、封装AJAX、Promise

    AJAX的所有功能

    • 客户端的JS发起请求(浏览器上的)
    • 服务器端的JS发送响应(Node.js上的
    1. JS可以设置任意请求header

    第一部分 request.open('get','/xxx')

    第二部分 request.setRequestHeader('Content-Type','x-www-form-urlencoded')

    第四部分 request.send('a=1&b=2')//body)

    1. JS可以获取来自服务器端的任意响应header

    第一部分 request.status/request.statusText//200/OK

    第二部分 request.getResponseHeader()/request.getAllResponseHeaders

    第四部分 request.reponseText

    window.jQuery = function(nodeOrSelector){
      let nodes = {}
      nodes.addClass = function(){}
      nodes.html = function(){}
      return nodes
    }
    window.$ = window.jQuery
    
    window.Promise = function(fn){ //Promise的简单原理
      // ...
      return {
        then: function(){}
      }
    }
    
    window.jQuery.ajax = function({url,method,body,headers}){// es6解构赋值
      return new Promise(function(resolve,reject){
        let request = new XMLHttpRequest()
        request.open(method,url)
        for(let key in headers){
          let value = headers[key]
          request.setRequestHeader(key,value)
        }
        request.onreadystatechange = (e) => {
          if(request.readyState === 4){
            if(request.status >= 200 && request.status < 300){
              resolve.call(undefined,request.responseText)
            }else if (request.status >= 400){
              reject.call(nudefined,request)
            }
          }
        }
        request.send(body) //若是POST
      })
    }
    
    aButton.addEventListener('click', (e) => {
      let promise1 = window.jQuery.ajax({
        url:'/xxx',
        method:'get',
        headers: {
          'content-type':'application/x-www-form-urlencoded',
          'frank': '18'
        }
      })
    
      promise1.then( //在不知道回调函数名字的情况下,拿到绑定的参数
        (text) => {console.log(text)},
        (request) => {console.log(request)}
      )  
    })
    // callback call a function back
    

    回调函数

    callback = call a function back

    回调的问题

    每个程序员的回调名不一样。

    Promise解决了这个问题

    function xxx(){
        return new Promise((f1, f2) => {
            doSomething()
            setTimeout(()=>{
                // 成功就调用 f1,失败就调用 f2
            },3000)
        })
    }
    
    xxx().then(success, fail)
    
    // 链式操作
    xxx().then(success, fail).then(success, fail)
    

    ??? 关于then return

    相关文章

      网友评论

          本文标题:同源政策于跨源通信

          本文链接:https://www.haomeiwen.com/subject/pnwwdctx.html