前端跨域方案
共 1260字,需浏览 3分钟
·
2020-12-05 21:19
所谓跨域,顾名思义,跨到了另外的域,域不仅仅指的是不同的域名网站,可能同一个域名不同的端口号也算不同的域。浏览器是有规则的,只要 协议、域名、端口 有任何一个不同,都被当作是不同的域。协议指的是 http,或者 https 等。
跨域概念
一个域下的文档或脚本试图去请求另一个域下的资源。
跨域形式
标签嵌入跨域脚本。语法错误信息只能在同源脚本中捕捉到
标签嵌入 CSS。由于 CSS 的松散的语法规则,CSS 的跨域需要一个设置正确的 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 等攻击。所谓同源是指 协议+域名+端口 三者相同,即便两个不同的域名指向同一个 IP 地址,也非同源。
协议相同
域名相同
端口相同
同源策略目的
为了保证用户信息的安全,防止恶意的网站窃取数据。
同源策略限制范围
Cookie、LocalStorage 和 IndexDB 无法读取
DOM 无法获得
AJAX 请求不能发送
跨域场景
跨域解决方案
jsonp 跨域
document.domain + iframe 跨域
location.hash + iframe 跨域
window.name + iframe 跨域
postMessage 跨域
跨域资源共享(CORS)
Nginx 代理跨域
NodeJS 中间件代理跨域
WebSocket 协议跨域
jsonp 跨域
原生方案
前端代码:
var script = document.createElement('script')
script.type = 'text/javascript'
script.src = 'http://www.sample.com/test?callback=mycallback'
document.head.appendChild(script)
// 回调函数
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();
以上的方式看起来不错,但遗憾的是仅支持 GET 请求。
document.domain + iframe 跨域
这个跨域仅限主域相同,子域不同的跨域应用场景。
两个页面都通过 js 强制设置 document.domain
为主域,就实现了同域。
父窗口代码:
<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() 方法可以安全地实现 Window 对象之间的跨域通信。postMessage()
方法允许来自不同源的脚本采用异步方式进行有限的通信,可以实现跨文本档、多窗口、跨域消息传递。
发送代码:
<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 协议跨域
WebSocket 是 HTML5 的一种新通信协议,它实现了浏览器与服务器之间的双向通讯,属于应用层协议。它基于 TCP 传输协议,并复用 HTTP 的握手通道。由于原生 WebSocket API 使用起来不太方便,我们使用 Socket.IO,而 Socket.IO 是一个完全由 JavaScript 实现、基于 Node.js、支持 WebSocket 的协议用于实时通信、跨平台的开源框架,它包括了客户端的 JavaScript 和服务器端的 Node.js。Socket.IO 除了支持 WebSocket 通讯协议外,还支持许多种轮询(Polling)机制以及其它实时通信方式,并封装成了通用的接口,并且在服务端实现了这些实时机制的相应代码。Socket.IO 实现的 Polling 通信机制包括 Adobe Flash Socket、AJAX 长轮询、AJAX multipart streaming、持久 Iframe、JSONP 轮询等。Socket.IO 能够根据浏览器对通讯机制的支持情况自动地选择最佳的方式来实现网络实时应用。它的设计目的是构建能够在不同浏览器和移动设备上良好运行的实时应用,如实时分析系统、二进制流数据处理应用、在线聊天室、在线客服系统、评论系统、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.value
console.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();