NestJS中Express-Session+Redis实现记住我功能时Cookie过期值为Null问题
我基于NestJS开发应用,使用PassportJs、Express-Session并以Redis作为会话存储,目前在实现“记住我”功能时遇到问题:会话已成功存入Redis且无报错,但Redis中存储的Cookie的maxAge和expires均为null。
相关代码
app.module.ts
export class AppModule implements NestModule{ constructor(@InjectConnection(ConnectionName.DB_ALPHA_REDIS_CONNECTION_NAME) private readonly connection: Redis) {} configure(consumer: MiddlewareConsumer) { consumer .apply( session({ name: process.env.ALPHA_AUTH_SESSION, store: new RedisStore({ client: this.connection }), saveUninitialized: false, secret: process.env.SESSION_SECRET, rolling: false, resave: false, cookie:{ httpOnly: true, secure: false, sameSite: true } }), passport.initialize(), passport.session(), ) .forRoutes('*'); } }
local-auth.guard.ts
@Injectable() export class LocalAuthGuard extends AuthGuard('local'){ async canActivate(context: ExecutionContext){ const REQUEST = context.switchToHttp().getRequest() const RESPONSE = context.switchToHttp().getResponse() const BODY = plainToClass(LoginBody, REQUEST.body) const ERRORS = await validate(BODY) const ERR_MESSAGES = ERRORS.flatMap(({ constraints }) => Object.values(constraints), ); if(ERR_MESSAGES.length > 0) RESPONSE.status(404).send(buildResponse(404, 'There is some errors', ERR_MESSAGES)) REQUEST.sessionStore.ttl = BODY.rememberMe ? 60 * 60 : 60 REQUEST.session.cookie.maxAge = BODY.rememberMe ? 60 * 60 * 1000 : 60 * 1000 // 输出结果符合预期:3600 console.log( REQUEST.sessionStore.ttl) /** 输出结果符合预期: cookie: { path: '/', _expires: 2023-06-20T00:24:26.712Z, originalMaxAge: 3600000, httpOnly: true, secure: false, sameSite: true } 但Redis中存储的cookie.originalMaxAge = null,cookie.expires = null */ console.log( REQUEST.session.cookie) const RESULT = (await super.canActivate(context)) as boolean await super.logIn(REQUEST) return RESULT } }
Redis存储内容
{"cookie":{"originalMaxAge":null,"expires":null,"secure":false,"httpOnly":true,"path":"/","sameSite":true},"passport":{"user":{"id":"648a59999f34ed94915ed3ba"}}}
原因分析及解决方案
核心原因
- 会话修改时机错误:你在
super.canActivate()和super.logIn()之前修改session的cookie配置,此时会话还未完成登录初始化,后续Passport的登录逻辑可能会重置会话的cookie设置,导致你的修改没有被持久化到Redis。 - 直接修改sessionStore.ttl无效:RedisStore的ttl参数是在会话中间件初始化时确定的,后续直接修改
REQUEST.sessionStore.ttl不会被存储逻辑读取,应该通过cookie的maxAge来间接控制会话过期时间。 - 未手动触发会话保存:修改session配置后,没有主动调用
session.save(),可能中间件自动保存时使用的是原始的cookie配置。
修复方案
调整代码逻辑,在登录完成后修改session的cookie,并手动触发保存:
@Injectable() export class LocalAuthGuard extends AuthGuard('local'){ async canActivate(context: ExecutionContext){ const REQUEST = context.switchToHttp().getRequest() const RESPONSE = context.switchToHttp().getResponse() const BODY = plainToClass(LoginBody, REQUEST.body) const ERRORS = await validate(BODY) const ERR_MESSAGES = ERRORS.flatMap(({ constraints }) => Object.values(constraints)); if(ERR_MESSAGES.length > 0) { RESPONSE.status(404).send(buildResponse(404, 'There is some errors', ERR_MESSAGES)) return false; } // 先完成登录流程 const RESULT = (await super.canActivate(context)) as boolean await super.logIn(REQUEST) // 登录成功后再修改cookie的过期时间 const maxAge = BODY.rememberMe ? 60 * 60 * 1000 : 60 * 1000 REQUEST.session.cookie.maxAge = maxAge // 手动保存会话,确保修改同步到Redis await new Promise((resolve, reject) => { REQUEST.session.save((err) => { if(err) reject(err) else resolve(null) }) }) return RESULT } }
额外注意:
- 移除
REQUEST.sessionStore.ttl的修改代码,RedisStore会自动根据cookie.maxAge的值设置会话在Redis中的过期时间(单位为秒,maxAge需转成对应秒数)。 - 由于配置了
saveUninitialized: false,未初始化的会话不会被保存,登录后会话才会被正式初始化,此时修改cookie配置才会生效。
内容的提问来源于stack exchange,提问作者TheMlouden
相关产品推荐
相关产品推荐

