从零到一完成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容器间…

ASP.net数据从Controller传递到视图

最常见的方式是使用模型或 ViewBag。 使用模型传递数据&#xff1a; 在控制器中&#xff0c;创建一个模型对象&#xff0c;并将数据赋值给模型的属性。然后将模型传递给 View 方法。 public class HomeController : Controller {public IActionResult Index(){// 创建模型对…

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;称为种群 # 定义函数&#…

政务窗口服务满意度调查如何开展

首先要明确调查的目的和重点&#xff0c;群狼调研(长沙窗口满意度调查)根据客户需求开展政务窗口的整体服务满意度&#xff0c;首先要确定调查的范围和对象&#xff0c;如面向全市的所有政务窗口&#xff0c;还是只针对某个区县的政务窗口进行调查。 根据调查目的和范围&#…

Hadoop3教程(二):HDFS的定义及概述

文章目录 &#xff08;40&#xff09;HDFS产生的背景和定义&#xff08;41&#xff09;HDFS的优缺点&#xff08;42&#xff09;HDFS组成架构&#xff08;43&#xff09;HDFS文件块大小&#xff08;面试重点&#xff09;参考文献 &#xff08;40&#xff09;HDFS产生的背景和定…

功能集成,不占空间,同为科技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;我们一起来…

Vue_Bug npm install报错 code:128

Bug描述&#xff1a; npm install报错 code&#xff1a;128 npm ERR! Warning: Permanently added ‘github.com’ (ED25519) to the list of known hosts. npm ERR! gitgithub.com: Permission denied (publickey). npm ERR! fatal: Could not read from remote repository. n…

lil-gui

前言 你是否因为想做个demo&#xff0c;要写配置项看效果&#xff0c;但是antd等组件库太大了&#xff0c;自己写又太累而烦恼&#xff1f;lil-gui库可以解决这个问题。这是一个轻量级浮窗配置项ui组件库。 官网 https://lil-gui.georgealways.com/#Examples由于是脱离框架写…

解决 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…

多线程并发篇---第十四篇

系列文章目录 文章目录 系列文章目录一、什么是AQS?二、了解Semaphore吗?三、什么是Callable和Future?一、什么是AQS? 简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。 如果说java.util.concurrent的基础是CAS的话,那么AQS就是整个J…

微信小程序开发之框架篇

目录 一&#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 …