NestJS Session

概述

Session 是一种在服务器端存储用户状态的机制,用于在多个 HTTP 请求之间保持用户会话信息

在 Web 应用中,由于 HTTP 协议是无状态的,服务器无法直接识别不同请求是否来自同一用户。Session 机制通过在服务器端存储用户状态,并使用 Session ID 与客户端进行关联,解决了这一问题

NestJS 作为一个基于 Express 或 Fastify 的框架,支持通过中间件的方式集成 Session 功能。本文将详细介绍 Session 的核心概念、工作原理、在 NestJS 中的实现方式,以及服务端 Session 的安全机制与应用场景

核心概念

Session

Session 是服务器端为每个用户会话创建的存储空间,用于存储用户的状态信息,如登录状态、用户偏好设置等

Session ID

Session ID 是服务器为每个会话生成的唯一标识符,通常存储在客户端的 Cookie 中。当客户端发送请求时,会携带这个 Session ID,服务器通过它来识别用户并获取对应的 Session 数据

Cookie

Cookie 是存储在客户端浏览器中的小型文本文件,用于存储 Session ID 等信息。当浏览器发送请求时,会自动携带与当前域名相关的 Cookie

工作原理

Session 的工作流程如下:

  1. 客户端首次请求:客户端向服务器发送第一次请求,此时服务器还不知道该客户端的身份

  2. 服务器创建 Session:服务器为该客户端创建一个 Session,并生成一个唯一的 Session ID

  3. 服务器响应:服务器将 Session ID 存储在 Cookie 中,并将响应发送给客户端

  4. 客户端存储 Cookie:客户端收到响应后,将包含 Session ID 的 Cookie 存储起来

  5. 客户端后续请求:当客户端再次发送请求时,会自动携带包含 Session ID 的 Cookie

  6. 服务器识别 Session:服务器从请求中提取 Session ID,根据它找到对应的 Session 数据,从而识别用户身份和状态。

  7. Session 过期:当用户长时间不活动时,Session 会自动过期并被服务器清理

NestJS 中的 Session 实现

安装依赖

在 NestJS 中使用 Session,需要安装 express-session 包:

npm install express-session

配置 Session

main.ts 文件中配置 Session 中间件:

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import session from 'express-session';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  
  // 配置 Session 中间件
  app.use(
    session({
      // 该配置是Session默认用内存存储,未配置存储对象会自动使用内存对象
      // store: new MemoryStore(),
      // 如需其他配置例如Redis,可以手动设置Redis连接实例
      store: new RedisStore({
        host: 'localhost',
        port: 6379,
        // 其他 Redis 配置
      }),
      secret: 'linzex',  // 用于签名 Session ID 的密钥
      name: 'l.session',  // Cookie 名称
      resave: false,  // 当 Session 未被修改时是否重新保存
      rolling: true,  // 每次请求时是否重置 Cookie 过期时间
      cookie: {
        maxAge: 1000 * 60 * 5,  // Cookie 过期时间(5分钟)
      },
    }),
  );
  
  await app.listen(3000);
}
bootstrap();

使用 Session

在控制器中使用 @Session() 装饰器来访问 Session 对象:

import { Controller, Get, Post, Body, Session, Res, Req } from '@nestjs/common';
import svgCaptcha from 'svg-captcha';

@Controller('user')
export class UserController {
  @Get('code')
  createCaptcha(@Req() req, @Res() res, @Session() session) {
    const captcha = svgCaptcha.create({
      size: 4,
      fontSize: 50,
      width: 100,
      height: 40,
      background: '#18e1fcff',
      noise: 10,
    });
    
    // 存储验证码到 Session对象 中
    session.code = captcha.text;
    .........
  }
  
  @Post('verify')
  verifyCaptcha(@Body() body: { code: string }, @Session() session) {
    if (body.code !== session.code) {
      return {
        code: 400,
        msg: '验证码错误',
      };
    }
    return {
      code: 200,
      msg: '验证码正确',
    };
  }
}

服务端 Session 的安全机制

密钥保护

  • secret 配置secret 用于签名 Session ID,防止 Session ID 被篡改。应使用足够复杂的密钥,并定期更换

  • 环境变量存储:生产环境中,密钥应存储在环境变量中,不能硬编码在代码中

  • httpOnly:设置 cookie.httpOnly = true,防止客户端 JavaScript 访问 Cookie,减少 XSS 攻击风险

  • secure:生产环境中,设置 cookie.secure = true,确保 Cookie 只通过 HTTPS 传输

  • sameSite:设置 cookie.sameSite'strict''lax',防止 CSRF 攻击

Session 管理

  • 过期时间:设置合理的 maxAge,避免 Session 长期存在

  • 定期清理:服务器应定期清理过期的 Session 数据

  • Session 验证:重要操作时,应验证 Session 中的用户信息

防止 Session 固定攻击

  • rolling:设置 rolling: true,每次请求时重置 Cookie 过期时间

  • 重新生成 Session ID:用户登录时,应重新生成 Session ID

Session 应用场景

用户认证

  • 登录状态管理:存储用户登录状态,避免每次请求都需要重新认证

  • 权限验证:存储用户权限信息,用于权限控制

临时数据存储

  • 验证码:存储图形验证码或短信验证码

  • 表单数据:存储用户未提交的表单数据

  • 购物车:存储用户购物车信息

用户偏好设置

  • 界面设置:存储用户的界面偏好设置

  • 语言选择:存储用户的语言偏好

图形验证码案例

验证码生成流程

  1. 客户端请求验证码:客户端访问 /user/code 接口获取验证码图片。

  2. 服务器生成验证码:服务器使用 svg-captcha 生成随机验证码图片和文本。

  3. 存储验证码到 Session:服务器将验证码文本存储到 Session 中。

  4. 返回验证码图片:服务器将生成的验证码图片返回给客户端。

  5. 客户端提交验证码:用户输入验证码后,客户端将验证码提交到 /user/verify 接口。

  6. 服务器验证验证码:服务器从 Session 中获取存储的验证码,与客户端提交的验证码进行比较。

  7. 返回验证结果:服务器返回验证结果给客户端。

代码解析

验证码生成接口
@Get('code')
createCaptcha(@Req() req, @Res() res, @Session() session) {
  const captcha = svgCaptcha.create({
    size: 4,           // 验证码长度
    fontSize: 50,      // 字体大小
    width: 100,        // 图片宽度
    height: 40,        // 图片高度
    background: '#18e1fcff',  // 背景颜色
    noise: 10,         // 干扰线数量
  });
  
  // 存储验证码到 Session 中
  session.code = captcha.text;
  
  res.type('image/svg+xml');
  res.send(captcha.data);
}
验证码验证接口
@Post('verify')
verifyCaptcha(@Body() body: { code: string }, @Session() session) {
  console.log(body, session.code);
  if (body.code !== session.code) {
    return {
      code: 400,
      msg: '验证码错误',
    };
  }
  return {
    code: 200,
    msg: '验证码正确',
  };
}

前端调用


// 获取验证码图片
const codeUrl = ref('/api/user/code');

// 刷新验证码
const resetCode = () => (codeUrl.value = '/api/user/code?' + Math.random());

// 验证验证码
const submit = async () => {
  await fetch('/api/user/verify', {
    method: 'POST',
    body: JSON.stringify(formLabelAlign),
    headers: {
      'content-type': 'application/json',
    },
  }).then((res) => res.json());
};

总结

Session 是 Web 应用中用于保持用户状态的重要机制,在 NestJS 中通过集成 express-session 中间件实现。本文详细介绍了 Session 的核心概念、工作原理、在 NestJS 中的实现方式,以及服务端 Session 的安全机制与应用场景

通过合理配置和使用 Session,可以实现用户认证、临时数据存储、用户偏好设置等功能,提升用户体验。同时,通过采取适当的安全措施,可以有效防止 Session 相关的安全攻击