为什么需要 Token?
客户端是无状态的,正常情况下无法识别是哪个用户发送的请求。传统做法是使用 Cookie 或 Session,但它们各有缺点:
- Cookie:存储在客户端,每次发送请求会自动携带,但容易触发 CSRF 攻击,且存在跨域问题。
- Session:存储在服务端,通过 Cookie 存储用户 ID 等非敏感信息,服务端生成用户 ID - 用户详情的映射表,根据每次请求的 Cookie 找到对应的用户。缺点是对服务端压力大,且容易丢失。
Token:存储在客户端,解决了上述问题。
Token 流程
- 客户端发送用户名和密码进行登录。
- 服务端校验用户发送的数据是否正确。
- 如果正确,生成一个 Token 并返回。
- 后续客户端请求需携带 Token。
- 服务端验证 Token,成功后返回数据。
生成 Token
使用 JWT
什么是 JWT?
JWT(JSON Web Token)是一个开放标准(RFC 7519),用于在网络应用环境间安全地传输信息。JWT 是一个很长的字符串,中间使用 .
分隔为三部分:
- Header(头部)
- Payload(负载)
- Signature(签名)
写成一行,就是下面的样子:
Header.Payload.Signature
在 NestJS 中使用 JWT
-
安装 JWT 包:
npm install @nestjs/jwt
-
配置 JWT 模块:
在
user.module.ts
中配置 JWT 模块:import { Module } from '@nestjs/common'; import { JwtModule } from '@nestjs/jwt'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { jwtSecret } from './config'; @Module({ imports: [ JwtModule.register({ secret: jwtSecret, signOptions: { expiresIn: '1d' }, }), ], providers: [UsersService], controllers: [UsersController], }) export class UsersModule {}
-
生成 Token:
在
users.service.ts
中生成 Token:import { Injectable } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { User } from './interfaces/user.interface'; @Injectable() export class UsersService { private readonly users: User[] = [ { userId: 1, username: 'zs', password: '123' }, { userId: 2, username: 'lisi', password: '456' }, ]; constructor(private readonly jwtService: JwtService) {} findUserByName(username: string, password: string): User | undefined { const user = this.users.find(u => u.username === username && u.password === password); if (user) { const payload = { userId: user.userId, username: user.username }; const token = this.jwtService.sign(payload); return { ...user, token }; } return undefined; } }
-
控制器中使用:
import { Controller, Get, Query } from '@nestjs/common'; import { UsersService } from './users.service'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @Get() login(@Query('username') username: string, @Query('password') password: string) { return this.usersService.findUserByName(username, password); } }
-
发送请求:
发送请求
http://127.0.0.1:3000/users?username=zs&password=123
,客户端将收到一个 Token。
校验 Token
使用 Guard 进行前置校验
-
生成 Guard 文件:
nest g guard auth --no-spec
-
实现 Guard:
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common'; import { Request } from 'express'; import { JwtService } from '@nestjs/jwt'; @Injectable() export class AuthGuard implements CanActivate { constructor(private readonly jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest<Request>(); const token = request.headers.authorization?.split(' ')[1]; if (!token) { return false; } try { const payload = await this.jwtService.verifyAsync(token); request.user = payload; return true; } catch { return false; } } }
-
控制器中使用 Guard:
import { Controller, Get, UseGuards, Request } from '@nestjs/common'; import { AuthGuard } from '../auth.guard'; @Controller('users') export class UsersController { constructor(private readonly usersService: UsersService) {} @UseGuards(AuthGuard) @Get('profile') getProfile(@Request() req) { return req.user; } }
-
发送请求:
发送请求
http://127.0.0.1:3000/users/profile
,并在请求头中添加Authorization: Bearer <token>
。-
未加 Token:
-
加上 Token:
-
通过以上步骤,你可以在 NestJS 中生成、校验和使用 Token,确保应用程序的安全性和可靠性。
希望这篇文章对你有所帮助!如果有任何问题或需要进一步的帮助,请随时提问。