摘要:在使用過程中我們可以通過增加哈希次數來提高數據的安全性。當然,對密碼的哈希操作應該在保存數據之前。
毫無疑問,幾乎所有的應用都會涉及到數據存儲。但是 Express 框架本身只能通過程序變量來保存數據,它并不提供數據持久化功能。而僅僅通過內存來保存數據是無法應對真實場景的。因為內存本身并不適用于大規模的數據儲存而且服務停止后這些數據也會消失。雖然我們還可以通過文件的形式保存數據,但是文件中的數據對于查詢操作明顯不友好。所有,接下來我們將學習如何在 Express 中通過 MongoDB 數據庫的形式來對數據進行持久化存儲。
本文包含的主要內容有:
MongoDB 是如何工作的。
如何使用 Mongoose 。
如何安全的創建用戶賬戶。
如何使用用戶密碼進行授權操作。
為什么是 MongoDB ?對于 Web 應用來說,通常數據庫的選擇可以劃分為兩大類:關系型和非關系型。其中前者優點類型于電子表格,它的數據是結構化并且伴隨著嚴格規定。典型的關系型數據庫包括:MySQL、 SQL Server 以及 PostgreSQL。而后者通常也被稱為 NoSQL 數據庫,它的結構相對更加靈活,而這一點與 JS 非常類似。
但是為什么 Node 開發者會特別中意 NoSQL 中的 Mongo 數據庫,還形成了流行的 MEAN 技術棧呢?
第一個原因是:Mongo 是 NoSQL 類型數據里最流行的一個。這也讓網上關于 Mogon 的資料非常豐富,所有你在實際使用過程中可能會遇到的坑大幾率都能找到答案。而且作為一個成熟的項目,Mongo 也已經被大公司認可和應用。
另一個原因則是 Mongo 自身非常可靠、有特色。它使用高性能的 C++ 進行底層實現,也讓它贏得了大量的用戶信賴。
雖然 Mongo 不是用 JavaScript 實現的,但是原生的 shell 卻使用的是 JavaScript 語言。這意味著可以使用 JavaScript 在控制臺操作 Mongo 。另外,對于 Node 開發者來說它也減少了學習新語言的成本。
當然,Mongo 并不是所有 Express 應用的正確選擇,關系數據庫依然占據著非常重要的地位。順便提一下,NoSQL 中的 CouchDB 功能也非常強大。
Mongo 是如何工作的注意:雖然本文只會介紹 Mongo 以及 Mongoose 類庫的使用。但是如果你和我一樣對 SQL 非常熟悉并且希望在 Express 使用關系數據庫的話,你可以去查看 Sequelize。它為很多關系型數據庫提供了良好的支持。
在正式使用 Mongo 前,我們先來看看 Mongo 是如何工作的。
對于大多數應用來說都會在服務器中使用 Mongo 這樣的數據庫來進行持久化工作。雖然,你可以在一個應用中創建多個數據庫,但是絕大多數都只會使用一個。
如果你想正常訪問這些數據庫的話,首先你需要運行一個 Mongo 服務。客戶端通過給服務端發送指令來實現對數據庫的各種操作。而連接客戶端與服務端的程序通常都被稱為數據庫驅動。對于 Mongo 數據庫來說它在 Node 環境下的數據庫驅動程序是 Mongoose。
每個數據庫都會有一個或多個類似于數組一樣的數據集合。例如,一個簡單的博客應用,可能就會有文章集合、用戶集合。但是這些數據集合的功能遠比數組來的強大。例如,你可以查詢集合中 18 歲以上的用戶。
而每一個集合里面存儲了 JSON 形式的文檔,雖然在技術上并沒有采用 JSON。每一個文檔都對應一條記錄,而每一條記錄都包含若干個字段屬性。另外,同一集合里的文檔記錄并不一定擁有一樣的字段屬性。這也是 NoSQL 與 關系型數據庫最大的區別之一。
實際上文檔在技術上采用的是簡稱為 BSON 的 Binary JSON。在實際寫代碼過程中,我們并不會直接操作 BSON 。多數情況下會將其轉化為 JavaScript 對象。另外,BSON 的編碼和解碼方式與 JSON 也有不同。BSON 支持的類型也更多,例如,它支持日期、時間戳。下圖展示了應用中數據庫使用結構:
最后還有一點非常重要:Mongo 會給每個文檔記錄添加一個 _id 屬性,用于標示該記錄的唯一性。如果兩個同類型的文檔記錄的 id 屬性一致的話,那么就可以推斷它們是同一記錄。
SQL 使用者需要注意的問題如果你有關系型數據庫的知識背景的話,其實你會發現 Mongo 很多概念是和 SQL 意義對應的。
首先, Mongo 中的文檔概念其實就相當于 SQL 中的一行記錄。在應用的用戶系統中,每一個用戶在 Mongo 中是一個文檔而在 SQL 中則對應一條記錄。但是與 SQL 不同的是,在數據庫層 Mongo 并沒有強制的 schema,所以一條沒有用戶名和郵件地址的用戶記錄在 Mongo 中是合法的。
其次,Mongo 中的集合對應 SQL 中的表,它們都是用來存儲同一類型的記錄。
同樣,Mongo 中的數據庫也和 SQL 數據庫概念非常相似。通常一個應用只會有一個數據庫,而數據庫內部則可以包含多個集合或者數據表。
更多的術語對應表可以去查看官方的這篇文檔。
Mongo 環境搭建在使用之前,首要的任務當然就是機器上安裝 Mongo 數據庫并拉起服務了。如果你的機器是 macOS 系統并且不喜歡命令行模式的話,你可以通過安裝 Mongo.app 應用完成環境搭建。如果你熟悉命令行交互的話可以通過 Homebrew 命令 brew install mongodb 進行安裝。
Ubuntu 系統可以參照文檔,同時 Debian 則可以參照文檔 進行 Mongo 安裝。
另外,在本書中我們會假設你安裝是使用的 Mongo 數據庫的默認配置。也就是說你沒有對 Mongo 的服務端口號進行修改而是使用了默認的 27017 。
使用 Mongoose 操作 Mongo 數據庫安裝 Mongo 后接下來問題就是如何在 Node 環境中操作數據庫。這里最佳的方式就是使用官方的 [Mongoose] [6]類庫。其官方文檔描述為:
Mongoose 提供了一個直觀并基于 schema 的方案來應對程序的數據建模、類型轉換、數據驗證等常見數據庫問題。
換句話說,除了充當 Node 和 Mongo 之間的橋梁之外,Mongoose 還提供了更多的功能。下面,我們通過構建一個帶用戶系統的簡單網站來熟悉 Mongoose 的特性。
準備工作為了更好的學習本文的內容,下面我們會開發一個簡單的社交應用。該應用將會實現用戶注冊、個人信息編輯、他人信息的瀏覽等功能。這里我們將它稱為 Learn About Me 或者簡稱為 LAM 。應用中主要包含以下頁面:
主頁,用于列出所有的用戶并且可以點擊查看用戶詳情。
個人信息頁,用于展示用戶姓名等信息。
用戶注冊頁。
用戶登錄頁。
和之前一樣,首先我們需要新建工程目錄并編輯 package.json 文件中的信息:
{ "name": "learn-about-me", "private": true, "scripts": { "start": "node app" }, "dependencies": { "bcrypt-nodejs": "0.0.3", "body-parser": "^1.6.5", "connect-flash": "^0.1.1", "cookie-parser": "^1.3.2", "ejs": "^1.0.0", "express": "^4.0.0", "express-session": "^1.7.6", "mongoose": "^3.8.15", "passport": "^0.2.0", "passport-local": "^1.0.0" } }
接下來,運行 npm install 安裝這些依賴項。在后面的內容中將會一一對這些依賴項的作用進行介紹。
需要注意的是,這里我們引入了一個純 JS 實現的加密模塊 bcrypt-nodejs 。其實 npm 中還有一個使用 C 語言實現的加密模塊 bcrypt 。雖然 bcrypt 性能更好,但是因為需要編譯 C 代碼所有安裝起來沒 bcrypt-nodejs 簡單。不過,這兩個類庫功能一致可以進行自由切換。
創建 user 模型前面說過 Mongo 是以 BSON 形式進行數據存儲的。例如,Hello World 的 BSON 表現形式為:
x16x00x00x00x02hellox00x06x00x00x00worldx00x00
雖然計算機完全能夠理解 BSON 格式,但是很明顯 BSON 對人類來說并不是一種易于閱讀的格式。因此,開發者發明了更易于理解的數據庫模型概念。數據庫模型以一種近似人類語言的方式對數據庫對象做出了定義。一個模型代表了一個數據庫記錄,通常也代表了編程語言中的對象。例如,這里它就代表一個 JavaScript 對象。
除了表示數據庫的一條記錄之外,模型通常還伴隨數據驗證、數據拓展等方法。下面通過具體示例來見識下 Mongoose 中的這些特性。
在示例中,我們將創建一個用戶模型,該模型帶有以下屬性:
用戶名,該屬性無法缺省且要求唯一。
密碼,同樣無法缺省。
創建時間。
用戶昵稱,用于信息展示且可選。
個人簡介,非必須屬性。
在 Mongoose 中我們使用 schema 來定義用戶模型。除了包含上面的屬性之外,之后還會在其中添加一些類型方法。在項目的根目錄創建 models 文件夾,然后在其中創建一個名為 user.js 的文件并復制下面代碼:
var mongoose = require("mongoose"); var userSchema = mongoose.Schema({ username: { type: String, require: true, unique: true }, password: { type: String, require: true }, createdAt: {type: Date, default: Date.now }, displayName: String, bio: String });
從上面的代碼中,我們能看到屬性字段的定義非常簡單。同時我們還對字段的數據類型、唯一性、缺省、默認值作出了約定。
當模型定義好之后,接下來就是在模型中定義方法了。首先,我們添加一個返回用戶名稱的簡單方法。如果用戶定義了昵稱則返回昵稱否則直接返回用戶名。代碼如下:
... userSchema.methods.name = function() { return this.displayName || this.username; }
同樣,為了確保數據庫中用戶信息安全,密碼字段必須以密文形式存儲。這樣即使出現數據庫泄露或者入侵行為也能載一定程度上確保用戶信息的安全。這里我們將會使用對 Bcrypt 程序對用戶密碼進行單向哈希散列,然后在數據庫中存儲加密后的結果。
首先,我們需要在 user.js 文件頭部引入 Bcrypt 類庫。在使用過程中我們可以通過增加哈希次數來提高數據的安全性。當然,哈希操作是非常操作,所以我們應該選取一個相對適中的數值。例如,下面的代碼中我們將哈希次數設定為了 10 。
var bcrypt = require("bcrypt-nodejs"); var SALT_FACTOR = 10;
當然,對密碼的哈希操作應該在保存數據之前。所以這部分代碼應該在數據保存之前的回調函數中完成,代碼如下:
... var noop = function() {}; // 保存操作之前的回調函數 userSchema.pre("save", function(done) { var user = this; if (!user.isModified("password")) { return done(); } bcrypt.genSalt(SALT_FACTOR, function(err, salt) { if (err) { return done(err); } bcrypt.hash(user.password, salt, noop, function(err, hashedPassword) { if (err) { return done(err); } user.password = hashedPassword; done(); } ); }); });
該回調函數會在每次進行數據庫保存之前被調用,所以它能確保你的密碼會以密文形式得到保存。
處理需要對密碼進行加密處理之外,另一個常見需求就是用戶授權驗證了。例如,在用戶登錄操作時的密碼驗證操作。
... userSchema.methods.checkPassword = function(guess, done) { bcrypt.compare(guess, this.password, function(err, isMatch) { done(err, isMatch); }); }
出于安全原因,這里我們使用的是 bcrypt.compare 函數而不是簡單的相等判斷 === 。
完成模型定義和通用方法實現后,接下來我們就需要將其暴露出來供其他代碼使用了。不過暴露模型的操作非常簡單只需兩行代碼:
... var User = mongoose.model("User", userSchema); module.exports = User;
models/user.js 文件中完整的代碼如下:
// 代碼清單 8.8 models/user.js編寫完成之后 var bcrypt = require("bcrypt-nodejs"); var SALT_FACTOR = 10; var mongoose = require("mongoose"); var userSchema = mongose.Schema({ username: { type: String, require: true, unique: true }, password: { type: String, require: true }, createdAt: {type: Date, default: Date.now }, displayName: String, bio: String }); userSchema.methods.name = function() { return this.displayName || this.username; } var noop = function() {}; userSchema.pre("save", function(done) { var user = this; if (!user.isModified("password")) { return done(); } bcrypt.genSalt(SALT_FACTOR, function(err, salt) { if (err) { return done(err); } bcrypt.hash(user.password, salt, noop, function(err, hashedPassword) { if (err) { return done(err); } user.password = hashedPassword; done(); } ); }); }); userSchema.methods.checkPassword = function(guess, done) { bcrypt.compare(guess, this.password, function(err, isMatch) { done(err, isMatch); }); } var User = mongoose.model("User", userSchema); module.exports = User;模型使用
模型定義好之后,接下來就是在主頁、編輯頁面、注冊等頁面進行使用了。相比于之前的模型定義,使用過程相對來說要更簡單。
首先,在項目根目錄創建主入口文件 app.js 并復制下面的代碼:
var express = require("express"); var mongoose = require("mongoose"); var path = require("path"); var bodyParser = require("body-parser"); var cookieParser = require("cookie-parser"); var session = require("express-session"); var flash = require("connect-flash"); var routes = require("./routes"); var app = express(); // 連接到你MongoDB服務器的test數據庫 mongoose.connect("mongodb://localhost:27017/test"); app.set("port", process.env.PORT || 3000); app.set("views", path.join(__dirname, "views")); app.set("view engine", "ejs"); app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(session({ secret: "TKRv0IJs=HYqrvagQ#&!F!%V]Ww/4KiVs$s,<接下來,我們需要實現上面使用到的路由中間件。在根目錄新建 routes.js 并復制代碼:
var express = require("express"); var User = require("./models/user"); var router = express.Router(); router.use(function(req, res, next) { res.locals.currentUser = req.user; res.locals.errors = req.flash("error"); res.locals.infos = req.flash("info"); next(); }); router.get("/", function(req, res, next) { User.find() .sort({ createdAt: "descending" }) .exec(function(err, users) { if (err) { return next(err); } res.render("index", { users: users }); }); }); module.exports = router;這兩段代碼中,首先,我們使用 Mongoose 進行了數據庫連接。然后,在路由中間件中通過 User.find 異步獲取用戶列表并將其傳遞給了主頁視圖模版。
接下來,我們就輪到主頁視圖的實現了。首先在根目錄創建 views 文件夾,然后在文件夾中添加第一個模版文件 _header.ejs :
Learn About Me <% errors.forEach(function(error) { %><%= error %><% }) %> <% infos.forEach(function(info) { %><%= info %><% }) %>你可能注意到了這些文件的名字是以下劃線開始的。這是一個社區約定,所有組件模版都會以下劃線進行區分。
接下來,添加第二個通用組件模版 _footer.js:
最后,我們添加主頁視圖模版文件。該視圖模版會接受中間件中傳入的 users 變量并完成渲染:
<% include _header %>Welcome to Learn About Me!
<% users.forEach(function(user) { %><% if (user.bio) { %><% }) %> <% include _footer %><%= user.bio %><% } %>確保代碼無誤后,接下來啟動 Mongo 數據庫服務并使用 npm start 拉起工程。然后,通過瀏覽器訪問 localhost:3000 就能類型下圖的主頁界面:
當然,因為此時數據庫中并沒有任何記錄所有這里并沒有出現任何用戶信息。
接下來,我們就來實現用戶用戶注冊和登錄功能。不過在此之前,我們需要在 app.js 中引入 body-parser 模塊并用于后面請求參數的解析。
var bodyParser = require("body-parser"); ... app.use(bodyParser.urlencoded({ extended: false })); …為了提高安全性,這里我們將 body-parser 模塊的 extended 設置為 false 。接下來,我們在 routes.js 添加 sign-up 功能的中間件處理函數:
var passport = require("passport"); ... router.get("/signup", function(req, res) { res.render("signup"); }); router.post("/signup", function(req, res, next) { // 參數解析 var username = req.body.username; var password = req.body.password; // 調用findOne只返回一個用戶。你想在這匹配一個用戶名 User.findOne({ username: username }, function(err, user) { if (err) { return next(err); } // 判斷用戶是否存在 if (user) { req.flash("error", "User already exists"); return res.redirect("/signup"); } // 新建用戶 var newUser = new User({ username: username, password: password }); // 插入記錄 newUser.save(next); }); // 進行登錄操作并實現重定向 }, passport.authenticate("login", { successRedirect: "/", failureRedirect: "/signup", failureFlash: true }));路由中間件定義完成后,下面我們就來實現視圖模版 signup.ejs 文件。
// 拷貝代碼到 views/signup.ejs <% include _header %><% include _footer %>Sign up
如果你成功創建用戶并再次訪問主頁的話,你就能看見一組用戶列表:
而注冊頁的 UI 大致如下:
在實現登錄功能之前,我們先把個人信息展示功能先補充完整。在 routes.js 添加如下中間件函數:
... router.get("/users/:username", function(req, res, next) { User.findOne({ username: req.params.username }, function(err, user) { if (err) { return next(err); } if (!user) { return next(404); } res.render("profile", { user: user }); }); }); ...
接下來編寫視圖模版文件 profile.ejs :
// 保存到 views 文件夾中 <% include _header %> <% if ((currentUser) && (currentUser.id === user.id)) { %> Edit your profile <% } %><%= user.name() %>
Joined on <%= user.createdAt %>
<% if (user.bio) { %><%= user.bio %>
<% } %> <% include _footer %>
如果現在你通過首頁進入用戶詳情頁話,那么你就會出現類似下圖的界面:
通過 Passport 來進行用戶身份驗證除了上面這些基本功能之外,User 模型做重要的功能其實是登錄以及權限認證。而這也是 User 模型與其他模型最大的區別。所以接下來的任務就是實現登錄頁并進行密碼和權限認證。
為了減少很多不必要的工作量,這里我們會使用到第三方的 Passport 模塊。該模版是特地為請求進行驗證而設計處理的 Node 中間件。通過該中間件只需一小段代碼就能實現復雜的身份認證操作。不過 Passport 并沒有指定如何進行用戶身份認證,它只是提供了一些模塊化函數。
設置 PassportPassport 的設置過程主要有三件事:
設置 Passport 中間件。
設置 Passport 對 User 模型的序列化和反序列化的操作。
告訴 Passport 如何對 User 進行認證。
首先,在初始化 Passport 環境時,你需要在工程中引入一些其他中間件。它們分別為:
body-parser
cookie-parser
express-session
connect-flash
passport.initialize
passport.session
其中前面 4 個中間件已經引入過了。它們的作用分別為: body-parser 用于參數解析;cookie-parser 處理從瀏覽器中獲取的cookies;express-session 用于處理用戶 session;而 connect-flash 則用戶展示錯誤信息。
最后,我們需要在 app.js 中引入 Passport 模塊并在后面調用其中的兩個中間件函數。
var bodyParser = require("body-parser"); var cookieParser = require("cookie-parser"); var flash = require("connect-flash"); var passport = require("passport"); var session = require("express-session"); ... app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(session({ // 需要一串隨機字母序列,字符串不一定需要跟此處一樣 secret: "TKRv0IJs=HYqrvagQ#&!F!%V]Ww/4KiVs$s,<代碼中,我們使用一串隨機字符串來對客戶端的 session 進行編碼。這樣就能在一定程度上增加 cookies 的安全性。而將 resave 設置為 true 則保證了即使 session 沒有被修改也依然會被刷新。
接下來就是第二步操作:設置 Passport 對 User 模型的序列化和反序列化操作了。這樣 Passport 就能實現 session 和 user 對象的互相轉化了。Passport 文檔對這一操作的描述為:
在標準的 web 應用中,只有當客戶端發送了登錄請求才會需要對用戶進行身份認證。如果認證通過的話,兩者之間就會新建一個 session 并將其保存到 cookie 中進行維護。任何后續操作都不會再進行認證操作,取而代之的是使用 cookie 中唯一指定的 session 。所以,Passport 需要通過序列化和反序列化實現 session 和 user 對象的互相轉化。
為了后期代碼維護方便,這里我們新建一個名為 setuppassport.js 的文件并將序列化和反序列化的代碼放入其中。最后,我們將其引入到 app.js 中:
… var setUpPassport = require("./setuppassport"); … var app = express(); mongoose.connect("mongodb://localhost:27017/test"); setUpPassport(); …下面就是 setuppassport.js 中的代碼實現了。因為 User 對象都有一個 id 屬性作為唯一標識符,所以我們就根據它來進行 User 對象的序列化和反序列化操作:
// setuppassport.js 文件中的代碼 var passport = require("passport"); var User = require("./models/user"); module.exports = function() { passport.serializeUser(function(user, done) { done(null, user._id); }); passport.deserializeUser(function(id, done) { User.findById(id, function(err, user) { done(err, user); }); }); }接下來就是最難的部分了,如何進行身份認證?
在開始進行認證前,還有一個小工作需要完成:設置認證策略。雖然 Passport 附帶了 Facebook 、Google 的身份認證策略,但是這里我們需要的將其設置為 local strategy 。因為驗證部分的規則和代碼是由我們自己來實現的。
首先,我們在 setuppassport.js 中引入 LocalStrategy
... var LocalStrategy = require("passport-local").Strategy; …接下來,按照下面的步驟使用 LocalStrategy 來進行具體的驗證:
查詢該用戶。
用戶不存在則提示無法通過驗證。
用戶存在則進行密碼比較。如果匹配成功則返回當前用戶否則提示“密碼錯誤”。
下面就是將這些步驟轉化為具體的代碼:
// setuppassport.js 驗證代碼 ... passport.use("login", new LocalStrategy(function(username, password, done) { User.findOne({ username: username }, function(err, user) { if(err) { return done(err); } if (!user) { return done(null, false, { message: "No user has that username!" }); } user.checkPassword(password, function(err, isMatch) { if (err) { return done(err); } if (isMatch) { return done(null, user); } else { return done(null, false, { message: "Invalid password." }); } }); }); })); ...完成策略定義后,接下來就可以在項目的任何地方進行調用。
最后,我們還需要完成一些視圖和功能:
登錄
登出
登錄完成后的個人信息編輯
首先,我們實現登錄界面視圖。在 routes.js 中添加登錄路由中間件:
... router.get("/login", function(req, res) { res.render("login"); }); ...在登錄視圖 login.ejs 中,我們會接收一個用戶名和一個密碼,然后發送登錄的 POST 請求:
<% include _header %><% include _footer %>Log in
接下來,我們就需要處理該 POST 請求。其中就會使用到 Passport 的身份認證函數。
// routes.js 中登陸功能代碼 var passport = require("passport"); ... router.post("/login", passport.authenticate("login", { successRedirect: "/", failureRedirect: "/login", failureFlash: true })); ...
其中 passport.authenticate 函數會返回一個回調。該函數會根據我們的指定對不同的驗證結果分別進行重定向。例如,登錄成功會重定向到首頁,而失敗則會重定向到登錄頁。
登出操作相對來說要簡單得多,代碼如下
// routes.js 登出部分 ... router.get("/logout", function(req, res) { req.logout(); res.redirect("/"); }); ...
Passport 還附加了 req.user 和 connect-flash 信息。再回顧一下前面的這段代碼,相信你能有更深的體會。
... router.use(function(req, res, next) { // 為你的模板設置幾個有用的變量 res.locals.currentUser = req.user; res.locals.errors = req.flash("error"); res.locals.infos = req.flash("info"); next(); }); ...
登錄和登出玩抽,下面就該輪到個人信息編輯功能了。
首先,我們來實現一個通用的中間件工具函數 ensureAuthenticated 。該中間件函數會對當前用戶的權限進行檢查,如果檢查不通過則會重定向到登錄頁。
// routes.js 中的 ensureAuthenticated 中間件 ... function ensureAuthenticated(req, res, next) { // 一個Passport提供的函數 if (req.isAuthenticated()) { next(); } else { req.flash("info", "You must be logged in to see this page."); res.redirect("/login"); } } ...
接下來,我們會在編輯中間件中調用該函數。因為我們需要確保在開始編輯之前,當前用戶擁有編輯權限。
// GET /edit(在router.js中) ... // 確保用戶被身份認證;如果它們沒有被重定向的話則運行你的請求處理 router.get("/edit", ensureAuthenticated, function(req, res) { res.render("edit"); }); ...
接下來我們需要實現 edit.ejs 視圖模版文件。該視圖模版的內容非常簡單,只包含用戶昵稱和簡介的修改。
// views/edit.ejs <% include _header %><% include _footer %>Edit your profile
最后,我們需要對修改后提交的請求作出處理。在進行數據庫更新之前,這里同樣需要進行權限認證。
// POST /edit(在routes.js中) ... // 通常,這會是一個PUT請求,不過HTML表單僅僅支持GET和POST router.post("/edit", ensureAuthenticated, function(req, res, next) { req.user.displayName = req.body.displayname; req.user.bio = req.body.bio; req.user.save(function(err) { if (err) { next(err); return; } req.flash("info", "Profile updated!"); res.redirect("/edit"); }); }); ...
該代碼僅僅只是對數據庫對應記錄的字段進行了更新。最終渲染的編輯視圖如下:
最后,你可以創建一些測試數據對示例應用的所有功能進行一遍驗證。
總結本文包含的內容有:
Mongo 的工作原理。
Mongoose 的使用。
使用 bcrypt 對特定字段進行加密來提高數據安全性。
使用 Passport 進行權限認證。
原文地址
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/84845.html
摘要:沒有耐心閱讀的同學,可以直接前往學習全棧最后一公里。我下面會羅列一些,我自己錄制過的一些項目,或者其他的我覺得可以按照這個路線繼續深入學習的項目資源。 showImg(https://segmentfault.com/img/bVMlke?w=833&h=410); 本文技術軟文,閱讀需謹慎,長約 7000 字,通讀需 5 分鐘 大家好,我是 Scott,本文通過提供給大家學習的方法,...
摘要:前言要做一個全沾的工程師,對于后端和數據庫來說,即使不認識也要見個面的。基本了解的概念就好,主要是安裝上數據庫,并進行簡單的增刪操作。 前言:要做一個全沾的工程師,對于后端和數據庫來說,即使不認識也要見個面的。本文給的例子很簡單,也貼出來源碼,只要一步步下來,就可以跑起來啦~~~ 思考一個需求:做一個登錄頁面,自己搭建服務和數據庫,將用戶輸入的登錄信息保存到數據庫如何完成呢:首先選擇...
摘要:中文資料導航官網七牛鏡像深入淺出系列進階必讀中文文檔被誤解的編寫實戰系列熱門模塊排行榜,方便找出你想要的模塊多線程,真正的非阻塞淺析的類利用編寫異步多線程的實例中與的區別管道拒絕服務漏洞高級編程業界新聞看如何評價他們的首次嘗鮮程序員如何說服 node.js中文資料導航 Node.js HomePage Node官網七牛鏡像 Infoq深入淺出Node.js系列(進階必讀) Nod...
摘要:一個標準性的事件就是年的橫空出世。引擎快速處理能力和異步編程風格,讓開發者從多線程中解脫了出來。其次,通過異步編程范式將其高并發的能力發揮的淋漓盡致。它也僅僅是一個處理請求并作出響應的函數,并無任何特殊之處。 showImg(https://segmentfault.com/img/remote/1460000010819116); 在正式學習 Express 內容之前,我們有必要從大...
閱讀 2792·2021-09-01 10:30
閱讀 1686·2019-08-30 15:52
閱讀 973·2019-08-29 18:40
閱讀 1129·2019-08-28 18:30
閱讀 2399·2019-08-23 17:19
閱讀 1330·2019-08-23 16:25
閱讀 2703·2019-08-23 16:18
閱讀 2983·2019-08-23 13:53