Введение
Для Четузаи,перекрестный доменЭто очень знакомый термин. Хотя браузеры беспокоятся о безопасности нашего веб-сайта, нам часто приходится обходить это ограничение, чтобы пользователи могли нормально обращаться к веб-сайту.cors
Это одно из часто используемых решений для решения междоменных задач.
установивAccess-Control-Allow-Credentials: true
а такжеxhr.withCredentials = true
, который может реализовать междоменную передачуCookie
, для достижения цели сохранения статуса входа пользователя. Эта схема хороша, но при неправильном использовании будетCSRFриски. Итак, изChrome 51
запуск, браузерCookie
добавил новыйSameSite
имущество для предотвращенияCSRF
Атака и отслеживание пользователей.
Характеристики тока отключены по умолчанию, нонекоторые пользователибыл затронут заранее. Если вы не можете использовать стороннюю функцию входа на некоторые веб-сайты, проверьте, не влияет ли на нее этот параметр.
испытай это сам
инвалидSameSite
проверять
Хотя официальное заявление отChrome 79
Пуск, эта функция будет включена по умолчанию (ранее утверждалось, что изChrome 80
В начале была изменена последняя инструкция), но после тестирования выяснилось, что у некоторых пользователей эта функция по-прежнему отключена по умолчанию, поэтому сначала отключаем ееSameSite
Подтвердите и посмотрите, что произойдет.
ОткрытьChrome
установить, будетchrome://flags/#same-site-by-default-cookies
Сначала отключите, а затем перезапустите браузер.
Используйте последний示例代码
, имитируя операцию входа в систему локально для получения междоменногоCookie
, а потом нестиCookie
Получить информацию о пользователе.
Можно обнаружить, что мы можем использовать запись на стороне сервера в обычном режиме.Cookie
отправить запрос и получить информацию о пользователе, но будетConsole
увидеть предупреждающее сообщение.
Согласно последовательному принципу программиста «предупредил и проигнорировал», кажется, что мы можем игнорировать эту функцию. Но однаждыChrome
Браузер полностью открытSameSite
функция, и пользователь обновляет браузер, а затем на основеCookie
Веб-сайты с междоменным входом не смогут войти в систему. Далее моделируем этот процесс.
включитьSameSite
проверять
также открытьChrome
установить, будетchrome://flags/#same-site-by-default-cookies
Включите, затем перезапустите браузер.
пустойCookie
и войдите снова. Уведомление:Cookie
Он находится под внутренним доменным именем, не очищайте соответствующее внешнее доменное имя.Cookie
.
В это время мы можем найти: запрошенныйResponse Cookies
Вниз,SameSite
Свойство имеет подсказку, которая говорит намSameSite
Свойство не задано, будет использоваться значение по умолчаниюLax
.
В настоящее время, если вы попытаетесь получить информацию о пользователе, вы не сможете получить ее успешно, потому чтоCookie
Не доводится до серверной службы вместе с запросом. При осмотре было установлено, чтоCookie
Не удалось записать в браузер пользователя.
Видно, что если вы не хотите потерпеть неудачу в 2020 году, то начинать заниматься этой проблемой нужно заранее.
иметь дело сSameSite
проверять
SameSite
значение свойства по умолчаниюLax
Только разрешеноget
запросить переносCookie
, что явно не выполняется, поэтому будемSameSite
Значение свойства меняется наNone
, в то время какsecure
свойство установлено наtrue
. Это также означает, что доменное имя вашей серверной службы должно использоватьсяhttps
Доступ к протоколу.
// 注意:cookie 模块必需要更新到最新的版本(0.4.0),才支持 sameSite=none
res.cookie('token', 'token 123', { maxAge: 2592000000, httpOnly: true, sameSite: 'none', secure: true, });
Попробуйте еще раз, чтобы убедиться, что проблема решена.
Однако это лишь временное решение, так как установкаsameSite
дляNone
Позже,CSRF
Риск вернулся. Итак, замените его наtoken
способ проверки, не полагаясь наCookie
, может быть более разумным решением.
Полный пример
Ниже приведен полный пример кода иNginx
конфигурация.
app.js
var path = require('path');
var cors = require('cors');
var express = require('express');
var cookieParser = require('cookie-parser');
var app = express();
app.use(cors({ origin: true, credentials: true, }));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public'))); // index.html放在public目录下
app.get('/api/info', function login(req, res) {
let token = req.cookies['token'];
if (token) {
res.json({ success: true, data: { name: 'somebody', age: 21 } });
} else {
res.json({ success: false, message: '请先登录' });
}
});
app.get('/api/login', function login(req, res) {
res.cookie('token', 'token 123', { maxAge: 2592000000, httpOnly: true, });
res.end();
});
app.get('/api/login/security', function login(req, res) {
// 注意:cookie 模块必需要更新到最新的版本(0.4.0),才支持 sameSite=none
res.cookie('token', 'token 123', { maxAge: 2592000000, httpOnly: true, sameSite: 'none', secure: true, });
res.end();
});
app.listen(8888, function () {
console.log('http://localhost:8888');
});
index.html
<html>
<head>
<title>Demo</title>
<style>
button {
width: 80px;
height: 32px;
line-height: 32px;
text-align: center;
}
</style>
</head>
<body>
<div>
<p>
<button id="login">登录</button>
<button id="security">安全登录</button>
</p>
<p>
<button id="check">查询</button>
</p>
</div>
<script>
(function() {
var get = function get(url, callback) {
var xhr = new XMLHttpRequest();
xhr.withCredentials = true;
xhr.open('get', url);
xhr.onreadystatechange = function onreadystatechange() {
if (xhr.readyState === 4) {
var res = xhr.response;
try {
res = JSON.parse(res);
} catch (e) {}
typeof callback === 'function' && callback(res);
}
};
xhr.send(null);
};
var login = document.querySelector('#login');
var check = document.querySelector('#check');
var security = document.querySelector('#security');
login.addEventListener('click', function onLogin() {
get('https://api.server.cn/login');
});
check.addEventListener('click', function onLogin() {
get('https://api.server.cn/info', function callback(res) {
if (res.success) {
console.log(res.data);
} else {
alert(res.message);
}
});
});
security.addEventListener('click', function onLogin() {
get('https://api.server.cn/login/security');
});
})();
</script>
</body>
</html>
nginx.conf
server {
listen 443 ssl;
server_name api.server.cn;
ssl_certificate /path/to/ssl/server.crt;
ssl_certificate_key /path/to/ssl/server.key;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://localhost:8888/api/;
}
}
hosts
127.0.0.1 api.server.cn
Уведомление
Поскольку наш сертификат является самоподписанным и не может пройти проверку сертификата браузера, нам нужно вручную нажать «Продолжить до xxx (небезопасно)», чтобы нормально отправлять запросы к серверной службе.