1. Что такое междоменный домен
Политика того же источника ограничивает взаимодействие документов или сценариев, загруженных из одного источника, с ресурсами из другого источника. Это важный механизм безопасности для изоляции потенциально вредоносных файлов. Гомология означает, что протокол, имя домена и номер порта должны быть одинаковыми.
Политика одного и того же источника управляет взаимодействием между разными источниками, например, при использовании XMLHttpRequest илитеги подчиняются политике одного и того же происхождения. Эти взаимодействия обычно делятся на три категории:
- Запись между источниками, как правило, разрешена. Например, ссылки (ссылки), редиректы и отправки форм. Необходимо добавить несколько HTTP-запросовpreflight.
- Встраивание кросс-происхождения обычно разрешено.
- Чтение из разных источников, как правило, не допускается. Но часто разумный доступ для чтения может быть реализован через встроенные ресурсы. Например, вы можете прочитать высоту и ширину встроенного изображения, вызвать методы встроенного скрипта илиavailability of an embedded resource.
Ниже приведены примеры разрешения встраивания ресурсов из разных источников, то есть некоторые примеры тегов, на которые не распространяется политика одного и того же источника:
-
<script src="..."></script>
Теги включают междоменные сценарии. Сообщения об ошибках синтаксиса могут быть обнаружены только в сценариях того же происхождения. -
<link rel="stylesheet" href="...">
Теги встроены в CSS. из-за CSSсвободные правила грамматики, кросс-доменный CSS требует корректного набораContent-Type
Заголовок сообщения. В разных браузерах разные ограничения:IE, Firefox, Chrome, Safariа такжеOpera. -
<img>
Вставить картинки. Поддерживаемые форматы изображений включают PNG, JPEG, GIF, BMP, SVG. -
<video>
а также<audio>
Встраивание мультимедийных ресурсов. -
<object>
,<embed>
а также<applet>
плагин. -
@font-face
импортный шрифт. Некоторые браузеры позволяют использовать шрифты разных источников, некоторые требуют шрифты одного происхождения. -
<frame>
а также<iframe>
загружены любые ресурсы. сайт может использоватьX-Frame-Options
Сообщение должно предотвратить эту форму перекрестного взаимодействия.
2. Междоменные решения
jsonp
Схема, сформированная с использованием тегов script без междоменных ограничений.
// index.html
function jsonp({url, param, cb}){
return new Promise((resolve, reject)=>{
let script = document.createElement('script')
window[cb] = function(data){
resolve(data);
document.body.removeChild(script)
}
params = {...params, cb}
let arrs = [];
for(let key in params){
arrs.push(`${key}=${params[key]}`)
}
script.src = `${url}?${arrs.join('&')}`
document.body.appendChild(script)
})
}
jsonp({
url: 'http://localhost:3000/say',
params: {wd: 'haoxl'},
cb: 'show'
}).then(data=>{
console.log(data)
})
//server.js
let express = require('express')
let app = express()
app.get('/say', function(req, res){
let {wd,cb} = req.query
console.log(wd)
res.end(`${cb}('hello')`)
})
app.listen(3000)
Недостатки: поддерживает только получение запросов, а не отправку, размещение, удаление и т. д.; небезопасно, уязвимо для [xss][18] атак.
cors
Стандарт Cross-Origin Resource Sharing добавляет новый набор полей заголовка HTTP, которые позволяют серверам объявлять, какие исходные сайты имеют разрешение на доступ к тем или иным ресурсам. Кроме того, спецификация требует, чтобы методы HTTP-запроса, которые могут иметь побочные эффекты для данных сервера (в частности,GETкроме HTTP-запросов или с определенными типами MIMEPOSTзапрос), браузер должен сначала использоватьOPTIONSМетод инициирует предварительный запрос, чтобы узнать, разрешает ли сервер междоменный запрос. После того, как сервер подтвердит разрешение, инициируется фактический HTTP-запрос. В ответ на предварительный запрос сервер также может уведомить клиента о том, нужно ли ему передавать идентификационные данные (в том числеCookiesданные, связанные с HTTP-аутентификацией).
<!--index.html-->
<body>
Nice to meet you
</body>
<script>
let xhr = new XMLHttpRequest;
// 强制前端设置必须带上请示头cookie
document.cookie = 'name=haoxl'
xhr.withCredentials = true
xhr.open('GET','http://localhost:4000/getData', true);
// 设置自定义请求头
xhr.setRequestHeader('name','haoxl')
xhr.onreadystatechange = function(){
if(xhr.readyState === 4){
if(xhr.status>=200 && xhr.status < 300 || xhr.status === 304){
console.log(xhr.response);
//获取后台传来的已改变name值的请示头
console.log(xhr.getResponseHeader('name'));
}
}
}
xhr.send()
</script>
// server1.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000)
// server2.js
let express = require('express');
let app = express();
let whiteList = ['http://localhost:3000']
app.use(function(req, res, next){
let origin = req.headers.origin;
if(whiteList.includes(origin)){
//设置那个源可以访问我,参数为 * 时,允许任何人访问,但是不可以和 cookie 凭证的响应头共同使用
res.setHeader('Access-Control-Allow-Origin', origin);
//允许带有name的请求头的可以访问
res.setHeader('Access-Control-Allow-Headers','name');
// 设置哪些请求方法可访问
res.setHeader('Access-Control-Allow-Methods', 'PUT');
// 设置带cookie请求时允许访问
res.setHeader('Access-Control-Allow-Credentials', true);
// 后台改了前端传的name请示头后,再传回去时浏览器会认为不安全,所以要设置下面这个
res.setHeader('Access-Control-Expose-Headers','name');
// 预检的存活时间-options请示
res.setHeader('Access-Control-Max-Age',3)
// 设置当预请求发来请求时,不做任何处理
if(req.method === 'OPTIONS'){
res.end();//OPTIONS请示不做任何处理
}
}
next();
});
app.put('/getData', function(req, res){
console.log(req.headers)
res.setHeader('name','hello');
res.end('hello world');
}
app.get('/getData', function(){
res.end('Nice to meet you')
})
app.use(express.static(__dirname));
app.listen(3000)
postMessage
Скрипты для двух разных страниц могут выполняться только в том случае, если страницы, на которых они выполняются, имеют одинаковый протокол (обычно https), номер порта (443 по умолчанию для https) и хост (модуль двух страниц).
Document.domain
установлено на одно и то же значение), два скрипта могут взаимодействовать друг с другом.window.postMessage()
методы обеспечивают управляемый механизм для обхода этого ограничения и безопасны при правильном использовании.
window.postMessage()
Когда метод вызывается, после выполнения всех скриптов страницы (например, событие, установленное после метода, событие тайм-аута, установленное до, и т. д.) будет отправлено в целевое окно.MessageEvent
Информация.
грамматика:otherWindow.postMessage(message, targetOrigin, [transfer])
;
- otherWindow: относится к целевому окну, то есть к какому окну отправить сообщение, является членом свойства window.frames или окном, созданным методом window.open;
- Свойство message — сообщение для отправки, тип — String, Object (IE8, 9 не поддерживаются);
- targetOrigin: атрибут, указывающий, какие окна могут получать сообщения, его значение может быть строкой "*" (что означает неограниченное количество) или URI.
- Transfer: это строка объектов Transferable, которые передаются одновременно с сообщением. Право собственности на эти объекты будет передано получателю сообщения, а отправитель больше не сохранит право собственности.
Свойства сообщения:
- Свойство данных является первым параметром window.postMessage;
- Атрибут origin представляет текущее состояние вызывающей страницы при вызове метода window.postMessage();
- Свойство source записывает информацию об окне, которое вызывает метод window.postMessage();
Случай: a.html отправляет сообщение b.html
// a.html
<iframe src="http://localhost:4000/b.html" id="frame" onload="load()"></iframe>
<script>
function load(params){
let frame = document.getElementById('frame');
//获取iframe中的窗口,给iframe里嵌入的window发消息
frame.contentWindow.postMessage('hello','http://localhost:4000')
// 接收b.html回过来的消息
window.onmessage = function(e){
console.log(e.data)
}
}
</script>
// b.html
<script>
//监听a.html发来的消息
window.onmessage = function(e){
console.log(e.data)
//给发送源回消息
e.source.postMessage('nice to meet you',e.origin)
}
</script>
window.name
Страница может изменить свой источник из-за определенных ограничений. Скрипты могут
document.domain
В качестве значения устанавливается текущий домен или супердомен текущего домена. Если он установлен как супердомен текущего домена, более короткий домен будет использоваться для последующих проверок источника.
a и b — это http://localhost:3000 в одном домене, а c — это независимый http://localhost:4000. a вводит c через iframe,c поместите значение в window.name, а затем указать его src на b в том же домене, что и a, а затем вынуть значение name в окне, где находится iframe.
// a.html
<iframe src="http://localhost:4000/c.html" onload="load()"></iframe>
<script>
let first = true
function load(){
if(first){
let iframe = document.getElementById('iframe');
// 将a中的iframe再指向b
iframe.src='http://localhost:3000/b.html';
first = false;
}else{
//在b中则可得到c给窗口发的消息
console.log(iframe.contentWindow.name);
}
}
</script>
// c.html
<script>
window.name = 'nice to meet you'
</script>
//server.js
let express = require('express')
let app = express();
app.use(express.static(__dirname));
app.listen(4000);
location.hash
window.location
свойство только для чтения, возвращаетLocation
Объект, содержащий информацию о текущем расположении документа. **window.location : Все буквы должны быть строчными! ** Пока вы назначаете новое значение объекту местоположения, документ будет загружаться с новым URL-адресом, как если бы он вызывался с измененным URL-адресом.window.location.assign()Такой же. Обратите внимание, что такие параметры безопасности, какCORS (обмен ресурсами между источниками), что может ограничить фактическую загрузку новых страниц.
Случай: a и b находятся в одном домене, а c — в отдельном домене. a теперь хочет получить доступ к c: a передает хэш-значение в c через iframe, c получает хэш-значение, а затем создает iframe для передачи значения в b через хэш, а b помещает результат хеширования в хеш-значение a .
// a.html
<iframe src="http://localhost:4000/c.html#iloveyou"></iframe>
<script>
//接收b传来的hash值
window.onhashchange = function(){
console.log(location.hash)
}
</script>
// c.html
//接收a传来的hash值
console.log(location.hash)
//创建一个iframe,把回复的消息传给b
let iframe = document.createElement('iframe');
iframe.src='http://localhost:3000/b.html#idontloveyou';
document.body.appendChild(iframe);
//b.html
<script>
//a.html引的c, c又引的b,所以b.parent.parent即是a
window.parent.parent.location.hash = location.hash
</script>
window.domain
window.domain
: получить/установить исходную доменную часть текущего документа. Случай: Решите связь между доменом первого уровня и доменом второго уровня. При моделировании вам нужно создать два разных доменных имени для тестирования.Откройте C:\Windows\System32\drivers\etc.Найдите файл hosts по этому пути и создайте доменное имя первого уровня и доменное имя второго уровня по адресу дно. Измените его на:
127.0.0.1 www.haoxl.com
127.0.0.1 test.haoxl.com
По умолчанию a.html =www.haoxl.com, b.html = test.haoxl.com
// a.html
<iframe src="http://test.haoxl.com" onload="load()"></iframe>
<script>
function load(){
//告诉页面它的主域名,要与b.html的主域名相同,这样才可在a中访问b的值
document.domain = 'haoxl.com'
function load(){
// 在a页面引入b页面后,直接通过下面方式获取b中的值
console.log(frame.contentWindow.a);
}
}
</script>
// b.html
document.domain = 'haoxl.com'
var a = 'hello world'
websocket
Объект WebSocket предоставляет API для создания и управления подключением WebSocket, а также для отправки и получения данных через это подключение. Это полнодуплексная связь на основе TCP, то есть сервер и клиент могут обмениваться данными в обоих направлениях и разрешать междоменную связь. Основное соглашение имеет
ws://
(незашифрованное) иwss://
(шифрование)
//socket.html
let socket = new WebSocket('ws://localhost:3000');
// 给服务器发消息
socket.onopen = function() {
socket.send('hello server')
}
// 接收服务器回复的消息
socket.onmessage = function(e) {
console.log(e.data)
}
// server.js
let express = require('express');
let app = express();
let WebSocket = require('ws');//npm i ws
// 设置服务器域为3000端口
let wss = new WebSocket.Server({port:3000});
//连接
wss.on('connection', function(ws){
// 接收客户端传来的消息
ws.on('message', function(data){
console.log(data);
// 服务端回复消息
ws.send('hello client')
})
})
Nginx
Nginx (engine x)это высокая производительностьHTTPа такжеобратный проксисервер, а также
IMAP/POP3/SMTP
сервер.
Пример: создайте файл json/a.json в корневом каталоге nginx и поместите в него некоторый контент.
// client.html
let xhr = new XMLHttpRequest;
xhr.open('get', 'http://localhost/a.json', true);
xhr.onreadystatechange = function() {
if(xhr.readyState === 4){
if(xhr.status >= 200 && xhr.status < 300 || xhr.status === 304 ){
console.log(xhr.response);
}
}
}
// server.js
let express = require('express');
let app = express();
app.use(express.static(__dirname));
app.listen(3000);
// nginx.conf
location / {// 代表输入/时默认去打开root目录下的html文件夹
root html;
index index.html index.htm;
}
location ~.*\.json{//代表输入任意.json后去打开json文件夹
root json;
add_header "Access-Control-Allow-Origin" "*";
}
http-proxy-middleware
NodeJS middleware http-proxy-middleware реализует междоменный прокси.Принцип примерно такой же как и у nginx.Пересылка данных реализована через запуск прокси-сервера.Также можно изменить доменное имя в куке в заголовке ответа установив параметр cookieDomainRewrite для реализации текущего домена.Файл cookie записывается для облегчения аутентификации при входе в интерфейс.
- Фреймворк Vue: кросс-доменный с использованием прокси-интерфейса node + webpack + webpack-dev-server. В среде разработки, поскольку служба рендеринга Vue и прокси-служба интерфейса являются серверами webpack-dev-server, страница и прокси-интерфейс больше не являются междоменными, и нет необходимости устанавливать междоменную информацию в заголовке.
module.exports = {
entry: {},
module: {},
...
devServer: {
historyApiFallback: true,
proxy: [{
context: '/login',
target: 'http://www.proxy2.com:8080', // 代理跨域目标接口
changeOrigin: true,
secure: false, // 当代理某些 https 服务报错时用
cookieDomainRewrite: 'www.domain1.com' // 可以为 false,表示不修改
}],
noInfo: true
}
}
- Междоменный фреймворк, отличный от Vue (2 междоменных)
<!-- index.html -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>nginx跨域</title>
</head>
<body>
<script>
var xhr = new XMLHttpRequest();
// 前端开关:浏览器是否读写 cookie
xhr.withCredentials = true;
// 访问 http-proxy-middleware 代理服务器
xhr.open('get', 'http://www.proxy1.com:3000/login?user=admin', true);
xhr.send();
</script>
</body>
</html>
// 中间代理服务器
var express = require("express");
var proxy = require("http-proxy-middleware");
var app = express();
app.use(
"/",
proxy({
// 代理跨域目标接口
target: "http://www.proxy2.com:8080",
changeOrigin: true,
// 修改响应头信息,实现跨域并允许带 cookie
onProxyRes: function(proxyRes, req, res) {
res.header("Access-Control-Allow-Origin", "http://www.proxy1.com");
res.header("Access-Control-Allow-Credentials", "true");
},
// 修改响应信息中的 cookie 域名
cookieDomainRewrite: "www.proxy1.com" // 可以为 false,表示不修改
})
);
app.listen(3000);
// 服务器
var http = require("http");
var server = http.createServer();
var qs = require("querystring");
server.on("request", function(req, res) {
var params = qs.parse(req.url.substring(2));
// 向前台写 cookie
res.writeHead(200, {
"Set-Cookie": "l=a123456;Path=/;Domain=www.proxy2.com;HttpOnly" // HttpOnly:脚本无法读取
});
res.write(JSON.stringify(params));
res.end();
});
server.listen("8080");