前端跨域方案
所謂跨域,顧名思義,跨到了另外的域,域不僅僅指的是不同的域名網(wǎng)站,可能同一個(gè)域名不同的端口號(hào)也算不同的域。瀏覽器是有規(guī)則的,只要?協(xié)議、域名、端口?有任何一個(gè)不同,都被當(dāng)作是不同的域。協(xié)議指的是 http,或者 https 等。
跨域概念
一個(gè)域下的文檔或腳本試圖去請(qǐng)求另一個(gè)域下的資源。
跨域形式
?標(biāo)簽嵌入跨域腳本。語法錯(cuò)誤信息只能在同源腳本中捕捉到
?標(biāo)簽嵌入 CSS。由于 CSS 的松散的語法規(guī)則,CSS 的跨域需要一個(gè)設(shè)置正確的 Content-Type 消息頭。不同瀏覽器有不同的限制:IE、Firefox、Chrome、Safari(跳至CVE-2010-0051)部分和 Opera
?嵌入圖片。支持的圖片格式包括 PNG、JPEG、GIF、BMP、SVG、…
@font-face?引入的字體。一些瀏覽器允許跨域字體(cross-origin fonts),一些需要同源字體(same-origin fonts)
?和?
同源策略
同源策略-SOP(Same Origin Policy)是一種約定,由 Netscape 公司 1995 年引入瀏覽器,它是瀏覽器最核心也最基本的安全功能,如果缺少了同源策略,瀏覽器很容易受到 XSS、CSFR 等攻擊。所謂同源是指?協(xié)議+域名+端口?三者相同,即便兩個(gè)不同的域名指向同一個(gè) IP 地址,也非同源。
協(xié)議相同
域名相同
端口相同
同源策略目的
為了保證用戶信息的安全,防止惡意的網(wǎng)站竊取數(shù)據(jù)。
同源策略限制范圍
Cookie、LocalStorage 和 IndexDB 無法讀取
DOM 無法獲得
AJAX 請(qǐng)求不能發(fā)送
跨域場(chǎng)景

跨域解決方案
jsonp 跨域
document.domain + iframe 跨域
location.hash + iframe 跨域
window.name + iframe 跨域
postMessage 跨域
跨域資源共享(CORS)
Nginx 代理跨域
NodeJS 中間件代理跨域
WebSocket 協(xié)議跨域
jsonp 跨域
原生方案
前端代碼:
var script = document.createElement('script')script.type = 'text/javascript'script.src = 'http://www.sample.com/test?callback=mycallback'document.head.appendChild(script)// 回調(diào)函數(shù)function mycallback(response) {console.log(response)}
后端代碼:
use Psr\Http\Message\RequestInterface as Request;use Psr\Http\Message\ResponseInterface as Response;use Slim\Factory\AppFactory;require __DIR__ . '/../vendor/autoload.php';$app = AppFactory::create();$app->get('/test', static function (Request $request, Response $response, array $args) {$content = ['status' => true, 'message' => 'OK', 'result' => []];$content = json_encode($content);$response->getBody()->write("mycallback({$content})");return $response;});$app->run();
jQuery Ajax 方案
前端代碼:
$.ajax({type: 'get',url: 'http://www.sample.com/test',dataType: 'jsonp',jsonpCallback: "mycallback",data: { hello: 'world' }})function mycallback(response) {console.log(response)}
后端代碼:
use Psr\Http\Message\RequestInterface as Request;use Psr\Http\Message\ResponseInterface as Response;use Slim\Factory\AppFactory;require __DIR__ . '/../vendor/autoload.php';$app = AppFactory::create();$app->get('/test', static function (Request $request, Response $response, array $args) {$params = $request->getQueryParams();$content = ['status' => true, 'message' => 'OK', 'result' => [$params]];$payload = json_encode($content);$response->getBody()->write("mycallback({$payload})");return $response->withHeader('Content-Type', 'application/json')->withStatus(200);});$app->run();
以上的方式看起來不錯(cuò),但遺憾的是僅支持 GET 請(qǐng)求。
document.domain + iframe 跨域
這個(gè)跨域僅限主域相同,子域不同的跨域應(yīng)用場(chǎng)景。
兩個(gè)頁面都通過 js 強(qiáng)制設(shè)置?document.domain?為主域,就實(shí)現(xiàn)了同域。
父窗口代碼:
<iframe src="http://sub.example.com" frameborder="0">iframe><script>document.domain = 'example.com'var content = {status: true,message: 'OK',result: {}}script>
子窗口代碼:
document.domain = 'example.com'console.log(window.parent.content)
location.hash + iframe 跨域
window.name + iframe 跨域
postMessage 跨域
window.postMessage()?方法可以安全地實(shí)現(xiàn) Window 對(duì)象之間的跨域通信。postMessage()?方法允許來自不同源的腳本采用異步方式進(jìn)行有限的通信,可以實(shí)現(xiàn)跨文本檔、多窗口、跨域消息傳遞。
發(fā)送代碼:
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Documenttitle>head><body><div><iframe src="http://www.sample.com/index.html" id="frame" style="display:none;">iframe><div id="message">div>div><script>const domain = 'http://www.sample.com'let iframe = document.getElementById('frame')iframe.onload = (e) => {let data = { message: 'Hello' }iframe.contentWindow.postMessage(JSON.stringify(data), domain)console.group('Mesage request from ' + document.location.href)console.log(data)console.groupEnd('adsfa')}window.addEventListener('message', (e) => {console.group('Mesage response from ' + e.origin)console.log(e.data)console.groupEnd()}, false)script>body>html>
接收代碼:
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Documenttitle>head><body><script>const domain = 'http://www.example.com'window.addEventListener('message', (e) => {if (e.origin !== domain) {return}let data = JSON.parse(e.data)data.status = true;data.message += ' World!'data.result = {}window.parent.postMessage(data, domain);})script>body>html>
WebSocket 協(xié)議跨域
WebSocket?是 HTML5 的一種新通信協(xié)議,它實(shí)現(xiàn)了瀏覽器與服務(wù)器之間的雙向通訊,屬于應(yīng)用層協(xié)議。它基于 TCP 傳輸協(xié)議,并復(fù)用 HTTP 的握手通道。由于原生 WebSocket API 使用起來不太方便,我們使用?Socket.IO,而 Socket.IO 是一個(gè)完全由 JavaScript 實(shí)現(xiàn)、基于 Node.js、支持 WebSocket 的協(xié)議用于實(shí)時(shí)通信、跨平臺(tái)的開源框架,它包括了客戶端的 JavaScript 和服務(wù)器端的 Node.js。Socket.IO 除了支持 WebSocket 通訊協(xié)議外,還支持許多種輪詢(Polling)機(jī)制以及其它實(shí)時(shí)通信方式,并封裝成了通用的接口,并且在服務(wù)端實(shí)現(xiàn)了這些實(shí)時(shí)機(jī)制的相應(yīng)代碼。Socket.IO 實(shí)現(xiàn)的 Polling 通信機(jī)制包括 Adobe Flash Socket、AJAX 長(zhǎng)輪詢、AJAX multipart streaming、持久 Iframe、JSONP 輪詢等。Socket.IO 能夠根據(jù)瀏覽器對(duì)通訊機(jī)制的支持情況自動(dòng)地選擇最佳的方式來實(shí)現(xiàn)網(wǎng)絡(luò)實(shí)時(shí)應(yīng)用。它的設(shè)計(jì)目的是構(gòu)建能夠在不同瀏覽器和移動(dòng)設(shè)備上良好運(yùn)行的實(shí)時(shí)應(yīng)用,如實(shí)時(shí)分析系統(tǒng)、二進(jìn)制流數(shù)據(jù)處理應(yīng)用、在線聊天室、在線客服系統(tǒng)、評(píng)論系統(tǒng)、WebIM 等。
前端代碼:
<html lang="en"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><meta http-equiv="X-UA-Compatible" content="ie=edge"><title>Documenttitle>head><body><div>Input: <input type="text" id="input">div><script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.3.0/socket.io.js">script><script>const socket = io('http://www.sample.com:6001');socket.on('connect', () => {socket.on('message', (message) => {console.group('From server: ')console.log(message)console.groupEnd()})socket.on('disconnect', () => {console.log('Server closed')})})document.getElementById('input').onblur = (e) => {let value = e.target.valueconsole.group('Send message:')console.log(value)console.groupEnd();socket.emit('message', value);}script>body>html>
后端代碼:
use PHPSocketIO\SocketIO;use Workerman\Worker;require __DIR__ . '/../vendor/autoload.php';$socket = new SocketIO(6001);$socket->on('connection', function ($socket) {$socket->on('message', function ($message) use ($socket) {$data = ['status' => true,'message' => $message,'result' => [],];$socket->emit('message', $data);});$socket->on('discount', static function () {});});Worker::runAll();
