6種跨域解決方案

同源策略

瀏覽器有同源策略,協議 域名 端口不一樣,就不能互相訪問資源javascript

實現跨域

  • jsonp
  • cors
  • postMessage
  • Window.name
  • http-proxy
  • Document.domain

JSONP

  1. 本地聲明一個函數
function show(data){
  console.log(data)
}
複製代碼
  1. 後段返回該函數的執行
<script src="https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=lol&cb=show"></script>
// cb就是厚度返回的執行函數名
複製代碼
//後端會返回
show({
    p: false
    q: "lol"
    s: (10) ["lol官網", "lol手遊", "lol轉區", "lol是什麼意思",
    "lol半價吧", "lol春季賽", "lol轉區系統"]
})
複製代碼

封裝一個通用jonsp函數

//使用
jsonp({
  url:'xxx',
  params:{wd:'xxx'},
  cb:'xx'
}).then(res=>console.log(data))
複製代碼
let jsonp = ({url,params,cb})=>{
 return new Promise((resolve,reject)=>{
    //1.建立一個script標籤
   let script = document.createElement('script')
   //3. 將cb掛載到全局中,後端會返回cb(數據),接收數據返回數據便可
   window[cb]=function(data){
     resolve(data)
   //4. 記得將增長到script刪除
     script.remove()
   }
   //2.將params和cb轉化成wd=xxx&cb=xxx
   params = {...params,cb}
   let arr = []
   for(let key in params){
     arr.push(`${key}=${params[key]}`)
   }
   script.src=url+'?'+arr.join('&')
   document.body.appendChild(script)
 })
}
複製代碼

jsonp的缺點

  1. 只能發送get請求,不支持post put delete
  2. 不安全,引用連接的網站若是返回了一段攻擊代碼,可能形成危害,xss攻擊,不支持

node模擬下提供jsonp功能

let express = require('express')
let app = new express()
app.get('/say',function(req,res){
	let {wd,cb}=req.query
	res.end(`${cb}('返回的數據')`)
})
app.listen(3000,function(){console.log('啓動')})
複製代碼

cors

這是一個後端的解決方案,能夠用node來模擬html

image-20200506115345498

啓動兩個服務器,一個3000端口,一個4000端口java

3000端口啓動一個網頁,在網頁中訪問4000端口的資源node

const express = require('express');
let app = express();
//將這個目錄下的資源以靜態資源的方式提供訪問
app.use(express.static(__dirname));
app.listen(3000, function () {
	console.log('3000啓動');
});
複製代碼
// index.html
<script> let xhr = new XMLHttpRequest(); xhr.open('get', 'http://localhost:4000/say'); xhr.onreadystatechange = function () { if(xhr.readyState === 4 && 300 >= xhr.status&&xhr.status >= 200) { console.log(xhr.response); } }; xhr.send(); </script>
複製代碼

瀏覽器輸入:localhost:3000/index.html,會警告跨域問題git

image-20200506115939888

而且端口:4000的服務是有接受到端口3000的請求的,只是返回的內容被瀏覽器屏蔽了github

image-20200506120309199

提示:Access-Control-Allow-Origin沒有容許http://localhost:3000訪問資源,能夠ajax

設置容許訪問的域名

//設置容許訪問的域名的白名單
let whiteList = ['http://localhost:3000'];
app.use(function (req, res, next) {
  //獲取請求的origin
    let origin = req.headers.origin;
    if (whiteList.includes(origin)) {
    //若是在白名單內,則容許他訪問
        res.setHeader('Access-Control-Allow-Origin', origin);
    }
    next();
});
複製代碼

設置容許訪問的請求方式

但這種方式只容許get,post,head這種簡單的請求,若是是put呢express

xhr.open('put','http://localhost:3000/say')
複製代碼
app.put('/say',function(req,res){
  res.end('post請求')
})
複製代碼

image-20200506122225939

瀏覽器報錯,咱們須要後端設置容許put請求json

app.use(function(req,res){
    res.setHeader('Access-Control-Allow-Methods','PUT')//記得大寫
})
複製代碼

設置須要容許訪問的請求頭

若是咱們在請求頭中加點信息呢,後端

xhr.setRequestHeader('name','huzhiwu')
複製代碼

瀏覽器警告說,須要後端容許那個請求頭

image-20200506123535070

但端口4000能接受到這個請求頭

屏幕快照 2020-05-06 下午12.37.02

設置容許的請求頭

res.setHeaders('Access-Control-Allow-Headers','name')
複製代碼

設置max-age

咱們在端口4000的服務中,打印每次請求的method

app.use(function(req,res){
	console.log(req.method)
})
複製代碼

刷新瀏覽器時,會發現。端口4000打印了兩個method

image-20200506125901401

options是瀏覽器在發現跨域時,會先向服務器一個預檢請求,該方法返回容許訪問的methods,若是請求方法不在methods中則報錯,存在的話,就發送真正的請求

這樣子每次發送一個請求,實際發送了兩個請求,是比較耗性能的,若是咱們的方法不常常改變,可讓

瀏覽器在一段時間內預檢一次就夠了

req.setHeader('Access-Control-Max-Age',5)//單位秒
複製代碼

容許跨域攜帶cookie

let xhr = new XMLHttpRequest();
xhr.open('put', 'http://localhost:4000/say');
document.cookie='name=huzhiwu'
xhr.onreadystatechange = function () {
  if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) {
  	console.log(xhr.response);
  }
};
xhr.send();
複製代碼
// 端口4000的服務器
app.use(function(req,res){
  console.log(req.headers)
})
複製代碼

image-20200506131123492

發現沒有咱們攜帶的cookie

在ajax請求中增長

xhr.withCredentials = true;
複製代碼

瀏覽器又報錯了

image-20200506131343155

在node中設置容許跨域攜帶cookie便可

res.serHeader('Access-Control-Allow-Credentials')
複製代碼

容許瀏覽器獲取服務器返回的headers

app.put('/say',function(req,res){
  res.setHeader('name','huzhiwu')
  res.end('put')
})
複製代碼
let xhr = new XMLHttpRequest();
xhr.open('put', 'http://localhost:4000/say');
xhr.onreadystatechange = function () {
	if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) {
		console.log(xhr.getResponseHeader('name'));
	}
};
xhr.send();
複製代碼

瀏覽器又報錯

image-20200506131909812

瀏覽器以爲服務器返回給你的請求頭不安全,不給你獲取

在node中設置

res.setHeader('Access-Control-Expose-Headers','name')
複製代碼

小結

//容許哪源能夠訪問我
res.setHeader('Access-Control-Allow-Origin',origin)
//容許那個方法能夠訪問我
res.setHeader('Access-Control-Allow-Methods','PUT')
//預檢存活時間
res.setHeader('Access-Control-Max-Age',5)//單位秒
//容許請求攜帶的headers
res.setHeader('Access-Control-Allow-Headers','name')
//容許瀏覽器獲取服務器返回的headers
res.setHeader('Access-Control-Expose-Headers','name')
複製代碼

postMessage跨域

頁面跟嵌套在iframe中的頁面通訊

image-20200506140707910

分別開啓a,b兩個服務,端口號分別是3000,4000

//a.js b.js
const express = require('express');
const app = express();
app.use(express.static(__dirname));
app.listen(4000/3000, function () {
	console.log('4000/3000啓動');
});
複製代碼

a頁面放在3000端口,b頁面放在4000端口

在b頁面中引用a頁面

<body>
  <iframe src='http://localhost:3000/a.html' id='aIframe' onload='load()'>
  </iframe>
  <script> function load(){ let frame = document.getElementById('aIframe') //給嵌套在iframe中的頁面的window發送信息 //第二個參數是origin frame.contentWindow.postMessage('你好我是b頁面','http://localhost:3000/a.html') } //b頁面監聽來自a頁面的信息 window.onmessage=function(e){ console.log(e.data) } </script>
</body>
複製代碼

在a頁面中監聽b頁面的信息

window.onmessage=function(e){
  console.log(e.data)
  //收到信息後給b頁面發送信息,
  e.source.postMessage('你好,我是a頁面',e.origin)
}
複製代碼

瀏覽器打開http://localhost:4000/b.html

image-20200506141444334

小結

發送信息

window.postMessage('信息',orign)
複製代碼

接收信息

window.onmessage=function(e){
  console.log(e.data)
}
複製代碼

window.name跨域

image-20200506142309227

window.name默認爲空字符串,咱們能夠在name中存入信息,跨域訪問他

  1. a和b是同域的http://localhost:3000
  2. c是另外一個域的http://localhost:4000
  3. a頁面先iframe引用c,再iframe引用b,
  4. a頁面便可獲取c頁面的window.name

image-20200506142808012

開啓兩個服務器,分別是3000和4000端口

c頁面

<script> window.name='我是c頁面' </sctript> 複製代碼

a頁面

<body>
  <iframe src='http://localhost:4000/c.html' id='frame' onload='load()'>
  </iframe>
  <script> let first=true let frame = document.getElementById('frame') function load(){ //第一次加載完成,當即將frame的src轉到同源下 if(first){ frame.src='http://localhost:3000/b.html' first=false; }else{ //同源下,便可訪問name console.log(frame.contentWindow.name) } } </script>
</body>
複製代碼

image-20200506143706273

http-proxy

當網絡請求跨域時,可使用代理。

服務器訪問服務器沒有跨域問題.因此,咱們的作法是利用中間的代理瀏覽器向目標瀏覽器發請求。

image-20200506160700384

  1. 分別開啓3000端口和4000端口的服務
  2. a頁面放在3000端口中
  3. a頁面發送請求,3000端口的服務器轉發這個請求到4000端口
a.js
const express = require('express');
const { createProxyMiddleware } = require('http-proxy-middleware');
//將請求轉發到另外一個服務器
const apiProxy = createProxyMiddleware('/say', {
	target: 'http://localhost:4000',
});

const app = express();
app.use(express.static(__dirname));
app.use(apiProxy);
app.listen(3000, function () {
	console.log('3000啓動');
});
複製代碼
b.js
const express = require('express');
const app = express();
app.use(express.static(__dirname));
app.get('/say', function (req, res) {
	res.end('我是b服務器的信息');
});
app.listen(4000, function () {
	console.log('4000啓動');
});
複製代碼
<script> let xhr = new XMLHttpRequest(); xhr.open('get', 'http://localhost:3000/say'); xhr.onreadystatechange = function () { if (xhr.readyState === 4 && 300 >= xhr.status && xhr.status >= 200) { console.log(xhr.response); } }; xhr.send(); </script>
複製代碼

image-20200506161055860

http-proxy-middleware的更多參數請看https://github.com/chimurai/http-proxy-middleware

document.domain跨域

舉個例子www.video.baidu.com和www.map.baidu.com是兩個二級域名,

// www.map.baidu.com
<body>
  <script> window.name='huzhiwu' </script>
</body>
// www.video.baidu.com
<body>
  <iframe src='www.map.baidu.com' id='frame' onload='load'>
  </iframe>
  <script> function load(){ let frame = document.getElementById('frame') console.log(frame.contentWindow.name)//跨域訪問不到 } </script>
</body>
複製代碼

由於跨域訪問不到,

但只要在兩個二級域名中加個document.domain='baidu.com'便可互相訪問

代碼

github.com/huzhiwu1/Sa…

結語

做者:胡志武

時間:2020/05/06

若是喜歡的話,請點個贊吧,若是有錯漏處,請指正

相關文章
相關標籤/搜索