介绍
就像身份验证在api中很重要一样,它也是某些 Web 应用程序中的一个重要功能——那些页面和机密应该只有注册和经过身份验证的用户才能访问的应用程序。
在本教程中,您将构建一个简单的 Web 应用程序,同时学习如何创建用户注册。
应用程序设置
创建一个新目录,您将在其中工作。为了本教程,我调用了我的 site-auth。在刚刚创建的新目录中初始化 npm。这是初始化 npm 的方法。
npm init -y
该-y
标志告诉 npm 使用默认选项。
编辑package.json文件的依赖项部分,使其看起来像我的内容。
#package.json { "name": "site-auth", "version": "1.0.0", "description": "", "main": "app.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, "keywords": [], "author": "izuchukwu1", "license": "ISC", "dependencies": { "bcryptjs": "^2.4.3", "body-parser": "^1.17.1", "connect-flash": "^0.1.1", "cookie-parser": "^1.4.3", "express": "^4.15.2", "express-handlebars": "^3.0.0", "express-messages": "^1.0.1", "express-session": "^1.15.2", "joi": "^13.0.1", "mongoose": "^4.11.12", "morgan": "^1.8.1", "passport": "^0.4.0", "passport-local": "^1.0.0" } }
完成后,运行命令安装依赖项。
npm install
在您的工作目录中创建一个名为app.js的文件。
首先要求您安装的依赖项和必要的文件。
#app.js const express = require('express'); const morgan = require('morgan') const path = require('path'); const cookieParser = require('cookie-parser'); const bodyParser = require('body-parser'); const expressHandlebars = require('express-handlebars'); const flash = require('connect-flash'); const session = require('express-session'); const mongoose = require('mongoose') const passport = require('passport') require('./config/passport')
这些依赖项是在您运行 npm install 时安装的。要在您的应用程序中使用它们,您必须要求它们并将它们保存在各自的常量中。
对于本教程,您将使用mongodb作为数据库。您需要将用户信息存储在数据库中。要使用 MongoDB,您将使用 Mongoose——一个用于 node.js 的 MongoDB 建模工具。设置 Mongoose 很简单,就像这样。
#app.js mongoose.Promise = global.Promise mongoose.connect('mongodb://localhost:27017/site-auth')
此时,让我们设置我们的中间件。
// 1 const app = express() app.use(morgan('dev')) // 2 app.set('views', path.join(__dirname, 'views')) app.engine('handlebars', expressHandlebars({ defaultLayout: 'layout' })) app.set('view engine', 'handlebars') // 3 app.use(bodyParser.json()) app.use(bodyParser.urlencoded({ extended: false })) app.use(cookieParser()) app.use(express.static(path.join(__dirname, 'public'))) app.use(session({ cookie: { maxAge: 60000 }, secret: 'codeworkrsecret', saveUninitialized: false, resave: false })); app.use(passport.initialize()) app.use(passport.session()) // 4 app.use(flash()) app.use((req, res, next) => { res.locals.success_mesages = req.flash('success') res.locals.error_messages = req.flash('error') next() }) // 5 app.use('/', require('./routes/index')) app.use('/users', require('./routes/users')) // 6 // catch 404 and forward to error handler app.use((req, res, next) => { res.render('notFound') }); // 7 app.listen(5000, () => console.log('Server started listening on port 5000!'))
Express 被初始化并分配给
app
.设置了处理视图的中间件。对于视图,您将使用
handlebars
.bodyparser
您为、cookie
、session
和设置中间件passport
。当用户想要登录时,将使用 Passport。在某些时候,您将显示 Flash 消息。因此,您需要为此设置中间件,并创建所需的 Flash 消息类型。
路由中间件——这将处理对 URL 路径的任何请求。此处指定的 URL 路径用于索引和用户路径。
处理 404 错误的中间件。当请求没有映射到在它上面创建的任何中间件时,这个中间件就会启动。
服务器设置为***端口 5000。
视图设置
创建一个名为views的新目录。在 views 目录中,创建另外两个名为layouts和partials的目录。您想在视图中实现这样的树结构,因此在它们各自的目录中创建必要的文件。
├── dashboard.handlebars ├── index.handlebars ├── layouts │ └── layout.handlebars ├── login.handlebars ├── notFound.handlebars ├── partials │ └── navbar.handlebars └── register.handlebars
完成后,是时候删除代码了。
#dashboard.handlebars <!-- Jumbotron --> <div class="jumbotron"> <h1>User DashBoard</h1> </div>
这是一个只有注册用户才能看到的仪表板。对于本教程,这将是您的秘密页面。
现在应用程序的索引页面应该如下所示。
#index.handlebars <!-- Jumbotron --> <div class="jumbotron"> <h1>Site Authentication!</h1> <p class="lead">Welcome aboard.</p> </div>
应用程序需要一个将要使用的布局,这是您将要使用的布局。
#layout/layout.handlebars <!DOCTYPE html> <html> <head> <title>Site Authentication</title> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous"> <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap-theme.min.css" integrity="sha384-rHyoN1iRsVXV4nD0JutlnGaslCJuC7uwjduW9SVrLvRYooPp2bWYgmgJQIXwl/Sp" crossorigin="anonymous"> <link rel="stylesheet" href="/css/style.css"> </head> <body> {{#if success_messages }} <div class="alert alert-success">{{success_messages}}</div> {{/if}} {{#if error_messages }} <div class="alert alert-danger">{{error_messages}}</div> {{/if}} <div class="container"> {{> navbar}} {{{body}}} </div> <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/js/bootstrap.min.js" integrity="sha384-Tc5IQib027qvyjSMfHjOMaLkfuWVxZxUPnCJA7l2mCWNIpG9mGCD8wGNIcPD7Txa" crossorigin="anonymous"></script> </body> </html>
您需要注册用户的登录页面。
#views/login.handlebars <form class="form-signin" action="/users/login" method="post"> <h2 class="form-signin-heading">Please sign in</h2> <label for="inputEmail" class="sr-only">Email address</label> <input type="email" id="inputEmail" name="email" class="form-control" placeholder="Email address" required autofocus> <label for="inputPassword" class="sr-only">Password</label> <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required> <br/> <button class="btn btn-lg btn-default btn-block" type="submit">Sign in</button> </form>
notFound .handlebars文件将用作您的错误页面。
#views/notFound.handlebars <!-- Jumbotron --> <div class="jumbotron"> <h1>Error</h1> </div>
您的注册页面应该是这样的。
<form class="form-signin" action="/users/register" method="POST"> <h2 class="form-signin-heading">Please sign up</h2> <label for="inputEmail" class="sr-only">Email address</label> <input type="email" id="inputEmail" name="email" class="form-control" placeholder="Email address" required autofocus> <label for="inputUsername" class="sr-only">Username</label> <input type="text" id="inputUsername" name="username" class="form-control" placeholder="Username" required> <label for="inputPassword" class="sr-only">Password</label> <input type="password" id="inputPassword" name="password" class="form-control" placeholder="Password" required> <label for="inputconfirmPassword" class="sr-only">Confirm Password</label> <input type="password" id="inputConfirmPassword" name="confirmationPassword" class="form-control" placeholder="Confirm Password" required> <br/> <button class="btn btn-lg btn-default btn-block" type="submit">Sign up</button> </form>
最后为了您的意见,这里是您的导航栏。
#partials/navbar.handlebars <div class="masthead"> <h3 class="text-muted">Site Authentication</h3> <nav> <ul class="nav nav-justified"> <li class="active"><a href="/">Home</a></li> {{#if isAuthenticated}} <li><a href="/users/dashboard">Dashboard</a></li> <li><a href="/users/logout">Logout</a></li> {{else}} <li><a href="/users/register">Sign Up</a></li> <li><a href="/users/login">Sign In</a></li> {{/if}} </ul> </nav> </div>
完成后,你就可以进入一些更深的部分了。
数据验证
您将需要一个用户模型。从上面的视图代码中,您可以推断出 User 模型所需的属性是电子邮件、用户名和密码。创建一个名为models的目录,并在其中创建一个名为user.js的文件。
#models/user.js // 1 const mongoose = require('mongoose') const Schema = mongoose.Schema const bcrypt = require('bcryptjs') // 2 const userSchema = new Schema({ email: String, username: String, password: String }, { // 3 timestamps: { createdAt: 'createdAt', updatedAt: 'updatedAt' } }) // 4 const User = mongoose.model('user', userSchema) module.exports = User
导入依赖项并将它们保存在常量中。
创建一个新的模式。对于每个用户,您都希望将 、 和 保存
email
到username
数据库password
中。Schema 显示了如何为每个文档构建模型。在这里,您希望电子邮件、用户名和密码为字符串类型。对于保存到数据库的每个用户,您还需要创建
timestamps
. 您利用 Mongoose 获取createdAt
andupdatedAt
,然后将其保存到数据库中。该模型被定义并分配给一个名为 的常量
User
,然后将其导出为一个模块,以便可以在应用程序的其他部分中使用它。
密码的加盐和散列
您不想将用户的密码存储为纯文本。当用户在注册时输入纯文本密码时,您需要执行以下操作。应该使用将由您的应用程序生成的盐(使用 bcryptjs)对纯文本密码进行哈希处理。然后将此散列密码存储在数据库中。
听起来不错,对吧?让我们在user.js文件中实现它。
#models/user.js module.exports.hashPassword = async (password) => { try { const salt = await bcrypt.genSalt(10) return await bcrypt.hash(password, salt) } catch(error) { throw new Error('Hashing failed', error) } }
您刚刚创建了一个将在用户注册事件中调用的方法。该方法将接收用户输入的纯文本密码。正如我之前提到的,纯文本密码将使用生成的盐进行哈希处理。散列后的密码将作为用户的密码返回。
索引和用户路由
创建一个名为routes的新目录。在这个新目录中,创建两个新文件:index.js和users.js。
index.js文件将非常简单。它将映射到您的应用程序的索引。请记住,当您执行此操作时,您在app.js文件中为您的路由设置了中间件。
app.use('/', require('./routes/index')) app.use('/users', require('./routes/users'))
因此,您的索引路由(仅呈现索引页面)应该如下所示。
#routes/index.js const express = require('express') const router = express.Router() router.get('/', (req, res) => { res.render('index') }) module.exports = router
现在到用户路线。目前,这个路由文件将做四件事。
需要依赖。您将需要使用 NPM 安装的依赖项。
验证用户输入。您要确保用户不提交空表单。所有输入都是必需的,并且都必须是字符串类型。电子邮件有一个称为特殊验证的
.email()
方法,可确保输入的内容与电子邮件格式匹配,同时使用正则表达式验证密码。对于确认密码,您希望它与输入的密码相同。这些验证是使用Joi完成的。设置你的路由器。GET请求呈现注册页面,而POST请求在用户点击按钮提交表单时启动。
路由器作为模块导出。
这是代码的样子。
#routes/users.js const express = require('express'); const router = express.Router() const Joi = require('joi') const passport = require('passport') const User = require('../models/user') //validation schema const userSchema = Joi.object().keys({ email: Joi.string().email().required(), username: Joi.string().required(), password: Joi.string().regex(/^[a-zA-Z0-9]{6,30}$/).required(), confirmationPassword: Joi.any().valid(Joi.ref('password')).required() }) router.route('/register') .get((req, res) => { res.render('register') }) .post(async (req, res, next) => { try { const result = Joi.validate(req.body, userSchema) if (result.error) { req.flash('error', 'data entered is not valid. Please try again.') res.redirect('/users/register') return } const user = await User.findOne({ 'email': result.value.email }) if (user) { req.flash('error', 'Email is already in use.') res.redirect('/users/register') return } const hash = await User.hashPassword(result.value.password) delete result.value.confirmationPassword result.value.password = hash const newUser = await new User(result.value) await newUser.save() req.flash('success', 'Registration successfully, go ahead and login.') res.redirect('/users/login') } catch(error) { next(error) } }) module.exports = router
让我们更深入地了解该POST请求中发生的情况。
在注册表单中输入的值可通过 访问req.body
,值如下所示。
value: { email: 'chineduizuchkwu1@gmail.com', username: 'izu', password: 'chinedu', confirmationPassword: 'chinedu' },
这是使用userSchema
您在上面创建的验证的,并且用户输入的值被分配给一个名为 result 的常量。
如果由于验证而遇到错误,则会向用户显示错误消息并重定向到注册页面。
否则,我们会尝试查找是否存在具有相同电子邮件地址的用户,因为您不希望两个或更多用户具有相同的电子邮件地址。如果找到用户,则告知用户该电子邮件地址已在使用中。
在没有注册用户拥有该电子邮件地址的情况下,下一步是对密码进行哈希处理。这是您调用hashPassword
在 user.js 文件中创建的方法的地方。新的散列密码被分配给一个称为散列的常量。
无需将其存储confirmationPassword
在数据库中。因此将其删除。结果中可用的密码仍然是普通密码。由于您不想将纯密码存储在数据库中,因此将密码值重新分配给已创建的哈希非常重要。这是通过一行代码完成的。
result.value.password = hash
新的用户实例被保存到数据库中。将显示一条表明注册成功的闪烁消息,并将用户重定向到登录页面。
通过运行以下命令从终端启动服务器:
node app.js
将您的浏览器指向http://localhost:5000,您应该会看到您的新应用程序。
结论
现在您知道如何在 Node Web 应用程序中实现注册功能。您已经了解了验证用户输入的重要性以及如何使用 Joi 进行验证。您还使用了bcryptjs
对您的密码进行加盐和哈希处理。