Сookies или куки – это данные в виде пар ключ=значение, которые хранятся в файлах на компьютере пользователя.
Для хранимых данных существуют несколько ограничений:
- Одна пара запись не должна занимать более 4Кб.
- Общее количество кук на один домен ограничивается примерно 20.
1
Проверка включены ли cookies в браузере
Чтобы узнать, включены ли cookies в браузере пользователя до их использования, можно проверить свойство navigator.cookieEnabled
(содержит true
или false
).
if (navigator.cookieEnabled === false){
alert("Cookies отключены!");
}
JS
В старых браузерах navigator.cookieEnabled
может быть неопределенным.
2
Запись cookies
Запись в document.cookie
происходит особым образом, например следующий пример обновит только данные с ключом «name», но при этом не затронет все остальные.
document.cookie = "name=user";
JS
Такая установка будет хранится до закрытия браузера. Чтобы продлить время жизни cookies есть два типа параметров:
Max-age и Expires
max-age
устанавливает время жизни куки в секундах, а параметр expires
задает непосредственно дату окончания в формате RFC-822 или RFC-1123 (Mon, 03 Jul 2021 10:00:00 GMT
).
Следующие примеры устанавливают куки name=user
сроком на один месяц:
document.cookie = "name=user;max-age=2629743";
/* или */
var cookie_date = new Date();
cookie_date.setMonth(cookie_date.getMonth() + 1);
document.cookie = "name=user;expires=" + cookie_date.toUTCString();
JS
Куки сроком на год:
document.cookie = "name=user;max-age=31556926";
/* или */
var cookie_date = new Date();
cookie_date.setYear(cookie_date.getFullYear() + 1);
document.cookie = "name=user;expires=" + cookie_date.toUTCString();
JS
Другие параметры:
Path
Параметр указывает URL-префикс пути т.е. на каких страницах будут доступны установленные куки. Значение path должно быть относительным URL (без домена).
Например, установка кук для страницы http://example.com/admin/
и всех её дочерних:
document.cookie = "name=user;path=/admin";
JS
Как правило, в качестве пути указывают корень сайта path=/
, чтобы куки были доступны на всем сайте.
Domain
Параметр указывает домен, на котором будут доступны куки, включая поддомены.
document.cookie = "name=user;domain=example.com";
JS
Secure
Параметр позволяет делать установку куки только на страницах с HTTPS-протоколом. С этой настройкой, установленные куки не будут доступны на том же сайте с протоколом HTTP.
document.cookie = "name=user;secure";
JS
3
Чтение cookies
Чтение кук не совсем удобное, в JS нет нативного метода, который получит значение по ключу. Объект document.cookie
возвращает все установленные значения в виде строки, например:
document.cookie = "name=user";
document.cookie = "fullname=Ivanov";
document.cookie = "date=10.01.2021";
console.log(document.cookie); // name=user; date=10.01.2021; fullname=Ivanov
JS
Как видно никаких дополнительный данных о куках (expires
, path
, domain
, secure
) в document.cookie
не содержится.
Чтобы получить значение по ключу можно использовать регулярное выражение:
var results = document.cookie.match(/name=(.+?)(;|$)/);
console.log(results[1]); // user
var results = document.cookie.match(/fullname=(.+?)(;|$)/);
console.log(results[1]); // Ivanov
var results = document.cookie.match(/date=(.+?)(;|$)/);
console.log(results[1]); // 10.01.2021
JS
Функция для получения значения cookie по ключу:
function getCookie(name) {
var matches = document.cookie.match(new RegExp("(?:^|; )" + name.replace(/([.$?*|{}()[]\/+^])/g, '\$1') + "=([^;]*)"));
return matches ? decodeURIComponent(matches[1]) : undefined;
}
console.log(getCookie('name')); // user
JS
Получить все значения cookies в виде объекта:
var obj = {};
var cookies = document.cookie.split(/;/);
for (var i = 0, len = cookies.length; i < len; i++) {
var cookie = cookies[i].split(/=/);
obj[cookie[0]] = cookie[1];
}
console.dir(obj); // {date: "10.01.2021", fullname: "Ivanov", name: "user"}
JS
4
Удаление cookies
Удаление данных происходит путём установки новой куки с параметром max-age=-1
или expires с прошедшей датой.
document.cookie = "user=;max-age=-1";
/* или через expires */
var cookie_date = new Date();
cookie_date.setMonth(cookie_date.getMonth() - 1);
document.cookie = "name=user;expires=" + cookie_date.toUTCString();
JS
Удаление всех кук сайта:
var cookies = document.cookie.split(/;/);
for (var i = 0, len = cookies.length; i < len; i++) {
var cookie = cookies[i].split(/=/);
document.cookie = cookie[0] + "=;max-age=-1";
}
JS
5
Отладка и просмотр cookies в браузерах
Просмотр, редактирование и удаление кук доступно в браузерах, в панелях для разработчиков:
Google Chrome
В DevTools (Windows: F12, MacOS: ⌘ + ⌥ + i), вкладка «Application», раздел «Storage» – «Cookies».
Mozilla Firefox
В веб-консоли (Windows: F12, MacOS: ⌘ + ⌥ + k), вкладка «Хранилище», раздел «Куки».
6
Пример использования cookies
В примере выполняется сохранение выбранного пункта селекта на нескольких страницах:
<h4>Страница №1</h4>
<div class="form-group">
<label for="example">Выберите любой пункт:</label>
<select class="form-control" id="example">
<option>1</option>
<option>2</option>
<option>3</option>
<option>4</option>
<option>5</option>
</select>
</div>
<script src="/jquery.min.js"></script>
<script>
$(document).ready(function(){
var selected = document.cookie.match(/select=(.+?);/);
if (selected) {
$('#example').val(selected);
}
$('#example').change(function(){
document.cookie = "select=" + $(this).val();
});
});
</script>
HTML
Куки, document.cookie
Куки — это небольшие строки данных, которые хранятся непосредственно в браузере. Они являются частью HTTP-протокола, определённого в спецификации RFC 6265.
Куки обычно устанавливаются веб-сервером при помощи заголовка Set-Cookie
. Затем браузер будет автоматически добавлять их в (почти) каждый запрос на тот же домен при помощи заголовка Cookie
.
Один из наиболее частых случаев использования куки — это аутентификация:
- При входе на сайт сервер отсылает в ответ HTTP-заголовок
Set-Cookie
для того, чтобы установить куки со специальным уникальным идентификатором сессии (“session identifier”). - Во время следующего запроса к этому же домену браузер посылает на сервер HTTP-заголовок
Cookie
. - Таким образом, сервер понимает, кто сделал запрос.
Мы также можем получить доступ к куки непосредственно из браузера, используя свойство document.cookie
.
Куки имеют множество особенностей и тонкостей в использовании, и в этой главе мы подробно с ними разберёмся.
Чтение из document.cookie
Хранит ли ваш браузер какие-то куки с этого сайта? Посмотрим:
Если предположить, что вы зашли на сайт, то куки можно посмотреть вот так:
// На javascript.info мы используем сервис Google Analytics для сбора статистики, // поэтому какие-то куки должны быть alert( document.cookie ); // cookie1=value1; cookie2=value2;...
Значение document.cookie
состоит из пар ключ=значение
, разделённых ;
. Каждая пара представляет собой отдельное куки.
Чтобы найти определённое куки, достаточно разбить строку из document.cookie
по ;
, и затем найти нужный ключ. Для этого мы можем использовать как регулярные выражения, так и функции для обработки массивов.
Оставим эту задачу читателю для самостоятельного выполнения. Кроме того, в конце этой главы вы найдёте полезные функции для работы с куки.
Запись в document.cookie
Мы можем писать в document.cookie
. Но это не просто свойство данных, а акcессор (геттер/сеттер). Присваивание к нему обрабатывается особым образом.
Запись в document.cookie
обновит только упомянутые в ней куки, но при этом не затронет все остальные.
Например, этот вызов установит куки с именем user
и значением John
:
document.cookie = "user=John"; // обновляем только куки с именем 'user' alert(document.cookie); // показываем все куки
Если вы запустите этот код, то, скорее всего, увидите множество куки. Это происходит, потому что операция document.cookie=
перезапишет не все куки, а лишь куки с вышеупомянутым именем user
.
Технически, и имя и значение куки могут состоять из любых символов, для правильного форматирования следует использовать встроенную функцию encodeURIComponent
:
// специальные символы (пробелы), требуется кодирование let name = "my name"; let value = "John Smith" // кодирует в my%20name=John%20Smith document.cookie = encodeURIComponent(name) + '=' + encodeURIComponent(value); alert(document.cookie); // ...; my%20name=John%20Smith
Существует несколько ограничений:
- После `encodeURIComponent` пара `name=value` не должна занимать более 4Кб. Таким образом, мы не можем хранить в куки большие данные.
- Общее количество куки на один домен ограничивается примерно 20+. Точное ограничение зависит от конкретного браузера.
У куки есть ряд настроек, многие из которых важны и должны быть установлены.
Эти настройки указываются после пары ключ=значение
и отделены друг от друга разделителем ;
, вот так:
document.cookie = "user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT"
path
path=/mypath
URL-префикс пути, куки будут доступны для страниц под этим путём. Должен быть абсолютным. По умолчанию используется текущий путь.
Если куки установлено с path=/admin
, то оно будет доступно на страницах /admin
и /admin/something
, но не на страницах /home
или /adminpage
.
Как правило, указывают в качестве пути корень path=/
, чтобы наше куки было доступно на всех страницах сайта.
domain
domain=site.com
Домен определяет, где доступен файл куки. Однако на практике существуют определённые ограничения. Мы не можем указать здесь какой угодно домен.
Нет никакого способа разрешить доступ к файлам куки из другого домена 2-го уровня, поэтому other.com
никогда не получит куки, установленный по адресу site.com
.
Это ограничение безопасности, позволяющее нам хранить конфиденциальные данные в файлах куки, которые должны быть доступны только на одном сайте.
По умолчанию куки доступны лишь тому домену, который его установил.
Пожалуйста, обратите внимание, что по умолчанию файл куки также не передаётся поддомену, например forum.site.com
.
// если мы установим файл куки на веб-сайте site.com... document.cookie = "user=John" // ...мы не увидим его на forum.site.com alert(document.cookie); // нет user
…Но это можно изменить. Если мы хотим разрешить поддоменам типа forum.site.com
получать куки, установленные на site.com
, это возможно.
Чтобы это произошло, при установке файла куки в site.com
, мы должны явно установить параметр domain
для корневого домена: domain=site.com
. После этого все поддомены увидят такой файл cookie.
Например:
// находясь на странице site.com // сделаем куки доступным для всех поддоменов *.site.com: document.cookie = "user=John; domain=site.com" // позже // на forum.site.com alert(document.cookie); // есть куки user=John
По историческим причинам установка domain=.site.com
(с точкой перед site.com
) также работает и разрешает доступ к куки для поддоменов. Это старая запись, но можно использовать и её, если нужно, чтобы поддерживались очень старые браузеры.
Таким образом, опция domain
позволяет нам разрешить доступ к куки для поддоменов.
expires, max-age
По умолчанию, если куки не имеют ни одного из этих параметров, то они удалятся при закрытии браузера. Такие куки называются сессионными (“session cookies”).
Чтобы помочь куки “пережить” закрытие браузера, мы можем установить значение опций expires
или max-age
.
expires=Tue, 19 Jan 2038 03:14:07 GMT
Дата истечения срока действия куки, когда браузер удалит его автоматически.
Дата должна быть точно в этом формате, во временной зоне GMT. Мы можем использовать date.toUTCString
, чтобы получить правильную дату. Например, мы можем установить срок действия куки на 1 день.
// +1 день от текущей даты let date = new Date(Date.now() + 86400e3); date = date.toUTCString(); document.cookie = "user=John; expires=" + date;
Если мы установим в expires
прошедшую дату, то куки будет удалено.
max-age=3600
Альтернатива expires
, определяет срок действия куки в секундах с текущего момента.
Если задан ноль или отрицательное значение, то куки будет удалено:
// куки будет удалено через 1 час document.cookie = "user=John; max-age=3600"; // удалим куки (срок действия истекает прямо сейчас) document.cookie = "user=John; max-age=0";
secure
secure
Куки следует передавать только по HTTPS-протоколу.
По умолчанию куки, установленные сайтом http://site.com
, также будут доступны на сайте https://site.com
и наоборот.
То есть, куки, по умолчанию, опираются на доменное имя, они не обращают внимания на протоколы.
С этой настройкой, если куки будет установлено на сайте https://site.com
, то оно не будет доступно на том же сайте с протоколом HTTP, как http://site.com
. Таким образом, если в куки хранится конфиденциальная информация, которую не следует передавать по незашифрованному протоколу HTTP, то нужно установить этот флаг.
// предполагается, что сейчас мы на https:// // установим опцию secure для куки (куки доступно только через HTTPS) document.cookie = "user=John; secure";
samesite
Это ещё одна настройка безопасности, применяется для защиты от так называемой XSRF-атаки (межсайтовая подделка запроса).
Чтобы понять, как настройка работает и где может быть полезной, посмотрим на XSRF-атаки.
Атака XSRF
Представьте, вы авторизовались на сайте bank.com
. То есть: у вас есть куки для аутентификации с этого сайта. Ваш браузер отправляет его на сайт bank.com
с каждым запросом, чтобы сервер этого сайта узнавал вас и выполнял все конфиденциальные финансовые операции.
Теперь, просматривая веб-страницу в другом окне, вы случайно переходите на сайт evil.com
, который автоматически отправляет форму <form action="https://bank.com/pay">
на сайт bank.com
с заполненными полями, которые инициируют транзакцию на счёт хакера.
Браузер посылает куки при каждом посещении bank.com
, даже если форма была отправлена с evil.com
. Таким образом, банк узнает вас и выполнит платёж.
Такая атака называется межсайтовая подделка запроса (или Cross-Site Request Forgery, XSRF).
Конечно же, в реальной жизни банки защищены от такой атаки. Во всех сгенерированных сайтом bank.com
формах есть специальное поле, так называемый “токен защиты от xsrf”, который вредоносная страница не может ни сгенерировать, ни каким-либо образом извлечь из удалённой страницы (она может отправить форму туда, но не может получить данные обратно). И сайт bank.com
при получении формы проверяет его наличие.
Но такая защита требует усилий на её реализацию: нам нужно убедиться, что в каждой форме есть поле с токеном, также мы должны проверить все запросы.
Настройка samesite
Параметр куки samesite
предоставляет ещё один способ защиты от таких атак, который (теоретически) не должен требовать “токенов защиты xsrf”.
У него есть два возможных значения:
samesite=strict
(или, что то же самое,samesite
без значения)
Куки с samesite=strict
никогда не отправятся, если пользователь пришёл не с этого же сайта.
Другими словами, если пользователь переходит по ссылке из почты, отправляет форму с evil.com
или выполняет любую другую операцию, происходящую с другого домена, то куки не отправляется.
Если куки имеют настройку samesite
, то атака XSRF не имеет шансов на успех, потому что отправка с сайта evil.com
происходит без куки. Таким образом, сайт bank.com
не распознает пользователя и не произведёт платёж.
Защита довольно надёжная. Куки с настройкой samesite
будет отправлено только в том случае, если операции происходят с сайта bank.com
, например отправка формы сделана со страницы на bank.com
.
Хотя есть небольшие неудобства.
Когда пользователь перейдёт по ссылке на bank.com
, например из своих заметок, он будет удивлён, что сайт bank.com
не узнал его. Действительно, куки с samesite=strict
в этом случае не отправляется.
Мы могли бы обойти это ограничение, используя два куки: одно куки для “общего узнавания”, только для того, чтобы поздороваться: “Привет, Джон”, и другое куки для операций изменения данных с samesite=strict
. Тогда пользователь, пришедший на сайт, увидит приветствие, но платежи нужно инициировать с сайта банка, чтобы отправилось второе куки.
samesite=lax
Это более мягкий вариант, который также защищает от XSRF и при этом не портит впечатление от использования сайта.
Режим Lax так же, как и strict
, запрещает браузеру отправлять куки, когда запрос происходит не с сайта, но добавляет одно исключение.
Куки с samesite=lax
отправляется, если два этих условия верны:
-
Используются безопасные HTTP-методы (например, GET, но не POST).
Полный список безопасных HTTP-методов можно посмотреть в спецификации RFC7231. По сути, безопасными считаются методы, которые обычно используются для чтения, но не для записи данных. Они не должны выполнять никаких операций на изменение данных. Переход по ссылке является всегда GET-методом, то есть безопасным.
-
Операция осуществляет навигацию верхнего уровня (изменяет URL в адресной строке браузера).
Обычно это так, но если навигация выполняется в
<iframe>
, то это не верхний уровень. Кроме того, JavaScript-методы для сетевых запросов не выполняют никакой навигации, поэтому они не подходят.
Таким образом, режим samesite=lax
, позволяет самой распространённой операции “переход по ссылке” передавать куки. Например, открытие сайта из заметок удовлетворяет этим условиям.
Но что-то более сложное, например, сетевой запрос с другого сайта или отправка формы, теряет куки.
Если это вам подходит, то добавление samesite=lax
, скорее всего, не испортит впечатление пользователей от работы с сайтом и добавит защиту.
В целом, samesite
отличная настройка.
Но у неё есть важный недостаток:
samesite
игнорируется (не поддерживается) старыми браузерами, выпущенными до 2017 года и ранее.
Так что, если мы будем полагаться исключительно на samesite
, то старые браузеры будут уязвимы.
Но мы, безусловно, можем использовать samesite
вместе с другими методами защиты, такими как XSRF-токены, чтобы добавить дополнительный слой защиты, а затем, в будущем, когда старые браузеры полностью исчезнут, мы, вероятно, сможем полностью удалить XSRF-токены.
httpOnly
Эта настройка не имеет ничего общего с JavaScript, но мы должны упомянуть её для полноты изложения.
Веб-сервер использует заголовок Set-Cookie
для установки куки. И он может установить настройку httpOnly
.
Эта настройка запрещает любой доступ к куки из JavaScript. Мы не можем видеть такое куки или манипулировать им с помощью document.cookie
.
Эта настройка используется в качестве меры предосторожности от определённых атак, когда хакер внедряет свой собственный JavaScript-код в страницу и ждёт, когда пользователь посетит её. Это вообще не должно быть возможным, хакер не должен быть в состоянии внедрить свой код на ваш сайт, но могут быть ошибки, которые позволят хакеру сделать это.
Обычно, если такое происходит, и пользователь заходит на страницу с JavaScript-кодом хакера, то этот код выполняется и получает доступ к document.cookie
, и тем самым к куки пользователя, которые содержат аутентификационную информацию. Это плохо.
Но если куки имеет настройку httpOnly
, то document.cookie
не видит его, поэтому такое куки защищено.
Приложение: Функции для работы с куки
Вот небольшой набор функций для работы с куки, более удобных, чем ручная модификация document.cookie
.
Для этого существует множество библиотек, так что они, скорее, в демонстрационных целях. Но при этом полностью рабочие.
getCookie(name)
Самый короткий способ получить доступ к куки — это использовать регулярные выражения.
Функция getCookie(name)
возвращает куки с указанным name
:
// возвращает куки с указанным name, // или undefined, если ничего не найдено function getCookie(name) { let matches = document.cookie.match(new RegExp( "(?:^|; )" + name.replace(/([.$?*|{}()[]\/+^])/g, '\$1') + "=([^;]*)" )); return matches ? decodeURIComponent(matches[1]) : undefined; }
Здесь new RegExp
генерируется динамически, чтобы находить ; name=<value>
.
Обратите внимание, значение куки кодируется, поэтому getCookie
использует встроенную функцию decodeURIComponent
для декодирования.
setCookie(name, value, options)
Устанавливает куки с именем name
и значением value
, с настройкой path=/
по умолчанию (можно изменить, чтобы добавить другие значения по умолчанию):
function setCookie(name, value, options = {}) { options = { path: '/', // при необходимости добавьте другие значения по умолчанию ...options }; if (options.expires instanceof Date) { options.expires = options.expires.toUTCString(); } let updatedCookie = encodeURIComponent(name) + "=" + encodeURIComponent(value); for (let optionKey in options) { updatedCookie += "; " + optionKey; let optionValue = options[optionKey]; if (optionValue !== true) { updatedCookie += "=" + optionValue; } } document.cookie = updatedCookie; } // Пример использования: setCookie('user', 'John', {secure: true, 'max-age': 3600});
deleteCookie(name)
Чтобы удалить куки, мы можем установить отрицательную дату истечения срока действия:
function deleteCookie(name) { setCookie(name, "", { 'max-age': -1 }) }
Обратите внимание: когда мы обновляем или удаляем куки, нам следует использовать только такие же настройки пути и домена, как при установке куки.
Всё вместе: cookie.js.
Приложение: Сторонние куки
Куки называются сторонними, если они размещены с домена, отличающегося от страницы, которую посещает пользователь.
Например:
-
Страница
site.com
загружает баннер с другого сайта:<img src="https://ads.com/banner.png">
. -
Вместе с баннером удалённый сервер
ads.com
может установить заголовокSet-Cookie
с куки, например,id=1234
. Такие куки создаются с доменаads.com
и будут видны только на сайтеads.com
: -
В следующий раз при доступе к
ads.com
удалённый сервер получит кукиid
и распознает пользователя: -
Что ещё более важно, когда пользователь переходит с
site.com
на другой сайтother.com
, на котором тоже есть баннер, тоads.com
получит куки, так как они принадлежатads.com
, таким образомads.com
распознает пользователя и может отслеживать его перемещения между сайтами:
Сторонние куки в силу своей специфики обычно используются для целей отслеживания посещаемых пользователем страниц и показа рекламы. Они привязаны к исходному домену, поэтому ads.com может отслеживать одного и того же пользователя на разных сайтах, если оттуда идёт обращение к нему.
Естественно, некоторым пользователям не нравится, когда их отслеживают, поэтому браузеры позволяют отключать такие куки.
Кроме того, некоторые современные браузеры используют специальные политики для таких куки:
- Safari вообще не разрешает сторонние куки.
- У Firefox есть “чёрный список” сторонних доменов, чьи сторонние куки он блокирует.
Если мы загружаем скрипт со стороннего домена, например `<script src="https://google-analytics.com/analytics.js">`, и этот скрипт использует `document.cookie`, чтобы установить куки, то такое куки не является сторонним.
Если скрипт устанавливает куки, то нет разницы откуда был загружен скрипт -- куки принадлежит домену текущей веб-страницы.
Приложение: GDPR
Эта тема вообще не связана с JavaScript, но следует её иметь в виду при установке куки.
В Европе существует законодательство под названием GDPR, которое устанавливает для сайтов ряд правил, обеспечивающих конфиденциальность пользователей. И одним из таких правил является требование явного разрешения от пользователя на использование отслеживающих куки.
Обратите внимание, это относится только к куки, используемым для отслеживания/идентификации/авторизации.
То есть, если мы установим куки, которые просто сохраняют некоторую информацию, но не отслеживают и не идентифицируют пользователя, то мы свободны от этого правила.
Но если мы собираемся установить куки с информацией об аутентификации или с идентификатором отслеживания, то пользователь должен явно разрешить это.
Есть два основных варианта как сайты следуют GDPR. Вы наверняка уже видели их в сети:
-
Если сайт хочет установить куки для отслеживания только для авторизованных пользователей.
То в регистрационной форме должен быть установлен флажок “принять политику конфиденциальности” (которая определяет, как используются куки), пользователь должен установить его, и только тогда сайт сможет использовать авторизационные куки.
-
Если сайт хочет установить куки для отслеживания всем пользователям.
Чтобы сделать это законно, сайт показывает модальное окно для пользователей, которые зашли в первый раз, и требует от них согласие на использование куки. Затем сайт может установить такие куки и показать пользователю содержимое страницы. Хотя это создаёт неудобства для новых посетителей – никому не нравится наблюдать модальные окна вместо контента. Но GDPR в данной ситуации требует явного согласия пользователя.
GDPR касается не только куки, но и других вопросов, связанных с конфиденциальностью, которые выходят за рамки материала этой главы.
Итого
document.cookie
предоставляет доступ к куки.
- Операция записи изменяет только то куки, которое было указано.
- Имя и значение куки должны быть закодированы.
- Одно куки вмещает до 4kb данных, разрешается более 20 куки на сайт (зависит от браузера).
Настройки куки:
path=/
, по умолчанию устанавливается текущий путь, делает куки видимым только по указанному пути и ниже.domain=site.com
, по умолчанию куки видно только на текущем домене, если явно указан домен, то куки видно и на поддоменах.expires
илиmax-age
устанавливает дату истечения срока действия, без них куки умрёт при закрытии браузера.secure
делает куки доступным только при использовании HTTPS.samesite
запрещает браузеру отправлять куки с запросами, поступающими извне, помогает предотвратить XSRF-атаки.
Дополнительно:
- Сторонние куки могут быть запрещены браузером, например Safari делает это по умолчанию.
- Установка отслеживающих куки пользователям из стран ЕС требует их явного согласия на это в соответствии с законодательством GDPR.
JavaScript дает возможность устанавливать и читать куки в браузере. В данном уроке мы рассмотрим как происходит работа с куками, а также сделаем простую страницу, которая будет помнить введеное имя и отображать его при каждом входе.
Что такое куки (cookie)?
Куки – это небольшой объем данных, которые хранятся вэб браузером. Они позволяют Вам сохранять определенную информацию о пользователе и получать ее каждый раз, когда он посещает Вашу страницу. Каждый пользователь имеет свой собственный уникальный набор куков.
Обычно куки используются веб сервером для выполнения таких функций как отслеживание посещений сайта, регистрации на сайте и сохранения сведений о заказах или покупках. Однако нам не нужно придумывать программу для вэб сервера чтобы использовать куки. Мы можем использовать их с помощью JavaScript.
Свойство document.cookie
.
В JavaScript куки доступны с помощью свойства cookie объекта document. Создать куки можно следующим образом:
document.cookie = "name=значение; expires=дата; path=путь;
domain=домен; secure";
… и получить весь сохраненый набор куков так:
var x = document.cookie;
Давайте рассмотрим сохранение и получение куков более подробно.
Сохранение куки
Для сохранения куки нужно присвоить document.cookie текстовую строку, которая содержит свойства куки, которые мы хотим создать:
document.cookie = "name=значение; expires=дата; path=путь;
domain=домен; secure";
Свойства описаны в таблице:
Свойство | Описание | Пример |
---|---|---|
name =значение |
Устанавливает имя куки и его значение. | username=Вася |
expires=дата |
Устанавливает дату истечения срока хранения куки. Дата должна быть представлена в формате, который возвращает метод toGMTString() объекта Date . Если значение expires не задано, куки будет удалено при закрытии браузера. |
expires= |
path=путь |
Данная опция устанавливает путь на сайте, в рамках которого действует куки. Получить значение куки могут только документы из указанного пути. Обычно данное свойство оставляют пустым, что означает что только документ установивший куки может получит доступ к нему. | path=/demo/ |
domain=домен |
Данная опция устанавливает домен, в рамках которого действует куки. Получить значение куки могут только сайты из указанного домена. Обычно данное свойство оставляют пустым, что означает, что только домен установивший куки может получит доступ к нему. | domain=ruseller.com |
secure |
Данная опция указывает браузеру, что для пересылки куки на сервер следует использовать SSL. Очень редко используется. | secure |
Давайте посмотрим пример установки куки:
document.cookie = "username=Вася;
expires=15/02/2011 00:00:00";
Данный код устанавливает куки username
, и присваивает ему значение "Вася"
, которое будет храниться до 15-го февраля 2011 года (используется Европейский формат времени!).
var cookie_date = new Date ( 2003, 01, 15 );
document.cookie = "username=Вася;
expires=" + cookie_date.toGMTString();
Данный код выполняет точно такое же действие, как и предыдущий пример, но для установки даты используется метод Date.toGMTString()
. Учтите, что нумерация месяца в объекте Date
начинается с 0, то есть февраль – это 01
.
document.cookie = "logged_in=yes";
Данный код устанавливает куки logged_in
, и присваивает ему значение "yes"
. Так как атрибут expires
не установлен, то куки удалится при закрытии браузера.
var cookie_date = new Date ( ); // Текущая дата и время
cookie_date.setTime ( cookie_date.getTime() - 1 );
document.cookie = "logged_in=;
expires=" + cookie_date.toGMTString();
Данный код устанавливает куки
logged_in
и присваивает строку хранения значение времени за одну секунду перед текущим - такая операция приведет к немедленному удалению куки. Ручной способ удалить куки!
Перекодирование значения куки!
Следует перекодировать значение куки для корректного хранения и отображения таких символов как пробел и двоеточие. Такая операция гарантирует, что браузер корректно воспримет значение. Перекодирование лекго выполняется функцией JavaScript escape()
. Например:
document.cookie = "username=" + escape("Вася Пупкин")
+ "; expires=15/02/2003 00:00:00";
Функция для установки куки
Установка куки станет проще, если мы напишем специальную функцию, которая будет выполнять простые операции, такие как перекодирование значений и построение строки document.cookie
. Например:
function set_cookie ( name, value, exp_y, exp_m, exp_d, path, domain, secure )
{
var cookie_string = name + "=" + escape ( value );
if ( exp_y )
{
var expires = new Date ( exp_y, exp_m, exp_d );
cookie_string += "; expires=" + expires.toGMTString();
}
if ( path )
cookie_string += "; path=" + escape ( path );
if ( domain )
cookie_string += "; domain=" + escape ( domain );
if ( secure )
cookie_string += "; secure";
document.cookie = cookie_string;
}
Функция получает данные для куки в качестве аргументов, затем строит соответствующую строку и устанавливает куки.
Например, установка куки без срока хранения:
set_cookie ( "username", "Вася Пупкин" );
Установка куки со сроком хранения до 15 февраля 2011:
set_cookie ( "username", "Вася Пупкин", 2011, 01, 15 );
Установка куки со сроком хранения, доменом ruseller.com
, использованием SSL, но без пути:
set_cookie ( "username", "Вася Пупкин", 2003, 01, 15, "",
"ruseller.com", "secure" );
Функция для удаления куки.
Другая полезная функция для работы с куки представлена ниже. Функция “удаляет” куки из браузера посредством установки срока хранения на одну секунду раньше текущего значения времени.
function delete_cookie ( cookie_name )
{
var cookie_date = new Date ( ); // Текущая дата и время
cookie_date.setTime ( cookie_date.getTime() - 1 );
document.cookie = cookie_name += "=; expires=" + cookie_date.toGMTString();
}
Для использования данной функции нужно только передать ей имя удаляемого куки:
delete_cookie ( "username" );
Получение значения куки
Для того, чтобы получить значение предварительно установленного куки для текущего документа, нужно использовать свойство document.cookie
:
var x = document.cookie;
Таким образом возвращается строка, которая состоит из списка пар имя/значение, разделенных точкой с запятой для всех куки, которые действуют для текущего документа. Например:
"username=Вася; password=abc123"
В данном примере 2 куки, которые были предварительно установлены: username
, который имеет значение "Вася"
, и password
, который имеет значение "abc123"
.
Функция для получения значения куки
Обычно, нам нужно только значение одного куки за один раз. Поэтому строка куки не удобна для использования! Здесь приводится функция, которая обрабатывает строку document.cookies
, возвращет только то куки, которое представляет интерес в конкретный момент:
function get_cookie ( cookie_name )
{
var results = document.cookie.match ( '(^|;) ?' + cookie_name + '=([^;]*)(;|$)' );
if ( results )
return ( unescape ( results[2] ) );
else
return null;
}
Данная функция использует регулярное выражение для поиска имени куки, которое представляет интерес, а затем возвращает значение, которое обработано функцией unescape()
для перекодирования к нормальному символьному виду. (Если куки не найдено, возвращается значение null.)
Данная функция проста в использовании. Например, для возврата значения куки username
:
var x = get_cookie ( "username" );
Простой пример использования
В данном примере мы сделали страницу, которая запрашивает Ваше имя при первом посещении, затем она сохраняет Ваше имя в куки и показывает его при следующих посещениях.
Откройте страницу в новом окне. При первом посещении она попросит ввести имя и сохранит его в куки. Если Вы посетите страницу еще раз, она отобразит на экране введенное имя из куки.
Для куки задаем срок хранения в 1 год от текущей даты, это означает, что браузер сохранит Ваше имя даже если Вы закроете его.
Вы можете удалить куки нажав на ссылку Забудь обо мне!, которая вызывает функцию delete_cookie()
и обновляет страницу, чтобы снова запросить имя у Вас.
Вы можете посмотреть код страницы в браузере, выбрав функцию просмотра исходного кода. Здесь приводится основная часть кода:
if ( ! get_cookie ( "username" ) )
{
var username = prompt ( "Пожалуйста, введите Ваше имя", "" );
if ( username )
{
var current_date = new Date;
var cookie_year = current_date.getFullYear ( ) + 1;
var cookie_month = current_date.getMonth ( );
var cookie_day = current_date.getDate ( );
set_cookie ( "username", username, cookie_year, cookie_month, cookie_day );
}
}
else
{
var username = get_cookie ( "username" );
document.write ( "Привет, " + username + ", добро пожаловать на страницу!" );
document.write ( "<br><a href="javascript:delete_cookie('username');
document.location.reload ( );">
Forget about me!</a>" );
}
Данный урок показал Вам, как использовать куки в JavaScript для хранения информации о Ваших посетителях. Спасибо за внимание! 🙂
Для хранения какой-либо информации на стороне клиента (в браузере) используются cookie
. Они представлены в виде пары имя=значение
. А также для cookie
можно установить дополнительные параметры (срок действия и т.п.).
DOM-интерфейс позволяет работать с cookie
. Для добавления и получения cookie
используется свойство document.cookie
.
Добавление cookie
Чтобы добавить новый cookie
, свойству document.cookie
присваивается строка, содержащая необходимую информацию о cookie
. За одно присвоение можно добавить только один cookie
. Строка должна быть в виде имя=значение
. Имя cookie
и его значение не должны содержать запятые, точки с запятой или пробельные символы. Поэтому их предварительно необходимо закодировать с помощью метода encodeURIComponent()
.
Дополнительно в строке со значением cookie
через точку с запятой могут быть указаны его параметры:
domain
- Домен, на котором доступен
cookie
. Если не указан, тогда по умолчанию используется домен текущего расположения документа (но не включая поддомены). В отличие от более ранних спецификаций, ведущие точки в доменных именах игнорируются. Если домен указан, поддомены всегда включены. path
- Директория, в которой может быть использован
cookie
, то есть страница должна находиться внутри указанной директории, чтобы иметь доступ кcookie
. Если путь не указан, тогда по умолчанию используется директория, в которой находится текущий документ. Можно указывать только абсолютный путь, но при этом домен не указывается. Например,
сделаетpath=/docs
cookie
доступным на любой странице внутри директории
. Чтобыexample.ru/docs/
cookie
был доступен в любом месте сайта, надо указать
, что соответсвует директорииpath=/
.example.ru/
expires
- Время, до которого хранится и может использоваться
cookie
. Указывается время по Гринвичу в формате
. Если указанное время уже прошло, тогдаWdy, DD Mon YYYY HH:MM:SS GMT
cookie
не установится. По умолчанию, если время не указано,cookie
хранится до окончания сессии, то есть до закрытия браузера. max-age
- Время, до которого хранится и может использоваться
cookie
. Время указывается в секундах от текущего времени. По истечении указанного времениcookie
будет удалён. Если установить значение0
, тоcookie
будет удалён немедленно. secure
- Логический параметр, отвечающий за то, как передаётся
cookie
по сети. По умолчанию, если параметр не указан,cookie
передаётся при любом соединении, включая незащищённое HTTP-соединение. Если указать данный параметр, тоcookie
будет передаваться только при безопасном соединении (HTTPS или т.п.).
Существует ещё один параметр для cookie
. Это логический параметр HttpOnly
, который запрещает браузеру работать с данным cookie
. Такие cookie
могут только отправляться на сервер.
Установить параметр HttpOnly
с помощью JavaScript нельзя.
Каждый cookie
характеризуется тремя значениями: его имя и параметры domain
и path
. Добавление cookie
, у которого хотя бы одно из данных значений отличается от уже установленного, приведёт к созданию нового cookie
. При этом могут появиться cookie
с одинаковыми именами, но отличающимися параметрами domain
или path
.
Параметры expires
и max-age
определяют время, в течение которого может использоваться cookie
. Если установлены оба параметра, то cookie
удаляется при достижении любого из них.
Примеры использования параметров при установке cookie
:
// Все параметры имеют значение по умолчанию document.cookie = 'cookie_name=cookie_value';
// cookie доступен в любых директориях основного домена и его поддоменов document.cookie = 'cookie_name=cookie_value; domain=example.ru; path=/';
// cookie передаётся только по безопасному соединению, но время находится в прошлом, поэтому cookie не установится document.cookie = 'cookie_name=cookie_value; expires=Sun, 10 Jan 2016 23:32:15 GMT; secure';
// cookie удалится через час document.cookie = 'cookie_name=cookie_value; max-age=3600';
Получение cookie
Получить значение установленного cookie
можно из свойства document.cookie
. В нём содержится строка, состоящая из пар имя=значение
, разделённых точками с запятой:
'имя1=значение1; имя2=значение2; имя3=значение3'
На данной странице document.cookie
можно посмотреть, нажав на кнопку:
Если на странице установлено несколько cookie
с одинаковыми именами, в свойстве document.cookie
будет содержаться значение только первого из них.
Чтобы найти значение конкретного cookie
, обычно используют регулярные выражения, хотя можно разбить строку по
, а затем работать с массивом.;
Параметры установленных cookie
получить нельзя.
Редактирование cookie
Чтобы изменить параметры установленного cookie
, его необходимо установить заново. При этом cookie
примет новые значения автоматически.
<script> document.cookie = 'example_cookie=old_value'; alert(document.cookie); // example_cookie=old_value document.cookie = 'example_cookie=new_value'; alert(document.cookie); // example_cookie=new_value </script>
Однако, не все параметры можно изменить. При попытке добавить cookie
, у которого значение domain
или path
отличается от уже установленного, установится новый cookie
. При этом старый cookie
тоже останется. Поэтому при смене значений domain
или path
старый cookie
следует удалить самостоятельно.
Удаление cookie
Удаление cookie
выполняется посредством изменения времени его жизни. Это делается изменением одного из параметров max-age
или expires
. Для expires
указывается время в прошлом:
'expires=Thu, 01 Jan 1970 00:00:00 GMT'
Или max-age
устанавливается равным 0:
'max-age=0'
Для изменения cookie
необходимо знать значения domain
и path
, которые использовались при установке. В противном случае удалить cookie
не удастся.
Объект для работы с cookie
Ниже приведён объект, который позволяет добавлять или получать значения cookie
, а также удалять их.
<script> var Cookie = { set: function (str_name, str_value, var_expires, str_path, str_domain, bool_secure) { if (!str_name) { return false; } var str_expires = ''; if (var_expires) { if (typeof var_expires == 'number') { if (var_expires === Infinity) { str_expires = '; expires=Tue, 19 Jan 2038 03:14:07 GMT'; } else { str_expires = '; max-age=' + var_expires; } } else if (typeof var_expires == 'string') { str_expires = "; expires=" + var_expires; } else if (typeof var_expires == 'object') { str_expires = "; expires=" + var_expires.toUTCString(); } } document.cookie = encodeURIComponent(str_name) + '=' + (str_value ? encodeURIComponent(str_value) : '') + str_expires + (str_path ? '; path=' + str_path : '') + (str_domain ? '; domain=' + str_domain : '') + (bool_secure ? '; secure' : ''); return true; }, get: function (str_name) { if (!str_name) { return false; } var arr = document.cookie.match( new RegExp('(?:^|; )' + encodeURIComponent(str_name).replace(/[-.*()']/g, '\$&') + '\=([^;]*)') ); return arr ? decodeURIComponent(arr[1]) : null; }, remove: function (str_name, str_path, str_domain) { if (!str_name || (this.get(str_name) === null)) { return false; } document.cookie = encodeURIComponent(str_name) + '=; max-age=0' + (str_path ? '; path=' + str_path : '') + (str_domain ? '; domain=' + str_domain : ''); if (this.get(str_name) === null) { return true; } else { return false; } } }; </script>
Добавление cookie
Для установки cookie
используется метод Cookie.set()
.
Cookie.set(имя[, значение[, истекает[, путь[, домен[, безопасность]]]]])
- имя
- Строка, содержащая имя
cookie
. - значение
- Необязательный аргумент. Строка, содержащая новое значение
cookie
. - истекает
-
Необязательный аргумент. Время, когда истекает срок действия
cookie
. Варианты значений:Infinity
– для установкиcookie
навсегда.- Количество секунд, через которое
cookie
удалится. - Строка в формате GMT.
- Объект даты.
- путь
- Необязательный аргумент. Строка, содержащая значение параметра
, илиpath
null
. - домен
- Необязательный аргумент. Строка, содержащая значение параметра
, илиdomain
null
. - безопасность
- Необязательный аргумент. Булевое значение, определяющее необходимость использования безопасного соединения.
Данный метод возвращает false
, если вызван без аргументов. Если установка cookie
выполнена, то возвращается true
.
Получение cookie
Чтобы получить значение cookie
, используется метод Cookie.get()
.
Cookie.get(имя)
- имя
- Строка, содержащая имя
cookie
.
Данный метод возвращает false
, если вызван без аргументов. Если cookie
существует, то возвращается его значение, иначе возвращается null
.
Удаление cookie
Для удаления cookie
используется метод Cookie.remove()
.
Cookie.remove(имя[, путь[, домен]])
- имя
- Строка, содержащая имя
cookie
. - путь
- Необязательный аргумент. Строка, содержащая значение параметра
, илиpath
null
. - домен
- Необязательный аргумент. Строка, содержащая значение параметра
, илиdomain
null
.
Данный метод возвращает false
в случаях, если:
- вызван без аргументов.
cookie
с таким именем отсутствует.- не удалось удалить
cookie
.
Если cookie
успешно удалён, то возвращается true
.
Пример работы методов
<script> alert(Cookie.set('cook[1]', 'value{1}', 3600, '/')); // true - cookie добавлен alert(Cookie.get('cook[1]')); // value{1} alert(Cookie.remove('cook[1]', '/')); // true - cookie удалён alert(Cookie.get('cook[1]')); // null - cookie с таким именем не существует </script>
Современный подход к работе с куки
Время на прочтение
4 мин
Количество просмотров 22K
Вы когда-нибудь работали с куки? Казалось ли вам при этом, что их использование организовано просто и понятно? Полагаю, что в работе с куки есть множество нюансов, о которых стоит знать новичкам.
Свойство document.cookie
Взглянем на классический способ работы с куки. Соответствующая спецификация существует, благодаря Netscape, с 1994 года. Компания Netscape реализовала свойство document.cookie
в Netscape Navigator в 1996 году. Вот определение куки из тех времён:
Куки — это небольшой фрагмент информации, хранящийся на клиентской машине в файле cookies.txt.
Главу про document.cookie
даже можно найти во втором издании книги «Javascript. The Definitive Guide», которое вышло в январе 1997 года. Это было 24 года тому назад. И мы всё ещё пользуемся тем же старым способом работы с куки ради обратной совместимости.
Как же это выглядит?
Получение куки
const cookies = document.cookie;
// возвращает "_octo=GH1.1.123.456; tz=Europe%2FMinsk" on GitHub
Да, именно так всё и делается. В нашем распоряжении оказывается строка со всеми значениями, хранящимися в куки-файле, разделёнными точкой с запятой.
Как вытащить из этой строки отдельное значение? Если вам кажется, что для этого надо самостоятельно разбить строку на части — знайте, что так оно и есть:
function getCookieValue(name) {
const cookies = document.cookie.split(';');
const res = cookies.find(c => c.startsWith(name + '='));
if (res) {
return res.substring(res.indexOf('=') + 1);
}
}
Как узнать о том, когда истекает срок действия какого-нибудь из куки? Да, в общем-то, никак.
А как узнать домен, с которого установлен какой-нибудь куки? Тоже никак.
Правда, если надо, можно распарсить HTTP-заголовок Cookie
.
Установка куки
document.cookie = 'theme=dark';
Вышеприведённая команда позволяет создать куки с именем theme
, значением которого является dark
. Хорошо. Значит ли это, что document.cookie
— это строка. Нет, не значит. Это — сеттер.
document.cookie = 'mozilla=netscape';
Эта команда не перезапишет куки с именем theme
. Она создаст новый куки с именем mozilla
. Теперь у нас имеются два куки.
По умолчанию срок действия куки, созданного так, как показано выше, истекает после закрытия браузера. Но при создании куки можно указать срок его действия:
document.cookie = 'browser=ie11; expires=Tue, 17 Aug 2021 00:00:00 GMT';
Да. Это — как раз то, что мне нужно — подбирать дату и время истечения срока действия куки в формате GMT каждый раз, когда нужно установить куки. Ладно, давайте воспользуемся для решения этой задачи JavaScript-кодом:
const date = new Date();
date.setTime(date.getTime() + (30 * 24 * 60 * 60 * 1000)); // здорово-то как
document.cookie = `login=mefody; expires=${date.toUTCString()}; path=/`;
Но, к счастью, у нас есть и другой способ установки момента истечения срока действия куки:
document.cookie = 'element=caesium; max-age=952001689';
Свойство max-age
представляет собой срок «жизни» куки в секундах. Соответствующее значение имеет более высокий приоритет, чем то, которое задано с помощью свойства expires
.
И не надо забывать о свойствах path
и domain
. По умолчанию куки устанавливаются для текущего расположения и текущего хоста. Если надо установить куки для всего домена — надо будет добавить к команде установки куки конструкцию такого вида:
; path=/; domain=example.com
Удаление куки
document.cookie = 'login=; expires=Thu, 01 Jan 1970 00:00:00 GMT';
Для удаления куки надо установить дату и время истечения срока действия куки на какой-нибудь момент из прошлого. Для того чтобы всё точно сработало бы — тут рекомендуется использовать начало эпохи Unix.
Работа с куки в сервис-воркерах
Это просто невозможно. Дело в том, что работа с document.cookie
— это синхронная операция, в результате воспользоваться ей в сервис-воркере нельзя.
Cookie Store API
Существует черновик стандарта одного замечательного API, направленного на работу с куки, который способен значительно облегчить нам жизнь в будущем.
Во-первых — это асинхронный API, а значит — пользоваться им можно, не блокируя главный поток. Применять его можно и в сервис-воркерах.
Во-вторых — этот API устроен гораздо понятнее, чем существующий механизм работы с куки.
▍Получение куки
const cookies = await cookieStore.getAll();
const sessionCookies = await cookieStore.getAll({
name: 'session_',
matchType: 'starts-with',
});
Метод getAll
возвращает массив, а не строку. Именно этого я и жду, когда пытаюсь получить некий список.
const ga = await cookieStore.get('_ga');
/**
{
"domain": "mozilla.org",
"expires": 1682945254000,
"name": "_ga",
"path": "/",
"sameSite": "lax",
"secure": false,
"value": "GA1.2.891784426.1616320570"
}
*/
А вот — приятная неожиданность. Можно узнать и дату истечения срока действия куки, и сведения о домене и пути, и при этом не пользоваться никакими хаками.
▍Установка куки
await cookieStore.set('name', 'value');
Или так:
await cookieStore.set({
name: 'name',
value: 'value',
expires: Date.now() + 86400,
domain: self.location.host,
path: '/',
secure: self.location.protocol === 'https:',
httpOnly: false,
});
Мне очень нравится этот синтаксис!
▍Удаление куки
await cookieStore.delete('ie6');
Или можно, как раньше, установить дату истечения срока действия куки на некий момент в прошлом, но не вижу причины поступать именно так.
▍События куки
cookieStore.addEventListener('change', (event) => {
for (const cookie in event.changed) {
console.log(`Cookie ${cookie.name} changed to ${cookie.value}`);
}
});
Как видите, теперь у нас есть возможность подписываться на изменения куки, не занимаясь опросом document.cookie
, блокирующим главный поток. Фантастика!
▍Сервис-воркеры
// service-worker.js
await self.registration.cookies.subscribe([
{
name: 'cookie-name',
url: '/path-to-track',
}
]);
self.addEventListener('cookiechange', (event) => {
// обработка изменений
});
Можно ли пользоваться этим API прямо сейчас?
Хотя этим API уже можно пользоваться, но тут надо проявлять осторожность. Cookie Store API работоспособно в Chrome 87+ (Edge 87+, Opera 73+). В других браузерах можно воспользоваться полифиллом, который, правда, не возвращает полной информации о куки, как это сделано в настоящем API. Прогрессивные улучшения — это вещь.
И учитывайте, что спецификация этого API всё ещё находится в статусе «Draft Community Group Report». Но если в вашем проекте важен хороший «опыт разработчика» — попробуйте современный способ работы с куки.
Пробовали ли вы работать с куки по-новому?