切图仔做全栈: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 对象。

最后

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

相关文章
|
19天前
|
监控 前端开发 JavaScript
React 静态网站生成工具 Next.js 入门指南
【10月更文挑战第20天】Next.js 是一个基于 React 的服务器端渲染框架,由 Vercel 开发。本文从基础概念出发,逐步探讨 Next.js 的常见问题、易错点及解决方法,并通过具体代码示例进行说明,帮助开发者快速构建高性能的 Web 应用。
51 10
|
18天前
|
资源调度 前端开发 数据可视化
构建高效的数据可视化仪表板:D3.js与React的融合之道
【10月更文挑战第25天】在数据驱动的时代,将复杂的数据集转换为直观、互动式的可视化表示已成为一项至关重要的技能。本文深入探讨了如何结合D3.js的强大可视化功能和React框架的响应式特性来构建高效、动态的数据可视化仪表板。文章首先介绍了D3.js和React的基础知识,然后通过一个实际的项目案例,详细阐述了如何将两者结合使用,并提供了实用的代码示例。无论你是数据科学家、前端开发者还是可视化爱好者,这篇文章都将为你提供宝贵的洞见和实用技能。
41 5
|
1月前
|
开发框架 前端开发 JavaScript
React、Vue.js 和 Angular主流前端框架和选择指南
在当今的前端开发领域,选择合适的框架对于项目的成功至关重要。本文将介绍几个主流的前端框架——React、Vue.js 和 Angular,探讨它们各自的特点、开发场景、优缺点,并提供选择框架的建议。
42 6
|
2月前
|
前端开发 JavaScript 开发者
React 和 Vue.js 框架的区别是什么?
React 和 Vue.js 框架的区别是什么?
|
2月前
|
前端开发 JavaScript API
React、Vue.js 和 Angular前端三大框架对比与选择
前端框架是用于构建用户界面的工具和库,它提供组件化结构、数据绑定、路由管理和状态管理等功能,帮助开发者高效地创建和维护 web 应用的前端部分。常见的前端框架如 React、Vue.js 和 Angular,能够提高开发效率并促进团队协作。
102 4
|
1月前
|
前端开发 JavaScript 开发者
深入理解React Hooks:提升前端开发效率的关键
【10月更文挑战第5天】深入理解React Hooks:提升前端开发效率的关键
|
11天前
|
前端开发 JavaScript 开发者
颠覆传统:React框架如何引领前端开发的革命性变革
【10月更文挑战第32天】本文以问答形式探讨了React框架的特性和应用。React是一款由Facebook推出的JavaScript库,以其虚拟DOM机制和组件化设计,成为构建高性能单页面应用的理想选择。文章介绍了如何开始一个React项目、组件化思想的体现、性能优化方法、表单处理及路由实现等内容,帮助开发者更好地理解和使用React。
38 9
|
1月前
|
前端开发
深入解析React Hooks:构建高效且可维护的前端应用
本文将带你走进React Hooks的世界,探索这一革新特性如何改变我们构建React组件的方式。通过分析Hooks的核心概念、使用方法和最佳实践,文章旨在帮助你充分利用Hooks来提高开发效率,编写更简洁、更可维护的前端代码。我们将通过实际代码示例,深入了解useState、useEffect等常用Hooks的内部工作原理,并探讨如何自定义Hooks以复用逻辑。
|
1月前
|
前端开发 JavaScript API
探索React Hooks:前端开发的革命性工具
【10月更文挑战第5天】探索React Hooks:前端开发的革命性工具
|
1月前
|
前端开发 数据管理 编译器
引领前端未来:React 19的重大更新与实战指南🚀
React 19 即将发布,带来一系列革命性的新功能,旨在简化开发过程并显著提升性能。本文介绍了 React 19 的核心功能,如自动优化重新渲染的 React 编译器、加速初始加载的服务器组件、简化表单处理的 Actions、无缝集成的 Web 组件,以及文档元数据的直接管理。这些新功能通过自动化、优化和增强用户体验,帮助开发者构建更高效的 Web 应用程序。
90 1
引领前端未来:React 19的重大更新与实战指南🚀