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

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

最后

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

相关文章
|
2天前
|
JSON 前端开发 JavaScript
【好书推荐1】基于React低代码平台开发:构建高效、灵活的应用新范式
【好书推荐1】基于React低代码平台开发:构建高效、灵活的应用新范式
9 0
|
14天前
|
SQL 存储 前端开发
React&Nest.js全栈社区平台(五)——👋封装通用分页Service实现文章流与详情
React&Nest.js全栈社区平台(五)——👋封装通用分页Service实现文章流与详情
React&Nest.js全栈社区平台(五)——👋封装通用分页Service实现文章流与详情
|
14天前
|
存储 SQL 前端开发
React&Nest.js社区平台(四)——✏️文章发布与管理实战
React&Nest.js社区平台(四)——✏️文章发布与管理实战
|
14天前
|
存储 前端开发 API
React&Nest.js全栈社区平台(三)——🐘对象存储是什么?为什么要用它?
React&Nest.js全栈社区平台(三)——🐘对象存储是什么?为什么要用它?
|
19天前
|
前端开发 测试技术 开发工具
探索前端框架React Hooks的优势与应用
本文将深入探讨前端框架React Hooks的优势与应用。通过分析React Hooks的特性以及实际应用案例,帮助读者更好地理解和运用这一现代化的前端开发工具。
|
2月前
|
前端开发 JavaScript UED
使用React Hooks优化前端应用性能
本文将深入探讨如何使用React Hooks来优化前端应用的性能,重点介绍Hooks在状态管理、副作用处理和组件逻辑复用方面的应用。通过本文的指导,读者将了解到如何利用React Hooks提升前端应用的响应速度和用户体验。
|
12天前
|
开发框架 Dart 前端开发
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
【4月更文挑战第30天】对比 Flutter(Dart,强类型,Google支持,快速热重载,高性能渲染)与 React Native(JavaScript,庞大生态,热重载,依赖原生渲染),文章讨论了开发语言、生态系统、性能、开发体验、学习曲线、社区支持及项目选择因素。两者各有优势,选择取决于项目需求、团队技能和长期维护考虑。参考文献包括官方文档和性能比较文章。
【Flutter前端技术开发专栏】Flutter与React Native的对比与选择
|
11天前
|
前端开发 JavaScript 开发者
【专栏:HTML与CSS前端技术趋势篇】前端框架(React/Vue/Angular)与HTML/CSS的结合使用
【4月更文挑战第30天】前端框架React、Vue和Angular助力UI开发,通过组件化、状态管理和虚拟DOM提升效率。这些框架与HTML/CSS结合,使用模板语法、样式管理及组件化思想。未来趋势包括框架简化、Web组件标准采用和CSS在框架中角色的演变。开发者需紧跟技术发展,掌握新工具,提升开发效能。
|
13天前
|
开发框架 缓存 前端开发
|
16天前
|
前端开发 JavaScript Linux
relectron框架——打包前端vue3、react为pc端exe可执行程序
relectron框架——打包前端vue3、react为pc端exe可执行程序
28 1