HTTP en HTTPS zijn internetprotocollen waarmee gegevens via internet kunnen worden verzonden door een verzoek via een webbrowser te verzenden. Omdat ze stateless zijn, wordt elk verzoek dat naar de browser wordt verzonden, onafhankelijk behandeld. Dit betekent dat de browser de bron van een verzoek niet kan onthouden, zelfs niet als dezelfde gebruiker het doet. HTTP-sessies lossen dit probleem op.
Dit artikel gaat in op sessiebeheer en hoe tools zoals Passport, Redis en MySQL ons kunnen helpen bij het beheren van Node.js-sessies. Laten we erin duiken.
Hoe werken HTTP-sessies?
HTTP-sessies stellen webservers in staat om de gebruikersidentiteit te behouden en gebruikersspecifieke gegevens op te slaan over meerdere verzoek-/antwoordinteracties tussen een client-app en een web-app. Wanneer een client zich aanmeldt bij de toepassing, genereert de server een SessionID. De sessie wordt in het geheugen opgeslagen met behulp van een niet-gerepliceerd permanent opslagmechanisme op één server. Voorbeelden van dergelijke mechanismen zijn JDBC-persistentie, bestandssysteempersistentie, op cookies gebaseerde sessiepersistentie en replicatie in het geheugen. Wanneer de gebruiker een volgend verzoek verzendt, wordt de sessionID doorgegeven in de verzoekheader en controleert de browser of de ID overeenkomt met een van de in de geheugenopslag en verleent de gebruiker toegang totdat de sessie verloopt.
HTTP-sessies slaan de volgende gegevens op in het geheugen:
- Specificaties over de sessie (sessie-ID, aanmaaktijd, laatste keer geopend, enz.)
- Contextuele informatie over de gebruiker (bijvoorbeeld de aanmeldingsstatus van de klant)
Wat is Redis?
Redis (Remote Dictionary Server) is een snelle, open-source, in-memory key-value datastore die wordt gebruikt als database, cache, berichtenmakelaar en wachtrij.
Redis heeft responstijden van minder dan een milliseconde, waardoor miljoenen verzoeken per seconde mogelijk zijn voor realtime toepassingen in sectoren zoals gaming, ad-tech, financiën, gezondheidszorg en IoT. Als gevolg hiervan is Redis nu een van de meest populaire open-source-engines, die vijf jaar op rij door Stack Overflow is uitgeroepen tot de "Most Loved"-database. Vanwege de snelle prestaties is Redis een populaire keuze voor caching, sessiebeheer, gaming, leaderboards, realtime analyses, geospatial, ride-hailing, chat/messaging, mediastreaming en pub/sub-apps.
Wat zijn we aan het bouwen?
Om sessiebeheer in Node.js te demonstreren, zullen we een eenvoudige aanmeldings- en aanmeldingstoepassing maken. Gebruikers zullen zich aanmelden voor en inloggen op deze applicatie door hun e-mailadres en wachtwoord op te geven. Er wordt een sessie gemaakt en opgeslagen in de Redis-winkel voor toekomstige verzoeken wanneer een gebruiker zich aanmeldt. Wanneer een gebruiker uitlogt, zullen we zijn sessie verwijderen. Genoeg gepraat; laten we beginnen!
Vereisten
Deze tutorial is een praktische demonstratie. Zorg ervoor dat u het volgende hebt geïnstalleerd voordat u aan de slag gaat:
- Node.js
- Redis CLI
- MySQL-database
- Arctype
De code voor deze tutorial is beschikbaar op mijn Github-repository. Voel om te klonen en volg mee.
Projectconfiguratie
Laten we beginnen met het maken van een projectmap voor de applicatie met de onderstaande opdracht:
mkdir Session_management && cd Session_management
Initialiseer vervolgens een Node.js-toepassing om een package.json-bestand te maken met de onderstaande opdracht:
npm init -y
De -y
vlag in de bovenstaande opdracht vertelt npm om de standaardconfiguratie te gebruiken. Maak nu de volgende mappenstructuur in de hoofdmap van uw project.
Nu ons package.json is gemaakt, laten we het vereiste pakket voor dit project in de volgende sectie installeren.
Afhankelijkheden installeren
We zullen de volgende afhankelijkheden voor onze applicatie installeren:
- Bcryptjs - Deze module wordt gebruikt om het wachtwoord van de gebruiker te hashen.
- Connect-redis - Deze module biedt Redis-sessieopslag voor Express.
- Express-sessie - Deze module wordt gebruikt om sessies te maken.
- Ejs - Deze module is onze template engine
- Paspoort - Deze module wordt gebruikt voor gebruikersauthenticatie
- Paspoort-lokaal - Deze module wordt gebruikt voor lokale gebruikersnaam en wachtwoordverificatie
- Vervolg - Deze module is onze MySQL ORM om onze applicatie te verbinden met de MySQL-database.
- Dotenv - Deze module wordt gebruikt om onze omgevingsvariabelen te laden.
Gebruik de onderstaande opdracht om alle vereiste afhankelijkheden te installeren.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Wacht tot de installatie is voltooid. Zodra de installatie is voltooid, gaat u verder met het instellen van de MySQL-database in de volgende sectie.
MySQL-database instellen
We zullen een MySQL-database maken voor onze applicatie. Maar voer eerst de onderstaande opdracht uit om een MySQL-gebruikersaccount aan te maken.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Maak nu een database session_db en verleen de nieuwegebruiker toegang tot de database met het onderstaande commando:
#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';
Laad nu alle privileges opnieuw met de onderstaande opdracht:
FLUSH PRIVILEGES;
Laten we met onze MySQL-databaseconfiguratie onze users
. maken databasemodel in de volgende sectie.
Express-server maken
Laten we met onze MySQL-databaseconfiguratie een expressserver voor onze applicatie maken. Open het bestand src/server.js en voeg het onderstaande codefragment toe:
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}`);
});
In het bovenstaande codefragment maken we een express-server die luistert naar verzoeken op poort 4300. Vervolgens parsen we de inkomende verzoeken met JSON-payloads met behulp van de express.json()
middleware en ontleden inkomende verzoeken met urlencoded
met behulp van Express.urlencoded()
middleware.
Maak het databasemodel
Op dit punt is onze Express-server ingesteld. Nu maken we een Users
model om de gebruikersgegevens weer te geven, we zullen de database zien met behulp van Sequelize
. Open de src/models/index.js
bestand en voeg het onderstaande codefragment toe.
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,
},
});
In het bovenstaande codefragment importeren we Sequelize
en DateTypes
van sequelize
om verbinding te maken met onze MySQL-database en een gegevenstype toe te wijzen aan onze modeleigenschappen. Vervolgens maken we verbinding met MySQL door een sequelize
. te maken instantie uit de Sequelize
klasse en het doorgeven van onze databasereferenties. Bijvoorbeeld met de sequelize
we hebben bijvoorbeeld ons model en zijn eigenschappen gedefinieerd. We willen alleen de id-, e-mail- en wachtwoordvelden van deze tutorial. Maar sequelize creëert twee extra velden, de createdAt
, en updatedAt
velden.
Paspoort en Redis instellen
Om de inloggegevens van onze gebruikers te verwerken en op te slaan, gebruiken en configureren we Redis
. Open hiervoor de src/index.js
bestand en importeer de volgende afhankelijkheden hieronder:
const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
Zoek vervolgens het gebied met commentaar //Redis configurations
en voeg het onderstaande codefragment toe:
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
In het bovenstaande codefragment hebben we een verbinding tot stand gebracht met onze database, die de gebruikersnaamgegevens van onze gebruikers zal beheren.
Zoek vervolgens het gebied met commentaar //Commented session middleware
en voeg het onderstaande codefragment toe:
//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());
In het bovenstaande codefragment hebben we een SESSION_SECRET
. gemaakt variabele in een .env
bestand om onze sessie geheim te houden, maakte vervolgens een sessie-middleware en gebruikte Redis als onze winkel. Om de sessie te laten werken, voegen we nog twee middlewares toe, de passport.initialize()
, en passport.session()
.
Applicatiecontrollers maken
Met onze Redis- en express-sessieconfiguratie creëren we een route om gebruikersinformatie te verwerken. Open hiervoor de src/controllers/index.js
bestand en voeg het onderstaande codefragment toe:
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);
}
};
In het bovenstaande codefragment importeren we bcrypt
en onze User
model, we destructureren de email
van de gebruiker en password
van de req.body
voorwerp. Vervolgens hebben we het wachtwoord gehasht met behulp van bcrypt en een nieuwe gebruiker gemaakt met behulp van de sequelize create
methode.
Maak vervolgens een home page
, registration page
, login page
met onderstaand codefragment:
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");
};
Op de HomePage
, geven we enkele gegevens van de geverifieerde gebruiker weer naast de home
bekijken.
Maak ten slotte de logout
route, om de gebruikersnaamgegevens van de gebruiker te verwijderen met het onderstaande codefragment:
exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Maak de paspoortstrategie
Op dit punt kunnen gebruikers zich registreren, inloggen en uitloggen bij onze applicatie. Laten we nu de paspoortstrategie maken om de gebruikers te authenticeren en een sessie te creëren. Open hiervoor de src/utils/passport.js
bestand en voeg het onderstaande codefragment toe:
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);
});
};
In het bovenstaande codefragment importeren we passport
, bcrypt
, en ons gebruikersmodel, en we maken een paspoort-middleware om de local-strategy
te gebruiken . Daarna hernoemen we de standaard bestandsnaam naar de veldnamen ( email
, password
) die we gebruiken om de gebruikers te authenticeren. Nu controleren we of de gebruikersgegevens in de database bestaan voordat er een sessie voor hen kan worden aangemaakt.
De Passport.serialize
en passport.deserialize
commando's worden gebruikt om de gebruikers-ID als een cookie in de browser van de gebruiker te bewaren en om de ID indien nodig uit de cookie op te halen, die vervolgens wordt gebruikt om gebruikersinformatie op te halen in een callback.
De done()
functie is een intern passport.js
functie die de gebruikers-ID als tweede parameter neemt.
Maak de aanvraagroutes
Nu onze paspoortstrategie is gemaakt, gaan we verder met het maken van routes voor onze controllers. Open hiervoor de src/routes/index.js
bestand en voeg het volgende codefragment hieronder toe:
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;
In het bovenstaande codefragment importeren we onze controllerfuncties en hebben we er een route voor gemaakt. Voor de signin route
,we gebruikten de passport.authenticate
methode om de gebruikers te authenticeren met behulp van de local
strategie in setup in de vorige sectie.
Nu terug naar onze server.js
bestand, maken we een middleware voor onze routes. Daarvoor moeten we onze router
. importeren en de passportConfig
functie.
const router = require("./routes");
const { passportConfig } = require("./utils/passport");
Vervolgens bellen we de passportConfig
functie direct onder de code in de gebieden met commentaar //Configure session middleware
.
passportConfig();
Vervolgens maken we onze route-middleware direct na het gebied met commentaar//Router middleware
.
app.use(router);
Maak onze applicatieweergaven
Met onze routes gemaakt, zullen we weergaven maken die worden weergegeven in onze HomePage
, LoginPage
, en RegisterPage
controleurs. Daarvoor stellen we onze ejs-weergave-engine in het server.js-bestand in met een codefragment hieronder, direct onder het gebied met commentaar //app middleware
.
app.set("view engine", "ejs");
Vervolgens beginnen we met de startpagina, openen de views/home.ejs
bestand en voeg de volgende opmaak toe.
<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>
Hier op onze startpagina hebben we bootstrap gebruikt om wat styling aan onze markeringen toe te voegen. Vervolgens controleren we of de gebruiker is geauthenticeerd om de uitlogknop te tonen. We tonen ook de Email
. van de gebruiker , sessionID
, en ExpirationTime
vanaf de backend.
Open vervolgens de src/views/auth/resgister
en voeg de volgende opmaak hieronder toe voor de registerpagina.
<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>
Op de registratiepagina hebben we een html-formulier gemaakt om de gebruikersgegevens te accepteren. In het formulier voegen we ook het actieve kenmerk toe en specificeren we het aanmeldingseindpunt. Dit betekent dat wanneer een gebruiker op de verzendknop klikt, een verzoek wordt verzonden naar de /api/v1/signup
eindpunt.
Open ten slotte de src/views/auth/signin.js
bestand en voeg het volgende markup-fragment hieronder toe:
<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>
In de bovenstaande opmaak hebben we een html-formulier toegevoegd dat zal worden gebruikt om een gebruiker in te loggen door een verzoek te sturen naar de /api/v1/signin
eindpunt.
Gegevens van gebruikers bekijken met Arctype
We hebben nu met succes een Node.js-sessiebeheertoepassing gemaakt. Laten we eens kijken naar de gebruikersgegevens met Arctype. Start om te beginnen Arctype, klik op het tabblad MySQL en voer de volgende MySQL-inloggegevens in, zoals weergegeven in de onderstaande schermafbeelding:
Klik vervolgens op de users
tabel om de geregistreerde gebruikers te tonen zoals getoond in de onderstaande schermafbeelding:
Conclusie
Door een demo-inlogtoepassing te bouwen, hebben we geleerd hoe we sessiebeheer in Node.js kunnen implementeren met Passport en Redis. We zijn begonnen met de introductie van HTTP-sessies en hoe ze werken, daarna hebben we gekeken naar wat Redis is en hebben we een project opgezet om dit allemaal in de praktijk te brengen. Nu u de kennis heeft die u zoekt, hoe zou u de projecten van gebruikers verifiëren?