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

Nest 身份鉴权与权限控制

登录验证是服务端开发非常核心的一个环节,通用方案我们会选择 jwt,也就是大家熟知的 token 作为用户身份令牌,用户登录时分配 token,后续请求都携带此 token 以表明用户身份以此实现身份验证与权限控制等功能。

1. Nest 集成 jwt

在 Nest 中,JWT(JSON Web Token)身份鉴权通过 Passport 模块来实现,它提供了一种基于令牌的认证方式。

1.1. AuthController

auth.controller.ts 中定义了两个API端点:登录接口( /auth/login )和获取用户信息接口( /me )。

登录接口:

  • 使用 @UseGuards(AuthGuard('local')) 注解,这是应用了 local 策略的认证守卫,local 策略用于验证用户的用户名和密码。

  • 当客户端发送登录请求时,守卫会触发 LocalStrategy 中的 validate 方法进行用户验证。

  • 验证成功后,通过 this.authService.login(req.user) 生成JWT令牌并返回给客户端。

获取用户信息接口:

  • 该接口通过 @UseGuards(AuthGuard('jwt')) 应用了 jwt 守卫,只有携带有效JWT令牌的请求才能访问。

  • 当用户成功通过JWT守卫的认证后,可以访问其个人信息。

@UseGuards(AuthGuard('jwt'))
@Get('me')
getProfile(@Request() req) {return req.user;
}

此接口只在用户携带有效的JWT令牌时允许访问,守卫会解析并验证令牌的有效性。

1.2. AuthModule

auth.module.ts 是身份鉴权功能的配置中心。它引入了 PassportModule 和 JwtModule,并注册了 AuthService、LocalStrategy、JwtStrategy。

  • PassportModule:提供守卫机制,允许使用多种认证策略。

  • JwtModule:用于JWT的创建和验证,其中的 secret 用于签署和解析JWT,signOptions 设置令牌的有效期。

JwtModule.register({secret: jwtConstants.secret,signOptions: { expiresIn: '60s' },
})

1.3. AuthService

auth.service.ts 中定义了验证用户信息和生成JWT令牌的逻辑。

  • validateUser:用于用户验证,通常会通过 UsersService 查找用户,并验证用户的密码是否正确。这里用一个硬编码的用户示例替代真实用户查找逻辑。

  • login:登录成功后,调用 jwtService.sign(payload) 方法生成JWT令牌。 payload 中包含了用户的 username 和 sub(一般为用户ID)。

async login(user: any): Promise<any> {const payload = {username: user.username, sub: user.userId};return {access_token: this.jwtService.sign(payload),};
}

生成的令牌会返回给前端,前端在后续的请求中需要携带该令牌来访问受保护的接口。

1.4. LocalStrategy

local.strategy.ts 定义了用户名和密码的本地策略,主要用于用户登录验证。

  • LocalStrategy 继承自 PassportStrategy(Strategy),并实现 validate 方法。

  • 在 validate 方法中,它会调用 AuthService.validateUser(username, password) 来验证用户名和密码。

async validate(username: string, password: string): Promise<any> {return {username, password};const user = await this.authService.validateUser(username, password);if (!user) {throw new HttpException({ message: 'authorized failed', error: 'please try again later.' },HttpStatus.BAD_REQUEST);}return user;
}

如果验证成功,用户信息将被附加到请求对象中,后续的 login 方法会根据此用户信息生成JWT令牌。

1.5. JwtStrategy

jwt.strategy.ts 定义了JWT验证的策略,主要用于解析和验证用户提供的JWT令牌。

  • JwtStrategy 继承自 PassportStrategy(Strategy),并配置了JWT的来源和解密密钥。

  • jwtFromRequest:指定如何从请求中提取JWT,代码中使用 ExtractJwt.fromHeader('token') 表示JWT从请求头中的 token 字段提取。

super({jwtFromRequest: ExtractJwt.fromHeader('token'),ignoreExpiration: false,secretOrKey: jwtConstants.secret,
})

validate 方法:验证通过后,validate 方法会被调用。这个方法的参数是JWT中的 payload,它通常包含用户的标识信息,如 username 和 sub(用户ID)。该方法返回的用户信息会附加到请求对象中,供后续路由处理使用。

async validate(payload: any) {return {userId: payload.sub, username: payload.username};
}

1.6. JWT 身份验证流程总结

整个JWT身份鉴权的处理过程如下:

  • 用户通过 /auth/login 登录,local 策略验证用户名和密码。

  • 验证成功后,AuthService.login 方法生成JWT令牌,令牌通过响应返回给用户。

  • 用户在后续的请求中将JWT令牌附加到请求头的 token 字段中。

  • 受保护的路由如 /me 使用 jwt 策略守卫,守卫通过 JwtStrategy 提取并验证JWT令牌。

  • 验证成功后,用户信息被添加到请求对象,路由处理器可以基于此用户信息返回数据。

2. 身份鉴权方案盘点

在现代Web应用中,身份鉴权与权限控制是非常重要的一部分,尤其是涉及用户登录、资源访问的安全性时。身份鉴权的方案有多种,每种方案都有其适用的场景和优缺点。

2.1. 使用 Cookie 进行身份鉴权

主要特点:

  • 适用于传统的Web系统。

  • 通常会在服务器端创建Session来存储用户的认证信息,客户端通过Cookie携带Session ID来与服务器通信。

  • Cookie受限于同源策略,因此在跨平台场景中(如移动App)使用不便。

适用场景:

  • 单纯的Web应用,安全性要求较高时,可以结合服务器的Session机制。

实现示例:

1. 服务端创建Session并设置Cookie

import { Controller, Post, Req, Res } from '@nestjs/common';
import { Response, Request } from 'express';@Controller('auth')
export class AuthController {@Post('login')login(@Req() req: Request, @Res() res: Response) {const user = { id: 1, name: 'test' };  // 模拟用户信息req.session.user = user;  // 将用户信息存储在Session中res.cookie('sessionId', req.sessionID);  // 设置Cookiereturn res.send({ message: 'Logged in successfully' });}@Post('logout')logout(@Req() req: Request, @Res() res: Response) {req.session.destroy(() => {res.clearCookie('sessionId');  // 清除Cookieres.send({ message: 'Logged out successfully' });});}
}

2. 配置Session

在NestJS中,可以使用 express-session 来管理会话。

import * as session from 'express-session';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';async function bootstrap() {const app = await NestFactory.create(AppModule);app.use(session({secret: 'my-secret', // 设置Session密钥resave: false,saveUninitialized: false,}));await app.listen(3000);
}
bootstrap();

2.2. 使用单 Token(accessToken)

主要特点:

  • 适合跨平台使用,如Web、App等场景。

  • 只使用一个短时效的 accessToken 进行身份验证。

  • 但由于 accessToken 直接用于认证,如果被盗用,容易造成安全问题。

适用场景:

  • 对安全性要求较低的系统或短时交互需求的应用。

实现示例:

1. 用户登录获取 accessToken

@Controller('auth')
export class AuthController {constructor(private readonly jwtService: JwtService) {}@Post('login')async login(@Req() req: Request, @Res() res: Response) {const user = { id: 1, name: 'test' }; // 模拟用户const payload = { userId: user.id };const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' }); // 签发15分钟有效return res.send({ accessToken });}
}

2. 保护受限路由

@UseGuards(AuthGuard('jwt'))
@Get('protected')
getProtectedData(@Request() req) {return { message: 'This is protected data', user: req.user };
}

2.3. 使用双 Token(refreshToken + accessToken)

主要特点:

  • 常见的方案,用于实现“无感刷新”。

  • accessToken 有效期较短,用于认证请求;refreshToken 有效期较长,仅用于刷新新的 accessToken。

  • refreshToken 在服务端安全存储,较少暴露,防止滥用。

适用场景:

  • 安全性要求高且需要长期登录状态的场景,如电子商务、社交平台等。

实现示例:

1. 用户登录获取 accessToken 与 refreshToken。

@Controller('auth')
export class AuthController {constructor(private readonly jwtService: JwtService) {}@Post('login')async login(@Req() req: Request, @Res() res: Response) {const user = { id: 1, name: 'test' };const payload = { userId: user.id };// 生成短效的accessToken (15分钟)const accessToken = this.jwtService.sign(payload, { expiresIn: '15m' });// 生成长效的refreshToken (7天)const refreshToken = this.jwtService.sign(payload, { expiresIn: '7d' });return res.send({ accessToken, refreshToken });}
}

2. 刷新accessToken

@Controller('auth')
export class AuthController {constructor(private readonly jwtService: JwtService) {}@Post('refresh')async refresh(@Req() req: Request, @Res() res: Response) {const { refreshToken } = req.body;try {const payload = this.jwtService.verify(refreshToken);const newAccessToken = this.jwtService.sign({ userId: payload.userId }, { expiresIn: '15m' });return res.send({ accessToken: newAccessToken });} catch (e) {return res.status(401).send({ message: 'Invalid refresh token' });}}
}

2.4. 改良版双 Token

主要特点:

  • accessToken 有效期较短,存储在 HTTP Only 的 Cookie 中,确保安全性,并避免通过 JS 访问到它。

  • refreshToken 存储在数据库中,并与用户绑定,每次发起请求时都会验证其有效性。

  • 能够通过失效服务器端存储的 refreshToken 来让用户下线。

适用场景:

  • 需要支持安全的跨平台身份验证,且需要能让用户手动或自动退出登录。

实现思路:

1. accessToken 存储在 Cookie 中,用于短时间的验证,过期后需要使用 refreshToken 刷新。

2. refreshToken 存储在服务器端数据库 redis 中,只有在 refreshToken 有效的情况下,才能生成新的 accessToken。

3. 用户踢下线功能:管理员可随时从数据库中删除或标记失效某个 refreshToken,这样该用户在 accessToken 过期时将无法刷新新的 accessToken,即被踢下线。

实现示例:

我们可以将 refreshToken 存储在 Redis 中,Redis是一种内存型数据库,非常适合用作缓存或短期数据存储,尤其是在存储如 refreshToken 这样的短期有效的数据时。我们可以将每个用户的 refreshToken 与其ID相关联,并设置过期时间,确保在Redis中存储的 refreshToken 能够自动失效。

在NestJS中,可以使用 @nestjs/redis 或 ioredis 库来集成Redis,以下是通过 ioredis 库实现的示例。

1. 安装依赖

npm install ioredis @nestjs/bull

2. 配置Redis

在NestJS的模块中配置Redis客户端:

import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import Redis from 'ioredis';@Module({providers: [AuthService,{provide: 'REDIS',useFactory: () => {return new Redis({host: 'localhost', // Redis服务器地址port: 6379, // Redis服务器端口});},},],exports: [AuthService],
})
export class AuthModule {}

2.4.1. 存储 refreshToken 到 Redis

我们将 refreshToken 和用户ID关联,并将其存储到Redis中,设置一个与Token有效期匹配的过期时间。

import { Injectable, Inject } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import Redis from 'ioredis';@Injectable()
export class AuthService {constructor(@Inject('REDIS') private readonly redisClient: Redis,private readonly jwtService: JwtService,) {}// 保存refreshToken到Redis,设置过期时间async saveRefreshToken(userId: number, refreshToken: string): Promise<void> {await this.redisClient.set(`refreshToken:${userId}`, refreshToken, 'EX', 7 * 24 * 60 * 60)}// 验证refreshToken的有效性async isRefreshTokenValid(userId: number, refreshToken: string): Promise<boolean> {const storedToken = await this.redisClient.get(`refreshToken:${userId}`);return storedToken === refreshToken;}// 使用户的refreshToken失效async invalidateUserRefreshTokens(userId: number): Promise<void> {await this.redisClient.del(`refreshToken:${userId}`);}
}

2.4.2. 用户登录并存储 accessToken 和 refreshToken

在用户登录时,生成并返回 accessToken 和 refreshToken,并将 refreshToken 存储到Redis中。

@Controller('auth')
export class AuthController {constructor(private readonly jwtService: JwtService, private readonly authService: AuthService) { }@Post('login')async login(@Req() req: Request, @Res() res: Response) {const user = { id: 1, name: 'testUser' };// 生成短效的accessTokenconst accessToken = this.jwtService.sign({ userId: user.id }, { expiresIn: '15m' });// 生成长效的refreshTokenconst refreshToken = this.jwtService.sign({ userId: user.id }, { expiresIn: '7d' });// 将refreshToken存储到Redis中await this.authService.saveRefreshToken(user.id, refreshToken);// 将accessToken存储在HTTP Only的Cookie中res.cookie('accessToken', accessToken, { httpOnly: true, secure: true });// 返回refreshToken给客户端存储return res.send({ refreshToken });}
}

2.4.3. 刷新 accessToken 时验证 refreshToken

用户通过 refreshToken 请求新的 accessToken。在刷新时,将从Redis中验证 refreshToken 的有效性。

@Controller('auth')
export class AuthController {constructor(private readonly jwtService: JwtService, private readonly authService: AuthService) { }@Post('refresh')async refreshToken(@Req() req: Request, @Res() res: Response) {const { refreshToken } = req.body;// 验证refreshTokenconst payload = this.jwtService.verify(refreshToken);// 检查Redis中是否存在该refreshTokenconst isValid = await this.authService.isRefreshTokenValid(payload.userId, refreshToken);if (!isValid) {return res.status(403).send({ message: 'Invalid or expired refresh token' });}// 生成新的accessTokenconst newAccessToken = this.jwtService.sign({ userId: payload.userId }, { expiresIn: '15m' });// 将新的accessToken存储在HTTP Only的Cookie中res.cookie('accessToken', newAccessToken, { httpOnly: true, secure: true });return res.send({ accessToken: newAccessToken });}
}

2.4.4. 将用户踢下线(失效 refreshToken)

管理员可以通过从Redis中删除用户的 refreshToken,让用户无法再刷新 accessToken,从而实现踢下线功能。

@Controller('auth')
export class AuthController {constructor(private readonly authService: AuthService) { }@Post('logout-user')async logoutUser(@Req() req: Request, @Res() res: Response) {const { userId } = req.body;// 删除该用户的refreshToken,失效该用户的登录状态await this.authService.invalidateUserRefreshTokens(userId);return res.send({ message: `User with ID ${userId} has been logged out.` });}
}

2.5. 总结

  • Cookie:适用于传统Web系统,安全性高,但跨平台能力弱。

  • 单Token(accessToken):具备跨平台能力,但安全性较低。

  • 双Token(refreshToken + accessToken):是常见的无感刷新方案,提升了安全性与用户体验。

  • 改良双Token:Redis 读取和写入 refreshToken,且可以通过删除或标记失效 refreshToken,使得用户无法刷新 accessToken,从而实现用户的手动或自动下线功能。这种方案适用于Web、移动端等多平台,用户登录状态可以长期保持。

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

相关文章:

  • C#系统日志
  • CMakeLists.txt语法(三)
  • 简单flash个人网站山东省建设教育集团网站首页
  • windows多显示器,独立的虚拟桌面
  • 国外的app设计网站企管宝官网
  • 深入解析 Redis 的两种持久化机制:RDB 与 AOF
  • 爱佳倍 北京网站软件外包公司是什么意思
  • SCNet平台—让AI更简单、更高效、更实用
  • 高流量网站设计菏泽网站开发公司
  • 做一个展示型网站要多少钱自己做本市网站
  • SSRF靶场环境命令执行靶场环境
  • 【数字孪生】02-数字孪生在各个领域的应用(1)
  • 网站字体样式重庆唐卡装饰口碑怎么样
  • wgcna 相关性热图中4个颜色 4个共表达模块 的模块基因是否都要做GO/KEGG分析”,核心取决于你的**研究目标和模块的生物学意义*
  • 什么是网站名称文件夹会展设计需要学什么
  • 第十六届蓝桥杯软件赛C组省赛C++题解(京津冀)
  • Spring Cloud 服务网关 Gateway 详解:微服务的 “统一入口” 实战
  • 基于 PyTorch 的模型测试与全局平均池化实践
  • 买软件网站建设福田祥菱v1单排
  • 江阴网站设计哪家好百度云用流量做网站
  • C++ 类型推导(第二部分)
  • C 内存布局
  • 编译Duckdb机器学习插件QuackML
  • 帝国cms仿站工具学网站建设 去那里
  • 《R for Data Science (2e)》免费中文翻译 (第9章) --- Layers(1)
  • 网站注册时间查询aspnet网站开发pdf
  • 企业管理说白了是干嘛的seo优化排名教程
  • 医院建设网站网页ui设计尺寸规范
  • 网站模板批量下载推广电话
  • 织梦网站如何做seo我的家乡网页设计模板