面試常問(wèn)到的跨域,了解一下马昙?

一桃犬、什么是跨域,為什么會(huì)出現(xiàn)跨域問(wèn)題行楞。

前面兩篇文章cookie了解一下攒暇?,網(wǎng)絡(luò)攻防(xss/csrf/xsrf)了解一下介紹了在網(wǎng)站及網(wǎng)站用戶的安全問(wèn)題。瀏覽器為隔離潛在的惡意文件子房,限制了從同一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互形用,所以,是瀏覽器的基于安全考慮的同源策略導(dǎo)致的跨域证杭。
同源(協(xié)議田度,域名,端口號(hào)三者均相同)解愤,只要有一樣不同則為跨域镇饺。
eg:IE比較特殊。

  1. IE瀏覽器不將端口劃在同源限制內(nèi)送讲,即:協(xié)議域名相同奸笤,只有端口不同惋啃,IE也認(rèn)為是同源的。
  2. 授信范圍(Trust Zones):兩個(gè)相互之間高度互信的域名监右,如公司域名(corporate domains)边灭,不遵守同源策略的限制。

二健盒、如何跨域(允許跨源訪問(wèn))

(1).JSONP

在HTML標(biāo)簽中绒瘦,一些標(biāo)簽比如:script,img這樣的src 是不受同源限制的味榛,可天然跨域椭坚。我們可以利用這一點(diǎn)來(lái)向后端請(qǐng)求數(shù)據(jù)。
思想: 動(dòng)態(tài)創(chuàng)建script標(biāo)簽搏色,將入?yún)⒑突卣{(diào)拼在url后面善茎,利用script標(biāo)簽的src去訪問(wèn),成功后刪除script標(biāo)簽
后端代碼

    // 處理成功失敗返回格式的工具
    const {successBody} = require('../utli')
    class CrossDomain {
      static async jsonp (ctx) {
      // 前端傳過(guò)來(lái)的參數(shù)
      const query = ctx.request.query
      // 設(shè)置一個(gè)cookies
      ctx.cookies.set('tokenId', '1')
      // query.cb是前后端約定的方法名字频轿,其實(shí)就是后端返回一個(gè)直接執(zhí)行的方法給前端垂涯,由于前端是用script標(biāo)簽發(fā)起的請(qǐng)求,所以返回了這個(gè)方法后相當(dāng)于立馬執(zhí)行航邢,并且把要返回的數(shù)據(jù)放在方法的參數(shù)里耕赘。
      ctx.body = `${query.cb}(${JSON.stringify(successBody({msg: query.msg}, 'success'))})`
      }
    }
    module.exports = CrossDomain

前端代碼

    /**
     * JSONP請(qǐng)求工具
     * @param url 請(qǐng)求的地址
     * @param data 請(qǐng)求的參數(shù)
     * @returns {Promise<any>}
   */
    const request = ({url, data}) => {
      return new Promise((resolve, reject) => {
        // 處理傳參成xx=yy&aa=bb的形式
        const handleData = (data) => {
          const keys = Object.keys(data)
          const keysLen = keys.length
          return keys.reduce((pre, cur, index) => {
            const value = data[cur]
            const flag = index !== keysLen - 1 ? '&' : ''
            return `${pre}${cur}=${value}${flag}`
          }, '')
        }
        // 動(dòng)態(tài)創(chuàng)建script標(biāo)簽
       const script = document.createElement('script')
        // 接口返回的數(shù)據(jù)獲取
        window.jsonpCb = (res) => {
          document.body.removeChild(script)
          delete window.jsonpCb
          resolve(res)
        }
        script.src = `${url}?${handleData(data)}&cb=jsonpCb`
        document.body.appendChild(script)
      })
    }
    // 使用方式
    request({
      url: 'http://localhost:9871/api/jsonp',
      data: {
        // 傳參
        msg: 'helloJsonp'
      }
    }).then(res => {
      console.log(res)
    })

特點(diǎn):

  • 只能get請(qǐng)求:請(qǐng)求方式有限制关翎,安全性不高蛮粮,容易被攻擊忠荞,url有長(zhǎng)度限制肿仑,請(qǐng)求的入?yún)⑹芟藁鹧妫唧w各家瀏覽器不同
  • 不能對(duì)請(qǐng)求頭進(jìn)行設(shè)置
  • 需要后端配合

(2). iframe + form

基于jsonp不能發(fā)送post請(qǐng)求兽泄≌哂酰可以考慮在新的iframe中使用from來(lái)提交數(shù)據(jù)
思想:動(dòng)態(tài)創(chuàng)建iframe標(biāo)簽萎攒,結(jié)合form表單提交勒极。設(shè)置form.target = ifrme.name是掰。則form提交時(shí)會(huì)在該名稱的框架內(nèi)打開鏈接。但是form還是要追加到主文檔中辱匿。在iframe的load事件中處理返回的事件键痛。
后端接口代碼

    // 處理成功失敗返回格式的工具
    const {successBody} = require('../utli')
    class CrossDomain {
      static async iframePost (ctx) {
        let postData = ctx.request.body
        console.log(postData)
        ctx.body = successBody({postData: postData}, 'success')
      }
    }
    module.exports = CrossDomain

前端代碼

    const requestPost = ({url, data}) => {
      // 首先創(chuàng)建一個(gè)用來(lái)發(fā)送數(shù)據(jù)的iframe.  
      const iframe = document.createElement('iframe')
      iframe.name = 'iframePost'
      iframe.style.display = 'none'
      document.body.appendChild(iframe)
      const form = document.createElement('form')
      const node = document.createElement('input')
      // 注冊(cè)iframe的load事件處理程序,如果你需要在響應(yīng)返回時(shí)執(zhí)行一些操作的話.
      iframe.addEventListener('load', function () {
        console.log('post success')
      })

      form.action = url
      // 在指定的iframe中執(zhí)行form
      form.target = iframe.name
      form.method = 'post'
      for (let name in data) {
        node.name = name
        node.value = data[name].toString()
        form.appendChild(node.cloneNode())
      }
      // 表單元素需要添加到主文檔中.
      form.style.display = 'none'
      document.body.appendChild(form)
      form.submit()

      // 表單提交后,就可以刪除這個(gè)表單,不影響下次的數(shù)據(jù)發(fā)送.
      document.body.removeChild(form)
    }
    // 使用方式
    requestPost({
      url: 'http://localhost:9871/api/iframePost',
      data: {
        msg: 'helloIframePost'
      }
    })

特點(diǎn):

  • 需要后端配合
  • 通過(guò)iframe的load事件,返回結(jié)果處理不是很清晰

(3). CORS

CORS是一個(gè)W3C標(biāo)準(zhǔn)匾七,全稱是”跨域資源共享”(Cross-origin resource sharing)跨域資源共享 CORS 詳解需要瀏覽器和服務(wù)器同時(shí)支持

  1. 簡(jiǎn)單請(qǐng)求
    后端接口配置

     // 處理成功失敗返回格式的工具
     const {successBody} = require('../utli')
     class CrossDomain {
       static async cors (ctx) {
         const query = ctx.request.query
         // *時(shí)cookie不會(huì)在http請(qǐng)求中帶上
         ctx.set('Access-Control-Allow-Origin', '*')
         ctx.cookies.set('tokenId', '2')
         ctx.body = successBody({msg: query.msg}, 'success')
       }
     }
     module.exports = CrossDomain
    

核心就是'Access-Control-Allow-Origin設(shè)置為 * 表示允許所有的遠(yuǎn)程訪問(wèn)該資源絮短,所以前端就什么也不用配置,直接請(qǐng)求后臺(tái)接口即可昨忆。但是當(dāng)設(shè)置為*的時(shí)候http頭是不會(huì)攜帶cookie的戚丸,所以,如果要攜帶cookie,則前后端都要設(shè)置一下

2.非簡(jiǎn)單請(qǐng)求
后端接口

    // 處理成功失敗返回格式的工具
    const {successBody} = require('../utli')
    class CrossDomain {
      static async cors (ctx) {
        const query = ctx.request.query
        // 如果需要http請(qǐng)求中帶上cookie限府,需要前后端都設(shè)置credentials夺颤,且后端設(shè)置指定的origin
        ctx.set('Access-Control-Allow-Origin', 'http://localhost:9099')
        ctx.set('Access-Control-Allow-Credentials', true)
        // 非簡(jiǎn)單請(qǐng)求的CORS請(qǐng)求,會(huì)在正式通信之前胁勺,增加一次HTTP查詢請(qǐng)求世澜,稱為"預(yù)檢"請(qǐng)求(preflight)
        // 這種情況下除了設(shè)置origin,還需要設(shè)置Access-Control-Request-Method以及Access-Control-Request-Headers
        ctx.set('Access-Control-Request-Method', 'PUT,POST,GET,DELETE,OPTIONS')
        ctx.set('Access-Control-Allow-Headers', 'Origin, X-Requested-With, Content-Type, Accept, t')
        ctx.cookies.set('tokenId', '2')

        ctx.body = successBody({msg: query.msg}, 'success')
      }
    }
    module.exports = CrossDomain

前端請(qǐng)求代碼

    fetch(`http://localhost:9871/api/cors?msg=helloCors`, {
      // 需要帶上cookie
      credentials: 'include',
      // 這里添加額外的headers來(lái)觸發(fā)非簡(jiǎn)單請(qǐng)求
      headers: {
        't': 'extra headers'
      }
    }).then(res => {
      console.log(res)
    })

(4). 代理

想一下署穗,如果我們請(qǐng)求的時(shí)候還是用前端的域名寥裂,然后有個(gè)東西幫我們把這個(gè)請(qǐng)求轉(zhuǎn)發(fā)到真正的后端域名上,不就避免跨域了嗎案疲。
nginx代理

    server{
      # 監(jiān)聽9099端口
      listen 9099;          
      # 域名是localhost
      server_name localhost;
       #凡是localhost:9099/api這個(gè)樣子的封恰,都轉(zhuǎn)發(fā)到真正的服務(wù)端地址http://localhost:9871 
         location ^~ /api {
            proxy_pass http://localhost:9871;
        }    
     }

前端開發(fā)時(shí)的dev-server代理

  webpackConfig.devServer = {
        host: '0.0.0.0', //加上這個(gè)配置才能讓別人訪問(wèn)你的本地服務(wù)器
        contentBase: './dist', //本地服務(wù)器所加載的頁(yè)面所在的目錄
        port: 8888,
        historyApiFallback: true, //不跳轉(zhuǎn)
        inline: true, //實(shí)時(shí)刷新
        //代理到j(luò)son-server的端口,模擬后端接口
        proxy: {
          '/api/*': {
            target: 'http://localhost:8787',
            secure: false,
            changeOrigin: true,
            pathRewrite: {
              '^/api/': '/'
            },
          }
        }
    };

(5). 設(shè)置瀏覽器快捷方式的 --args --disable-web-security --user-data-dir




同源策略限制DOM查詢下的 跨域方式

一褐啡、window.postMessage

postMessage是HTML5的一個(gè)新特性诺舔,專門用來(lái)解決跨域的方法,但是目前es6,es7不支持备畦。postMessage方法允許來(lái)自不同源的腳本低飒,采用異步的方式進(jìn)行通信,可以實(shí)現(xiàn)跨文本檔懂盐,多窗口褥赊,跨域消息傳遞。

  1. 跨域除了客戶端和服務(wù)端請(qǐng)求的接口跨域莉恼,還有:
    1)多窗口之間消息傳遞(newWin = window.open(..));
    2)頁(yè)面與嵌套的iframe消息傳遞
    postMessage跨域不是解決客戶端與服務(wù)端的跨域問(wèn)題拌喉,而是專注解決,客戶端中跨文檔消息傳送通信俐银。

2.向目標(biāo)窗口傳送消息
postMessage(dataStr,origin)方法接受兩個(gè)參數(shù)司光;dataStr是要傳送的消息字符串,origin是目標(biāo)窗口的源(協(xié) 議悉患,域名,端口)榆俺。

  1. 目標(biāo)窗口接收消息
    監(jiān)聽message事件

     window.addEventListener('message', function(messageEvent) {
         var data = messageEvent.data;  // messageEvent: {source, currentTarget, data}
         console.info('message from child:', data);
     }, false); 
    

    接收消息

     window.addEventListener('message', (e) => {
           // 這里一定要對(duì)來(lái)源做校驗(yàn)
           if (e.origin === 'http://localhost:9099') {
             // http://localhost:9099發(fā)來(lái)的信息
             console.log(e.data)
             // e.source可以是回信的對(duì)象售躁,其實(shí)就是http://localhost:9099窗口對(duì)象(window)的引用
             // e.origin可以作為targetOrigin
             e.source.postMessage(`我是[http://crossdomain.com:9099],我知道了兄弟茴晋,這就是你想知道的結(jié)果:${document.getElementById('app') ? '有id為app的Dom' : '沒有id為app的Dom'}`, e.origin);
           }
         })
    

e事件對(duì)象有三個(gè)屬性

  • source:發(fā)送消息的窗口對(duì)象
  • origin:發(fā)送消息窗口的源(協(xié)議+主機(jī)+端口號(hào))
  • data:顧名思義陪捷,是傳遞來(lái)的message

二、document.domain

這種方式只適合主域名相同诺擅,但子域名不同的iframe跨域市袖。
比如主域名是crossdomain.com:9099,子域名是child.crossdomain.com:9099,這種情況下給兩個(gè)頁(yè)面指定一下document.domain即document.domain = crossdomain.com就可以訪問(wèn)各自的window對(duì)象了苍碟。

三酒觅、canvas操作圖片的跨域問(wèn)題

這個(gè)應(yīng)該是一個(gè)比較冷門的跨域問(wèn)題,張大神已經(jīng)寫過(guò)了我就不再班門弄斧了解決canvas圖片getImageData,toDataURL跨域問(wèn)題

參考 https://juejin.im/entry/5b4d4721f265da0f926b78c8

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末微峰,一起剝皮案震驚了整個(gè)濱河市舷丹,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蜓肆,老刑警劉巖颜凯,帶你破解...
    沈念sama閱讀 210,914評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異仗扬,居然都是意外死亡症概,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,935評(píng)論 2 383
  • 文/潘曉璐 我一進(jìn)店門早芭,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)彼城,“玉大人,你說(shuō)我怎么就攤上這事逼友【啵” “怎么了?”我有些...
    開封第一講書人閱讀 156,531評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵帜乞,是天一觀的道長(zhǎng)司抱。 經(jīng)常有香客問(wèn)我,道長(zhǎng)黎烈,這世上最難降的妖魔是什么习柠? 我笑而不...
    開封第一講書人閱讀 56,309評(píng)論 1 282
  • 正文 為了忘掉前任,我火速辦了婚禮照棋,結(jié)果婚禮上资溃,老公的妹妹穿的比我還像新娘。我一直安慰自己烈炭,他們只是感情好溶锭,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,381評(píng)論 5 384
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著符隙,像睡著了一般趴捅。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上霹疫,一...
    開封第一講書人閱讀 49,730評(píng)論 1 289
  • 那天拱绑,我揣著相機(jī)與錄音,去河邊找鬼丽蝎。 笑死猎拨,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播红省,決...
    沈念sama閱讀 38,882評(píng)論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼额各,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了类腮?” 一聲冷哼從身側(cè)響起臊泰,我...
    開封第一講書人閱讀 37,643評(píng)論 0 266
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎蚜枢,沒想到半個(gè)月后缸逃,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,095評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡厂抽,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,448評(píng)論 2 325
  • 正文 我和宋清朗相戀三年需频,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片筷凤。...
    茶點(diǎn)故事閱讀 38,566評(píng)論 1 339
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡昭殉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出藐守,到底是詐尸還是另有隱情挪丢,我是刑警寧澤,帶...
    沈念sama閱讀 34,253評(píng)論 4 328
  • 正文 年R本政府宣布卢厂,位于F島的核電站乾蓬,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏慎恒。R本人自食惡果不足惜任内,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,829評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望融柬。 院中可真熱鬧死嗦,春花似錦、人聲如沸粒氧。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,715評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)外盯。三九已至摘盆,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間门怪,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,945評(píng)論 1 264
  • 我被黑心中介騙來(lái)泰國(guó)打工锅纺, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留掷空,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,248評(píng)論 2 360
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坦弟,于是被迫代替她去往敵國(guó)和親护锤。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,440評(píng)論 2 348

推薦閱讀更多精彩內(nèi)容

  • 原文出處: 寫B(tài)ug 寫下這篇文章后我想酿傍,要不以后就把這種基礎(chǔ)的常見知識(shí)都?xì)w到這個(gè)“不要再問(wèn)我XX的問(wèn)題”烙懦,形成一...
    一個(gè)敲代碼的前端妹子閱讀 1,111評(píng)論 2 8
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本赤炒。它是由瀏覽器的同源策略造成的氯析,是瀏覽器對(duì)JavaScript實(shí)...
    Yaoxue9閱讀 1,288評(píng)論 0 6
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本莺褒。它是由瀏覽器的同源策略造成的掩缓,是瀏覽器對(duì)JavaScript實(shí)...
    HeroXin閱讀 833評(píng)論 0 4
  • 什么是跨域 跨域,是指瀏覽器不能執(zhí)行其他網(wǎng)站的腳本遵岩。它是由瀏覽器的同源策略造成的你辣,是瀏覽器對(duì)JavaScript實(shí)...
    他方l閱讀 1,061評(píng)論 0 2
  • 大家好!今天是2018年1月25日,周四尘执。 霧霾影響大家的健康舍哄,抗霾是每個(gè)人的責(zé)任。每個(gè)人可以減少開車誊锭,減少或不吃...
    抗霾志愿者閱讀 231評(píng)論 0 0