В этой статье я попробую рассказать о том, как с помощью node.js и connect сделать простой сайт с авторизацией. Т.е. такой, где часть контента доступна всем, а часть — только зарегистрированным пользователям. Поскольку express.js основан на connect, практически все, что здесь написано, относится и к нему тоже.
Допустим, что вы уже знаете, что такое node.js и как с ним работать. Также допустим, что у вас уже есть простенький сайт с основной страницей и парой дополнительных. Вот здесь — исходники такого сайта, пример для этой статьи.
Теория
Есть такое понятие, как сессия — период времени, пока пользователь находится на сайте. Сессия начинается, когда пользователь впервые открывает сайт в браузере и заканчивается, когда у нее истечет срок действия (или когда сайт захочет ее прервать). С каждой сессией связывается определенный набор данных:
- уникальный идентификатор сессии
- срок действия
- имя пользователя (если он его введет)
- другая служебная информация, необходимая для идентификации: ip-адрес, user agent
- любая другая информация, которую сайт связывает с текущим пользователем. Например, электронный магазин может хранить в сессии список товаров, добавленных в корзину.
Для решения нашей задачи нужны две таблицы в базе данных: одна для хранения данных сессии, другая — для информации о пользователях. На самом деле, в данном случае говорить «таблица БД» не совсем правильно, информация может находиться в разных местах. Например, всю параметры сессии можно хранить в cookies (или в памяти приложения, хотя это и нехорошо). Данные о пользователе могут поступать извне, если он заходит с помощью OpenID/OAuth.
Connect
Всю работу по созданию сессии connect
берет на себя. Для этого нужно добавить два правила:
app.use(connect.cookieParser());
app.use(connect.session({ secret: 'your secret here'} ));
Порядок имеет значение, сами правила должны быть определены до задания маршрутов. Первое правило обеспечивает работу с куками в общем. Второе добавляет к обычному request
поле session
, через которое будут доступны данные сессии (дальше с примерами станет понятнее).
connect.session
получает такие параметры:
secret
— фраза, которая используется для шифрования информации в cookies.store
— обьект, который будет использоваться для хранения данных сессии. По умолчанию connect хранит все данные в памяти, но, естественно, в реальных приложениях так делать нельзя. Есть готовые решения для mongodb, redis, MySQL и т.д.cookie
— набор параметров cookie. Самый важный —maxAge
, время жизни в миллисекундах (или null)
Авторизация
Как уже было сказано, connect
будет добавлять поле session
к каждому запросу, но по умолчанию там ничего интересного нет. Если мы каким-то образом «узнаем» пользователя (собственно, если он введет правильный пароль), мы должны будем сами добавить информацию о нем к сессии. Приблизительно так:
if ((request.body.login==='Thor')&&(request.body.password==='111')) {
request.session.authorized = true;
request.session.username = request.body.login;
console.log('Thor is here!');
}
В принципе, хватило бы одной переменной username
(так делает автор вот этой статьи). Но тогда проверка, авторизирован ли пользователь, будет выглядеть некрасиво:
if (typeof req.session.username == 'undefined') {
// не залогинен, перенаправить на форму ввода пароля
}
Когда пользователь захочет разлогиниться, достаточно будет просто удалить добавленные поля:
delete req.session.authorized;
delete req.session.username ;
Для полной очистки есть метод session.destroy(). Он удаляет session
из текущего запроса, а при следующем это поле будет сгенерировано заново.
Контроль доступа
Наиболее очевидное решение — проверять request.session.authorized всякий раз, когда нужно сгенерировать защищенную страницу. Собственно, так и делают в статье, на которую я уже ссылался. Проблема в том, это противоречит «слоистой» идеологии connect
. Лучше задать специальное правило, которое будет проверять права пользователя и, если что не так, перенаправлять его на страницу ошибки. Идея описана здесь, в нашем случае будет
// адреса, которые поддерживает наш сайт;
var siteUrls = [
{pattern:'^/login/?$', restricted: false}
, {pattern:'^/logout/?$', restricted: true}
, {pattern:'^/$', restricted: false}
, {pattern:'^/single/\w+/?$', restricted: true}
];
function authorizeUrls(urls) {
function authorize(req, res, next) {
var requestedUrl = url.parse(req.url).pathname;
for (var ui in urls) {
var pattern = urls[ui].pattern;
var restricted = urls[ui].restricted;
if (requestedUrl.match(pattern)) {
if (restricted) {
if (req.session.authorized) {
// если все хорошо, просто переходим к следующим правилам
next();
return;
}
else{
// пользователь не авторизирован, отправляем его на страницу логина
res.writeHead(303, {'Location': '/login'});
res.end();
return;
}
}
else {
next();
return;
}
}
}
// сюда мы попадаем, только если в цикле не нашлось совпадений
console.log('common 404 for ', req.url);
res.end('404: there is no ' + req.url + ' here');
}
return authorize ;
}
app.use('/', authorizeUrls(siteUrls));
Все. Надеюсь, это кому-нибудь поможет.
Автор: beardog