HTTP och HTTPS är internetprotokoll som tillåter att data skickas över internet genom att skicka en begäran via en webbläsare. Eftersom de är statslösa behandlas varje begäran som skickas till webbläsaren oberoende. Det betyder att webbläsaren inte kan komma ihåg källan till en begäran, även om samma användare gör den. HTTP-sessioner löser det här problemet.
Den här artikeln kommer att titta på sessionshantering och hur verktyg som Passport, Redis och MySQL kan hjälpa oss att hantera Node.js-sessioner. Låt oss dyka in.
Hur fungerar HTTP-sessioner?
HTTP-sessioner tillåter webbservrar att upprätthålla användaridentitet och lagra användarspecifik data över flera förfrågnings-/svarsinteraktioner mellan en klientapp och en webbapp. När en klient loggar in i applikationen genererar servern ett sessions-ID. Sessionen sparas i minnet med hjälp av en enserver, icke-replikerad beständig lagringsmekanism. Exempel på sådana mekanismer inkluderar JDBC persistens, filsystem persistens, cookie-baserad session persistens och in-memory replikering. När användaren skickar en efterföljande begäran skickas sessions-ID:t i förfrågningshuvudet och webbläsaren kontrollerar om ID:t matchar någon på minneslagringen och ger användaren åtkomst tills sessionen löper ut.
HTTP-sessioner lagrar följande data i minnet:
- Specifikationer om sessionen (sessionsidentifierare, skapelsetid, senast åtkomsttid osv.)
- Kontextuell information om användaren (till exempel klientinloggningsstatus)
Vad är Redis?
Redis (Remote Dictionary Server) är en snabb, öppen källkod, i minnet nyckel-värde datalager som används som en databas, cache, meddelandeförmedlare och kö.
Redis har svarstider på under millisekunder, vilket tillåter miljontals förfrågningar per sekund för realtidsapplikationer inom branscher som spel, reklamteknik, finans, sjukvård och IoT. Som ett resultat är Redis nu en av de mest populära motorerna med öppen källkod, efter att ha utsetts till databasen "Most Loved" av Stack Overflow fem år i rad. På grund av dess snabba prestanda är Redis ett populärt val för cachning, sessionshantering, spel, topplistor, realtidsanalys, geospatial, ride-hailing, chatt/meddelanden, mediastreaming och pub/underappar.
Vad bygger vi?
För att demonstrera sessionshantering i Node.js kommer vi att skapa en enkel registrerings- och inloggningsapplikation. Användare kommer att registrera sig för och logga in på denna applikation genom att ange sin e-postadress och lösenord. En session skapas och sparas i Redis-butiken för framtida förfrågningar när en användare loggar in. När en användare loggar ut kommer vi att radera deras session. Nog pratat; låt oss komma igång!
Förutsättningar
Denna handledning är en praktisk demonstration. Se till att du har följande installerat innan du börjar:
- Node.js
- Redis CLI
- MySQL-databas
- Arctype
Koden för denna handledning är tillgänglig på mitt Github-förråd. Känn dig för att klona och följa med.
Projektinställning
Låt oss börja med att skapa en projektmapp för applikationen med kommandot nedan:
mkdir Session_management && cd Session_management
Initiera sedan en Node.js-applikation för att skapa en package.json-fil med kommandot nedan:
npm init -y
-y
flaggan i kommandot ovan säger till npm att använda standardkonfigurationen. Skapa nu följande mappstruktur i ditt projekts rotkatalog.
Med vår package.json skapad, låt oss installera det nödvändiga paketet för det här projektet i nästa avsnitt.
Installera beroenden
Vi installerar följande beroenden för vår applikation:
- Bcryptjs - Den här modulen kommer att användas för att hasha användarens lösenord.
- Connect-redis - Den här modulen kommer att tillhandahålla Redis-sessionslagring för Express.
- Express-session - Den här modulen kommer att användas för att skapa sessioner.
- Ejs - Den här modulen är vår mallmotor
- Pass - Den här modulen kommer att användas för användarens autentisering
- Passlokalt - Den här modulen kommer att användas för lokal användarnamn och lösenordsautentisering
- Uppföljare - Den här modulen är vår MySQL ORM för att koppla vår applikation till MySQL-databasen.
- Dotenv - Den här modulen kommer att användas för att ladda våra miljövariabler.
Använd kommandot nedan för att installera alla nödvändiga beroenden.
npm install bcryptjs connect-redis redis express-session ejs passport passport-local sequelize dotenv
Vänta tills installationen är klar. När installationen är klar, fortsätt med att ställa in MySQL-databasen i nästa avsnitt.
Konfigurera MySQL-databas
Vi skapar en MySQL-databas för vår applikation. Men innan det, kör kommandot nedan för att skapa ett MySQL-användarkonto.
CREATE USER 'newuser'@'localhost' IDENTIFIED BY '1234';
Skapa nu en databas session_db och ge den nya användaren tillgång till databasen med kommandot nedan:
#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';
Ladda nu om alla privilegier med kommandot nedan:
FLUSH PRIVILEGES;
Med vår MySQL-databasinstallation, låt oss skapa våra users
databasmodell i nästa avsnitt.
Skapa Express Server
Med vår MySQL-databasinstallation, låt oss skapa en expressserver för vår applikation. Öppna filen src/server.js och lägg till kodavsnittet nedan:
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 kodavsnittet ovan skapar vi en expressserver som lyssnar på förfrågningar på port 4300. Sedan analyserar vi de inkommande förfrågningarna med JSON-nyttolaster med hjälp av express.json()
middleware och analysera inkommande förfrågningar med urlencoded
med Express.urlencoded()
mellanprogram.
Skapa databasmodellen
Vid denna tidpunkt är vår Express-server inställd. Nu skapar vi en Users
modell för att representera användardata kommer vi att se databasen med Sequelize
. Öppna src/models/index.js
fil och lägg till kodavsnittet nedan.
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 kodavsnittet ovan importerar vi Sequelize
och DateTypes
från sequelize
för att ansluta till vår MySQL-databas och tilldela en datatyp till våra modellegenskaper. Sedan ansluter vi till MySQL genom att skapa en sequelize
instans från Sequelize
klass och godkänner vår databasuppgifter. Till exempel med sequelize
till exempel definierade vi vår modell och dess egenskaper. Vi vill bara ha den här handledningens ID-, e-post- och lösenordsfält. Men sequelize skapar ytterligare två fält, createdAt
och updatedAt
fält.
Konfigurera Passport och Redis
För att hantera och lagra våra användares autentiseringsuppgifter kommer vi att använda och konfigurera Redis
. För att göra det, öppna src/index.js
fil och importera följande beroenden nedan:
const session = require("express-session");
const connectRedis = require("connect-redis");
const dotenv = require("dotenv").config()
const { createClient } = require("redis");
const passport = require("passport");
Leta sedan upp området kommenterade //Redis configurations
och lägg till kodavsnittet nedan:
const redisClient = createClient({ legacyMode: true });
redisClient.connect().catch(console.error);
const RedisStore = connectRedis(session);
I ovanstående kodavsnitt upprättade vi en anslutning till vår databas, som kommer att hantera vår användares användarnamnsdata.
Leta sedan upp området för kommenterad //Commented session middleware
och lägg till kodavsnittet nedan:
//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 kodavsnittet ovan skapade vi en SESSION_SECRET
variabel i en .env
fil för att hålla vår session hemlig, skapade sedan en sessionmellanvara och använde Redis som vår butik. För att sessionen ska fungera lägger vi till ytterligare två mellanprogram, passport.initialize()
och passport.session()
.
Skapa programkontroller
Med vår Redis- och express-sessionskonfiguration skapar vi en rutt för att hantera användarnas information. För att göra det, öppna src/controllers/index.js
fil och lägg till kodavsnittet nedan:
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 kodavsnittet ovan importerar vi bcrypt
och vår Users
modell, destrukturerar vi användarens email
och password
från req.body
objekt. Sedan hashade vi lösenordet med bcrypt och skapar en ny användare med sequelize create
metod.
Skapa sedan en home page
, registration page
, login page
med kodavsnittet nedan:
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
, återger vi några av den autentiserade användarens detaljer vid sidan av home
visa.
Slutligen skapar du logout
rutt, för att radera användarens användarnamnsdata med kodavsnittet nedan:
exports.Logout = (req, res) => {
req.session.destroy((err) => {
if (err) {
return console.log(err);
}
res.redirect("/");
});
};
Skapa Passport-strategin
Vid det här laget kan användare registrera sig, logga in och logga ut från vår applikation. Låt oss nu skapa passstrategin för att autentisera användarna och skapa en session. För att göra det, öppna src/utils/passport.js
fil och lägg till kodavsnittet nedan:
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 kodavsnittet ovan importerar vi passport
, bcrypt
, och vår användarmodell, och vi skapar ett pass-mellanprogram för att använda local-strategy
. Sedan byter vi namn på standardfilnamnet till fältnamnen ( email
, password
) vi använder för att autentisera användarna. Nu kontrollerar vi om användarinformationen finns i databasen innan en session kan skapas för dem.
Passport.serialize
och passport.deserialize
kommandon används för att bevara användar-id som en cookie i användarens webbläsare och för att hämta ID från cookien vid behov, som sedan används för att hämta användarinformation i en återuppringning.
done()
funktionen är en intern passport.js
funktion som tar användar-id som den andra parametern.
Skapa applikationsvägarna
Med vår passstrategi skapad, låt oss fortsätta med att skapa rutter för våra kontroller. För att göra det, öppna src/routes/index.js
fil och lägg till följande kodavsnitt nedan:
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 kodavsnittet ovan importerar vi våra kontrollerfunktioner och skapade en rutt för dem. För signin route
, använde vi passport.authenticate
metod för att autentisera användarna med hjälp av local
strategi i inställningen i föregående avsnitt.
Nu tillbaka till vår server.js
fil skapar vi en mellanprogramvara för våra rutter. Innan dess måste vi importera vår router
och passportConfig
fungera.
const router = require("./routes");
const { passportConfig } = require("./utils/passport");
Sedan anropar vi passportConfig
fungerar precis under koden i de kommenterade områdena //Configure session middleware
.
passportConfig();
Sedan skapar vi vår ruttmellanvara direkt efter området kommenterade//Router middleware
.
app.use(router);
Skapa våra applikationsvyer
Med våra rutter skapade skapar vi vyer som återges på vår home page
, LoginPage
och RegisterPage
kontroller. Innan dess kommer vi att ställa in vår ejs view-motor i server.js-filen med ett kodavsnitt nedanför precis under det kommenterade området //app middleware
.
app.set("view engine", "ejs");
Sedan börjar vi med startsidan, öppna views/home.ejs
fil och lägg till följande markering.
<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>
Här på vår hemsida använde vi bootstrap för att lägga till lite styling till våra markeringar. Sedan kontrollerar vi om användaren är autentiserad för att visa utloggningsknappen. Vi visar också användarens email
, sessionID
och ExpirationTime
från backend.
Öppna sedan src/views/auth/resgister
och lägg till följande uppmärkning nedan för registreringssidan.
<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å registreringssidan skapade vi ett html-formulär för att acceptera användaruppgifterna. I formuläret lägger vi också till det aktiva attributet och anger slutpunkten för registreringen. Det betyder att när en användare klickar på knappen Skicka skickas en begäran till /api/v1/signup
slutpunkt.
Öppna slutligen src/views/auth/signin.js
fil och lägg till följande kodavsnitt nedan:
<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 ovanstående uppmärkning lade vi till ett HTML-formulär som kommer att användas för att logga in en användare genom att skicka en begäran till /api/v1/signin
slutpunkt.
Visa användarnas data med Arctype
Vi har nu skapat en Node.js sessionshanteringsapplikation. Låt oss titta på användarnas data med Arctype. För att börja, starta Arctype, klicka på MySQL-fliken och ange följande MySQL-uppgifter, som visas i skärmdumpen nedan:
Klicka sedan på users
tabell för att visa registrerade användare som visas i skärmdumpen nedan:
Slutsats
Genom att bygga en demo-inloggningsapplikation har vi lärt oss hur man implementerar sessionshantering i Node.js med Passport och Redis. Vi började med introduktionen av HTTP-sessioner och hur de fungerar, sedan tittade vi på vad Redis är och skapade ett projekt för att omsätta allt detta i praktiken. Nu när du har den kunskap du söker, hur skulle du autentisera användares projekt?