HTTP и HTTPS са интернет протоколи, които позволяват изпращане на данни по интернет чрез изпращане на заявка през уеб браузър. Тъй като те са без гражданство, всяка заявка, изпратена до браузъра, се третира независимо. Това означава, че браузърът не може да запомни източника на заявка, дори ако същият потребител го направи. HTTP сесиите решават този проблем.
Тази статия ще разгледа управлението на сесиите и как инструменти като Passport, Redis и MySQL могат да ни помогнат да управляваме сесиите на Node.js. Нека се потопим.
Как работят HTTP сесиите?
HTTP сесиите позволяват на уеб сървърите да поддържат потребителска идентичност и да съхраняват специфични за потребителя данни в множество взаимодействия на заявка/отговор между клиентско приложение и уеб приложение. Когато клиент влезе в приложението, сървърът генерира SessionID. Сесията се записва в паметта с помощта на един-сървър, нерепликиран механизъм за постоянно съхранение. Примери за такива механизми включват устойчивост на JDBC, устойчивост на файловата система, устойчивост на сесия, базирана на бисквитки, и репликация в паметта. Когато потребителят изпрати последваща заявка, sessionID се предава в заглавката на заявката и браузърът проверява дали идентификаторът съвпада с някое в паметта и предоставя на потребителя достъп до изтичане на сесията.
HTTP сесиите съхраняват следните данни в паметта:
- Подробности за сесията (идентификатор на сесията, време на създаване, време на последен достъп и т.н.)
- Контекстна информация за потребителя (например статус за влизане на клиента)
Какво е Redis?
Redis (сървър за отдалечен речник) е бързо, с отворен код, съхраняване на ключ-стойност в паметта, използвано като база данни, кеш, посредник на съобщения и опашка.
Redis има време за реакция под милисекунда, което позволява милиони заявки в секунда за приложения в реално време в индустрии като игри, рекламни технологии, финанси, здравеопазване и интернет на нещата. В резултат на това Redis сега е един от най-популярните двигатели с отворен код, след като е обявен за „Най-обичаната“ база данни от Stack Overflow пет години подред. Благодарение на бързата си производителност, Redis е популярен избор за кеширане, управление на сесии, игри, класации, анализи в реално време, геопространствени данни, придвижване, чат/съобщения, поточно предаване на медии и пъб/подприложения.
Какво изграждаме?
За да демонстрираме управлението на сесиите в Node.js, ще създадем просто приложение за регистрация и влизане. Потребителите ще се регистрират и ще влизат в това приложение, като предоставят своя имейл адрес и парола. Създава се сесия и се записва в магазина на Redis за бъдещи заявки, когато потребител влезе. Когато потребител излезе, ние ще изтрием неговата сесия. Стига приказки; нека да започнем!
Предпоставки
Този урок е практическа демонстрация. Уверете се, че имате инсталирано следното, преди да започнете:
- Node.js
- Redis CLI
- База данни MySQL
- Тип на дъга
Кодът за този урок е достъпен в моето хранилище на Github. Чувствайте се да клонирате и следвайте.
Настройка на проекта
Нека започнем със създаване на папка на проекта за приложението с командата по-долу:
mkdir Session_management && cd Session_management
След това инициализирайте приложение Node.js, за да създадете файл package.json с командата по-долу:
npm init -y
-y
флагът в горната команда казва на npm да използва конфигурацията по подразбиране. Сега създайте следната структура на папките в основната директория на вашия проект.
Със създаден нашия package.json, нека инсталираме необходимия пакет за този проект в следващия раздел.
Инсталиране на зависимости
Ще инсталираме следните зависимости за нашето приложение:
- Bcryptjs - Този модул ще се използва за хеширане на паролата на потребителя.
- Connect-redis - Този модул ще осигури съхранение на сесии на Redis за Express.
- Експресна сесия - Този модул ще се използва за създаване на сесии.
- Ejs - Този модул е нашата шаблонна машина
- Паспорт - Този модул ще се използва за удостоверяване на потребителя
- Паспорт-местен - Този модул ще се използва за локално удостоверяване на потребителско име и парола
- Sequelize - Този модул е нашият MySQL ORM за свързване на нашето приложение с MySQL база данни.
- Дотенв - Този модул ще се използва за зареждане на нашите променливи на средата.
Използвайте командата по-долу, за да инсталирате всички необходими зависимости.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Изчакайте инсталацията да завърши. След като инсталацията приключи, продължете с настройката на MySQL базата данни в следващия раздел.
Настройка на MySQL база данни
Ще създадем MySQL база данни за нашето приложение. Но преди това изпълнете командата по-долу, за да създадете потребителски акаунт в MySQL.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Сега създайте база данни session_db и дайте на новия потребител достъп до базата данни с командата по-долу:
#Create database
CREATE DATABASE session_db;
#grant access
GRANT ALL PRIVILEGES ON session_db TO 'newuser'@'localhost';
ALTER USER 'newuser'@'localhost' IDENTIFIED WITH mysql_native_password BY '1234';
Сега презаредете всички привилегии с командата по-долу:
FLUSH PRIVILEGES;
С настройката на нашата MySQL база данни, нека създадем нашите users
модел на база данни в следващия раздел.
Създаване на експресен сървър
С настройката на нашата MySQL база данни, нека създадем експресен сървър за нашето приложение. Отворете файла src/server.js и добавете кодовия фрагмент по-долу:
const express = require("express");
const app = express();
const PORT = 4300;
//app middleware
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
//Redis configurations
//Configure session middleware
//Router middleware
app.listen(PORT, () => {
console.log(`Server started at port ${PORT}`);
});
В горния кодов фрагмент създаваме експресен сървър, който ще слуша заявки на порт 4300. След това анализираме входящите заявки с JSON полезни натоварвания, използвайки express.json()
междинен софтуер и анализирайте входящите заявки с urlencoded
използвайки Express.urlencoded()
междинен софтуер.
Създайте модела на базата данни
В този момент нашият Express сървър е настроен. Сега ще създадем Users
модел за представяне на потребителските данни ще видим базата данни с помощта на Sequelize
. Отворете src/models/index.js
файл и добавете кодовия фрагмент по-долу.
const { Sequelize, DataTypes } = require("sequelize");
const sequelize = new Sequelize({
host: "localhost",
database: "session_db",
username: "newuser",
password: "1234",
dialect: "mysql",
});
exports.User = sequelize.define("users", {
// Model attributes are defined here
id: {
type: DataTypes.INTEGER,
autoIncrement: true,
primaryKey: true,
},
email: {
type: DataTypes.STRING,
},
password: {
type: DataTypes.STRING,
},
});
В горния кодов фрагмент импортираме Sequelize
и DateTypes
от sequelize
за да се свържете с нашата MySQL база данни и да присвоите тип данни на свойствата на нашия модел. След това се свързваме с MySQL, като създаваме sequelize
екземпляр от Sequelize
клас и предаване на идентификационни данни в нашата база данни. Например с sequelize
например дефинирахме нашия модел и неговите свойства. Искаме само полетата за идентификация, имейл и парола на този урок. Но sequelize създава две допълнителни полета, createdAt
и updatedAt
полета.
Настройте Passport и Redis
За да обработваме и съхраняваме идентификационните данни на нашия потребител, ще използваме и конфигурираме Redis
. За да направите това, отворете src/index.js
файл и импортирайте следните зависимости по-долу:
const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
След това намерете коментираната област //Redis configurations
и добавете кодовия фрагмент по-долу:
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
В горния кодов фрагмент установихме връзка с нашата база данни, която ще управлява данните за потребителското име на нашия потребител.
След това намерете коментираната област //Commented session middleware
и добавете кодовия фрагмент по-долу:
//Configure session middleware
const SESSION_SECRET = process.env.SESSION_SECRET;
app.use(
session({
store: new RedisStore({ client: redisClient }),
secret: SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: false, // if true only transmit cookie over https
httpOnly: false, // if true prevent client side JS from reading the cookie
maxAge: 1000 * 60 * 10, // session max age in milliseconds
},
})
);
app.use(passport.initialize());
app.use(passport.session());
В горния кодов фрагмент създадохме SESSION_SECRET
променлива в .env
файл, за да запази нашата тайна на сесията, след което създаде междинен софтуер за сесия и използва Redis като наш магазин. За да работи сесията, добавяме още два междинни софтуера passport.initialize()
и passport.session()
.
Създаване на контролери на приложения
С нашата настройка на Redis и експресна сесия ще създадем маршрут за обработка на информацията на потребителите. За да направите това, отворете src/controllers/index.js
файл и добавете кодовия фрагмент по-долу:
const { User } = require("../models");
const bcrypt = require("bcrypt");
exports.Signup = async (req, res) => {
try {
const { email, password } = req.body;
//generate hash salt for password
const salt = await bcrypt.genSalt(12);
//generate the hashed version of users password
const hashed_password = await bcrypt.hash(password, salt);
const user = await User.create({ email, password: hashed_password });
if (user) {
res.status(201).json({ message: "new user created!" });
}
} catch (e) {
console.log(e);
}
};
В горния кодов фрагмент импортираме bcrypt
и нашия User
модел, деструктурираме email
на потребителя и password
от req.body
обект. След това хеширахме паролата с bcrypt и създадохме нов потребител с помощта на sequelize create
метод.
След това създайте home page
, registration page
, login page
с кодовия фрагмент по-долу:
exports.HomePage = async (req, res) => {
if (!req.user) {
return res.redirect("/");
}
res.render("home", {
sessionID: req.sessionID,
sessionExpireTime: new Date(req.session.cookie.expires) - new Date(),
isAuthenticated: req.isAuthenticated(),
user: req.user,
});
};
exports.LoginPage = async (req, res) => {
res.render("auth/login");
};
exports.registerPage = async (req, res) => {
res.render("auth/register");
};
В HomePage
, ще изобразим някои от данните на удостоверения потребител заедно с home
изглед.
Накрая създайте logout
маршрут, за да изтриете данните за потребителското име на потребителя с кодовия фрагмент по-долу:
exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Създайте Passport стратегия
В този момент потребителите могат да се регистрират, да влизат и да излизат от нашето приложение. Сега, нека създадем стратегия за паспорти за удостоверяване на потребителите и създаване на сесия. За да направите това, отворете src/utils/passport.js
файл и добавете кодовия фрагмент по-долу:
const LocalStrategy = require("passport-local/lib").Strategy;
const passport = require("passport");
const { User } = require("../models");
const bcrypt = require("bcrypt");
module.exports.passportConfig = () => {
passport.use(
new LocalStrategy(
{ usernameField: "email", passwordField: "password" },
async (email, password, done) => {
const user = await User.findOne({ where: { email: email } });
if (!user) {
return done(null, false, { message: "Invalid credentials.\n" });
}
if (!bcrypt.compareSync(password, user.password)) {
return done(null, false, { message: "Invalid credentials.\n" });
}
return done(null, user);
}
)
);
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
const user = await User.findByPk(id);
if (!user) {
done(error, false);
}
done(null, user);
});
};
В горния кодов фрагмент импортираме passport
, bcrypt
, и нашия потребителски модел и създаваме междинен софтуер за паспорт, за да използваме local-strategy
. След това преименуваме името на файла по подразбиране на имената на полетата ( email
, password
) използваме за удостоверяване на потребителите. Сега проверяваме дали данните за потребителя съществуват в базата данни, преди да може да се създаде сесия за тях.
Passport.serialize
и passport.deserialize
Командите се използват за запазване на потребителския идентификатор като бисквитка в браузъра на потребителя и за извличане на идентификатора от бисквитката, когато е необходимо, която след това се използва за извличане на потребителска информация при обратно извикване.
done()
функцията е вътрешен passport.js
функция, която приема потребителския идентификатор като втори параметър.
Създайте маршрутите на приложението
Със създадената ни стратегия за паспорти, нека продължим със създаването на маршрути за нашите контролери. За да направите това, отворете src/routes/index.js
файл и добавете следния кодов фрагмент по-долу:
const express = require("express");
const {
Signup,
HomePage,
LoginPage,
registerPage,
Logout,
} = require("../controllers");
const passport = require("passport");
const router = express.Router();
router.route("/").get(LoginPage);
router.route("/register").get(registerPage);
router.route("/home").get(HomePage);
router.route("/api/v1/signin").post(
passport.authenticate("local", {
failureRedirect: "/",
successRedirect: "/home",
}),
function (req, res) {}
);
router.route("/api/v1/signup").post(Signup);
router.route("/logout").get(Logout);
module.exports = router;
В горния кодов фрагмент импортираме функциите на нашите контролери и създаваме маршрут за тях. За signin route
, ние използвахме passport.authenticate
метод за удостоверяване на потребителите с помощта на local
стратегия в настройката в предишния раздел.
Сега се върнете към нашия server.js
файл, ще създадем междинен софтуер за нашите маршрути. Преди това трябва да импортираме нашия router
и passportConfig
функция.
const router = require("./routes");
const { passportConfig } = require("./utils/passport");
След това ще извикаме passportConfig
функция точно под кода в областите, коментирани //Configure session middleware
.
passportConfig();
След това ще създадем междинния софтуер за маршрута веднага след коментираната област//Router middleware
.
app.use(router);
Създайте нашите изгледи на приложения
Със създадените ни маршрути ще създадем изгледи, изобразени в нашата HomePage
, LoginPage
и RegisterPage
контролери. Преди това ще настроим нашата машина за изглед на ejs във файла server.js с фрагмент от код по-долу точно под областта, коментирана //app middleware
.
app.set("view engine", "ejs");
След това ще започнем с началната страница, отворете views/home.ejs
файл и добавете следната маркировка.
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section>
<!-- As a heading -->
<nav class="navbar navbar-light bg-light">
<div class="container-fluid">
<a class="navbar-brand">Navbar</a>
<% if(isAuthenticated){ %>
<a href="/logout" class="btn btn-danger btn-md">Logout</a>
<% } %>
</div>
</nav>
<div class="">
<p class="center">
Welcome: <b><%= user.email %></b> your sessionID is <b><%= sessionID %></b>
</p>
<p>Your session expires in <b><%= sessionExpireTime %></b> seconds</p>
</div>
</section>
</body>
</html>
Тук, в нашата начална страница, използвахме bootstrap, за да добавим малко стил към нашите маркировки. След това проверяваме дали потребителят е удостоверен, за да покаже бутона за излизане. Също така показваме Email
на потребителя , sessionID
и ExpirationTime
от бекенда.
След това отворете src/views/auth/resgister
и добавете следната маркировка по-долу за страницата на регистъра.
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section class="vh-100" style="background-color: #9a616d">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-xl-10">
<div class="card" style="border-radius: 1rem">
<div class="row g-0">
<div class="col-md-6 col-lg-5 d-none d-md-block">
<img
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
alt="login form"
class="img-fluid"
style="border-radius: 1rem 0 0 1rem"
/>
</div>
<div class="col-md-6 col-lg-7 d-flex align-items-center">
<div class="card-body p-4 p-lg-5 text-black">
<form action="api/v1/signup" method="post">
<h5
class="fw-normal mb-3 pb-3"
style="letter-spacing: 1px"
>
Signup into your account
</h5>
<div class="form-outline mb-4">
<input
name="email"
type="email"
id="form2Example17"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example17"
>Email address</label
>
</div>
<div class="form-outline mb-4">
<input
name="password"
type="password"
id="form2Example27"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example27"
>Password</label
>
</div>
<div class="pt-1 mb-4">
<button
class="btn btn-dark btn-lg btn-block"
type="submit"
>
Register
</button>
</div>
<a class="small text-muted" href="#!">Forgot password?</a>
<p class="mb-5 pb-lg-2" style="color: #393f81">
Don't have an account?
<a href="/" style="color: #393f81">Login here</a>
</p>
<a href="#!" class="small text-muted">Terms of use.</a>
<a href="#!" class="small text-muted">Privacy policy</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
В страницата за регистрация създадохме html формуляр, за да приемем данните за потребителите. Във формуляра добавяме и активния атрибут и указваме крайната точка за регистрация. Това означава, че когато потребител щракне върху бутона за изпращане, заявка ще бъде изпратена до /api/v1/signup
крайна точка.
Накрая отворете src/views/auth/signin.js
файл и добавете следния фрагмент за маркиране по-долу:
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
<link
href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css"
rel="stylesheet"
integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC"
crossorigin="anonymous"
/>
</head>
<body>
<section class="vh-100" style="background-color: #9a616d">
<div class="container py-5 h-100">
<div class="row d-flex justify-content-center align-items-center h-100">
<div class="col col-xl-10">
<div class="card" style="border-radius: 1rem">
<div class="row g-0">
<div class="col-md-6 col-lg-5 d-none d-md-block">
<img
src="https://mdbcdn.b-cdn.net/img/Photos/new-templates/bootstrap-login-form/img1.webp"
alt="login form"
class="img-fluid"
style="border-radius: 1rem 0 0 1rem"
/>
</div>
<div class="col-md-6 col-lg-7 d-flex align-items-center">
<div class="card-body p-4 p-lg-5 text-black">
<form action="api/v1/signin" method="post">
<h5
class="fw-normal mb-3 pb-3"
style="letter-spacing: 1px"
>
Sign into your account
</h5>
<div class="form-outline mb-4">
<input
name="email"
type="email"
id="form2Example17"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example17"
>Email address</label
>
</div>
<div class="form-outline mb-4">
<input
name="password"
type="password"
id="form2Example27"
class="form-control form-control-lg"
/>
<label class="form-label" for="form2Example27"
>Password</label
>
</div>
<div class="pt-1 mb-4">
<button
class="btn btn-dark btn-lg btn-block"
type="submit"
>
Login
</button>
</div>
<a class="small text-muted" href="#!">Forgot password?</a>
<p class="mb-5 pb-lg-2" style="color: #393f81">
Don't have an account?
<a href="/register" style="color: #393f81"
>Register here</a
>
</p>
<a href="#!" class="small text-muted">Terms of use.</a>
<a href="#!" class="small text-muted">Privacy policy</a>
</form>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
</body>
</html>
В горната маркировка добавихме html формуляр, който ще се използва за влизане на потребител чрез изпращане на заявка до /api/v1/signin
крайна точка.
Преглед на потребителските данни с Arctype
Вече успешно създадохме приложение за управление на сесии Node.js. Нека да разгледаме данните на потребителите с Arctype. За да започнете, стартирайте Arctype, щракнете върху раздела MySQL и въведете следните идентификационни данни за MySQL, както е показано на екранната снимка по-долу:
След това щракнете върху users
таблица за показване на регистрираните потребители, както е показано на екранната снимка по-долу:
Заключение
Чрез изграждането на демонстрационно приложение за влизане научихме как да внедрим управление на сесиите в Node.js с помощта на Passport и Redis. Започнахме с въвеждането на HTTP сесиите и как те работят, след това разгледахме какво представлява Redis и създадохме проект, за да приложим всичко това на практика. Сега, когато имате знанията, които търсите, как бихте удостоверили проектите на потребителите?