从零到一完成Midway.js登录、注册、鉴权功能

news2024/11/20 13:35:07

您好,如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

前言

本文将从项目搭建到实现从零到一开发一个登录、注册、鉴权的简易版注册登录系统,主要功能和技术选型如下:

  • 服务端框架———Midway.js;
  • 密码加密存储———bcrypt.js;
  • 数据库存储———typeormmysql;
  • 登录鉴权———jwt;

准备工作

安装mysql环境、建好数据库和一张user表,Dbeavervscode database用于初始化表字段。

image.png

全流程

首先我们创建一个Midway项目。

npm init midway@latest -y

初始化数据库环境

然后第一步先初始化项目数据库环境,连接mysql,安装数据库相关依赖包。

npm i @midwayjs/typeorm@3 typeorm mysql2 --save

然后在configuration.ts中引入typeorm组件:

// configuration.ts
import { Configuration } from '@midwayjs/core';
import * as orm from '@midwayjs/typeorm';
import { join } from 'path';

@Configuration({
  imports: [
    // ...
    orm                                                         // 加载 typeorm 组件
  ],
  importConfigs: [
    join(__dirname, './config')
  ]
})
export class MainConfiguration {

}

然后在config目录中配置数据库信息:

import { MidwayConfig } from '@midwayjs/core';
import { User } from '../entity/user.entity';

export default {
  // use for cookie sign key, should change to your own and keep security
  keys: '1697424147281_6188',
  koa: {
    port: 7001,
  },
  typeorm: {
    dataSource: {
      default: {
        /**
         * 单数据库实例
         */
        type: 'mysql',
        host: 'localhost',
        port: 3306,
        username: 'root',
        password: 'xxxxx',
        database: '数据库名',
        synchronize: false, // 如果第一次使用,不存在表,有同步的需求可以写 true,注意会丢数据
        logging: false,

        // 配置实体模型
        entities: [User],
      },
    },
  },
} as MidwayConfig;

最后我们还需要一个数据表实例,新建/entity/user.entity.ts,代码如下:

import { Entity, Column, PrimaryColumn } from 'typeorm';

@Entity('userInfo')
export class User {
  @PrimaryColumn()
  id: number;

  @Column()
  username: string;

  @Column()
  password: string;
}

至此关于数据库配置环境已经OK了,项目已经和数据库关联起来了。

登录注册接口

然后新建一个user.controller.tsuser.service.ts,controller用于中转服务,service用于存放业务逻辑代码。

user.controller.ts:

import { Inject, Controller, Post } from '@midwayjs/core';
import { Context } from '@midwayjs/koa';
import { UserService } from '../service/user.service';

@Controller('/api')
export class APIController {
  @Inject()
  ctx: Context;

  @Inject()
  userService: UserService;

  @Post('/register')
  async register() {
    const params = this.ctx.request.body as {
      username: string;
      password: string;
    };
    const user = await this.userService.register(params);
    return { success: true, message: 'OK', data: user };
  }

  @Post('/login')
  async login() {
    const params = this.ctx.request.body as {
      username: string;
      password: string;
    };
    const user = await this.userService.login(params);
    return { success: true, message: 'OK', data: user };
  }
}

我们再把service的雏形给写出来,代码如下:

user.service.ts

import { Provide, httpError, Inject, Context } from '@midwayjs/core';
import { User } from '../entity/user.entity';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
const { v4: uuidv4 } = require('uuid');
import { JwtService } from '@midwayjs/jwt';

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModal: Repository<User>;

  @Inject()
  jwtService: JwtService;

  @Inject()
  ctx: Context;

  async register(options: { username: string; password: string }) {
    const { username, password } = options;
    return {
      success: true,
      username,
      res: '注册成功',
    };
  }

  async login(options: { username: string; password: string }) {
    const { username, password } = options;
    return {
      accessToken: 'xxxx',
    };
  }
}

前面已经让项目和数据库关联了,现在需要让接口与数据表绑定起来,我们可以通过InjectEntityModel在接口服务中注入表信息,来进行增删改查操作,有了操作数据库的能力,就可以开始开发主体逻辑了。

注册

user.service.ts:

/*
 * @Author: 幻澄
 * @Date: 2023-10-16 10:42:27
 * @LastEditors: 幻澄
 * @LastEditTime: 2023-10-16 16:06:07
 * @FilePath: /midway-jwt/src/service/user.service.ts
 */
import { Provide, httpError, Inject, Context } from '@midwayjs/core';
import { User } from '../entity/user.entity';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
const { v4: uuidv4 } = require('uuid');
import { JwtService } from '@midwayjs/jwt';

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModal: Repository<User>;

  @Inject()
  jwtService: JwtService;

  @Inject()
  ctx: Context;

  async register(options: { username: string; password: string }) {
    const { username, password } = options;
    const user = new User();
    const findRes = await this.userModal.findOne({
      where: {
        username,
      },
    });
    if (findRes) return new httpError.BadRequestError('用户已存在');
    user.id = uuidv4();
    user.username = username;
    user.password = password;
    const res = await this.userModal.save(user);

    return {
      success: true,
      username,
      res: '注册成功',
    };
  }

  async login(options: { username: string; password: string }) {
    const { username, password } = options;
    return {
      accessToken: 'xxxx',
    };
  }
}

注册接口的逻辑如下:

  1. 获取请求参数usernamepassword
  2. user表查重,重复则响应异常;
  3. 生成ID,落库,响应用户信息;

我们通过ApiScout来模拟请求:

image.png

可以看到,注册信息被插入到数据表中了:

image.png

相同的入参再调一次,会返回重复用户的异常信息:

image.png

这样功能实现了,但是有个安全问题————账号密码应该加密存储在数据表中,因此我们使用bcryptjs来解决。

npm i bcryptjs --save

安装好之后我们将password加密一下,改造后的代码如下:

import { Provide, httpError, Inject, Context } from '@midwayjs/core';
import { User } from '../entity/user.entity';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
const { v4: uuidv4 } = require('uuid');
import { JwtService } from '@midwayjs/jwt';
import * as bcryptjs from 'bcryptjs';

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModal: Repository<User>;

  @Inject()
  jwtService: JwtService;

  @Inject()
  ctx: Context;

  async register(options: { username: string; password: string }) {
    const { username, password } = options;
    const user = new User();
    const findRes = await this.userModal.findOne({
      where: {
        username,
      },
    });
    if (findRes) return new httpError.BadRequestError('用户已存在');
    user.id = uuidv4();
    user.username = username;
    user.password = bcryptjs.hashSync(password, 10);
    const res = await this.userModal.save(user);

    return {
      success: true,
      username,
      res: '注册成功',
    };
  }

  async login(options: { username: string; password: string }) {
    const { username, password } = options;
    return {
      accessToken: 'xxxx',
    };
  }
}

OK,至此,注册接口就开发好了。

登录

有了注册,登录就大差不差了,简易版代码如下:

import { Provide, httpError, Inject, Context } from '@midwayjs/core';
import { User } from '../entity/user.entity';
import { InjectEntityModel } from '@midwayjs/typeorm';
import { Repository } from 'typeorm';
const { v4: uuidv4 } = require('uuid');
import { JwtService } from '@midwayjs/jwt';
import * as bcryptjs from 'bcryptjs';

@Provide()
export class UserService {
  @InjectEntityModel(User)
  userModal: Repository<User>;

  @Inject()
  jwtService: JwtService;

  @Inject()
  ctx: Context;

  async register(options: { username: string; password: string }) {
    const { username, password } = options;
    const user = new User();
    const findRes = await this.userModal.findOne({
      where: {
        username,
      },
    });
    if (findRes) return new httpError.BadRequestError('用户已存在');
    user.id = uuidv4();
    user.username = username;
    user.password = bcryptjs.hashSync(password, 10);
    const res = await this.userModal.save(user);
    console.log(55, res);

    return {
      success: true,
      username,
      res: '注册成功',
    };
  }

  async login(options: { username: string; password: string }) {
    const { username, password } = options;
    const findRes = await this.userModal.findOne({
      where: {
        username,
      },
    });
    if (!findRes) return new httpError.BadRequestError('不存在该用户');
    const compareRes: boolean = bcryptjs.compareSync(
      password,
      findRes.password
    );
    if (!compareRes) return new httpError.BadRequestError('密码错误');
    return {
      success: true
    };
  }
}

登录接口主要做了这些事情:

  1. 获取请求带来的usernamepassword
  2. user表查用户名,不存在的话返回异常信息;
  3. 通过bcryptjs将登陆的明文密码和注册落库的加密密码比较,如果密码错误,返回异常信息;
  4. 登录完成;

JWT

接下来我们加入鉴权,完善整个登录系统业务流程。

首先安装依赖包:

npm i @midwayjs/jwt --save

然后在configuration.ts中引入JWT组件:

import { Configuration, IMidwayContainer } from '@midwayjs/core';
import { IMidwayContainer } from '@midwayjs/core';
import * as jwt from '@midwayjs/jwt';

@Configuration({
  imports: [
    // ...
    jwt,
  ],
})
export class MainConfiguration {
  // ...
}

然后在config中加入JWT加密配置信息:

// src/config/config.default.ts
export default {
  // ...
  jwt: {
    secret: 'xxxxxxxxxxxxxx', // fs.readFileSync('xxxxx.key')
    expiresIn: '2d', // https://github.com/vercel/ms
  },
};

配置结束,接下来分两步走:

  • 对于登录接口,产出token,返回给前端;
  • 对于业务接口,依赖token,做中间件拦截判断鉴权;

先实现第一步,我们只需要在之前的login接口中增加token的逻辑即可。

user.service.ts:

export class UserService {
  @InjectEntityModel(User)
  userModal: Repository<User>;

  @Inject()
  jwtService: JwtService;

  @Inject()
  ctx: Context;

  async login(options: { username: string; password: string }) {
    const { username, password } = options;
    const findRes = await this.userModal.findOne({
      where: {
        username,
      },
    });
    if (!findRes) return new httpError.BadRequestError('不存在该用户');
    const compareRes: boolean = bcryptjs.compareSync(
      password,
      findRes.password
    );
    if (!compareRes) return new httpError.BadRequestError('密码错误');
    const token = this.jwtService.signSync({ username });
    return {
      accessToken: token,
    };
  }
}

当登录成功时,基于用户信息生成加密token,并返回给前端,前端保存在请求头的authorization,接下来每次请求都带给后端。

然后我们封装一个jwt.middleware.ts鉴权中间件,除了登录注册以外依赖个人账号相关的业务接口,都先走到中间件中,代码如下:

import { Inject, Middleware, httpError } from '@midwayjs/core';
import { Context, NextFunction } from '@midwayjs/koa';
import { JwtService } from '@midwayjs/jwt';

@Middleware()
export class JwtMiddleware {
  @Inject()
  jwtService: JwtService;

  resolve() {
    return async (ctx: Context, next: NextFunction) => {
      // 判断下有没有校验信息
      if (!ctx.headers['authorization']) {
        throw new httpError.UnauthorizedError();
      }
      // 从 header 上获取校验信息
      const parts = ctx.get('authorization').trim().split(' ');
      if (parts.length !== 2) {
        throw new httpError.UnauthorizedError();
      }

      const [scheme, token] = parts;

      if (/^Bearer$/i.test(scheme)) {
        //jwt.verify方法验证token是否有效
        await this.jwtService.verify(token, {
          complete: true,
        });
        await next();
      }
    };
  }

  // 配置忽略认证校验的路由地址
  public match(ctx: Context): boolean {
    const ignore = ['/api/login'].includes(ctx.path);
    return !ignore;
  }
}

然后在configuration.ts中引入中间件:

import { Configuration, App } from '@midwayjs/core';
import * as koa from '@midwayjs/koa';
import * as validate from '@midwayjs/validate';
import * as info from '@midwayjs/info';
import { join } from 'path';
import * as orm from '@midwayjs/typeorm';
import * as jwt from '@midwayjs/jwt';
import { JwtMiddleware } from './middleware/jwt.middleware';

@Configuration({
  imports: [
    koa,
    validate,
    {
      component: info,
      enabledEnvironment: ['local'],
    },
    orm,
    jwt,
  ],
  importConfigs: [join(__dirname, './config')],
})
export class ContainerLifeCycle {
  @App()
  app: koa.Application;

  async onReady() {
    // add middleware
    this.app.useMiddleware([JwtMiddleware]);
  }
}

这样除了中间件内部白名单的接口以外,都会先走到JWT中间件中。

简单测试一下,首先写一个/getShop接口,不在jwt白名单中,首先前端不带token发一次注册请求:

image.png

符合预期,无法访问,被中间件拦下来了。然后我们调一下/login接口,保存一下token,再带着去请求一下/getShop接口:

image.png

OK,至此,midway搭建的登录注册鉴权功能完成。

写在后面

这篇文章对你有帮助的话,非常荣幸。

如果喜欢我的文章,可以关注我的公众号「量子前端」,将不定期关注推送前端好文~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1098785.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

mac 版本 Lightroom Classic 2024 正式版来了 七个有趣的新功能值得更新

mac 版本 Lightroom Classic 2024 正式版终于来了&#xff01;此次更新有七大功能&#xff1a;新增高动态范围编辑和输出、智能 AI 交互式镜头模糊、全局和局部焦点颜色、预设搜索、支持新型相机和镜头等功能。本文将做详细解读&#xff0c;看看这七个有趣的新功能值得去更新吗…

RK3568驱动指南|第六篇-平台总线-第54章 点亮LED灯实验

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

流量渠道分析

文章目录 流量渠道分析一、为什么需要流量渠道分析1.了解流量来源&#xff0c;制定营销策略2.发现流量缺失点&#xff0c;提高转化率3.提高用户体验&#xff0c;增加用户留存 二、流量渠道分析方法1.确定分析目标2.选择分析工具3.设置分析参数4.收集数据5.数据分析6.制定优化方…

2024上海国际智慧城市展览会(世亚智博会)智慧城市,数字中国

在数字化、智能化的时代背景下&#xff0c;智慧城市成为了全球瞩目的焦点。而作为智慧城市领域的重要盛会&#xff0c;2024上海国际智慧城市展览会&#xff08;简称&#xff1a;世亚智博会&#xff09;则将再次汇聚全球目光。此次展览将于2024年3月26日至28日在上海跨国采购会展…

解决java.lang.IllegalArgumentException: servlet映射中的<url pattern>[demo1]无效

当我使用tomcat启动使用servlet项目时&#xff0c;出现了报错&#xff1a; java.lang.IllegalArgumentException: servlet映射中的<url pattern>[demo1]无效 显示路径错误&#xff0c;于是去检查Web.xml中的配置&#xff0c;发现是配置文件的路径写错了&#xff0c;少写了…

【Python第三方包】快速获取硬件信息和使用情况(psutil、platform)

文章目录 前言一、psutil包1.1 安装psutil包1.2 psutil 使用方式获取CPU使用率获取内存使用情况将内存的获取的使用情况变成GB和MB获取磁盘使用情况磁盘内存进行转换获取网络信息网络info 二、platform2.1 platform的介绍2.2 platform 使用方式获取操作系统的名称获取架构的名称…

unapp项目发布h5 详细操作流程以及注意事项 (hash模式)

首先需要在manifest.json文件中的web配置中 这里因为公司项目都是hash模式 所以写的./ 接下来看源码视图会发现多了一个配置项&#xff1a; 接下来点击hbx上面的 发行》网站-PC Web或手机H5(仅适用于uni-app)(H) 此时查看控制台会显示打包后的存放路径 接下来就直接给后端同…

Windows下Redis3.0主从模式架构搭建

redis版本&#xff1a;Redis-x64-3.0.504 复制相同文件 修改文件夹下redis.windows.conf 文件配置(注意&#xff1a;主有密码&#xff0c;从必须有密码且跟主相同) 修改端口&#xff1a; 主库&#xff1a;端口号6379 从库1&#xff1a;修改端口号为6380 从库2&#xff1a;修…

金融用户实践|分布式存储支持数据仓库业务系统性能验证

作者&#xff1a;深耕行业的 SmartX 金融团队 闫海涛 估值是指对资产或负债的价值进行评估的过程&#xff0c;这对于投资决策具有重要意义。每个金融公司资管业务人员都期望能够实现实时的业务估值&#xff0c;快速获取最新的数据和指标&#xff0c;从而做出更明智的投资决策。…

【学习笔记】RabbitMQ01:基础概念认识以及快速部署

参考资料 RabbitMQ官方网站RabbitMQ官方文档噼咔噼咔-动力节点教程 文章目录 一、认识RabbitMQ1.1 消息中间件&#xff08;MQ Message Queue 消息队列1.2 主流的消息中间件1.3 MQ的应用场景1.3.1 异步处理1.3.2 系统解耦1.3.3 流量削峰1.3.4 日志处理 二、RabbitMQ运行环境搭建…

Windows10不常用操作(录屏、开启超级管理员、关闭自动IP配置、Edge崩溃等)

Win10家庭版开启超级管理员 Win10家庭版开启或禁用超级管理员账户步骤如下&#xff1a; 在搜索框中输入CMD&#xff0c;右键以管理员方式运行。 开启 net user administrator /active:yes禁用 net user administrator /active:no Win10关闭自动IP配置 win10设置完静态ip&am…

微服务架构 | 超时管理

INDEX LSA 级别与全年停机时间速查表LSA 级别实战TP 性能超时时间设计原则 LSA 级别与全年停机时间速查表 计算公式&#xff1a;60 * 60 * 24 * 365 * (1-LSA) 31,536,000‬ * (1-LSA) 系统级别LSA级别全年停机时间099.999%5分钟099.99%52分钟199.9%8.8小时299%3.65 天 LSA…

QT学习day1

一、思维导图 二、作业&#xff1a;实现登录界面 #include "widget.h" #include<QDebug> #include<QIcon>Widget::Widget(QWidget *parent): QWidget(parent) {/**********************窗口******************///设置窗口图标this->setWindowTitle…

C++多线程编程(第四章 案例1,C++11和C++17 多核并行计算样例)

目录 4.1手动实现多核base16编码4.1.1 实现base16编码4.1.2无多线程代码4.1.3 C 11多线程代码4.1.4 C 17多线程并发4.1.5 所有测试代码汇总 4.1手动实现多核base16编码 4.1.1 实现base16编码 二进制转换为字符串 一个字节8位&#xff0c;拆分为两个4位字节&#xff08;最大值…

产品经理必备的14款需求管理工具推荐!

产品管理需求在产品经理的日常工作中扮演着至关重要的角色&#xff0c;这一关键任务为产品开发工作和资源投入提供了坚实的基础&#xff0c;它是创造杰出产品的必不可少前提。 面对各式各样的需求&#xff0c;产品经理可以使用专业的需求管理工具来进行集中收集和管理&#xf…

VulnHub Alice

一、信息收集 发现开发了22、80 2.访问ip&#xff0c;右击查看源代码 发现需要利用X-Forwarded-For 火狐插件&#xff1a;X-Forwarded-For Header 挂上代理后&#xff1a; 出现以下页面&#xff1a; 先注册一个账户&#xff0c;然后再登录 发现有参数进行传参 发现传参&a…

网站如何有效防止网络攻击

互联网上的网站和应用程序受到各种威胁&#xff0c;如黑客、恶意软件和数据泄漏。因此&#xff0c;了解如何解决网站被攻击的问题至关重要。本文将介绍一些简单的步骤&#xff0c;帮助您提高您的网站的安全性。 确认攻击 要解决网站被攻击的问题&#xff0c;首先需要识别是否遭…

MES管理系统的设计与实施

随着制造业的快速发展&#xff0c;MES生产管理系统逐渐成为企业提高生产效率和管理水平的重要工具。然而&#xff0c;在实施MES管理系统的过程中&#xff0c;如何确保其有效性和可持续性成为了一个亟待解决的问题。本文将从以终为始、自我完善与适应变化三个方面探讨MES管理系统…

数据结构与算法课后题-第五章(树、森林)

1、 2、 3、 4、 5、 6、 7、 8、 9、

Studio One6.5最新版本新增了对Linux的支持

音乐制作人们&#xff0c;这是你们翘首以待的消息。数字音频工作站&#xff08;DAW&#xff09;已经成为音乐制作专业人士重要工具之一。 遗憾的是&#xff0c;对于 Linux 用户而言&#xff0c;选择十分有限。最受欢迎的选择通常是开源 DAW&#xff0c;如 Ardour、Audacity和闭…