nestjs实战-登录、鉴权(一)
一个完整的登录流程中至关重要的就是它的认证方式,现有的认证方式主要有一下3种:
- session/cookie
- JWT(Json Web Token)
- Oauth
一、鉴权方式
Session/Cookie
原理:用户登录后,服务器在内存中创建 Session,并将 Session ID 通过 Cookie 返回给客户端。后续请求自动携带 Cookie。
特点:适用于传统Web应用,有状态,不适合跨域或高并发环境
优点:
- 较易扩展
- 开发简单
缺点:
- 需要在服务端存储session,性能低、多服务器同步session困难
- 由于cookie只在浏览器上能使用,所以它跨平台困难
JWT
原理:服务端通过特定密钥生成包含用户信息的签名字符串(Token),客户端在请求头(Authorization: Bearer Token)中携带。服务器不存储会话,只验证签名。
特点:无状态、扩展性好,适用于微服务和单页应用(SPA)、跨平台能力
JWT 本质上是一个经过数字签名的字符串,它由三部分组成,用点号(.)分隔:Header.Payload.Signature。token长什么样?
Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJsb2dpblR5cGUiOiJsb2dpbiIsImxvZ2luSWQiOiIyMDAwNCIsInJuU3RyIjoiQVpVUVY2dnAyUllkOFRucnJoaXNsQWtRcTFhSEFxeTAiLCJ0ZW5hbnRJZCI6IjEiLCJzY29wZSI6IlJPTEU6Ok1JQ1JPX0FQUCIsImFwcEtleSI6InJIWnJZeVB1eW1oaXZlSUUiLCJuaWNrTmFtZSI6IuWui-Wwj-aXrSIsImxvZ2luVGltZSI6MTc3NTYxMzI4NzYxNX0.bq8qEE-imza7pLucKOvKw2WvukW2lSPr1WMbAcuJLmU
优点:
- 支持跨平台:移动端、跨应用
- 安全、承载信息丰富
缺点:
- 刷新与过期处理
- payload不易过大
- 中间人攻击(没有绝对的安全)
Oauth
第三方授权,例如 微信、支付宝、QQ、谷歌账号登录等
优点:
- 开放、安全、简单
- 权限指定
缺点:
- 需要增加授权服务器
- 增加网络请求
二、实战
首先 创建身份验证模块,在指定的目录下执行如下命令:
nest g res auth
整个流程我把它分成两部分:
- 用户登录过程
- 登录成功后,带token请求业务接口的过程
2.1 前置知识
此段内容篇理论介绍,可以先看后面的代码实现,有疑问再来看这里的内容
模块之间的复用
auth模块中如何使用 users 模块中的 users.service.ts 中的方法
之前的章中我们创建了 Users 模块,现在 auth 模块中想要使用 users.service 中的方法:
-
首先需要在 users.module.ts 中导出 users.service.ts
// ... 省略 @Module({ imports: [TypeOrmModule.forFeature([UserEntity])], controllers: [UsersController], providers: [UsersService], exports: [UsersService], // 导出 }) export class UsersModule {} -
在
auth.module.ts中导入import { Module } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthController } from './auth.controller'; import { UsersModule } from '../users/users.module'; @Module({ imports: [UsersModule], controllers: [AuthController], providers: [AuthService], }) export class AuthModule {} -
这样就能在
auth.service.ts中使用了import { Injectable } from '@nestjs/common'; // 注意这里在使用中还是需要 显示的引入,而不是会自动引入 import { UsersService } from '../users/users.service'; @Injectable() export class AuthService { constructor(private readonly usersService: UsersService) {} async register(createUserDto: any) { return this.usersService.findAll(); } }
JWT 相关包介绍
首先安装依赖:
pnpm add @nestjs/passport passport passport-jwt @nestjs/jwt
pnpm add @types/passport @types/passport-jwt -D
在集成 JWT 之前,先学习一下使用到的包,并了解它都提供了哪些功能。这里是掌握整个流程非常重要的环境,参考了网上(包括官网)一上来就是介绍怎么使用、代码怎么写,确实是让人很迷惑。
1. @nestjs/passport
- 集成 Passport.js:让 NestJS 应用能够使用 Passport.js 提供的超过 500 种认证策略(如本地用户名密码、JWT、OAuth 2.0 等)。
-
提供装饰器:它提供了一系列装饰器(如
@UseGuards(AuthGuard('jwt'))),让你可以非常方便地在控制器(Controller)或路由上应用认证保护。 -
简化策略创建:通过
PassportStrategy类,你可以轻松地创建自定义策略,而无需直接处理 Passport.js 的底层 API。
import { PassportModule, PassportStrategy, AuthGuard } from '@nestjs/passport';
PassportModule
-
类型: NestJS 模块 (
@Module) -
作用: 它是整个 Passport 集成的入口。你需要把它导入到你的 NestJS 模块(如
AuthModule)中,才能启用 Passport 功能。 -
核心功能:
- 它通过
.register()方法允许你配置全局选项,比如指定默认的认证策略(defaultStrategy)。 - 它负责将 Passport 的服务注入到 NestJS 的依赖注入容器中
- 它通过
PassportStrategy-策略实现
-
类型:抽象类 / 辅助函数
-
作用:它是用来创建具体认证逻辑的基类。Passport.js 本身有各种策略(如 Local, JWT, Google),这个函数帮助我们将这些策略适配到 NestJS 的类结构中。
-
核心功能:
- 它接受一个具体的策略类(如
passport-local的Strategy)作为参数。 - 它让你重写
validate方法,在这里编写具体的验证逻辑(比如查数据库比对密码)。
- 它接受一个具体的策略类(如
AuthGuard-路由保护
-
类型:守卫 (
CanActivate) -
作用:它是 NestJS 的守卫,用于保护路由。它拦截请求,并告诉 Passport 执行哪个策略。
-
核心功能:
- 它通常配合
@UseGuards()装饰器使用。 - 它接收一个参数(字符串),这个字符串必须与你定义的策略名称(或默认名称)匹配,从而触发对应的验证流程。
- 它通常配合
2. @nestjs/jwt
@nestjs/jwt 是 Nest 官方对 JWT 的封装包,底层基于 jsonwebtoken。它主要解决三件事:
- 在 Nest 里统一配置 JWT 密钥、过期时间等(模块化)
- 提供
JwtService来签发和校验 token - 很容易和
Passport的jwt strategy集成成认证体系
import { JwtModule, JwtService } from '@nestjs/jwt';
JwtModule:模块注册器
-
用来在 Nest DI 容器里注册 JWT 相关配置和服务
-
常见用法:
-
JwtModule.register({...})静态配置 -
JwtModule.registerAsync({...})动态读取配置(比如从ConfigService取JWT_SECRET)
-
JwtService:具体干活的服务
用它生成 token、验证 token、解码 token,一般在 AuthService 里注入并调用
-
sign(payload, options?)- 作用:根据 payload 生成 JWT 字符串
- 示例:
this.jwtService.sign({ sub: user.id, name: user.name }) -
options可以覆盖模块默认配置,比如expiresIn,secret
-
verify(token, options?)- 作用:验证 token 是否合法、是否过期,并返回解码后的 payload
- 验证失败会抛异常(如签名不对、过期)
- 示例:
this.jwtService.verify(token)
3. passport-jwt
passport-jwt 这个包的核心作用是:给 Passport 提供“JWT 认证策略”。
import { ExtractJwt, Strategy } from 'passport-jwt';
主要用到两个能力:
-
Strategy- JWT 的 Passport 策略类
- 你在 Nest 里通常写
extends PassportStrategy(Strategy, 'jwt') - 用来定义:用什么密钥验签、是否忽略过期、验证成功后返回什么用户信息
-
ExtractJwt- token 提取器工具
- 最常用:
ExtractJwt.fromAuthHeaderAsBearerToken() - 表示从
Authorization: Bearer <token>里取 token - 也支持从 cookie、query、或自定义函数提取
简化理解:
-
@nestjs/jwt偏向“签发/校验 token 的服务能力” -
passport-jwt偏向“请求进来时怎么从请求里拿 token 并走认证策略”
二者经常一起用:登录时 JwtService.sign() 发 token,请求鉴权时 passport-jwt 的 Strategy 来验。
4. 总结
首先为什么需要安装 passport:
仅安装 @nestjs/passport 是不够的。简单来说,@nestjs/passport 只是一个“适配器”,它的核心作用是将 passport 的功能封装成 NestJS 熟悉的模块和依赖注入形式,但它本身并不包含 passport 的核心逻辑。
你可以这样理解它们的关系:
-
passport: 这是核心的认证库,提供了所有的认证策略和逻辑。 -
@nestjs/passport: 这是一个 NestJS 的官方封装包,它让你能以更符合 NestJS 风格(如使用装饰器、依赖注入)的方式来使用passport。
完整的登录鉴权依赖
要实现一个完整的登录鉴权功能,你通常需要安装以下几个包:
-
核心库:
-
passport: 认证的核心。 -
@nestjs/passport: NestJS 的适配器。
-
-
具体策略库 (根据你选择的认证方式):
-
用户名密码登录: 需要
passport-local。 -
JWT 无状态认证: 需要
passport-jwt。
-
用户名密码登录: 需要
-
类型定义 (TypeScript 项目需要):
@types/passport-
@types/passport-local(如果使用 local 策略) -
@types/passport-jwt(如果使用 jwt 策略)
2.2 JWT 集成
auth.module.ts
import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { UsersModule } from '../users/users.module';
import { securityRegToken, ISecurityConfig } from '~/config';
import { isDev } from '~/global/env';
@Module({
imports: [
PassportModule, // 引入 PassportModule 模块
// 引入 JwtModule 模块,及配置 JwtModule 模块
JwtModule.registerAsync({
imports: [ConfigModule], // 引入 ConfigModule 模块
useFactory: (configService: ConfigService) => {
const { jwtSecret, jwtExprire } = configService.get<ISecurityConfig>(securityRegToken)
return {
secret: jwtSecret, // 设置密钥
signOptions: { expiresIn: jwtExprire }, // 设置过期时间
ignoreExpiration: isDev, // 开发环境忽略过期时间
}
},
inject: [ConfigService], // 注入 ConfigService 服务
}),
UsersModule,
],
controllers: [AuthController],
providers: [
AuthService,
],
})
export class AuthModule {}
以上代码 主要是 引入了 PassportModule, JwtModule,并完善了相关配置;
2.3 用户登录过程
-
用户输入 用户名、密码 等信息
通过http(加密|明文),传输给后端
-
后端接受到来自前端 的http 请求
- 验证账号密码是否一致
- 生成 token 传递给前端
auth.controller.ts
import { Controller, Post, Body, UseGuards, Req } from '@nestjs/common';
import { AuthService } from './auth.service';
import { LoginDto } from './dto/auth.dto';
@Controller('auth')
export class AuthController {
constructor(
private readonly authService: AuthService,
) {}
// 登录
@Post('login')
login(@Body() loginDto: LoginDto) {
return this.authService.login(loginDto);
}
}
auth.service.ts
import { HttpException, Injectable, HttpStatus } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { isEmpty } from 'lodash';
import { JwtService } from '@nestjs/jwt';
@Injectable()
export class AuthService {
constructor(
private readonly usersService: UsersService,
private readonly jwtService: JwtService,
) {}
async login(loginDto: {
name: string;
password: string;
}) {
const { name, password } = loginDto;
const user = await this.usersService.findUserByUserName(name);
if (isEmpty(user)) {
throw new HttpException('用户不存在', HttpStatus.NOT_FOUND);
}
const { password: userPassword } = user;
if (userPassword !== password) {
throw new HttpException('密码错误', HttpStatus.BAD_REQUEST);
}
// 生成 JWT 签名
const jwtSign = await this.jwtService.signAsync({
id: user.id,
name: user.name,
pv: 1, // 版本号
})
return {
access_token: jwtSign
};
}
}
用户登录过程代码如上,还是比较简单:
- 获取用户传递过来的参数,校验用户是否存在,密码是否正确
- 生成JWT签名,返回token給前端
功能主线如此,密码加密、token管理等有待完善
2.4 业务接口token验证过程
由于文章篇幅,放到下一节讲解;