1. 跨域那點(diǎn)事~

        共 10972字,需瀏覽 22分鐘

         ·

        2020-07-12 19:14

        fbfce41b89c1bd7f7ebabe8a2873825c.webp

        作者:寫bug

        來(lái)源:https://segmentfault.com/a/1190000015597029


        引言

        跨域這兩個(gè)字就像一塊狗皮膏藥一樣黏在每一個(gè)前端開發(fā)者身上,無(wú)論你在工作上或者面試中無(wú)可避免會(huì)遇到這個(gè)問題。為了應(yīng)付面試,我每次都隨便背幾個(gè)方案,也不知道為什么要這樣干,反正面完就可以扔了,我想工作上也不會(huì)用到那么多亂七八糟的方案。

        到了真正工作,開發(fā)環(huán)境有webpack-dev-server搞定,上線了服務(wù)端的大佬們也會(huì)配好,配了什么我不管,反正不會(huì)跨域就是了。日子也就這么混過去了,終于有一天,我覺得不能再繼續(xù)這樣混下去了,我一定要徹底搞懂這個(gè)東西!于是就有了這篇文章。

        要掌握跨域,首先要知道為什么會(huì)有跨域這個(gè)問題出現(xiàn)

        確實(shí),我們這種搬磚工人就是為了混口飯吃嘛,好好的調(diào)個(gè)接口告訴我跨域了,這種阻礙我們輕松搬磚的事情真惡心!為什么會(huì)跨域?是誰(shuí)在搞事情?為了找到這個(gè)問題的始作俑者,請(qǐng)點(diǎn)擊瀏覽器的同源策略[1]。

        這么官方的東西真難懂,沒關(guān)系,至少你知道了,因?yàn)闉g覽器的同源策略導(dǎo)致了跨域,就是瀏覽器在搞事情。

        所以,瀏覽器為什么要搞事情?就是不想給好日子我們過?對(duì)于這樣的質(zhì)問,瀏覽器甩鍋道:“同源策略限制了從同一個(gè)源加載的文檔或腳本如何與來(lái)自另一個(gè)源的資源進(jìn)行交互。這是一個(gè)用于隔離潛在惡意文件的重要安全機(jī)制。”

        這么官方的話術(shù)真難懂,沒關(guān)系,至少你知道了,似乎這是個(gè)安全機(jī)制。
        所以,究竟為什么需要這樣的安全機(jī)制?這樣的安全機(jī)制解決了什么問題?別急,讓我們繼續(xù)研究下去。

        沒有同源策略限制的兩大危險(xiǎn)場(chǎng)景

        據(jù)我了解,瀏覽器是從兩個(gè)方面去做這個(gè)同源策略的,一是針對(duì)接口的請(qǐng)求,二是針對(duì)Dom的查詢。試想一下沒有這樣的限制上述兩種動(dòng)作有什么危險(xiǎn)。

        沒有同源策略限制的接口請(qǐng)求

        有一個(gè)小小的東西叫cookie大家應(yīng)該知道,一般用來(lái)處理登錄等場(chǎng)景,目的是讓服務(wù)端知道誰(shuí)發(fā)出的這次請(qǐng)求。

        如果你請(qǐng)求了接口進(jìn)行登錄,服務(wù)端驗(yàn)證通過后會(huì)在響應(yīng)頭加入Set-Cookie字段,然后下次再發(fā)請(qǐng)求的時(shí)候,瀏覽器會(huì)自動(dòng)將cookie附加在HTTP請(qǐng)求的頭字段Cookie中,服務(wù)端就能知道這個(gè)用戶已經(jīng)登錄過了。

        知道這個(gè)之后,我們來(lái)看場(chǎng)景:

        1.你準(zhǔn)備去清空你的購(gòu)物車,于是打開了買買買網(wǎng)站www.maimaimai.com,然后登錄成功,一看,購(gòu)物車東西這么少,不行,還得買多點(diǎn)。

        2.你在看有什么東西買的過程中,你的好基友發(fā)給你一個(gè)鏈接www.nidongde.com,一臉yin笑地跟你說(shuō):“你懂的”,你毫不猶豫打開了。

        3.你饒有興致地瀏覽著www.nidongde.com,誰(shuí)知這個(gè)網(wǎng)站暗地里做了些不可描述的事情!由于沒有同源策略的限制,它向www.maimaimai.com發(fā)起了請(qǐng)求!聰明的你一定想到上面的話“服務(wù)端驗(yàn)證通過后會(huì)在響應(yīng)頭加入Set-Cookie字段,然后下次再發(fā)請(qǐng)求的時(shí)候,瀏覽器會(huì)自動(dòng)將cookie附加在HTTP請(qǐng)求的頭字段Cookie中”,這樣一來(lái),這個(gè)不法網(wǎng)站就相當(dāng)于登錄了你的賬號(hào),可以為所欲為了!如果這不是一個(gè)買買買賬號(hào),而是你的銀行賬號(hào),那……

        這就是傳說(shuō)中的CSRF攻擊淺談CSRF攻擊方式[2]。

        看了這波CSRF攻擊我在想,即使有了同源策略限制,但cookie是明文的,還不是一樣能拿下來(lái)。

        于是我看了一些cookie相關(guān)的文章聊一聊 cookie[3]、Cookie/Session的機(jī)制與安全[4],知道了服務(wù)端可以設(shè)置httpOnly,使得前端無(wú)法操作cookie,如果沒有這樣的設(shè)置,像XSS攻擊就可以去獲取到cookieWeb安全測(cè)試之XSS[5];設(shè)置secure,則保證在https的加密通信中傳輸以防截獲。

        沒有同源策略限制的Dom查詢

        1.有一天你剛睡醒,收到一封郵件,說(shuō)是你的銀行賬號(hào)有風(fēng)險(xiǎn),趕緊點(diǎn)進(jìn)www.yinghang.com改密碼。你嚇尿了,趕緊點(diǎn)進(jìn)去,還是熟悉的銀行登錄界面,你果斷輸入你的賬號(hào)密碼,登錄進(jìn)去看看錢有沒有少了。

        2.睡眼朦朧的你沒看清楚,平時(shí)訪問的銀行網(wǎng)站是www.yinhang.com,而現(xiàn)在訪問的是www.yinghang.com,這個(gè)釣魚網(wǎng)站做了什么呢?

        //?HTML
        "yinhang"?src="www.yinhang.com">iframe>

        //?JS
        //?由于沒有同源策略的限制,釣魚網(wǎng)站可以直接拿到別的網(wǎng)站的Dom
        const?iframe?=?window.frames['yinhang']
        const?node?=?iframe.document.getElementById('你輸入賬號(hào)密碼的Input')
        console.log(`拿到了這個(gè)?${node},我還拿不到你剛剛輸入的賬號(hào)密碼嗎`)

        由此我們知道,同源策略確實(shí)能規(guī)避一些危險(xiǎn),不是說(shuō)有了同源策略就安全,只是說(shuō)同源策略是一種瀏覽器最基本的安全機(jī)制,畢竟能提高一點(diǎn)攻擊的成本。其實(shí)沒有刺不穿的盾,只是攻擊的成本和攻擊成功后獲得的利益成不成正比。

        跨域正確的打開方式

        經(jīng)過對(duì)同源策略的了解,我們應(yīng)該要消除對(duì)瀏覽器的誤解,同源策略是瀏覽器做的一件好事,是用來(lái)防御來(lái)自邪門歪道的攻擊,但總不能為了不讓壞人進(jìn)門而把全部人都拒之門外吧。沒錯(cuò),我們這種正人君子只要打開方式正確,就應(yīng)該可以跨域。

        下面將一個(gè)個(gè)演示正確打開方式,但在此之前,有些準(zhǔn)備工作要做。為了本地演示跨域,我們需要:

        1.隨便跑起一份前端代碼(以下前端是隨便跑起來(lái)的vue),地址是http://localhost:9099。

        2.隨便跑起一份后端代碼(以下后端是隨便跑起來(lái)的node koa2),地址是http://localhost:9971。

        同源策略限制下接口請(qǐng)求的正確打開方式

        1.JSONP在HTML標(biāo)簽里,一些標(biāo)簽比如script、img這樣的獲取資源的標(biāo)簽是沒有跨域限制的,利用這一點(diǎn),我們可以這樣干:

        后端寫個(gè)小接口

        //?處理成功失敗返回格式的工具
        const?{successBody}?=?require('../utli')
        class?CrossDomain?{
        ??static?async?jsonp?(ctx)?{
        ????//?前端傳過來(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

        簡(jiǎn)單版前端


        <html>
        ??<head>
        ????<meta?charset="utf-8">
        ??head>
        ??<body>
        ????<script?type='text/javascript'>
        ??????//?后端返回直接執(zhí)行的方法,相當(dāng)于執(zhí)行這個(gè)方法,由于后端把返回的數(shù)據(jù)放在方法的參數(shù)里,所以這里能拿到res。
        ??????window.jsonpCb?=?function?(res)?{
        ????????console.log(res)
        ??????}
        ????
        script>
        ????<script?src='http://localhost:9871/api/jsonp?msg=helloJsonp&cb=jsonpCb'?type='text/javascript'>script>
        ??body>
        html>

        簡(jiǎn)單封裝一下前端這個(gè)套路

        /**
        ?*?JSONP請(qǐng)求工具
        ?*?@param?url?請(qǐng)求的地址
        ?*?@param?data?請(qǐng)求的參數(shù)
        ?*?@returns?{Promise}
        ?*/

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

        2.空iframe加form
        細(xì)心的朋友可能發(fā)現(xiàn),JSONP只能發(fā)GET請(qǐng)求,因?yàn)楸举|(zhì)上script加載資源就是GET,那么如果要發(fā)POST請(qǐng)求怎么辦呢?

        后端寫個(gè)小接口

        //?處理成功失敗返回格式的工具
        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'
        ??}
        })

        3.CORS

        CORS是一個(gè)W3C標(biāo)準(zhǔn),全稱是"跨域資源共享"(Cross-origin resource sharing)跨域資源共享 CORS 詳解[6]??疵志椭肋@是處理跨域問題的標(biāo)準(zhǔn)做法。CORS有兩種請(qǐng)求,簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求。

        這里引用上面鏈接阮一峰老師的文章說(shuō)明一下簡(jiǎn)單請(qǐng)求和非簡(jiǎn)單請(qǐng)求。
        瀏覽器將CORS請(qǐng)求分成兩類:簡(jiǎn)單請(qǐng)求(simple request)和非簡(jiǎn)單請(qǐng)求(not-so-simple request)。

        只要同時(shí)滿足以下兩大條件,就屬于簡(jiǎn)單請(qǐng)求。
        (1) 請(qǐng)求方法是以下三種方法之一:

        • HEAD
        • GET
        • POST

        (2)HTTP的頭信息不超出以下幾種字段:

        • Accept
        • Accept-Language
        • Content-Language
        • Last-Event-ID
        • Content-Type:只限于三個(gè)值application/x-www-form-urlencoded、multipart/form-data、text/plain

        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

        前端什么也不用干,就是正常發(fā)請(qǐng)求就可以,如果需要帶cookie的話,前后端都要設(shè)置一下,下面那個(gè)非簡(jiǎn)單請(qǐng)求例子會(huì)看到。

        fetch(`http://localhost:9871/api/cors?msg=helloCors`).then(res?=>?{
        ??console.log(res)
        })

        2.非簡(jiǎn)單請(qǐng)求
        非簡(jiǎn)單請(qǐng)求會(huì)發(fā)出一次預(yù)檢測(cè)請(qǐng)求,返回碼是204,預(yù)檢測(cè)通過才會(huì)真正發(fā)出請(qǐng)求,這才返回200。這里通過前端發(fā)請(qǐng)求的時(shí)候增加一個(gè)額外的headers來(lái)觸發(fā)非簡(jiǎn)單請(qǐng)求。

        9ff5132b7770d3197e4546717daa3c74.webp

        后端

        //?處理成功失敗返回格式的工具
        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

        一個(gè)接口就要寫這么多代碼,如果想所有接口都統(tǒng)一處理,有什么更優(yōu)雅的方式呢?見下面的koa2-cors。

        const?path?=?require('path')
        const?Koa?=?require('koa')
        const?koaStatic?=?require('koa-static')
        const?bodyParser?=?require('koa-bodyparser')
        const?router?=?require('./router')
        const?cors?=?require('koa2-cors')
        const?app?=?new?Koa()
        const?port?=?9871
        app.use(bodyParser())
        //?處理靜態(tài)資源?這里是前端build好之后的目錄
        app.use(koaStatic(
        ??path.resolve(__dirname,?'../dist')
        ))
        //?處理cors
        app.use(cors({
        ??origin:?function?(ctx)?{
        ????return?'http://localhost:9099'
        ??},
        ??credentials:?true,
        ??allowMethods:?['GET',?'POST',?'DELETE'],
        ??allowHeaders:?['t',?'Content-Type']
        }))
        //?路由
        app.use(router.routes()).use(router.allowedMethods())
        //?監(jiān)聽端口
        app.listen(9871)
        console.log(`[demo]?start-quick?is?starting?at?port?${port}`)

        前端

        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ā)到真正的后端域名上,不就避免跨域了嗎?這時(shí)候,Nginx出場(chǎng)了。
        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;
        ????}????
        }

        前端就不用干什么事情了,除了寫接口,也沒后端什么事情了

        //?請(qǐng)求的時(shí)候直接用回前端這邊的域名http://localhost:9099,這就不會(huì)跨域,然后Nginx監(jiān)聽到凡是localhost:9099/api這個(gè)樣子的,都轉(zhuǎn)發(fā)到真正的服務(wù)端地址http://localhost:9871?
        fetch('http://localhost:9099/api/iframePost',?{
        ??method:?'POST',
        ??headers:?{
        ????'Accept':?'application/json',
        ????'Content-Type':?'application/json'
        ??},
        ??body:?JSON.stringify({
        ????msg:?'helloIframePost'
        ??})
        })

        Nginx轉(zhuǎn)發(fā)的方式似乎很方便!但這種使用也是看場(chǎng)景的,如果后端接口是一個(gè)公共的API,比如一些公共服務(wù)獲取天氣什么的,前端調(diào)用的時(shí)候總不能讓運(yùn)維去配置一下Nginx,如果兼容性沒問題(IE 10或者以上),CROS才是更通用的做法吧。

        同源策略限制下Dom查詢的正確打開方式

        1.postMessage
        window.postMessage() 是HTML5的一個(gè)接口,專注實(shí)現(xiàn)不同窗口不同頁(yè)面的跨域通訊。
        為了演示方便,我們將hosts改一下:127.0.0.1 crossDomain.com,現(xiàn)在訪問域名crossDomain.com就等于訪問127.0.0.1。

        這里是http://localhost:9099/#/crossDomain,發(fā)消息方

        <template>
        ??<div>
        ????<button?@click="postMessage">給http://crossDomain.com:9099發(fā)消息button>
        ????<iframe?name="crossDomainIframe"?src="http://crossdomain.com:9099">iframe>
        ??div>
        template>

        <script>
        export?default?{
        ??mounted?()?{
        ????window.addEventListener('message',?(e)?=>?{
        ??????//?這里一定要對(duì)來(lái)源做校驗(yàn)
        ??????if?(e.origin?===?'http://crossdomain.com:9099')?{
        ????????//?來(lái)自http://crossdomain.com:9099的結(jié)果回復(fù)
        ????????console.log(e.data)
        ??????}
        ????})
        ??},
        ??methods:?{
        ????//?向http://crossdomain.com:9099發(fā)消息
        ????postMessage?()?{
        ??????const?iframe?=?window.frames['crossDomainIframe']
        ??????iframe.postMessage('我是[http://localhost:9099],?麻煩你查一下你那邊有沒有id為app的Dom',?'http://crossdomain.com:9099')
        ????}
        ??}
        }
        script>

        這里是http://crossdomain.com:9099,接收消息方

        <template>
        ??<div>
        ????我是http://crossdomain.com:9099
        ??div>
        template>

        <script>
        export?default?{
        ??mounted?()?{
        ????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);
        ??????}
        ????})
        ??}
        }
        script>

        結(jié)果可以看到:

        ac21fe6ce6906e6af796bf7de916fa49.webp

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

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

        最后

        希望看完這篇文章之后,再有人問跨域的問題,你可以嘴角微微上揚(yáng),冷笑一聲:“不要再問我跨域的問題了。” ?揚(yáng)長(zhǎng)而去。

        如果學(xué)到了可以點(diǎn)在看讓更多的小伙伴看到哦 。

        參考資料

        [1]

        瀏覽器的同源策略:?https://developer.mozilla.org/zh-CN/docs/Web/Security/Same-origin_policy

        [2]

        淺談CSRF攻擊方式:?http://www.cnblogs.com/hyddd/archive/2009/04/09/1432744.html

        [3]

        聊一聊 cookie:?https://segmentfault.com/a/1190000004556040#articleHeader6

        [4]

        Cookie/Session的機(jī)制與安全:?https://harttle.land/2015/08/10/cookie-session.html

        [5]

        Web安全測(cè)試之XSS:?https://www.cnblogs.com/TankXiao/archive/2012/03/21/2337194.html

        [6]

        跨域資源共享 CORS 詳解:?http://www.ruanyifeng.com/blog/2016/04/cors.html

        [7]

        解決canvas圖片getImageData,toDataURL跨域問題:?https://www.zhangxinxu.com/wordpress/2018/02/crossorigin-canvas-getimagedata-cors/


        - END-?




        文末彩蛋

        掃一掃二維碼回復(fù),"2020",獲得2020年最新前端、后端、大數(shù)據(jù)、人工智能PHP等視頻教程的百度云盤鏈接,獲得之后,記得保存在自己的云盤里哦。



        編程·前端·社區(qū)


        瀏覽 27
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 亚洲老妇性另类 | 丁香花五月色5566资源网 | 女邻居丰满的奶水挤在我脸上 | 欧美乱大交xxxxx喷浆 | 水果派解说AV无码一区 |