You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义Redis Session Store的set方法为何被触发两次?

登录会话创建时RedisStore的set方法被调用两次问题排查

问题场景

使用NestJS、Passport、express-session开发Web应用,基于Redis(ioredis)实现自定义会话管理时,发现登录过程中自定义RedisStore的set方法被触发两次,其他方法仅触发一次。已验证Passport的serializeUser、deserializeUser方法,以及LocalAuthGuard的canActivate方法均只执行一次。

控制台日志触发顺序:

  1. get
  2. destroy
  3. set
  4. [Function: serialized]
  5. set

LocalAuthGuard实现

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext) {
    const result = (await super.canActivate(context)) as boolean;
    const request = context.switchToHttp().getRequest();

    await super.logIn(request);
    return result;
  }
}

express-session配置

app.use(
    session({
      name: 'dekada_session',
      store: new RedisStore(redisService),
      secret: 'keyboard cat',
      resave: false,
      saveUninitialized: false,
      rolling: false,
      cookie: {
        maxAge: 3600000,
        secure: false,
        sameSite: 'lax',
        httpOnly: true,
      },
    }),
);

自定义RedisStore的set方法

set(sid: string, session: SessionData, callback?: (error?: any) => void): void {
  console.log('set');
  const ttl = 3600;
  this.redisService.getClient().setex(`sess:${sid}`, ttl, JSON.stringify(session), (error) => {
    callback(error);
  });
}

根因分析

  1. Passport登录流程的冗余会话更新:super.canActivate(context)执行Local策略验证后,Passport内部已完成会话初始化并调用logIn序列化用户;手动再次调用super.logIn(request)会触发第二次会话内容更新,导致set方法被调用两次。
  2. express-session的状态变更触发逻辑:saveUninitialized: false配置下,会话从无到有、再到用户信息写入的两次状态变更,都会触发存储操作。

解决方案

方案1:移除冗余的logIn调用

AuthGuard('local')的默认实现已在验证通过后自动执行logIn流程,无需手动调用。修改后的LocalAuthGuard:

import { ExecutionContext, Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class LocalAuthGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext) {
    return super.canActivate(context) as Promise<boolean>;
  }
}

方案2:给RedisStore的set方法添加幂等校验

若需保留现有Guard逻辑,可在set方法中对比会话内容,仅当内容变化时执行Redis写入:

set(sid: string, session: SessionData, callback?: (error?: any) => void): void {
  const sessionStr = JSON.stringify(session);
  this.redisService.getClient().get(`sess:${sid}`, (err, existingSession) => {
    if (err) return callback(err);
    // 会话内容无变化时直接返回,避免重复写入
    if (existingSession === sessionStr) return callback();
    
    const ttl = 3600;
    this.redisService.getClient().setex(`sess:${sid}`, ttl, sessionStr, callback);
  });
}

内容的提问来源于stack exchange,提问作者Nazar Duma

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.29 17:00:56