Простой сайт с возможностью авторизации на node.js

в 13:10, , рубрики: connect, express.js, node.js, метки: , ,

В этой статье я попробую рассказать о том, как с помощью 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

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js