使用Node.js构建博客API:身份验证和验证(第3部分)
#node #api #jwt

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

isValidPasswordpassword作为参数,并使用bcryptcompare方法将其与用户的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,
};

我们正在导出两个中间件功能,即newArticleValidationMWupdateArticleValidationMWnewArticleValidationMW使用newArticleValidationSchema来验证新文章请求的请求主体是否包含所有必需字段(titlebody),并且所有提供的字段均以正确的格式。如果所有字段都是有效的,则调用下一个函数以继续请求。 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的路线和控制器的详细信息。敬请期待!