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

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

前言

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

  • 服务端框架———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 securitykeys: '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 middlewarethis.app.useMiddleware([JwtMiddleware]);}
}

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

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

image.png

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

image.png

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

写在后面

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

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

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

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

相关文章

【学习之路】Multi Agent Reinforcement Learning框架与代码

【学习之路】Multi Agent Reiforcement Learning框架与代码 Introduction 国庆期间&#xff0c;有个客户找我写个代码&#xff0c;是强化学习相关的&#xff0c;但我没学过&#xff0c;心里那是一个慌&#xff0c;不过好在经过详细的调研以及自身的实力&#xff0c;最后还是解…

安装程序2502/2503错误的解决方法

Windows Installer是在Windows 2000时提出&#xff0c;作为微软操作系统中的安装程序开发标准的操作系统服务。它可以支持安装程序所需要的许多功能&#xff0c;并且可以支持交易式安装&#xff08;Committable Installation&#xff09;&#xff0c;当安装程序发现错误或问题时…

Kubernetes 原生微服务开发 | 阿Q送书第七期

微服务开发并不容易。其中涉及大量的概念与复杂的技术&#xff0c;令很多开发者忘而却步。Quarkus 是一个全能的基础框架&#xff0c;除了基础的 Web 应用开发能力外&#xff0c;还包括服务发现与调用、熔断限流和观测等微服务治理体系。Quarkus 在提供强大特性的同时&#xff…

【Docker】Docker网络及容器间通信详解

目录 背景 默认网络 1、bridge 网络模式 2、host 网络模式 3、none 网络模式 4、container 网络模式 自定义网络 容器间网络通信 IP通信 Docker DNS server Joined容器 前言 本实验通过docker DNS server和joined 容器两种方法实现Docker容器间的通信。Docker容器间…

SQL利用Case When Then多条件判断

CASE WHEN 条件1 THEN 结果1 WHEN 条件2 THEN 结果2 WHEN 条件3 THEN 结果3 WHEN 条件4 THEN 结果4 ......... WHEN 条件N THEN 结果N ELSE 结果X END Case具有两种格式。简单Case函数和Case搜索函数。 --简单Case函数 CASE sex WHEN 1 THEN…

wordpress遇到的问题

一&#xff09; 403 Forbidden 我是lnmpwordpress&#xff0c;所以在 /etc/nginx/conf.d/default.conf中 修改location 加上 index.php刷新即可&#xff1b; 二&#xff09;wordpress插件更新&#xff0c;需要输入服务器的FTP登录凭证的问题 在 wp-config.php的文件中进行修改…

一键搞定!黑群晖虚拟机+内网穿透实现校园公网访问攻略!

文章目录 前言本教程解决的问题是&#xff1a;按照本教程方法操作后&#xff0c;达到的效果是前排提醒&#xff1a; 1. 搭建群晖虚拟机1.1 下载黑群晖文件vmvare虚拟机安装包1.2 安装VMware虚拟机&#xff1a;1.3 解压黑群晖虚拟机文件1.4 虚拟机初始化1.5 没有搜索到黑群晖的解…

1.算法-Python遗传算法实例

题记 以下是一个python遗传算法实例&#xff0c;包括全过程和解析。 编辑main.py文件 main.py文件如下&#xff1a; #导入生成伪随机数的模块 import random# 随机生成初始种群 # 1.初始化种群&#xff0c;在搜索空间内随机生成一组个体&#xff0c;称为种群 # 定义函数&#…

功能集成,不占空间,同为科技TOWE嵌入式桌面PDU超级插座

随着现代社会人们生活水平的不断提高&#xff0c;消费者对生活质量有着越来越高的期望。生活中&#xff0c;各式各样的电气设备为我们的生活带来了便利&#xff0c;在安装使用这些用电器时&#xff0c;需要考虑电源插排插座的选择。传统的插排插座设计多暴露于空间之中&#xf…

LeetCode讲解篇之138. 随机链表的复制

LeetCode讲解篇之138. 随机链表的复制 文章目录 LeetCode讲解篇之138. 随机链表的复制题目描述题解思路题解代码 题目描述 题解思路 先遍历一遍链表&#xff0c;用哈希表保存原始节点和克隆节点的映射关系&#xff0c;先只克隆节点的Val&#xff0c;然后再次遍历链表&#xff…

逐字稿 | 视频理解论文串讲(上)【论文精读】

大家好&#xff0c;前两期我们讲了视频理解领域里的两篇经典的论文&#xff0c;一个是双流网络&#xff0c;第一个是 I3D 网络&#xff0c;所以说对视频理解这个问题有了个基本的了解。 那今天我们就从 2014 年开始&#xff0c;一直到最近 2021 年的工作&#xff0c;我们一起来…

解决 vscode使用Prettier格式化js文件报错:Cannot find module ‘./parser-babylon‘

报错如下&#xff1a; ["ERROR" - 11:48:58] Error formatting document. ["ERROR" - 11:48:58] Cannot find module ./parser-babylon Require stack: - d:\VueCode\VueProject\myqqmusic\node_modules\prettier\index.js - c:\Users\Administrator.SKY-2…

微信小程序开发之框架篇

目录 一&#xff0c;框架 1.页面管理 2.基础组件 3.丰富的API 二、小程序视图层 1.响应的数据绑定 2.列表渲染 3.条件渲染 4.模板 三、逻辑层 App Service 1.注册小程序 2.注册页面 2.1.使用 Page 构造器注册页面 2.2.在页面中使用 behaviors 3.页面路由 4.小…

Linux文件系统 struct file 结构体解析

文章目录 一、open系统调用1.1 简介1.2 files_struct1.2.1 简介1.2.2 init_files1.2.2 CLONE_FILES 1.3 源码分析1.3.1 get_unused_fd_flags1.3.2 do_filp_open1.3.3 fd_install 二、struct file简介三、其他参考资料 一、open系统调用 1.1 简介 NAMEopen, creat - open and …

旧手机热点机改造成服务器方案

如果你也跟我一样有这种想法, 那真的太酷了!!! ok,前提是得有root,不然体验大打折扣 目录 目录 1.做一个能爬墙能走百度直连的热点机(做热点机用) 2.做emby视频服务器 3.做文件服务, 存取文件 4.装青龙面板,跑一些定时任务 5.做远程摄像头监控 6.做web服务器 7.内网穿…

51单片机点阵

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、点阵是什么&#xff1f;1.点阵的原理2. 3*3 点阵显示原理3. 8*8点阵实物图4. 8*8点阵内部原理图5. 16*16点阵实物图&#xff0c;显示原理 二、使用步骤1.先…

数据结构与算法--其他算法

数据结构与算法--其他算法 1 汉诺塔问题 2 字符串的全部子序列 3 字符串的全排列 4 纸牌问题 5 逆序栈问题 6 数字和字符串转换问题 7 背包问题 8 N皇后问题 暴力递归就是尝试 1&#xff0c;把问题转化为规模缩小了的同类问题的子问题 2&#xff0c;有明确的不需要继续…

设计模式之是简单工厂模式

分类 设计模式一般分为三大类&#xff1a;创建型模式、结构型模式、行为型模式。 创建型模式&#xff1a;用于创建对象&#xff0c;共五种&#xff0c;包括单例模式、简单工厂模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。结构型模式&#xff1a;用于处理类或对…

超火爆的6 个必学持续集成工具,测试人的福音

开发人员喜欢把写的代码当成自己的孩子&#xff0c;他们会被当成艺术品一样呵护。作为家长&#xff0c;总是会认为自己的孩子是最好的&#xff0c;也会尽全力给自己的孩子最好的&#xff0c;就算有时候会超出自己的能力范围。 最终&#xff0c;孩子会走出去&#xff0c;和其他…

android studio检测不到真机

我的情况是&#xff1a; 以前能检测到&#xff0c;有一天我使用无线调试&#xff0c;发现调试有问题&#xff0c;想改为USB调试&#xff0c;但是半天没反应&#xff0c;我就点了手机上的撤销USB调试授权&#xff0c;然后就G了。 解决办法&#xff1a; 我这个情况比较简单&…