当前位置: 首页 > news >正文

【Nest】登录鉴权

登录鉴权的整体流程:注册 -> 登录 -> 获取用户信息。

以下从流程的先后顺序入手。

注册

auth.controller.ts

    @Post('register')@ApiOperation({summary: '用户注册'})@ApiBody({type: CreateUserDto})@ApiResponse({status: 201, description: '注册成功'})@ApiResponse({status: 400, description: '注册失败'})async register(@Body() createUserDto: CreateUserDto) {return this.authService.register(createUserDto);}

api 相关的装饰器是 swagger 文档装饰器。

然后 dto 模型,主要使用 class-validator 做验证,全局注册 pipe 管道,对传入的参数进行 class-validator 和 class-transformer 验证和转换。

create-user.dto.ts

import { ApiProperty } from '@nestjs/swagger';
import { IsEmail, IsEnum, IsNotEmpty, IsOptional, IsString, MinLength } from 'class-validator';
import { Role } from '@prisma/client';export class CreateUserDto {@ApiProperty({ description: '用户名', example: 'john_doe' })@IsNotEmpty({ message: '用户名不能为空' })@IsString({ message: '用户名必须是字符串' })username: string;@ApiProperty({ description: '密码', example: 'password123' })@IsNotEmpty({ message: '密码不能为空' })@IsString({ message: '密码必须是字符串' })@MinLength(6, { message: '密码长度不能少于6个字符' })password: string;@ApiProperty({ description: '角色', enum: Role, default: Role.competitor })@IsOptional()@IsEnum(Role, { message: '角色值无效' })role?: Role;@ApiProperty({ description: '名称', example: '张三' })@IsOptional()@IsString({ message: '名称必须是字符串' })name?: string;@ApiProperty({ description: '邮箱', example: 'john@example.com' })@IsOptional()@IsEmail({}, { message: '邮箱格式不正确' })email?: string;@ApiProperty({ description: '手机号', example: '13800138000' })@IsOptional()@IsString({ message: '手机号必须是字符串' })phone?: string;
} 

main.ts

import { ValidationPipe } from '@nestjs/common';// 启用全局验证管道app.useGlobalPipes(new ValidationPipe({whitelist: true,transform: true,forbidNonWhitelisted: true,}),);

然后注册的 service 通过加密,对敏感数据去除。

这里的响应数据去除也可以使用 响应拦截器,配合序列化 exclude-properties 。

auth.service.ts

  async register(userData: any) {const hashedPassword = await bcrypt.hash(userData.password, 10);const newUser = await this.usersService.create({...userData,password: hashedPassword,});const { password, ...result } = newUser;return result;}

登录

auth.controller.ts

    @UseGuards(LocalAuthGuard)@Post('login')@ApiOperation({summary: '用户登录'})@ApiBody({type: LoginDto})@ApiResponse({status: 200, description: '登录成功'})@ApiResponse({status: 401, description: '登录失败'})async login(@Request() req) {return this.authService.login(req.user);}

auth.module.ts

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { PassportModule } from '@nestjs/passport';
import { JwtModule } from '@nestjs/jwt';
import { ConfigService } from '@nestjs/config';
import { JwtStrategy } from './strategies/jwt.strategy';
import { LocalStrategy } from './strategies/local.strategy';
import { UsersModule } from '../users/users.module';@Module({imports: [UsersModule,PassportModule,JwtModule.registerAsync({inject: [ConfigService],useFactory: (config: ConfigService) => {return {secret: config.get<string>('JWT_SECRET'),signOptions: {expiresIn: config.get<string>('JWT_EXPIRES_IN'),},};},}),],controllers: [AuthController],providers: [AuthService, JwtStrategy, LocalStrategy],exports: [AuthService],
})
export class AuthModule {} 

auth.service.ts

  async login(user: any) {const payload = { username: user.username, sub: user.id };return {access_token: this.jwtService.sign(payload),user: {id: user.id,username: user.username,role: user.role,name: user.name,},};}

这里使用到的守卫主要适用于做权限控制。
Local:(用户名/密码)相关的策略与 Guard
JWT(Token):相关的策略与 Guard


  • Strategy(策略):Passport 的实现单元,负责验证凭证(比如 username/password 或 JWT token),验证通过后返回 user(或抛出异常)。Nest 用 @nestjs/passport 封装 Passport,并把 validate() 作为 Passport 的 verify 回调。(docs.nestjs.com)
  • Guard(守卫):Nest 层面的拦截点,决定请求是否继续。@UseGuards(AuthGuard('xxx')) 会把请求交给 Passport 的对应策略去做认证。Guard 在 Nest 请求生命周期中能访问 ExecutionContext(也可用于权限判定等)。(docs.nestjs.com)

一、LocalStrategy + LocalAuthGuard(用户登录验证 — username/password)

  • 用于 登录(认证凭证):在登录接口上使用 LocalAuthGuard,它会触发 passport-local 的策略(LocalStrategy)去校验用户名和密码,成功后把 user 放到 req.user 上供 controller 使用(通常 controller 会再生成 JWT)。(docs.nestjs.com)

local.strategy.ts

import { Strategy } from 'passport-local';
import { PassportStrategy } from '@nestjs/passport';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { AuthService } from './auth.service';@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {constructor(private authService: AuthService) {// 如果表单字段不是 username/password,可在这里改: super({ usernameField: 'email' })super();}async validate(username: string, password: string): Promise<any> {const user = await this.authService.validateUser(username, password);if (!user) {throw new UnauthorizedException('用户名或密码错误');}// 返回的对象会被 passport 附加到 req.userreturn user;}
}

local-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {}

要点与注意:

  • LocalStrategy 的 validate(username, password) 是 Passport 的 verify 回调,Nest 要求这个签名(默认属性名是 usernamepassword,可通过 super({ usernameField: 'email' }) 改)。(docs.nestjs.com)
  • Local 验证本身可以配合 session(passport 的 session)使用,但在常见的 JWT 无状态登录流程里,Local 只是一次性验证并返回 user,真正的会话由 JWT 管理。(docs.nestjs.com)

二、JwtStrategy + JwtAuthGuard(Token 验证 — 保护接口)

  • 在用户登录拿到 JWT 后,客户端在后续请求里带上 token(通常是 Authorization: Bearer <token>)。受保护的路由用 JwtAuthGuard,它会触发 passport-jwt 策略(JwtStrategy)来解析 token、校验签名并执行 validate(payload),通过则把 user 注入到 req.user,请求继续执行。(docs.nestjs.com)

jwt.strategy.ts

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';
import { AuthService } from './auth.service';
import { ConfigService } from '@nestjs/config';@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {constructor(private authService: AuthService,private configService: ConfigService,) {super({jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), // 从 Authorization: Bearer 提取ignoreExpiration: false,secretOrKey: configService.get('JWT_SECRET') || process.env.JWT_SECRET,});}async validate(payload: any) {// payload 是 jwt 的解码内容(例如 { sub: userId, username, iat, exp })// 建议在这里再去 DB 校验用户是否存在 / 是否被禁用 / 是否已登出等const user = await this.authService.validateUserByJwtPayload(payload);if (!user) {throw new UnauthorizedException();}return user; // 会被附到 req.user}
}

jwt-auth.guard.ts

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}

要点与注意:

  • passport-jwt 提供多种 token 提取器(从 header, 从 cookie, 自定义 extractor),最常用的是 ExtractJwt.fromAuthHeaderAsBearerToken()。你可以根据客户端把 token 存哪里来自定义。(Passport.js)
  • 重要:如果 token 已过期或格式错误,Passport 不会调用 validate():策略会直接失败(因此 validate 一般假设拿到的是有效的未过期 token,并负责根据 payload 再查用户等)。这是常见调试点(如果 validate 没被调用,先检查 token 是否过期或提取方式是否正确)。(Stack Overflow)

常见坑 / 调试技巧:

  • “Unknown authentication strategy ‘jwt’”:通常是忘记在模块里 providers: [JwtStrategy] 或没安装 / 注册 @nestjs/passportpassport-jwt 等。记得在 AuthModuleimports: [PassportModule, JwtModule.register(...)] 并把策略作为 provider 注入。(若遇到这类错误,先确认 providers/imports 注册是否正确) 。
  • validate 不被调用:先确认 token 是否被正确提取(header 名、cookie 名),以及 token 是否过期(过期时 validate 不会被执行)。(GitHub)
  • 不要只信 payload:即使 JWT 签名校验通过,也建议在 validate(payload) 中去 DB 查用户状态(是否被删除/禁用、是否已登出/黑名单等),以便做 token 撤销等。
  • session vs stateless:LocalStrategy 可以配合 session(passport session)做基于 session 的 auth;如果是 JWT 流程,通常不会启用 session(stateless)。(docs.nestjs.com)

获取用户信息

auth.controller.ts

    @UseGuards(JwtAuthGuard)@Get('profile')@ApiOperation({summary: '获取用户信息'})@ApiResponse({status: 200, description: '获取成功'})@ApiResponse({status: 401, description: '未授权'})getProfile(@Request() req) {return req.user;}

Passport 的一个特性:Passport 根据 validate() 方法的返回值自动创建一个 user 对象,并将其赋值给 Request 对象,作为 req.user 。所以,这里如果通过了守卫,可以直接从 req 上面获取 user 。

http://www.dtcms.com/a/453883.html

相关文章:

  • 托福口语【2】
  • 主主复制·(互为主从)·高可用·keepalived 故障切换演示 并且描述故障切换
  • 营销网站建设流程wordpress设置客户端缓存时间
  • 辽宁网站建设的网络科技公司中国最权威的网站排名
  • 自然语言驱动的统计图表生成:图表狐AIGC技术架构与多场景实战
  • petri网学习笔记(三)
  • 鸿蒙next 跨设备互通开发指南
  • AI-调查研究-96-具身智能 机器人场景测试全攻略:从极端环境到实时仿真
  • 陕西宏远建设集团网站可以上传图片的网站怎么做
  • 企业OCR实战:基于OCR技术实现双节差旅报销单表格解析与文字信息自动化采集
  • 网站建设管理工作经验介绍去西安需要隔离吗
  • Java Database Connectivity
  • noexcept 的微妙平衡:性能、正确性与接口契约
  • 单片机为什么不能跑Linux
  • OSPF协议详解4:实验 - OSPF区域、网络类型与高级路由控制实践
  • 单词搜索(DFS)
  • 绵阳房产网站建设网站建设 创业
  • static-bind 概念及题目
  • 中卫企业管理培训网站wordpress离线更新
  • [Linux系统编程——Lesson3.进程概念 ]
  • SOLIDWORKS VBA 自学笔记018、复制字符串到剪贴板(代码示例)
  • CSP-J 2024 复赛题
  • 【算法训练营 · 汇总篇】数组、链表、哈希表、字符串、栈与队列
  • 网站备案万网如何推广一个新的app
  • 移动应用开发网站wordpress返回500
  • 茶叶公司网站源码辽阳建设网站
  • 网站下载免费的视频软件在百度上做广告推广要多少钱
  • gitee设置不公开邮箱地址,推送报错解决方案
  • 网站不备案怎么回事龙华新区网站建设
  • CoroutineScope(SupervisorJob() + Dispatchers.IO) 详解