App下載

在 Node.js 中使用 Passport 進(jìn)行本地身份驗(yàn)證

南館瀟湘 2021-09-28 17:17:02 瀏覽數(shù) (4152)
反饋

一、簡(jiǎn)介

Passport.js是專為 Nodejs 設(shè)計(jì)的身份驗(yàn)證中間件。passport-local使用通行證策略通過用戶名和密碼進(jìn)行身份驗(yàn)證。該模塊有助于在 nodejs 應(yīng)用程序中使用用戶名和密碼進(jìn)行身份驗(yàn)證。

1.1 設(shè)置 Node.js

要建立的Node.js在Windows上,您將需要前往Node官網(wǎng)下載。選擇適用于您平臺(tái)的安裝程序(還包括 NPM 包管理器)并運(yùn)行安裝程序以啟動(dòng) Node.js 安裝向?qū)?。按照向?qū)Р襟E操作,完成后單擊“完成”。如果一切順利,您可以導(dǎo)航到命令提示符以驗(yàn)證安裝是否成功,如圖 1 所示。

Node.js 中的 Passport - npm 安裝
圖 1:驗(yàn)證節(jié)點(diǎn)和 npm 安裝

2. 在 Node.js 中使用 Passport 進(jìn)行本地身份驗(yàn)證

要設(shè)置應(yīng)用程序,我們需要導(dǎo)航到項(xiàng)目所在的路徑。對(duì)于編程,我使用?Visual Studio Code?作為我的首選 IDE。您可以自由選擇自己喜歡的 IDE。

2.1 設(shè)置實(shí)現(xiàn)

讓我們編寫實(shí)際學(xué)習(xí)所需的不同文件。

2.1.1 設(shè)置依賴

導(dǎo)航到項(xiàng)目目錄并運(yùn)行npm init -y以創(chuàng)建package.json文件。該文件包含與項(xiàng)目相關(guān)的元數(shù)據(jù),用于管理項(xiàng)目依賴項(xiàng)、腳本、版本等。將以下代碼添加到文件中,我們將在其中指定所需的依賴項(xiàng)。

{
  "name": "passport-app",
  "version": "1.0.0",
  "description": "",
  "main": "server.js",
  "scripts": {
    "start": "nodemon server.js"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "bcrypt": "^5.0.1",
    "ejs": "^3.1.6",
    "express": "^4.17.1",
    "express-flash": "0.0.2",
    "express-session": "^1.17.2",
    "method-override": "^3.0.0",
    "passport": "^0.4.1",
    "passport-local": "^1.0.0"
  },
  "devDependencies": {
    "dotenv": "^10.0.0",
    "nodemon": "^2.0.12"
  }
}

要下載依賴項(xiàng),請(qǐng)導(dǎo)航到包含該文件的目錄路徑并使用npm install?命令。如果一切順利,依賴項(xiàng)將加載到?node_modules?文件夾中,您可以繼續(xù)執(zhí)行進(jìn)一步的步驟。

2.1.2 視圖 - 創(chuàng)建歡迎屏幕

在根文件夾中創(chuàng)建一個(gè)名為?views?的文件夾并將以下內(nèi)容添加到?index.ejs?. 此屏幕將負(fù)責(zé)在成功驗(yàn)證后顯示歡迎頁面。

<h1>Hi <%= name %></h1>
<form action="/logout?_method=DELETE" method="POST">
  <button type="submit">Sign Out</button>
</form>

2.1.3 查看——?jiǎng)?chuàng)建登錄界面

在根文件夾中創(chuàng)建一個(gè)名為?views?的文件夾并將以下內(nèi)容添加到?login.ejs?. 此屏幕將負(fù)責(zé)登錄。

<h1>Login</h1>
<% if (messages.error) { %> <%= messages.error %> <% } %>
<form action="/login" method="POST">
  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" required />
  </div>
  <button type="submit">Login</button>
</form>
<a href="/register">Sign up</a>

2.1.4 視圖 – 創(chuàng)建注冊(cè)屏幕

在根文件夾中創(chuàng)建一個(gè)名為?views?的文件夾并將以下內(nèi)容添加到?login.ejs?. 此屏幕將負(fù)責(zé)新用戶的注冊(cè)。

<h1>Register</h1>
<form action="/register" method="POST">
  <div>
    <label for="name">Name</label>
    <input type="text" id="name" name="name" required />
  </div>
  <div>
    <label for="email">Email</label>
    <input type="email" id="email" name="email" required />
  </div>
  <div>
    <label for="password">Password</label>
    <input type="password" id="password" name="password" required />
  </div>
  <button type="submit">Register</button>
</form>
<a href="/login">Login</a>

2.1.5 創(chuàng)建通行證配置

在根文件夾中,將以下內(nèi)容添加到配置文件中。該文件將負(fù)責(zé)使用用戶名和密碼配置策略。該策略還需要一個(gè)回調(diào),該回調(diào)將接受憑據(jù)并調(diào)用一個(gè)?done(…)?方法來提供用戶詳細(xì)信息。

// adding passport related configuration
 
const LocalStrategy = require("passport-local").Strategy;
const bcrypt = require("bcrypt");
 
function initialize(passport, getUserByEmail, getUserById) {
  const authenticateUser = async (email, password, done) => {
    const user = getUserByEmail(email);
    if (user == null) {
      return done(null, false, { message: "User not found" });
    }
 
    try {
      if (await bcrypt.compare(password, user.password)) {
        return done(null, user);
      } else {
        return done(null, false, { message: "Invalid credentials" });
      }
    } catch (e) {
      return done(e);
    }
  };
 
  passport.use(new LocalStrategy({ usernameField: "email" }, authenticateUser));
  passport.serializeUser((user, done) => done(null, user.id));
  passport.deserializeUser((id, done) => {
    return done(null, getUserById(id));
  });
}
 
module.exports = initialize;

2.1.6 創(chuàng)建控制器

在根文件夾中,將以下內(nèi)容添加到索引文件中。該文件將負(fù)責(zé)初始化導(dǎo)入、路由并指定通行證配置以驗(yàn)證請(qǐng)求。請(qǐng)記住?.env?在同一位置創(chuàng)建一個(gè)文件并指定敏感信息,例如會(huì)話機(jī)密、應(yīng)用程序端口號(hào)等。

if (process.env.NODE_ENV !== "production") {
  require("dotenv").config();
}
 
// imports
const express = require("express");
const app = express();
const bcrypt = require("bcrypt");
const passport = require("passport");
const flash = require("express-flash");
const session = require("express-session");
const methodOverride = require("method-override");
 
// todo - add external db support
const users = [];
 
// configuring and initializing passport
const initializePassport = require("./passport-config");
initializePassport(
  passport,
  (email) => users.find((user) => user.email === email),
  (id) => users.find((user) => user.id === id)
);
 
app.set("view-engine", "ejs");
app.use(express.urlencoded({ extended: false }));
app.use(flash());
app.use(
  session({
    secret: process.env.SESSION_SECRET || "8unto0n4oc7903zm",
    resave: false,
    saveUninitialized: false,
  })
);
app.use(passport.initialize());
app.use(passport.session());
app.use(methodOverride("_method"));
 
// routes
 
// welcome page
// display greetings message for the user and logout button
app.get("/", checkAuthenticated, (req, res) => {
  res.render("index.ejs", { name: req.user.name });
});
 
// login page
app.get("/login", checkNotAuthenticated, (req, res) => {
  res.render("login.ejs");
});
 
app.post(
  "/login",
  checkNotAuthenticated,
  passport.authenticate("local", {
    successRedirect: "/",
    failureRedirect: "/login",
    failureFlash: true,
  })
);
 
// new user sign-up page
app.get("/register", checkNotAuthenticated, (req, res) => {
  res.render("register.ejs");
});
 
app.post("/register", checkNotAuthenticated, async (req, res) => {
  try {
    const hashedPassword = await bcrypt.hash(req.body.password, 10);
    users.push({
      id: "_" + Math.random().toString(36).slice(2),
      name: req.body.name,
      email: req.body.email,
      password: hashedPassword,
    });
 
    res.redirect("/login");
  } catch (e) {
    // console.log(e);
    res.redirect("/redirect");
  }
 
  // check if the user is successfully added to array
  // console.log(users);
});
 
// logout of the application
app.delete("/logout", (req, res) => {
  req.logOut();
  res.redirect("/login");
});
 
// util methods
 
// only authenticated user should enter index page
function checkAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return next();
  } else {
    res.redirect("/login");
  }
}
 
// unauthenticated user should not enter index page
function checkNotAuthenticated(req, res, next) {
  if (req.isAuthenticated()) {
    return res.redirect("/");
  }
  next();
}
 
// start server
const port = process.env.APPLICATION_PORT || 6001;
app.listen(port, () => {
  console.log("Server listening at http://localhost:%s", port);
});

3. 運(yùn)行應(yīng)用程序

要運(yùn)行應(yīng)用程序,請(qǐng)導(dǎo)航到項(xiàng)目目錄并輸入以下命令,如圖 2 所示。如果一切順利,應(yīng)用程序?qū)⒃趶?.env?文件或6001.

Node.js 中的 Passport - 啟動(dòng)應(yīng)用程序
圖 2:?jiǎn)?dòng)應(yīng)用程序

4. 演示

應(yīng)用程序成功啟動(dòng)后,導(dǎo)航到以下 url 以顯示登錄屏幕。您可以單擊注冊(cè)按鈕以創(chuàng)建新用戶并在此之后使用該應(yīng)用程序。

http://localhost:YOUR_PORT_NUMBER

這就是本教程的全部?jī)?nèi)容,我希望這篇文章能夠?yàn)槟峁┧璧囊磺小?鞓穼W(xué)習(xí),不要忘記分享!

5. 總結(jié)

在本篇文章中,我們了解了passport 模塊以及如何在passport-local 依賴項(xiàng)的幫助下創(chuàng)建一個(gè)簡(jiǎn)單的登錄應(yīng)用程序。


0 人點(diǎn)贊