• Koa学习4:密码加密、验证登录、颁发token、用户认证


    请求体

    这里遇到了个问题,ctx.request.body 的值是一个字符串。明明已经使用了koa-body中间件

    查了一下原因是:

    ctx.request.body的值可能是一个对象或一个字符串,取决于请求的Content-Type和请求体的格式。
    当使用koa-body中间件时,它会根据请求的Content-Type自动解析请求体,并将解析后的结果存储在ctx.request.body中。如果Content-Type是application/json,则koa-body将解析请求体为JSON格式,并将其存储为对象;如果Content-Type是application/x-www-form-urlencoded,则koa-body将解析请求体为键值对形式,并将其存储为对象;如果Content-Type是multipart/form-data,则koa-body将解析请求体为多部分表单数据,并将其存储为对象。
    但是,如果没有使用koa-body中间件或者请求的Content-Type不是以上几种类型,ctx.request.body将保持为原始的字符串形式。在这种情况下,您需要根据请求的内容类型进行手动处理。

    因此请求参数要设置成json格式
    在这里插入图片描述

    密码加密

    这里使用bcryptjs

    bcryptjs是一个JavaScript库,用于将密码哈希化并进行比较。它使用bcrypt算法,这是一种密码哈希函数,可以将密码转换为不可读的字符串,以增加安全性。bcryptjs库提供了一种简单的方法来使用bcrypt算法,以便在应用程序中存储和比较密码。它还提供了一些其他功能,例如生成随机的salt(盐)值,以增加哈希的安全性。在Web应用程序中,使用bcryptjs可以保护用户密码,防止黑客攻击和数据泄露。

    安装

    npm install bcryptjs
    
    • 1

    这里将密码加密抽离成一个中间件,便于后期的维护。如果需要更换加密方式的时候,只需要使用一个新的中间件即可,不会对原有代码产生大的改动。

    编写中间件
    user.middleware.js

    // 密码加密
    const crpytPassword = async (ctx, next) => {
      const requestBidy = ctx.request.body;
      if (!requestBidy?.password) {
        console.error('密码为空,密码加密失败');
        return;
      }
      // 加盐
      const salt = bcrypt.genSaltSync(10);
      // 哈希加密
      const hash = bcrypt.hashSync(requestBidy.password, salt);
      // 更新密码
      ctx.request.body.password = hash;
      console.log('加密后的密码:', ctx.request.body);
    
      await next();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    使用中间件,在进行注册之前将密码进行加密
    user.route.js

    // 注册
    router.post('/register', userRegisterValidator, crpytPassword,register);
    
    • 1
    • 2

    在这里插入图片描述

    验证登录

    user.middleware.js中新定义一个中间件,用来校验登录

    // 用户登录校验
    const userLoginValidator = async (ctx, next) => {
      // 获取入参
      const requistBody = ctx.request.body;
      // 合法性判断
      if (!requistBody.user_name || !requistBody.password) {
        // console.error 打印的内容会被记录到服务器的日志里
        console.error('用户名或密码为空:', requistBody);
        //使用了Koa的错误处理机制
        ctx.app.emit('error', UserErr.userFormatError, ctx);
        return;
      }
      // 使用try catch 避免进行数据库操作时出现问题,导致程序异常
      try {
        // 判断数据库中是否存在该用户,若存在还需比较密码是否正确
        // 1、判断用户是否存在,不存在则提示用户进行注册
        const user = await getUserInfo({ user_name: requistBody.user_name });
        if (!user) {
          console.error('用户名不存在', requistBody.user_name);
          ctx.app.emit('error', UserErr.userNameErr, ctx);
          return;
        }
        // 2、用户存在,验证密码是否正确
        if (!bcrypt.compareSync(requistBody.password, user.password)) {
          console.error('密码不正确', requistBody.password);
          ctx.app.emit('error', UserErr.passwordErr, ctx);
          return;
        }
        // 验证通过后交由一个中间件处理
      } catch (error) {
        console.error('用户登录失败:', error);
        return ctx.app.emit('error', UserErr.userLoginErr, ctx);
      }
      await next();
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35

    user.err.type.js添加新的错误类型

    userNameErr:{
        code:'10004',
        message:'用户名不存在,请进行注册',
        data:null
    },
    passwordErr:{
        code:'10005',
        message:'密码错误,请确认',
        data:null
    },
    userLoginErr:{
        code:'10006',
        message:'用户登录失败',
        data:null
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    user.route.js中添加中间件

    // 登录
    router.post('/login', userLoginValidator, login);
    
    • 1
    • 2

    颁发token

    登录成功后给用户颁发一个token,用户在以后的每一次请求中都会携带token,服务端会对进行校验。这里我们使用JWT

    JWT(JSON Web Token)是一种开放标准(RFC 7519),它定义了一种紧凑且自包含的方式来在各方之间安全地传输信息。JWT 通常用于身份验证和授权场景,它可以在客户端和服务器之间传递信息,以验证用户的身份和授权用户访问资源。

    在 Node.js 中,我们可以使用第三方库 jsonwebtoken 来生成和验证 JWT。生成 JWT 时,我们需要提供一个包含用户信息的 JSON 对象和一个密钥,然后将其编码为一个字符串。验证 JWT 时,我们需要提供这个字符串和相同的密钥,然后解码出 JSON 对象,从而获取用户信息。

    JWT 由三部分组成:头部(Header)、载荷(Payload)和签名(Signature)。头部包含了 JWT 的类型和使用的算法,载荷包含了用户信息和其他元数据,签名用于验证 JWT 的完整性和真实性。

    安装

    npm install jsonwebtoken
    
    • 1

    颁发token
    user.controller.js

    Token通常会记录一些用户身份验证信息,例如用户ID、角色、权限等。但是,用户密码通常不会记录在Token中,因为这样做会增加安全风险。相反,用户密码通常会在用户登录时进行验证,然后在服务器端进行加密存储。在后续的请求中,服务器会使用Token来验证用户身份,而不是使用密码。这样可以保护用户密码的安全性。

    // 导入jsonwebtoken
    const jwt = require('jsonwebtoken');
    
    // 登录
      async login(ctx, next) {
        // 1、获取数据
        const requistBody = ctx.request.body;
    
        try {
          // 2、获取用户信息(在token的paykoad中要记录需要用到的用户信息,比如:id、user_name、is_vip)
          // 为了保证安全,token里不能记录密码
          const { password, ...res } = await getUserInfo({
            user_name: requistBody.user_name,
          });
          ctx.body = {
            code: 0,
            message: '登录成功',
            data: {
              // 用户信息和私钥
              token: jwt.sign(res, 'test', {
                expiresIn: '2h', // 过期时间2小时
              }),
            },
          };
        } catch (error) {
          console.error('用户登录失败', error);
        }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    在这里插入图片描述

    token验证

    当用户登录成功后,在访问某些功能时需要判断用户是否有这个权限,这时可以通过解析token拿到用户数据来进行权限的判断。
    下面以修改用户密码为例,来实现token的验证

    将token添加到请求头中

    参数名选择:Authorization,参数值为登录成功后返回的token值。我这里使用的软件是Apifox,前面介绍过就不说了。
    在这里插入图片描述
    注意:参数值里不要在外面加上引号

    创建处理用户鉴权的中间件
    后续的大部分操作都会进行权限验证,判断是否有该功能的权限。判断权限需要处理token,这里使用统一的中间件进行处理
    auth.middleware.js

    // 导入jwt
    const jwt = require('jsonwebtoken');
    // 导入工具函数
    const { createSecretKey } = require('../tool/tool');
    // 导入错误类型
    const { AuthErr } = require('../constant/err.type');
    
    const auth = async (ctx, next) => {
      // 获取token信息
      const { authorization } = ctx.request.header;
      // token信息通常放在请求头的Authorization属性下,并且在请求头中,可以使用Bearer前缀来标识
      const token = authorization.replace('Bearer ', '');
      // 验证token
      try {
        // 获取到用户名,用户名是唯一的,比如手机号,
        // 要保证请求每一个接口时都用用户名这个参数,这里ctx.request.body返回的是一个字符串
        const requistBody = JSON.parse(ctx.request.body);
        // 私钥
        let key = createSecretKey(requistBody.user_name);
        // 校验,校验成功后会解析出用户信息
        const userInfo = jwt.verify(token, key);
        // 将其挂载到ctx.state下,方便后续使用
        ctx.state.userInfo = userInfo;
      } catch (error) {
        // 错误信息见 https://www.npmjs.com/package/jsonwebtoken
        switch (error.name) {
          // token过期
          case 'TokenExpiredError':
            console.log('token已过期', error);
            return ctx.app.emit('error', AuthErr.tokenExpiredError, ctx);
          //   token验证错误
          case 'JsonWebTokenError':
            console.log('token验证失败', error);
            return ctx.app.emit('error', AuthErr.jsonWebTokenError, ctx);
          //   其他异常
          default:
            console.error('鉴权验证失败', error);
            return ctx.app.emit('error', AuthErr.authVerifyError, ctx);
        }
      }
      await next();
    };
    
    module.exports = {
      auth,
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46

    在路由里添加修改路由

    user.route.js

    // 修改密码
    router.post('/editUserInfo', auth, editUserInfo);
    
    • 1
    • 2

    在这里插入图片描述
    在这里遇到了一个问题,就是token一直验证失败;后来使用jwt.decode(token);进行token解析,结果一直失败。定位到问题在最开始的生成token中,后来发现是由于这两个时间导致的。如果需要用的时间的话,最好通过dayjs之类的库,对时间进行格式化。

    数据库中存储的时间是按照特定的格式进行存储的,而在请求返回时,服务器会将时间转换为ISO 8601格式的字符串,这是一种通用的时间格式。

  • 相关阅读:
    维修派工单,为什么要使用维修派工单
    [附源码]计算机毕业设计JAVAjsp智慧农产品朔源系统
    PTE-DI 练习 + 模板
    prompt 提示词如何写?
    【软件设计】软件开发的核心原则
    【HarmonyOS】【JAVA UI】鸿蒙怎么对图片编码为base64和base64解码为PixelMap,并显示在控件上
    Waves插件
    Excel必备!6种快速插入√或x标记的方法揭秘
    Inno Setup安装中文语言
    1.19.5.3.时态表、关联一张版本表、关联一张普通表、时态表、声明版本表、声明版本视图、声明普通表、时态表函数等
  • 原文地址:https://blog.csdn.net/weixin_41897680/article/details/131155893