切图仔做全栈:React&Nest.js社区平台(二)——👋手把手实现优雅的鉴权机制

本文涉及的产品
全局流量管理 GTM,标准版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
云解析 DNS,旗舰版 1个月
简介: 切图仔做全栈:React&Nest.js社区平台(二)——👋手把手实现优雅的鉴权机制

前言

这是一个 React+Nest 实现的全栈社区项目,在上一期中我们已经搭好了基础的架子以及实现了邮箱注册和登录态管理的功能。

在上一期中,我们把用户的登录态封装成了 JWT 的形式,并且把 JWT 写到了 cookie 中。今天我们要实现的是:

  • 在业务代码中便捷的获取用户的登录态
  • 以装饰器的方式区分是否需要登录态的接口
  • 对于需要登录态的接口,请求没有带上登录态,如何统一拦截

往期文章

仓库地址

切图仔做全栈:React&Nest.js 社区平台(一)——基础架构与邮箱注册、JWT 登录实现

中间件注入登录态

在上一期我们已经实现了 JWT 登录,并且把 JWT 存储在了 cookie 里面,这样每次前端请求的时候都会带上 cookie ,后端就知道当前请求的用户是谁。

可以预想到的是,我们会有很多个路由需要用到用户的 id 或者邮箱,那当我们需要用到这些信息的时候,需要如何解析呢?

大概的流程应该是这样的:

  1. cookie 中获取 token
  2. token 中解析出用户信息对象
  3. 获取对应的字段

Nest.js 中,中间件是一种用于处理HTTP请求的机制,它允许在请求到达处理程序之前或之后执行一些逻辑。中间件通常用于执行一些预处理、日志记录、授权等任务。

而中间件的生效范围,我自己把它归为以下三个范围:

  • 全局中间件:所有请求都会生效
  • 控制器中间件:只有对应控制器下的路由才会生效
  • 路由中间件:只有对应的路由才会生效

比如说我们实现了一个日志中间件,他会打印一些东西:

function loggerMiddleware(req: Request, res: Response, next: NextFunction) {
  console.log('打印一些东西');
  next();
}

全局中间件可以这样应用:

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';

@Injectable()
export class LoggerMiddleware implements NestMiddleware {
  use(req: Request, res: Response, next: NextFunction) {
    console.log('Request...');
    next();
  }
}
@Module({
  providers: [LoggerMiddleware],
})
export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(LoggerMiddleware)
      .forRoutes('*');
  }
}

主要关注 forRoutes('*') 这个配置,它说的是所有路由都生效。那么对某个路由生效配置起来也大同小异:

export class AppModule implements NestModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(LoggerMiddleware).forRoutes(
      { path: 'users/getUserInfo', method: RequestMethod.GET }, 
      { path: 'users/updateUserInfo', method: RequestMethod.POST },
    );
  }
}

通过上面的配置,这个中间件就只会在请求 users/getUserInfo 和 请求 users/updateUserInfo的时候生效。

然后也可以使中间件对某个路由生效:

import { Controller, Get, Patch, UseMiddleware } from '@nestjs/common';

function loggerMiddleware(req, res, next) {
  console.log('Request...');
  next();
}

@Controller('users')
@UseMiddleware(loggerMiddleware)
export class UsersController {
  @Get(':id')
  getUserInfo(): string {
    return 'getUserInfo';
  }

通过使用 UseMiddleware 装饰器,就可以使中间件在某个路由中生效。

鉴权中间件

我们可以写一个对所有路由都生效的中间件,在请求进入具体路由之前,就把用户信息从 token 中解析出来注入到请求对象里面。

这样每一个路由都能很轻易的获取到用户有没有登录、登录后的信息是什么。

新建一个 auth.middleware.ts 文件,使用到了cookie-parser这个库来解析 cookie

import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
import * as cookieParser from 'cookie-parser';
import { AuthService } from '../services/auth.service';
@Injectable()
export class AuthMiddleware implements NestMiddleware {
  constructor(private readonly authService: AuthService) {}
  async use(req: Request, res: Response, next: NextFunction) {
    cookieParser()(req, res, () => {});
    const token = req.cookies['token'];
    if (token) {
      const decoded = await this.authService.decodeJwtToken(token);
      req['user'] = decoded;
    }

    next();
  }
}

大致讲下上面的代码:

  1. 首先,使用 cookie-parser 来读取请求里的 cookie
  2. 查找 cookie 中的 token ,然后使用解析 token 获取到用户具体的信息,如 idemail 等。
  3. 注入请求对象中,方便后续获取

最后别忘了在 app.module.ts 中使用这个中间件

export class AppModule {
  configure(consumer: MiddlewareConsumer) {
    consumer.apply(AuthMiddleware).forRoutes('*');
  }
}

鉴权守卫

我们所实现的接口,是有登录态权限之分的。比如说注册、登录这种接口,它不需要校验用户的登录态,但是获取用户信息、点赞、评论这种接口,它是需要用户的登录态的。

这里相关的权限判断可以利用 nest 的守卫机制,守卫的主要目的是在请求到达路由处理程序之前或之后执行某些逻辑,以决定请求是否继续处理。

它们通常用于身份验证、授权、日志记录等场景。 Nest.js 提供了一些内置的守卫,并允许开发者创建自定义守卫。

在这里我们可以实现一个全局守卫,基于上面的鉴权中间件获取到的登录态信息,来对用户的登录态进行校验。

import {
  CanActivate,
  ExecutionContext,
  Injectable,
  UnauthorizedException,
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';

@Injectable()
export class AuthGuard implements CanActivate {
  constructor(private readonly reflector: Reflector) {}
  canActivate(context: ExecutionContext): boolean | Promise<boolean> {
    const request = context.switchToHttp().getRequest();
    // 是否不需要登录
    const noAuth = this.reflector.get<boolean>('noAuth', context.getHandler());
    if (noAuth) {
      return true;
    } else {
      const isExpire = request.user && request.user.exp < Date.now();
      if (!request.user || isExpire) {
        throw new UnauthorizedException('未登录');
      }

      return true;
    }
  }
}

解释一下上面的代码做了什么:

  1. 守卫都需要实现 CanActivate 接口的 canActivate 方法,这是 nest 中约定好的一种实现守卫的方式
  2. ExecutionContext 是对特定请求的上下文环境的抽象,包含了关于运行时的所有信息。比如请求对象、响应对象,路由处理函数的参照,控制器的参照等等。上面的例子中使用它获取到了当前请求的处理器对象以及请求对象。
  3. Reflector是一个帮助类,用于提取并获取元数据。在 nest 中,元数据是用于被装饰器在运行时获取信息的一种机制,比如类、方法、参数等。
  4. 首先判断一下这个接口需不需要登录态,如果不需要,直接放行;如果需要且当前登录态还没过期,则放行;否则抛出一个未登录的401异常。

自定义装饰器

从上面的代码可以看到, noAuth 变量是用来标识当前请求不需要鉴权的。要实现这个十分简单,可以实现一个自定义装饰器,代码如下:

import { SetMetadata } from '@nestjs/common';
export const NoAuth = () => SetMetadata('noAuth', true);

这里主要用到了 SetMetadata ,它会为被这个装饰器修饰的类或方法加上一个 noAuth 属性,值为true。

然后可以在不需要登录校验的地方加上这个装饰器修饰,比如注册接口:

  @Post('register')
  @NoAuth()
  async register(@Body() user: CreateUserDto): Promise<boolean> {
    await this.userService.createUser(user);
    return true;
  }                        

参数装饰器

我们在上面的代码中,把用户信息 user 对象添加到了 request 请求对象中,在 controller 中获取的时候当然也可以使用 @Req 装饰器来获取一整个请求对象,再获取用户对象。

还有另外一种比较好的方式就是实现一个自定义的参数装饰器,比如说我需要实现一个获取用户信息的接口,希望拿到用户的 id

  @Get('getUserInfo')
  async getUserInfo(@User('id') userId: number) {
    return await this.userService.getUserInfo(userId);
  }

这里我就实现了一个 @User 装饰器,用它来获取用户相关的参数,直接把参数注入到路由参数中,不需要再路由处理方法中再去获取一遍。

整个装饰器的实现起来也比较简单:

import { ExecutionContext, createParamDecorator } from '@nestjs/common';

export const User = createParamDecorator(
  (data: string, context: ExecutionContext) => {
    const request = context.switchToHttp().getRequest();
    if (!data) {
      return request.user;
    }
    return request.user ? request.user[data] : null;
  },
);

解释一下上面的代码:

  1. 使用 createParamDecorator 定义一个参数装饰器
  2. context 是当前请求的上下文,可以通过它拿到请求对象
  3. data 是这个装饰器使用的时候传入的参数,如果传了就返回 user 对应的字段,如果不传就返回一整个 user 对象。

最后

以上就是本文的内容,主要介绍了中间件、守卫、自定义装饰器在鉴权以及获取用户信息的实战应用。如果你觉得有帮助的话,点点关注点点赞吧~欢迎评论区一起交流

相关文章
|
3天前
|
存储 JavaScript 前端开发
基于 JavaScript/VuePress 搭建的远程工作平台:YuanCheng.works
为了提高团队的协作效率和信息共享能力,许多公司开始探索基于现代技术的远程工作平台。本文将介绍如何利用 JavaScript 和 VuePress 搭建一个高效的远程工作平台,助力团队在灵活的工作环境中实现卓越的协作。
81 56
|
25天前
|
前端开发 JavaScript 测试技术
React 中集成 Chart.js 图表库
本文介绍了如何在 React 项目中集成 Chart.js 创建动态图表,涵盖基础概念、安装步骤、代码示例及常见问题解决方法,帮助开发者轻松实现数据可视化。
35 11
|
21天前
|
前端开发 JavaScript 关系型数据库
基于 Vue2.0 + Nest.js 全栈开发的后台应用
Vue2 Admin 是一个基于 Vue2 和 Ant Design Pro 开发的前端项目,配合 Nest.js 构建的后端,提供了一个完整的全栈后台应用解决方案。该项目支持动态国际化、用户权限管理、操作日志记录等功能,适合全栈开发者学习参考。线上预览地址:https://vue2.baiwumm.com/,用户名:Admin,密码:abc123456。
|
1月前
|
监控 前端开发 JavaScript
React 静态网站生成工具 Next.js 入门指南
【10月更文挑战第20天】Next.js 是一个基于 React 的服务器端渲染框架,由 Vercel 开发。本文从基础概念出发,逐步探讨 Next.js 的常见问题、易错点及解决方法,并通过具体代码示例进行说明,帮助开发者快速构建高性能的 Web 应用。
82 10
|
1月前
|
资源调度 前端开发 数据可视化
构建高效的数据可视化仪表板:D3.js与React的融合之道
【10月更文挑战第25天】在数据驱动的时代,将复杂的数据集转换为直观、互动式的可视化表示已成为一项至关重要的技能。本文深入探讨了如何结合D3.js的强大可视化功能和React框架的响应式特性来构建高效、动态的数据可视化仪表板。文章首先介绍了D3.js和React的基础知识,然后通过一个实际的项目案例,详细阐述了如何将两者结合使用,并提供了实用的代码示例。无论你是数据科学家、前端开发者还是可视化爱好者,这篇文章都将为你提供宝贵的洞见和实用技能。
61 5
|
26天前
|
JavaScript 前端开发 开发者
JavaScript框架React vs. Vue:一场性能与易用性的较量
JavaScript框架React vs. Vue:一场性能与易用性的较量
31 0
|
2月前
|
消息中间件 JavaScript 前端开发
用于全栈数据流的 JavaScript、Node.js 和 Apache Kafka
用于全栈数据流的 JavaScript、Node.js 和 Apache Kafka
47 1
|
2月前
|
开发框架 前端开发 JavaScript
React、Vue.js 和 Angular主流前端框架和选择指南
在当今的前端开发领域,选择合适的框架对于项目的成功至关重要。本文将介绍几个主流的前端框架——React、Vue.js 和 Angular,探讨它们各自的特点、开发场景、优缺点,并提供选择框架的建议。
59 6
|
2月前
|
前端开发 JavaScript 程序员
【从前端入门到全栈】Node.js 之核心概念
【从前端入门到全栈】Node.js 之核心概念
|
1月前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
75 9