Node.js: express + MySQL实现注册登录,身份认证

        这篇文章中有些内容像字段校验,项目结构等部分在另一篇文章中有写到:Node.js: express + MySQL的使用_掉头发类型的选手的博客-CSDN博客

一,实现注册

        1,注册需要将新用户的账号和密码写入数据库,账号可以直接写入数据库,但密码一般不会直接存入到数据库中,会将密码加密后存入数据库中,能够提高账号的安全性。

        2,在登录的时候再将密码通过同样的方式进行加密,与数据库中的存储的密码进行比对,相同的话则登录成功。

        3,实现

        (1),先在数据库中创建一个表用来存储信息,我创建的这个表名是  user_login

         (2),密码加密用到一个包  bcryptjs  ,这个包可以对密码进行加密,用npm将它安装到项目中。

         (3),目录结构,在路由模块,处理函数模块,字段校验模块下都新建一个文件,用来完成这部分内容。

 app.js

// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");

// 配置解析表单数据的中间件  内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));

// 封装res.send() ,必须在路由之前封装
/**
 * 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
 * res.send({ state: 0, message: "查询成功", data: result })
 * 封装之后不需要写这么多
 */
app.use((req, res, next) => {
  // 定义一个输出的函数
  res.output = function (err, status = 1, data) {
    res.send({
      status,
      message: err instanceof Error ? err.message : err,
      data,
    });
  };
  next();
});

// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use(loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);

// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
  // 验证失败导致的错误
  if (err instanceof joi.ValidationError) return res.output(err);
  // // 身份认证失败后的错误
  // if(err.name === 'UnauthorizedError') return res.cc('身份认证失败!')

  // 未知的错误
  res.output(err);
  next();
});

// 启动服务器
app.listen(3007, () => {
  console.log("Server running at http://127.0.0.1:3007");
});

        因为app.js是整个项目的入口文件,所有所有的路由都应该在这个文件中导入并使用。

router文件夹下的login.js文件

const express = require("express");
const router = express.Router();

// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");

// 引入处理函数
const login_handler = require("../router_handler/login");

// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);

// 将路由导出
module.exports = router;

        和上一篇内容一样,这个文件中注册路由,并从router_handler中导入处理函数,导入校验规则的中间件和规则,最后将路由导出,在app.js文件中使用。

router_handler文件夹下的login.js文件  (处理函数)

// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");

// 注册处理函数并导出
exports.userRegister = (req, res) => {
  // 先取到从客户端传来的值
  let { username, password } = req.body;
  /**
   *  注册的时候用户名是唯一的,不能与其他人的用户名一样
   *  在将信息写入数据库之前,要先检查用户名是否被占用
   */
  // 查询有无相同的用户名
  const sql = "select username from user_login where username=?";
  // 执行查找sql语句
  db.query(sql, username, (err, results) => {
    // sql语句执行失败
    if (err) return res.output(err);
    // 执行成功,如果有数据,则说明有相同的用户名
    if (results.length === 1) return res.output("用户名被占用");
    // 执行成功,用户名没被占用

    console.log('加密之前', password);
    // 对密码进行加密,第二个参数可以提高密码的安全性,几也行
    password = bcryptjs.hashSync(password, 10);
    console.log('加密过后', password);

    // 定义新语句,增加用户
    const sqlStr = "insert into user_login set ?";
    // 执行增加sql语句
    db.query(sqlStr, { username, password }, (err, results) => {
      // 执行sql语句失败
      if (err) return res.output(err);
      // 执行sql语句成功 但影响行数不为1
      if (results.affectedRows !== 1) return res.output("用户注册失败!");
      // 执行成功,影响行数为1
      res.output("注册成功!");
    });
  });
};

        (1),先获取到从客户端传来要注册的用户名和密码,解构用户名和密码的时候要用let,因为在下面的操作中需要给密码加密,修改了变量。用const,重新定义一个值也行。

        (2),在注册的过程中分为三步,第一步先将用户名在数据库中查询有没有相同的值,用户名不能相同,存在相同的话返回用户名被占用。

        (3),如果没有相同的用户名和密码,则可以使用,将用户设定的密码进行加密,使用bcryptjs包进行加密。使用hashSync()方法,第一个参数是原密码,第二个参数可以提高密码的安全性,是一个数字。

        (4),加密过后可以在终端输出查看加密后的密码,可以将数据存储到数据库中,使用insert语句,若数据库语句执行成功,但影响行数不为1,也属于失败。

schema文件夹下的login.js文件(校验规则,在router目录下的login.js文件中使用)

// 导入校验规则的包
const joi = require("joi");
// 确定规则
const username = joi.string().alphanum().min(1).max(16).required();
const password = joi.string().pattern(/^[\S]{6,12}$/).required();

// 导出规则
exports.login_rules = {
  body: {
    username,
    password,
  },
};

都写好后用postman测试一下,先测试校验规则能不能用,输一个错的,报错密码不符合正则。

 然后把密码换成符合校验的:

 

 

注册成功,在终端看一下加密之前和之后的密码,数据库中也有了一条新数据。

之后再点击一次send,这次是进行注册的时候数据库中已有数据,并且用户名被占用。

 报错,用户名被占用。

二,实现登录

        实现登录,会用到一个包  jsonwebtoken  ,这个包能够生成token。

文件结构:

        这次多了一个全局的配置文件,config.js,这个文件设置生成token时的加密方式的密钥,和token的持续时间,密钥和时间直接写在文件中也可以,但加密需要用到,解密也需要用到,为了方便将他抽离出来。密钥可以随便设置。

config.js文件 

// 全局的配置文件
module.exports = {
    /**
     * 设置token加密和解密用到的密钥
     */
    jwtSecretKey: 'c^_^h',
    /**
     * 设置token的有效期
     */
    expiresIn: '10h',
}

router文件夹下的login.js文件(在这个文件中将路由配置好)

const express = require("express");
const router = express.Router();

// 导入校验规则的中间价
const expressJoi = require("@escook/express-joi");
// 引入规则
const { login_rules } = require("../schema/login");

// 引入处理函数
const login_handler = require("../router_handler/login");

// 注册
router.post("/register", expressJoi(login_rules), login_handler.userRegister);
// 登录,登录的时候字段也是相同的规则
router.post("/login", expressJoi(login_rules), login_handler.userLogin);

// 将路由导出
module.exports = router;

        注册和登录都用到了用户名和密码,这两个的规则是相同的。

router_handler文件夹下的login.js文件(登录的处理函数)

// 导入数据库模块
const db = require("../db/index");
// 引入对密码进行加密的包
const bcryptjs = require("bcryptjs");
// 导入生成token的包
const jwt = require("jsonwebtoken");
// 导入全局的配置文件,密文
const config = require("../config");

// 注册处理函数
exports.userRegister = (req, res) => {
  // 先取到从客户端传来的值
  let { username, password } = req.body;
  /**
   *  注册的时候用户名是唯一的,不能与其他人的用户名一样
   *  在将信息写入数据库之前,要先检查用户名是否被占用
   */
  // 查询有无相同的用户名
  const sql = "select username from user_login where username=?";
  // 执行查找sql语句
  db.query(sql, username, (err, results) => {
    // sql语句执行失败
    if (err) return res.output(err);
    // 执行成功,如果有数据,则说明有相同的用户名
    if (results.length === 1) return res.output("用户名被占用");
    // 执行成功,用户名没被占用
    // 定义新语句,增加用户
    const sqlStr = "insert into user_login set ?";

    console.log("加密之前", password);
    // 对密码进行加密,第二个参数可以提高密码的安全性,几也行
    password = bcryptjs.hashSync(password, 10);
    console.log("加密过后", password);

    // 执行增加sql语句
    db.query(sqlStr, { username, password }, (err, results) => {
      // 执行sql语句失败
      if (err) return res.output(err);
      // 执行sql语句成功 但影响行数不为1
      if (results.affectedRows !== 1) return res.output("用户注册失败!");
      // 执行成功,影响行数为1
      res.output("注册成功!");
    });
  });
};

// 登录处理函数
exports.userLogin = (req, res) => {
  // 接收表单数据
  const { username, password } = req.body;
  // 先查找用户名是否在数据库中,定义sql语句
  const sql = "select * from user_login where username=?";
  // 执行语句
  db.query(sql, username, (err, results) => {
    if (err) return res.output(err);
    // 语句执行成功,但没有相应的username
    if (results.length !== 1) return res.output("登录失败");
    // 语句执行成功,也有相应的username
    // 进行密码的比较
    // 前面是客户端的密码,后面是数据库中存储经过加密的密码
    const compareResult = bcryptjs.compareSync(password, results[0].password);
    // 会返回true或false
    if (!compareResult) {
      return res.output("密码错误,登录失败!");
    }
    // 密码比对正确,在服务端生成token字段
    // 获取到用户的信息,剔除掉密码,生成token
    const user = { ...results[0], password: "" };
    // 对用户的信息进行加密,生成token字符串,参数2和参数3可以直接写,也可以抽出去
    const tokenStr = jwt.sign(user, config.jwtSecretKey, {
      expiresIn: config.expiresIn,
    });
    // 调用res.send将token响应给客户端
    res.output("登录成功", 0, "Bearer " + tokenStr);
  });
};

        (1),先接收客户端传来的用户名和密码,然后在数据库中寻找是否有对应的用户名,如果没有,就是用户名不对,登录失败。

        (2),如果有对应的用户名,要比对密码是否相同,比对密码还是用到  bcryptjs  包,用到其中compareSync()方法,这个方法有两个参数,第一个是从客户端传来的密码,第二个参数是在数据库中存储经过加密的密码,根据结果会返回true或false,如果不相同,登录失败。

        (3),如果密码相同登录成功,要生成token返回客户端,用到  jsonwebtoken 包,sign()方法,它根据用户的信息生成token,用户信息一般是会将密码去掉的。第二个参数是密钥,第三个参数是时间,有多长时间的有效期,最后将token返回到客户端。

        (4),token客户端不能直接用,需要在前面加 'Bearer ',返回的时候将这个加上,前端就能直接用了。

登录成功。

三,登录内与登录外

        项目的功能分为登录内和登录外的功能,比如一个博客系统,在没有登录时,你可以查看里面的文章,但只有登录之后,才能发表文章。

        登录内的接口要在请求时在请求头里上送token,进行身份认证。只在app.js里有修改,其他文件不变。

app.js

// 引入express
const express = require("express");
// 创建服务器的实例对象
const app = express();
// 引入校验规则的包,在定义错误级别的中间件时会用到
const joi = require("joi");

// 配置解析表单数据的中间件  内置中间件,只能解析application/x-www-form-urlencoded格式的数据
app.use(express.urlencoded({ extended: false }));

// 封装res.send() ,必须在路由之前封装
/**
 * 不管是输出正确的数据还是错误的信息,每次都需要写这么多东西
 * res.send({ state: 0, message: "查询成功", data: result })
 * 封装之后不需要写这么多
 */
app.use((req, res, next) => {
  // 定义一个输出的函数
  res.output = function (err, status = 1, data) {
    res.send({
      status,
      message: err instanceof Error ? err.message : err,
      data,
    });
  };
  next();
});

// 在路由之前配置解析token的中间件
const { expressjwt: jwt } = require("express-jwt");
// 解析token需要token的密钥
const config = require("./config");
// 定义中间件,需要哪个密钥解析,.unless指定哪些接口不需要进行token身份认证
app.use(
  jwt({ secret: config.jwtSecretKey, algorithms: ["HS256"] }).unless({
    path: [/^\/api/],
  })
);

// 导入并使用登录,注册的路由模块
const loginRouter = require("./router/login");
app.use("/api", loginRouter);
// 导入并使用路由模块
const inforRouter = require("./router/userInfor");
app.use(inforRouter);

// 在所有路由下面调用错误级别的中间件
app.use((err, req, res, next) => {
  // 验证失败导致的错误
  if (err instanceof joi.ValidationError) return res.output(err);

  // 未知的错误
  res.output(err);
  next();
});

// 启动服务器
app.listen(3007, () => {
  console.log("Server running at http://127.0.0.1:3007");
});

        (1),在请求头里带了token,接口请求时要解析token,解析token需要一个包  express-jwt  ,还需要和生成token时相同的密钥。

        (2),在没有定义解析token的中间件时,请求这时所有的路由还是不需要token的,定义了之后,所有路由就都需要token了。unless()可以指定哪些路由不需要token。algorithms属性,设置jwt的算法,具体了解可以看  express-jwt  的文档。

        (3),app.use("/api", loginRouter);使用路由时,前面加‘/api',在请求时,请求路径前也需要加 '/api',登录注册的时候不需要上送token。

未上送token,报错。

 上送token,查询成功。

最后梳理一下整个过程中用到的包:

express:框架

mysql:数据库

@escook/express-joi:自动对表单数据进行验证

joi:字段规则

bcryptjs:对密码进行加密处理

jsonwebtoken:生成token

express-jwt:请求头上送token后解析token

        可以到下面链接获取文章中的代码。

        链接: https://pan.baidu.com/s/1t7bX0Nv3kggyf7IFzEffcA 提取码: 0000

猜你喜欢

转载自blog.csdn.net/h360583690/article/details/125583639