返回文章列表 →

Note

cookie-parser

NodeJS处理cookie的第三方库

发布于 更新于

安装和引入

npm install cookie-parser
// 类型识别
npm install --save-dev @types/cookie-parser

import { ConfigService } from '@nestjs/config';
// cookie-parser 用于解析浏览器请求中携带的 Cookie。
import cookieParser from 'cookie-parser';

配置

main.ts中的 bootstrap 配置

async function bootstrap() {
  const app = await NestFactory.create(AppModule, {});
}

// 注册 Cookie 解析中间件。
// 注册后,可以通过 request.cookies 读取浏览器发送过来的 Cookie。
app.use(cookieParser());

const configService = app.get(ConfigService);

// 如果前端和后端不是同一个源,例如:
// 前端:http://localhost:5173
// 后端:http://localhost:3000
// 就需要允许跨域请求携带 Cookie。
app.enableCors({
  // 指定允许访问后端的前端地址。
  // 携带 Cookie 时不能写成 "*"。建议使用环境变量
  origin: configService.getOrThrow<string>("FRONTEND_ORIGIN"),

  // 允许浏览器在跨域请求中接收和发送 Cookie。
  credentials: true,
});

Controller 中配置

import { ConfigService } from "@nestjs/config";
// CookieOptions 是 Cookie 配置项的类型。
import type { CookieOptions, Request, Response } from "express";

// Cookie 在浏览器中保存时使用的名字。
const REFRESH_TOKEN_COOKIE_NAME = "refresh_token";

// Cookie 在浏览器中保存 7 天,单位是毫秒。
// 这里要和 JWT_REFRESH_EXPIRES_IN=7d 保持一致。
const REFRESH_TOKEN_COOKIE_MAX_AGE = 7 * 24 * 60 * 60 * 1000;

@Controller("auth")
export class AuthController {
  constructor(
    private readonly authService: AuthService,
    private readonly configService: ConfigService,
  ) {}

  /**
   * 返回 refresh token Cookie 的公共配置。
   */
  private getRefreshTokenCookieOptions(): CookieOptions {
    // 生产环境一般使用 HTTPS,因此生产环境开启 Secure。
    const isProduction =
      this.configService.get<string>("NODE_ENV") === "production";

    return {
      // HttpOnly 禁止前端 JavaScript 通过 document.cookie 读取 refresh token。
      // 这可以降低 XSS 攻击直接窃取 refresh token 的风险。
      httpOnly: true,

      // Secure 表示只允许浏览器通过 HTTPS 发送这个 Cookie。
      // 本地开发通常使用 HTTP,所以开发环境设为 false。
      secure: isProduction,

      // Lax 可以阻止大部分跨站请求携带 Cookie,从而降低 CSRF 风险。
      // 前后端只是端口不同或使用同一主域名时,一般可以使用 Lax。
      sameSite: "lax",

      // 限制 Cookie 只发送给 /auth 路径下的接口。
      // 这样访问普通业务接口时不会携带 refresh token。
      path: "/auth",

      // 浏览器保存 Cookie 的时间,这里的单位是毫秒。
      maxAge: REFRESH_TOKEN_COOKIE_MAX_AGE,
    };
  }

  /**
   * 把 refresh token 写入 HttpOnly Cookie。
   */
  private setRefreshTokenCookie(
    response: Response,
    refreshToken: string,
  ): void {
    // response.cookie() 会在响应头中添加 Set-Cookie。
    // refresh token 不会出现在 Controller 返回的 JSON 数据里。
    response.cookie(
      REFRESH_TOKEN_COOKIE_NAME,
      refreshToken,
      this.getRefreshTokenCookieOptions(),
    );
  }
}