Nginx + Node.js: делаем идентификацию и аутентификацию

Kate

Administrator
Команда форума
Авторизация в системах одна из ключевых частей. Можно использовать какие то мощные решения, Firebase например, или что то из множества хороших библиотек (имеются в виду для node.js). Если хочется уменьшить количество зависимостей или для самообразования - то можно написать свое.

Вот так через несколько итераций, со временем, выработалось решение, которое построено с использованием Nginx. Все описанное является очень частным случаем используемого подхода, в том смысле что есть некоторые условия в которых требовалось создать решение, и данное решение хорошо только в в этих условиях.

В замечательном Nginx (вероятно не только в нем) есть простая и очень эффективная возможность (auth_request) конфигурировать выполнение подзапросов на каждый запрос. Иными словами при обращении клиентов к вашему API который принимает подключения на порту (например) 30000, Nginx выполнит auth_request - авторизационный подзапрос на определяемый вами порт (например) 25000. Запускаем приложение которое слушает 25000 порт, обрабатывает запрос и возвращает данные содержащие все необходимое для аутентификации в API инициатора запроса (клиента). Эта часть общеизвестна и с более подробной информацией проблем быть не должно.

Для реализации подобной схемы с помощью node.js приложений можно использовать любой модуль реализующий http сервер, даже встроенный в node.js модуль http прекрасно подойдет с минимальным написанием кода. В примере используется собственный модуль просто для удобства. Также я не буду заострять внимание на настройке Nginx - в данном случае это не важно, в крайнем случае по ссылке приведенной выше есть примеры конфига и описание директив.

7be5457113d3578c723b09c63fa1cb88.png
Описание
  1. Nginx получает запрос к /api
  2. Nginx выполняет подзапрос к /auth
  3. Auth берет данные из запроса и выполняет ряд действий в т.ч. (не обязательно) с использованием внешних модулей (например декодирование токена jwt полученного в cookie запроса )
  4. Внешний модуль возвращает данные в Auth
  5. Auth возвращает ответ в Nginx который полученные данные прикрепляет дополнительно к основному запросу ( auth_request_set)
  6. Nginx выполняет первоначальный запрос к API
  7. API читает дополнительные данные и по ним аутентифицирует запрос, возвращает ответ
Это в общих чертах. Перейдем к приложению. Для удобства и простоты демонстрации используется Json Web Token (JWT), в токене хранится только уникальный идентификатор пользователя (опять же для простоты пусть будет ID пользователя из БД, но никто не мешает сделать код сессии или хеш идентификатора пользователя или что то еще)

Сама обработка внутри AUTH - это последовательность методов, принимают на вход опции (о них ниже), результат вызова предыдущего, объекты модуля http : запроса (request) и ответа (response). Самый первый метод в качестве данных получает тоже request

Ссылка на код в конце статьи приведена.

После запуска и чтения конфигураций (./configs/.), идет построение для каждого пути (end-point) своей последовательности обработки из указанных методов и опций, передаваемых им. За подробностями можно посмотреть app.js метод BuildWorkers.
Все варианты методов - обработчиков расположены по пути ./workers/ и крайне просты. Например, конфигурационный файл ./configs/api.config.js

Первый метод reqprops с параметрами properties:['headers','apikey'] будет пытаться прочитать значение в свойствах с указанными именами, а именно - (на вход поступит request) request.headers, у полученного значения ищет заголовок с именем apikey. Если значение найдено - оно возвращается из метода и будет передано в следующий метод, согласно конфигурации - req. В свою очередь метод req получив первым аргументом options из конфига, выполнит запрос по указанному пути и порту передав второй аргумент (входные данные). В данном случае это и есть декодирование jwt. Полученные данные от внешнего сервиса декодирования jwt, модульвернет для обработки последующими модулями.

{
path: "/api/*",
workers:[
{
name:"reqprops",options:{properties:['headers','apikey']}
},
{
name:"req",
options:{port:jwt.port,path:jwt.decode_path,data:{secret: jwt_secret} }
},
{
name:"inlist",
options:{field:"id",list:["apidev"]}
},
{
name:"reqprops",options:{properties:['body']}
}
]
}
Дальше вызовется метод проверки наличие значения (в данном случае от jwt вернется объект с идентификатором в поле id) в списке разрешенных. Не обязательно держать в конфигурации список, здесь может быть такой же внешний запрос к другому модулю или к БД. И последним методом в цепочке стоит все тот же reqprops но уже с другим именем свойства: body которое будет прочитано и вернется. Поскольку дальше методов в конфигурации нет, это значение будет передано в следующий обработчик самого http сервера по этому пути - но это уже совсем другой модуль.

Поскольку в самом jwt получаемом от клиента хранится только ключ данных, то эти данные должны где то быть - за это отвечает класс Data (./data.js) И два метода: для чтения данных и записи. Внешний приложения могут в любой момент прочитать или записать эти данные через api приложения (./api/). В методах - воркерах есть метод getdata который и читает данные из Data передавая их дальше, и если следом поставить метод setdata - то эти самые данные и будут отправлены в nginx в ответ на запрос auth_request. Дальше уже в nginx эти данные устанавливаются в заголовке и читаются в конечном приложении обрабатывающем основной запрос.

Можно довольно много вариантов последовательностей задать исключительно через конфиги, но даже если будет этих воркеров недостаточно - легко добавить свой собственный. Вероятно редактирование конфигов через какую то визуальную реализацию было бы еще проще и удобнее.

Спасибо что дочитали, надеюсь подход/решение окажется кому то полезным.

 
Сверху