sql >> Database teknologi >  >> RDS >> Mysql

Sådan administreres sessioner i Node.js ved hjælp af Passport, Redis og MySQL

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");
};

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?


  1. SQL-nøgler, MUL vs PRI vs UNI

  2. Fejlfinding af SQL Server CPU-ydelsesproblemer

  3. Brug FILEGROUP_NAME() til at returnere navnet på en filgruppe i SQL Server

  4. Kompilere skrivebar mongo_fdw-udvidelse på binært format af PostgreSQL-installation.