在this series的先前文章中,我们设计了API,实现了数据模型并建立了数据库连接。在开始构建实际API之前,我们将介绍两个对任何API都很重要的主题:
- 身份验证
- 验证
让我们从安装本节所需的依赖项开始:
npm install joi jsonwebtoken passport passport-jwt passport-local passport-local-mongoose
使用Passport.js的身份验证
要在我们的API中验证用户,我们将使用流行的Passport.js库。当用户登录时,我们将为用户生成JWT令牌。令牌将用于授权用户的请求到API。
- 在 /authentication/passport.js文件中,设置JWT策略:
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const { UserModel } = require("../models");
const JWTstrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;
passport.use(
new JWTstrategy(
{
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
},
async (token, done) => {
try {
return done(null, token.user);
} catch (error) {
done(error);
}
}
)
);
JSON Web令牌(JWT)策略,使我们能够通过验证其JWT来验证用户。要使用此策略,我们将传递一个秘密密钥(我们存储在.env
文件中)和fucntion。 ExtractJWT.fromAuthHeaderAsBearerToken()
方法将作为持有人令牌从请求的Authorization
标头中提取JWT。如果找到JWT,它将被解码并与done
函数一起传递到该函数。该函数将检查JWT中的user
对象,以查看其是否有效。如果有效,它将使用user
对象调用done
函数,表明用户已经过身份验证,否则将带有错误调用done
函数。
- 对于
/signup
路线,我们将设置本地策略,该策略使用passport-local
模块使用用户名(在这种情况下为他们的电子邮件地址)和密码对用户进行身份验证。当用户注册时,我们将在数据库中创建一个新的User
文档,然后将其返回到Passport。
passport.use(
"signup",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.create({ ...req.body, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
- 登录策略还将使用
passport-local
模块。当用户登录时,我们将搜索数据库中的匹配电子邮件地址,并验证用户是否存在于数据库中。如果登录成功,我们将user
对象返回护照。
passport.use(
"login",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.findOne({ email });
if (!user) {
return done(null, false, { message: "User not found" });
}
return done(null, user, { message: "Logged in Successfully" });
} catch (error) {
return done(error);
}
}
)
);
我们仍然需要验证用户的密码。在/src/models/user.models.js
中,创建一种在User
模型中创建称为isValidPassword()
的方法。
UserModel.methods.isValidPassword = async function (password) {
const user = this;
const match = await bcrypt.compare(password, user.password);
return match;
};
isValidPassword
将password
作为参数,并使用bcrypt
的compare
方法将其与用户的Hashed密码进行比较。该方法返回一个布尔值,指示密码是否匹配。
在SRC/Authentication/Passport.js中,请致电isValidPassword
在登录策略中验证用户的密码:
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, {message: "Wrong Password" });
}
完整的文件应该看起来像这样:
const passport = require("passport");
const localStrategy = require("passport-local").Strategy;
const { UserModel } = require("../models");
const JWTstrategy = require("passport-jwt").Strategy;
const ExtractJWT = require("passport-jwt").ExtractJwt;
passport.use(
new JWTstrategy(
{
secretOrKey: process.env.JWT_SECRET,
jwtFromRequest: ExtractJWT.fromAuthHeaderAsBearerToken()
},
async (token, done) => {
try {
return done(null, token.user);
} catch (error) {
done(error);
}
}
)
);
passport.use(
"signup",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.create({ ...req.body, password });
return done(null, user);
} catch (error) {
done(error);
}
}
)
);
passport.use(
"login",
new localStrategy(
{
usernameField: "email",
passwordField: "password",
passReqToCallback: true
},
async (req, email, password, done) => {
try {
const user = await UserModel.findOne({ email });
if (!user) {
return done(null, false, { message: "User not found" });
}
const validate = await user.isValidPassword(password);
if (!validate) {
return done(null, false, { message: "Wrong Password" });
}
return done(null, user, { message: "Logged in Successfully" });
} catch (error) {
return done(error);
}
}
)
);
与Joi的验证
要验证用户输入,我们将使用Joi library。 JOI提供了一种简单而有力的方法来定义和验证Node.js应用程序中的数据结构。
- 在
/validators
目录中,创建一个名为author.validator.js
的文件:
const Joi = require("joi");
const newArticleValidationSchema = Joi.object({
title: Joi.string().trim().required(),
body: Joi.string().trim().required(),
description: Joi.string().trim(),
tags: Joi.string().trim(),
});
const updateArticleValidationSchema = Joi.object({
title: Joi.string().trim(),
body: Joi.string().trim(),
description: Joi.string().trim(),
tags: Joi.string().trim(),
state: Joi.string().trim(),
});
const newArticleValidationMW = async (req, res, next) => {
const article = req.body;
try {
await newArticleValidationSchema.validateAsync(article);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
const updateArticleValidationMW = async (req, res, next) => {
const article = req.body;
try {
await updateArticleValidationSchema.validateAsync(article);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
module.exports = {
newArticleValidationMW,
updateArticleValidationMW,
};
我们正在导出两个中间件功能,即newArticleValidationMW
和updateArticleValidationMW
。 newArticleValidationMW
使用newArticleValidationSchema
来验证新文章请求的请求主体是否包含所有必需字段(title
,body
),并且所有提供的字段均以正确的格式。如果所有字段都是有效的,则调用下一个函数以继续请求。 updateArticleValidationMW
与第一个类似,但它使用updateArticleValidationSchema
来验证更新文章请求的请求正文。
这两个函数都使用JOI库提供的validateAsync
方法执行验证。此方法采用对象(请求正文),并返回如果对象无效或解决该验证,该承诺是有效的。
- 在
/validators
目录中,创建一个名为user.validator.js
的文件:
const Joi = require("joi");
const validateUserMiddleware = async (req, res, next) => {
const user = req.body;
try {
await userValidator.validateAsync(user);
next();
} catch (error) {
return next({ status: 406, message: error.details[0].message });
}
};
const userValidator = Joi.object({
firstname: Joi.string().min(2).max(30).required(),
lastname: Joi.string().min(2).max(30).required(),
email: Joi.string().email({
minDomainSegments: 2,
tlds: { allow: ["com", "net"] },
}),
password: Joi.string()
.pattern(new RegExp("^(?=.*[a-z])(?=.*[A-Z])(?=.*[0-9])(?=.{8,})"))
.required(),
});
module.exports = validateUserMiddleware;
- 在
/validators/index.js
中:
const userValidator = require("./user.validator");
const {
newArticleValidationMW,
updateArticleValidationMW,
} = require("./author.validator");
module.exports = {
userValidator,
newArticleValidationMW,
updateArticleValidationMW,
};
有了身份验证和验证,我们准备开始构建API路由和控制器。在下一篇文章中,我们将深入了解我们博客API的路线和控制器的详细信息。敬请期待!