NestJS中Token与HttpOnly Cookie授权对比及实现咨询
一、两种方案优劣对比
1. 前端存储Bearer Token(localStorage/sessionStorage/内存)
- 优势:
- 跨域场景适配简单,无需额外配置Cookie跨域规则;
- 前端完全控制Token的存储、携带逻辑,灵活度高。
- 劣势:
- 易受XSS攻击,恶意脚本可通过JS读取到Token并窃取;
- 每次请求需手动在请求头添加
Authorization: Bearer <token>,增加前端代码工作量。
2. HttpOnly Cookie存储Token
- 优势:
- 安全性更高:HttpOnly标记的Cookie无法通过JS读取,从根源避免XSS攻击窃取Token;
- 浏览器自动携带Cookie,前端无需手动处理请求头,减少重复代码。
- 劣势:
- 跨域场景需额外配置:后端要设置Cookie的
Domain、Path、SameSite属性,前端请求需开启withCredentials; - 存在CSRF攻击风险,需配合CSRF Token等防护手段。
- 跨域场景需额外配置:后端要设置Cookie的
二、方案选择建议
- 若前后端同域部署:优先选择HttpOnly Cookie方案,安全性更优;
- 若为跨域场景:
- 可以选择HttpOnly Cookie + 配置CORS跨域规则(开启
withCredentials、设置SameSite: 'Lax'或'None'); - 或采用混合方案:HttpOnly Cookie存Refresh Token,前端内存中存Access Token(减少暴露时间),同时做好XSS防护(比如内容安全策略CSP)。
- 可以选择HttpOnly Cookie + 配置CORS跨域规则(开启
三、NestJS中实现HttpOnly Cookie方案的步骤
基于你提供的现有代码,做如下修改:
1. 修改AuthController的登录接口,返回Token到HttpOnly Cookie
不再返回access_token,改为在响应中设置Cookie:
import { Body, Controller, Get, HttpCode, HttpStatus, Post, Request, UseGuards, Res // 新增导入Res } from '@nestjs/common'; import { AuthService } from './auth.service'; import { AuthGuard } from './auth.guard'; import { Response } from 'express'; // 导入express的Response类型 @Controller('auth') export class AuthController { constructor(private authService: AuthService) {} @HttpCode(HttpStatus.OK) @Post('login') async signIn( @Body() signInDto: Record<string, any>, @Res({ passthrough: true }) res: Response // 注入Response,passthrough: true表示不接管响应 ) { // 调用AuthService生成Token const { access_token } = await this.authService.signIn(signInDto.username, signInDto.password); // 设置HttpOnly Cookie res.cookie('access_token', access_token, { httpOnly: true, // 核心:标记为HttpOnly secure: process.env.NODE_ENV === 'production', // 生产环境开启(HTTPS下生效) sameSite: 'lax', // 防止CSRF,根据场景可选'lax'/'strict'/'none' maxAge: 3600000, // Token有效期,比如1小时(毫秒) path: '/' // Cookie生效路径 }); // 可以返回用户信息,不需要返回Token return { message: '登录成功' }; } @UseGuards(AuthGuard) @Get('profile') getProfile(@Request() req) { return req.user; } }
2. 修改AuthGuard,从Cookie中提取Token
不再从Authorization头提取,改为读取Cookie:
import { CanActivate, ExecutionContext, Injectable, UnauthorizedException, } from '@nestjs/common'; import { JwtService } from '@nestjs/jwt'; import { jwtConstants } from './constants'; import { Request } from 'express'; @Injectable() export class AuthGuard implements CanActivate { constructor(private jwtService: JwtService) {} async canActivate(context: ExecutionContext): Promise<boolean> { const request = context.switchToHttp().getRequest(); // 从Cookie提取Token const token = request.cookies?.access_token; if (!token) { throw new UnauthorizedException(); } try { const payload = await this.jwtService.verifyAsync( token, { secret: jwtConstants.secret } ); request['user'] = payload; } catch { throw new UnauthorizedException(); } return true; } }
3. (可选)CSRF防护配置
使用HttpOnly Cookie时,需添加CSRF防护。NestJS中可以通过@nestjs/csurf包实现:
- 安装依赖:
npm install @nestjs/csurf
- 在AppModule中配置:
import { Module } from '@nestjs/common'; import { CsurfModule } from '@nestjs/csurf'; @Module({ imports: [ CsurfModule.forRoot({ cookie: { httpOnly: true, secure: process.env.NODE_ENV === 'production', sameSite: 'lax' } }) ] }) export class AppModule {}
- 在需要防护的接口中,前端需携带CSRF Token(从Cookie的
_csrf中读取,放到请求头XSRF-TOKEN中)。
四、注意事项
- 生产环境务必开启
secure: true,确保Cookie仅在HTTPS下传输; - 根据跨域场景调整
SameSite属性:跨域时设为'none',同时需开启secure;同域场景设为'lax'或'strict'; - 如果使用Refresh Token,同样建议存储在HttpOnly Cookie中,避免前端暴露。
内容的提问来源于stack exchange,提问作者KaneOgg88
相关产品推荐
相关产品推荐

