Аутентификация между серверов на node js. Маршруты Index и Users. Что такое Redis

Так же как аутентификация важна для API (* Application Programming Interface - программный интерфейс приложения. Здесь и далее примеч. пер.), она является важной особенностью определенных веб-приложений - тех, у которых имеются страницы и секреты, к которым должен быть доступ только у пользователей, прошедших регистрацию и аутентификацию.

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

Установка приложения

Создайте новую папку, в которой вы будете работать. В соответствии с темой данного руководства я назвал мою site-auth . Инициализируйте проект в только что созданной папке. Ниже показано, как вы можете это сделать.

Npm init -y

Флаг -y сообщает npm (* менеджер пакетов для JavaScript) о необходимости использования опций по умолчанию.

Отредактируйте часть файла package.json так, чтобы она выглядела, как моя.

#package.json { "name": "site-auth", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": , "author": "izuchukwu1", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.17.1", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.3", "express": "^4.15.2", "express-handlebars": "^3.0.0", "express-messages": "^1.0.1", "express-session": "^1.15.2", "joi": "^13.0.1", "mongoose": "^4.11.12", "morgan": "^1.8.1", "passport": "^0.4.0", "passport-local": "^1.0.0" } }

После этого запустите команду для установки пакетов.

Npm install

Создайте файл в вашей рабочей папке под названием app.js.

Начните с запрашивания зависимостей, которые вы установили, и необходимых файлов.

#app.js const express = require("express"); const morgan = require("morgan") const path = require("path"); const cookieParser = require("cookie-parser"); const bodyParser = require("body-parser"); const expressHandlebars = require("express-handlebars"); const flash = require("connect-flash"); const session = require("express-session"); const mongoose = require("mongoose") const passport = require("passport") require("./config/passport")

Эти зависимости были установлены после выполнения команды npm install. Для того чтобы их использовать в вашем приложении вам необходимо запросить и сохранить их в соответствующие константы.

В данном руководстве вы будете использовать в качестве базы данных MongoDB. Вам необходимо будет сохранить информацию о пользователе в базу данных. Для работы с MongoDB вы будете использовать Mongoose - инструмент для моделирования в Node.js (* ODM (Object Document Mapper - объектно-документный отобразитель)).Настройка Mongoose не вызывает трудностей. Выполняется следующим образом:

#app.js mongoose.Promise = global.Promise mongoose.connect("mongodb://localhost:27017/site-auth")

На данном этапе давайте настроим наше промежуточное ПО (* программное обеспечение).

// 1 const app = express() app.use(morgan("dev")) // 2 app.set("views", path.join(__dirname, "views")) app.engine("handlebars", expressHandlebars({ defaultLayout: "layout" })) app.set("view engine", "handlebars") // 3 app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, "public"))) app.use(session({ cookie: { maxAge: 60000 }, secret: "codeworkrsecret", saveUninitialized: false, resave: false })); app.use(passport.initialize()) app.use(passport.session()) // 4 app.use(flash()) app.use((req, res, next) => { res.locals.success_mesages = req.flash("success") res.locals.error_messages = req.flash("error") next() }) // 5 app.use("/", require("./routes/index")) app.use("/users", require("./routes/users")) // 6 // catch 404 and forward to error handler app.use((req, res, next) => { res.render("notFound") }); // 7 app.listen(5000, () => console.log("Server started listening on port 5000!"))

  • Результат инициализации Express присваивается константе app .
  • Происходит настройка промежуточного ПО, которое будет заниматься представлениями. Для создания представлений вы будете использовать handlebars .
  • Вы настроили промежуточное ПО для bodyparser , cookie , session и passport . Passport будет использоваться, когда пользователи хотят войти в приложение.
  • В определенные моменты вы будете отображать флэш-сообщения. Поэтому вам необходимо настроить промежуточное ПО для этого и создать необходимый вам тип флэш-сообщений.
  • Промежуточное ПО для настройки маршрутов. Будет обрабатывать любой запрос, сделанный к пути URL (Uniform Resource Locator - унифицированный указатель [местонахождения информационного] ресурса). Здесь указаны пути URL для путей, указанных в файлах index и users.
  • Промежуточное ПО для обработки ошибок 404. Данное промежуточное ПО начинает действовать, если запрос не соответствует никакому из вышеуказанных промежуточному ПО.
  • Сервер настраивается на прослушивание запросов по 5000 порту.
  • Настройка представлений

    views (* представления). Внутри нее создайте две другие папки под названиями layouts и partials . Сформируйте подобную древовидную структуру в папке views. Для этого создайте необходимые файлы в их соответствующих папках.

    ├── dashboard.handlebars ├── index.handlebars ├── layouts │ └── layout.handlebars ├── login.handlebars ├── notFound.handlebars ├── partials │ └── navbar.handlebars └── register.handlebars

    После этого пришло время писать код.

    #dashboard.handlebars User DashBoard

    Это - инструментальная панель, которая должна быть видна только зарегистрированным пользователям. В данном руководстве это будет ваша секретная страница.

    #index.handlebars Site Authentication!

    Welcome aboard.

    Для приложения необходим макет. Ниже приведен макет, который будете использовать.

    #layout/layout.handlebars Site Authentication {{#if success_messages }} {{success_messages}} {{/if}} {{#if error_messages }} {{error_messages}} {{/if}} {{> navbar}} {{{body}}}

    Вам понадобится страница входа для зарегистрированных пользователей.

    #views/login.handlebars Please sign in Email address Password
    Sign in

    Файл notFound .handlebars будет использоваться в качестве страницы для показа ошибок.

    #views/notFound.handlebars Error

    Ваша страница для регистрации должна выглядеть следующим образом.

    Please sign up Email address Username Password Confirm Password
    Sign up

    Последний файл для ваших представлений с кодом навигационной панели приводится ниже.

    #partials/navbar.handlebars Site Authentication

    После этого вы готовы к рассмотрению некоторых сложных частей приложения.

    Проверка данных

    Вам необходима будет модель User. Исходя из кода для представлений выше, вы можете прийти к заключению, что для модели User необходимы свойства email, username и password. Создайте папку под названием models и файл в ней под названием user.js .

    #models/user.js // 1 const mongoose = require("mongoose") const Schema = mongoose.Schema const bcrypt = require("bcryptjs") // 2 const userSchema = new Schema({ email: String, username: String, password: String }, { // 3 timestamps: { createdAt: "createdAt", updatedAt: "updatedAt" } }) // 4 const User = mongoose.model("user", userSchema) module.exports = User

  • Происходит импорт зависимостей и их сохранение в переменных.
  • Создается новая схема. Для каждого пользователя вы сохраняете email , username и password в базу данных. Схема описывает, как должна быть создана модель для каждого документа. Здесь вы указываете, что типом адреса электронной почты, имени пользователя пароль должен быть String.
  • Также для каждого пользователя вы хотите создать timestamps (* время создания/модификации файла). Вы используете Mongoose для получения значений createdAt и updatedAt . Затем сохраняете их в базу данных.
  • Определяется модель, и результат присваивается константе под названием User , которая потом экспортируется в качестве модуля. Поэтому ее можно использовать в других частях приложения.
  • Подсаливание и хеширование пароля

    Не стоит сохранять пароли пользователей в незашифрованном виде. Вот что следует сделать, когда пользователь вводит пароль в незашифрованном виде при регистрации. Пароль в незашифрованном виде должен быть захеширован при помощи соли (* в криптографии - случайное число или текст, которые добавляются к данным, шифруемым с помощью пароля), которая будет сгенерирована вашим приложением (используя модуль bcryptjs). Этот захешированный пароль затем сохраняется в базу данных.

    Звучит замечательно, неправда ли? Давайте реализуем это в файле user.js

    #models/user.js module.exports.hashPassword = async (password) => { try { const salt = await bcrypt.genSalt(10) return await bcrypt.hash(password, salt) } catch(error) { throw new Error("Hashing failed", error) } }

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

    Маршруты Index и Users

    Создайте новую папку под названием routes . В этой новой папке создайте два новых файла: index.js и users.js .

    Файл index.js будет очень простым. В нем будет вызываться на исполнение представление index вашего приложения. Помните, как вы настроили промежуточное ПО для ваших маршрутов в вашем файле app.js , когда вы сделали следующее:

    App.use("/", require("./routes/index")) app.use("/users", require("./routes/users"))

    Итак, ваш маршрут в файле index.js, просто исполняющий страницу index, должен выглядеть следующим образом:

    #routes/index.js const express = require("express") const router = express.Router() router.get("/", (req, res) => { res.render("index") }) module.exports = router

    Теперь переходим к маршруту users. Пока что в этом файле для маршрута будут выполняться четыре действия:

  • Запрос зависимостей. Вам необходимо будет запросить зависимости, которые вы установили при помощи npm.
  • Проверка пользовательских данных. Убедитесь, что пользователь не прислал пустую форму. Все поля должны быть заполнены. Введенные данные должны иметь тип String. Адрес электронной почты проходит специальную проверку при помощи метода.email() , благодаря которому гарантируется, что введенные пользователем данные соответствуют формату адреса электронной почты. Пароль же проверяется при помощи регулярного выражения. В пароле подтверждения следует сохранить то же значение, что и во введенном пароле. Эти проверки осуществляются при помощи модуля Joi .
  • При поступлении запросов по методу GET исполняется страница регистрации, в то время как запросы по методу POST поступают после того, как пользователь нажимает кнопку для отправки формы.
  • Маршрутизатор экспортируется в виде модуля.
  • Вот как выглядит код.

    #routes/users.js const express = require("express"); const router = express.Router() const Joi = require("joi") const passport = require("passport") const User = require("../models/user") //validation schema const userSchema = Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), password: Joi.string().regex(/^{6,30}$/).required(), confirmationPassword: Joi.any().valid(Joi.ref("password")).required() }) router.route("/register") .get((req, res) => { res.render("register") }) .post(async (req, res, next) => { try { const result = Joi.validate(req.body, userSchema) if (result.error) { req.flash("error", "Data entered is not valid. Please try again.") res.redirect("/users/register") return } const user = await User.findOne({ "email": result.value.email }) if (user) { req.flash("error", "Email is already in use.") res.redirect("/users/register") return } const hash = await User.hashPassword(result.value.password) delete result.value.confirmationPassword result.value.password = hash const newUser = await new User(result.value) await newUser.save() req.flash("success", "Registration successfully, go ahead and login.") res.redirect("/users/login") } catch(error) { next(error) } }) module.exports = router

    Давайте рассмотрим подробнее, что происходит при поступлении запросов по методу POST .

    Значения, введенные в форме для регистрации, доступны через свойство req.body и выглядят подобным образом:

    Value: { email: "[email protected]", username: "izu", password: "chinedu", confirmationPassword: "chinedu" },

    Это свойство проверяется с помощью схемы userSchema , которую вы создали ранее. Введенные пользователем значения присваиваются константе под названием result.

    В случае ошибки при проверке данных сообщение об ошибке отображается пользователю и происходит перенаправление пользователя на страницу регистрации.

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

    В ином случае следующим этапом является хеширование пароля. В этот момент вы и вызываете метод hashPassword , который определили в вашем файле user.js. Новый захешированный пароль присваивается константе hash.

    Нет необходимости в сохранении confirmationPassword в базу данных. Поэтому это свойство удаляется. Доступный в переменной пароль по прежнему незашифрован. Поскольку не следует сохранять незашифрованный пароль в вашей базе данных, важно заново присвоить в качестве значения пароля созданный ранее хеш. Это осуществляется при помощи следующей строки кода:

    Result.value.password = hash

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

    Запустите ваш сервер, выполнив в консоли команду:

    Node app.js

    Перейдите в вашем браузере на страницу http://localhost:5000 , и вы должны будете увидеть ваше новое приложение.

    Заключение

    Теперь вы знаете, как реализовать возможность регистрации в Node-приложениях. Вы поняли важность проверки пользовательских данных и процесс ее осуществления при помощи Joi. Также вы использовали модуль bcryptjs для подсаливания и хеширования вашего пароля.

    В этой статье я попробую рассказать о том, как с помощью 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.pattern; var restricted = urls.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));

    Все. Надеюсь, это кому-нибудь поможет.

    Теги: Добавить метки

    В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.

    This article was translated to Russian by Andrey Melikhov, a front-end developer from Yandex.Money and editor of the collective blog about front-end, devSchacht . Find Andrey on: Twitter , GitHub , Medium & SoundCloud

    Перевод этой статьи сделан Андреем Мелиховым, фронтенд-разработчиком из компании Яндекс.Деньги, редактором коллективного блога о фронтенде, devSchacht . Twitter | GitHub | Medium | SoundCloud



    Технологии, которые мы будем использовать

    Прежде чем перейти к написанию кода, давайте рассмотрим новые технологии, которые мы будем использовать в этой главе.

    Что такое Passport.js?

    Простая, ненавязчивая аутентификация для Node.js - passportjs.org

    Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.

    Что такое Redis?

    Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений - redis.io

    Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.

    Демонстрационное приложение

    Для демонстрационных целей создадим приложение, которое умеет только следующее:

    • предоставляет форму входа
    • предоставляет две защищённые страницы:
      • страницу профиля
      • безопасные заметки
    Структура проекта

    Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!

    Мы собираемся использовать следующую структуру:

    ├── app | ├── authentication | ├── note | ├── user | ├── index.js | └── layout.hbs ├── config | └── index.js ├── index.js └── package.json

    Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.

    Процесс аутентификации в Node.js

    Наша цель - реализовать в нашем приложении следующий процесс аутентификации:

  • Пользователь вводит имя и пароль
  • Приложение проверяет, являются ли они корректными
  • Если имя и пароль корректны, приложение отправляет заголовок Set-Cookie , который будет использоваться для аутентификации дальнейших страниц
  • Когда пользователь посещает страницы в том же домене, ранее установленный cookie будет добавлен ко всем запросам
  • Аутентификация на закрытых страницах происходит с помощью этого файла cookie
  • Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:

  • Настройте Express
  • Настройте Passport
  • Добавьте защищённые разделы на сайте
  • Шаг 1: Настройка Express

    Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».

    // file:app/index.js const express = require("express") const passport = require("passport") const session = require("express-session") const RedisStore = require("connect-redis")(session) const app = express() app.use(session({ store: new RedisStore({ url: config.redisStore.url }), secret: config.redisStore.secret, resave: false, saveUninitialized: false })) app.use(passport.initialize()) app.use(passport.session())

    Что мы тут делаем?

    Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.

    Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.

    Шаг 2: Настройка Passport

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

    Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.

    File:app/authenticate/init.js const passport = require("passport") const LocalStrategy = require("passport-local").Strategy const user = { username: "test-user", password: "test-password", id: 1 } passport.use(new LocalStrategy(function(username, password, done) { findUser(username, function (err, user) { if (err) { return done(err) } if (!user) { return done(null, false) } if (password !== user.password) { return done(null, false) } return done(null, user) }) }))

    Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.

    Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).

    Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.

    Шаг 3: Добавляем защищённые разделы на сайте

    Чтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:

    // file:app/user/init.js const passport = require("passport") app.get("/profile", passport.authenticationMiddleware(), renderProfile)

    Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.

    Использование этой middleware также просто, как добавление любой другой middleware в определение роута.

    // file:app/authentication/middleware.js function authenticationMiddleware () { return function (req, res, next) { if (req.isAuthenticated()) { return next() } res.redirect("/") } }

    Резюме

    В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .

    Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication

    Следующая глава Node Hero будет посвящена юнит-тестированию Node.js приложений. Вы узнаете такие концепции, как юнит-тестирование, пирамида тестирования, дублёры и многое другое!

    Gergely Nemeth

    Co-founder of RisingStack

    Please enable JavaScript to view the Node Hero: Глава 8

    В этой главе вы узнаете, как реализовать стратегию локальной аутентификации в Node.js приложении с использованием Passport.js и Redis.

    Технологии, которые мы будем использовать

    Прежде чем перейти к написанию кода, давайте рассмотрим новые технологии, которые мы будем использовать в этой главе.

    Что такое Passport.js?

    Простая, ненавязчивая аутентификация для Node.js -  passportjs.org

    Passport - это middleware для проверки подлинности, которую мы собираемся использовать для управления сессиями.

    Что такое Redis?

    Redis это опенсорс (лицензии BSD) хранилище структур данных в оперативной памяти, используемое как база данных, кэш и брокер сообщений -  redis.io

    Мы собираемся хранить информацию о сессии пользователя в Redis, а не в памяти процесса. Таким образом, наше приложение будет намного проще масштабировать.

    Демонстрационное приложение

    Для демонстрационных целей создадим приложение, которое умеет только следующее:

    • предоставляет форму входа
    • предоставляет две защищённые страницы:
    • страницу профиля
    • безопасные заметки
    Структура проекта

    Вы уже научились структурировать Node.js-проекты в предыдущей главе Node Hero, поэтому давайте использовать эти знания!

    Мы собираемся использовать следующую структуру:

    ├── app
    | ├── authentication
    | ├── note
    | ├── user
    | ├── index.js
    | └── layout.hbs
    ├── config
    | └── index.js
    ├── index.js
    └── package.json

    Как вы можете видеть, мы организуем файлы и каталоги вокруг функций. У нас будет страница пользователя, страница заметок и некоторые функции, связанные с проверкой подлинности.

    (Вы можете скачать исходный код по ссылке https://github.com/RisingStack/nodehero-authentication )

    Процесс аутентификации в Node.js

    Наша цель - реализовать в нашем приложении следующий процесс аутентификации:

  • Пользователь вводит имя и пароль
  • Приложение проверяет, являются ли они корректными
  • Если имя и пароль корректны, приложение отправляет заголовок Set-Cookie , который будет использоваться для аутентификации дальнейших страниц
  • Когда пользователь посещает страницы в том же домене, ранее установленный cookie будет добавлен ко всем запросам
  • Аутентификация на закрытых страницах происходит с помощью этого файла cookie
  • Чтобы настроить такую стратегию аутентификации, выполните следующие три действия:

  • Настройте Express
  • Настройте Passport
  • Добавьте защищённые разделы на сайте
  • Шаг 1: Настройка Express

    Мы будем использовать Express для серверной среды - вы можете узнать больше на эту тему, перечитав главу «Ваш первый сервер на Node.js».

    // file:app/index.js
    const express = require("express")

    const session = require("express-session")
    const RedisStore = require("connect-redis")(session) const app = express()
    app.use(session({
    store: new RedisStore({
    url: config.redisStore.url
    }),
    secret: config.redisStore.secret,
    resave: false,
    saveUninitialized: false
    }))
    app.use(passport.initialize())
    app.use(passport.session())

    Что мы тут делаем?

    Прежде всего, нам нужны все зависимости, которые требуются для управления сессией. После этого мы создали новый экземпляр из модуля express-session , который будет хранить наши сессии.

    Для хранения сессий мы используем Redis, но вы можете использовать любые другие, такие как MySQL или MongoDB.

    Шаг 2: Настройка Passport

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

    Для простоты в этом примере (см. ниже) мы не используем базу данных, вместо неё используется экземпляр объекта пользователя в памяти. В реальных приложениях findUser будет искать пользователя в базе данных.

    // file:app/authenticate/init.js
    const passport = require("passport")
    const LocalStrategy = require("passport-local").Strategy const user = {
    username: "test-user",
    password: "my-password",
    id: 1
    }
    passport.use(new LocalStrategy(
    function(username, password, done) {
    findUser(username, function (err, user) {
    if (err) {
    return done(err)
    }
    if (!user) {
    return done(null, false)
    }
    if (password !== user.password) {
    return done(null, false)
    }
    return done(null, user)
    })
    }
    ))

    Как только findUser возвращается с нашим объектом пользователя, остаётся только сравнить введённый пользователем и реальный пароль, чтобы увидеть, есть ли совпадение.

    Если они совпадают, мы разрешаем пользователю войти (возвращая объект пользователь в passport - return done(null, user)), если нет - возвращаем ошибку авторизации(путём возврата null - return done(null)).

    Примечание переводчика: В настоящих приложениях стараются никогда не хранить пароли пользователей в открытом виде. В базу данных записывают хэш пароля и сравнивают его с хэшом значения, введённого пользователем.

    Шаг 3: Добавляем защищённые разделы на сайте

    Чтобы добавить защищенные разделы на сайт, мы используем шаблон middleware Express. Для этого сначала создадим middleware для аутентификации:

    // file:app/user/init.js
    const passport = require("passport") app.get("/profile", passport.authenticationMiddleware(), renderProfile)

    Она имеет только одну задачу - если пользователь аутентифицирован (имеет правильные cookie-файлы), она просто вызывает следующую middleware. В противном случае пользователь перенаправляется на страницу, где он может войти в систему.

    Использование этой middleware также просто, как добавление любой другой middleware в определение роута.

    // file:app/authentication/middleware.js
    function authenticationMiddleware () {
    return function (req, res, next) {
    if (req.isAuthenticated()) {
    return next()
    }
    res.redirect("/")
    }
    }

    Резюме

    В этом разделе учебника по Node.js вы узнали, как добавить базовую аутентификацию в ваше приложение. Позже вы можете расширить его с помощью различных стратегий аутентификации, таких как Facebook или Twitter. Вы можете найти больше стратегий по адресу http://passportjs.org/ .

    Полный рабочий пример нашего демонстрационного приложения вы можете найти на GitHub: https://github.com/RisingStack/nodehero-authentication

    Следующая глава Node Hero будет посвящена юнит-тестированию Node.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.pattern; var restricted = urls.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));

    Все. Надеюсь, это кому-нибудь поможет.

    Есть вопросы?

    Сообщить об опечатке

    Текст, который будет отправлен нашим редакторам: