HTTP og HTTPS er internetprotokoller, der gør det muligt at sende data over internettet ved at sende en anmodning via en webbrowser. Fordi de er statsløse, behandles hver anmodning, der sendes til browseren, uafhængigt. Det betyder, at browseren ikke kan huske kilden til en anmodning, selvom den samme bruger laver den. HTTP-sessioner løser dette problem.
Denne artikel vil se på sessionsstyring og hvordan værktøjer som Passport, Redis og MySQL kan hjælpe os med at administrere Node.js-sessioner. Lad os dykke ned.
Hvordan fungerer HTTP-sessioner?
HTTP-sessioner giver webservere mulighed for at vedligeholde brugeridentitet og gemme brugerspecifikke data på tværs af flere anmodnings-/svarinteraktioner mellem en klientapp og en webapp. Når en klient logger på applikationen, genererer serveren et SessionID. Sessionen gemmes i hukommelsen ved hjælp af en enkelt-server, ikke-replikeret vedvarende lagringsmekanisme. Eksempler på sådanne mekanismer omfatter JDBC persistens, filsystem persistens, cookie-baseret session persistens og in-memory replikering. Når brugeren sender en efterfølgende anmodning, sendes sessions-id'et i anmodningshovedet, og browseren tjekker, om id'et matcher nogen på hukommelseslageret og giver brugeren adgang, indtil sessionen udløber.
HTTP-sessioner gemmer følgende data i hukommelsen:
- Specifikationer om sessionen (session-id, oprettelsestidspunkt, tidspunkt for sidst adgang osv.)
- Kontekstuelle oplysninger om brugeren (f.eks. klientloginstatus)
Hvad er Redis?
Redis (Remote Dictionary Server) er et hurtigt, open source, nøgleværdidatalager i hukommelsen, der bruges som database, cache, meddelelsesmægler og kø.
Redis har en responstid på under millisekunder, hvilket tillader millioner af anmodninger i sekundet for realtidsapplikationer i industrier som gaming, ad-tech, finans, sundhedspleje og IoT. Som et resultat er Redis nu en af de mest populære open source-motorer, efter at være blevet kåret som "Most Loved"-databasen af Stack Overflow fem år i træk. På grund af sin hurtige ydeevne er Redis et populært valg til caching, sessionsadministration, spil, leaderboards, realtidsanalyse, geospatial, ride-hailing, chat/beskeder, mediestreaming og pub/under-apps.
Hvad bygger vi?
For at demonstrere sessionsstyring i Node.js vil vi oprette en enkel tilmeldings- og loginapplikation. Brugere vil tilmelde sig og logge ind på denne applikation ved at angive deres e-mailadresse og adgangskode. En session oprettes og gemmes i Redis-butikken til fremtidige anmodninger, når en bruger logger ind. Når en bruger logger ud, sletter vi deres session. Nok snak; lad os komme i gang!
Forudsætninger
Denne tutorial er en praktisk demonstration. Sørg for, at du har følgende installeret, før du går i gang:
- Node.js
- Redis CLI
- MySQL-database
- Arctype
Koden til denne tutorial er tilgængelig på mit Github-lager. Føl dig for at klone og følge med.
Projektopsætning
Lad os starte med at oprette en projektmappe til applikationen med kommandoen nedenfor:
mkdir Session_management && cd Session_management
Initialiser derefter et Node.js-program for at oprette en package.json-fil med kommandoen nedenfor:
npm init -y
-y
flag i ovenstående kommando fortæller npm at bruge standardkonfigurationen. Opret nu følgende mappestruktur i dit projekts rodmappe.
Med vores package.json oprettet, lad os installere den nødvendige pakke til dette projekt i næste afsnit.
Installation af afhængigheder
Vi installerer følgende afhængigheder for vores applikation:
- Bcryptjs - Dette modul vil blive brugt til at hash brugerens adgangskode.
- Connect-redis - Dette modul giver Redis sessionslagring til Express.
- Express-session - Dette modul vil blive brugt til at oprette sessioner.
- Ejs - Dette modul er vores skabelonmotor
- Pas - Dette modul vil blive brugt til brugerens godkendelse
- Lokalt pas - Dette modul vil blive brugt til lokal brugernavn og adgangskodegodkendelse
- Forfølgelse - Dette modul er vores MySQL ORM til at forbinde vores applikation til MySQL-databasen.
- Dotenv - Dette modul vil blive brugt til at indlæse vores miljøvariabler.
Brug kommandoen nedenfor til at installere alle de nødvendige afhængigheder.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Vent på, at installationen er færdig. Når installationen er fuldført, skal du fortsætte med at opsætte MySQL-databasen i næste afsnit.
Opsætning af MySQL-database
Vi opretter en MySQL-database til vores applikation. Men før det, kør kommandoen nedenfor for at oprette en MySQL-brugerkonto.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Opret nu en database session_db, og giv den nye bruger adgang til databasen med kommandoen nedenfor:
#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';
Genindlæs nu alle privilegier med kommandoen nedenfor:
FLUSH PRIVILEGES;
Med vores MySQL-databaseopsætning, lad os oprette vores users
databasemodel i næste afsnit.
Opret Express Server
Med vores MySQL-databaseopsætning, lad os skabe en ekspresserver til vores applikation. Åbn filen src/server.js og tilføj kodestykket nedenfor:
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}`);
});
I ovenstående kodestykke opretter vi en ekspresserver, som vil lytte til anmodninger på Port 4300. Derefter analyserer vi de indkommende anmodninger med JSON-nyttelast ved hjælp af express.json()
middleware og parse indgående anmodninger med urlencoded
ved hjælp af Express.urlencoded()
middleware.
Opret databasemodellen
På dette tidspunkt er vores Express-server indstillet. Nu vil vi oprette en Users
model til at repræsentere brugerdata, vi vil se databasen ved hjælp af Sequelize
. Åbn src/models/index.js
fil og tilføj kodestykket nedenfor.
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,
},
});
I ovenstående kodestykke importerer vi Sequelize
og DateTypes
fra sequelize
at oprette forbindelse til vores MySQL-database og tildele en datatype til vores modelegenskaber. Derefter forbinder vi til MySQL ved at oprette en sequelize
instans fra Sequelize
klasse og bestå i vores database-legitimationsoplysninger. For eksempel med sequelize
for eksempel definerede vi vores model og dens egenskaber. Vi vil kun have denne tutorials id-, e-mail- og adgangskodefelter. Men sequelize opretter to ekstra felter, createdAt
og updatedAt
felter.
Konfigurer pas og Redis
For at håndtere og gemme vores brugers legitimationsoplysninger, bruger og konfigurerer vi Redis
. For at gøre det skal du åbne src/index.js
fil og importer følgende afhængigheder nedenfor:
const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
Find derefter området kommenterede //Redis configurations
og tilføj kodestykket nedenfor:
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
I ovenstående kodestykke etablerede vi en forbindelse til vores database, som vil administrere vores brugers brugernavnsdata.
Find derefter området kommenterede //Commented session middleware
og tilføj kodestykket nedenfor:
//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());
I ovenstående kodestykke oprettede vi en SESSION_SECRET
variabel i en .env
fil for at holde vores session hemmelig, og oprettede derefter en session-middleware og brugte Redis som vores butik. For at sessionen skal fungere, tilføjer vi yderligere to middlewares, passport.initialize()
, og passport.session()
.
Opret applikationscontrollere
Med vores Redis- og ekspressessionsopsætning opretter vi en rute til at håndtere brugernes oplysninger. For at gøre det skal du åbne src/controllers/index.js
fil og tilføj kodestykket nedenfor:
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);
}
};
I ovenstående kodestykke importerer vi bcrypt
og vores User
model, destrukturerer vi brugerens email
og password
fra req.body
objekt. Derefter hasherede vi adgangskoden ved hjælp af bcrypt og opretter en ny bruger ved hjælp af sequelize create
metode.
Opret derefter en home page
, registration page
, login page
med kodestykket nedenfor:
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");
};
På HomePage
, gengiver vi nogle af den godkendte brugers detaljer ved siden af HomePage
se.
Til sidst skal du oprette logout
rute, for at slette brugerens brugernavndata med kodestykket nedenfor:
exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Opret passtrategien
På dette tidspunkt kan brugere registrere, logge ind og logge ud af vores applikation. Lad os nu oprette passtrategien for at godkende brugerne og oprette en session. For at gøre det skal du åbne src/utils/passport.js
fil, og tilføj kodestykket nedenfor:
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);
});
};
I ovenstående kodestykke importerer vi passport
, bcrypt
, og vores brugermodel, og vi opretter en pas-middleware til at bruge local-strategy
. Derefter omdøber vi standardfilnavnet til feltnavnene ( email
, password
) vi bruger til at godkende brugerne. Nu kontrollerer vi, om brugeroplysningerne findes i databasen, før en session kan oprettes for dem.
Passport.serialize
og passport.deserialize
kommandoer bruges til at bevare bruger-id'et som en cookie i brugerens browser og til at hente id'et fra cookien, når det er nødvendigt, som derefter bruges til at hente brugeroplysninger i et tilbagekald.
done()
funktion er en intern passport.js
funktion, der tager bruger-id'et som den anden parameter.
Opret applikationsruterne
Med vores passtrategi oprettet, lad os fortsætte med at oprette ruter til vores controllere. For at gøre det skal du åbne src/routes/index.js
fil og tilføj følgende kodestykke nedenfor:
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;
I ovenstående kodestykke importerer vi vores controllerfunktioner og oprettede en rute for dem. Til signin route
, vi brugte passport.authenticate
metode til at godkende brugerne ved hjælp af local
strategi i opsætning i forrige afsnit.
Nu tilbage til vores server.js
fil, opretter vi en middleware til vores ruter. Før det skal vi importere vores router
og passportConfig
fungere.
const router = require("./routes");
const { passportConfig } = require("./utils/passport");
Derefter kalder vi passportConfig
funktion lige under koden i de kommenterede områder //Configure session middleware
.
passportConfig();
Derefter opretter vi vores rute-middleware lige efter området kommenterede//Router middleware
.
app.use(router);
Opret vores applikationsvisninger
Når vores ruter er oprettet, opretter vi visninger gengivet på vores home page
, LoginPage
og RegisterPage
controllere. Inden da sætter vi vores ejs view engine op i server.js-filen med et kodestykke nedenfor lige under det kommenterede område //app middleware
.
app.set("view engine", "ejs");
Derefter starter vi med startsiden, åbn views/home.ejs
fil og tilføj følgende opmærkning.
<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>
Her på vores hjemmeside brugte vi bootstrap til at tilføje lidt styling til vores markeringer. Derefter tjekker vi, om brugeren er godkendt til at vise logout-knappen. Vi viser også brugerens Email
, sessionID
og ExpirationTime
fra backend.
Åbn derefter src/views/auth/resgister
og tilføj følgende opmærkning nedenfor til registreringssiden.
<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>
På registreringssiden oprettede vi en html-formular for at acceptere brugernes detaljer. I formularen tilføjer vi også den aktive attribut og angiver tilmeldingsslutpunktet. Det betyder, at når en bruger klikker på indsend-knappen, vil en anmodning blive sendt til /api/v1/signup
slutpunkt.
Til sidst skal du åbne src/views/auth/signin.js
fil, og tilføj følgende markup-kodestykke nedenfor:
<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>
I ovenstående opmærkning tilføjede vi en html-formular, der vil blive brugt til at logge på en bruger ved at sende en anmodning til /api/v1/signin
slutpunkt.
Se brugernes data med Arctype
Vi har nu oprettet en Node.js session management applikation. Lad os se på brugernes data med Arctype. Start Arctype, klik på MySQL-fanen og indtast følgende MySQL-legitimationsoplysninger, som vist på skærmbilledet nedenfor:
Klik derefter på users
tabel for at vise de registrerede brugere som vist på skærmbilledet nedenfor:
Konklusion
Ved at bygge en demo-login-applikation har vi lært, hvordan man implementerer sessionsstyring i Node.js ved hjælp af Passport og Redis. Vi startede med introduktionen af HTTP-sessioner og hvordan de fungerer, derefter kiggede vi på, hvad Redis er, og lavede et projekt for at omsætte alt dette i praksis. Nu hvor du har den viden, du søger, hvordan ville du så autentificere brugernes projekter?