使用 NestJS、Passport 和 Redis 设置会话

2025-06-07

使用 NestJS、Passport 和 Redis 设置会话

Jay 是 NestJS 核心团队的成员,主要帮助 Discord 和 Github 上的社区并为框架的各个部分做出贡献。

如果您在这里,那么您要么是我的忠实读者之一,只是在 dev.to 上偶然发现了一些有趣的内容,要么正在寻找如何使用PassportNestJS实现会话。Nest的文档很好地展示了如何在 Passport 中使用JWT,但在如何使用会话方面却有所欠缺。也许您想使用会话存储来支持某些旧版软件。也许是因为 JWT 的范围过于复杂。也许是因为您正在寻找一种更简单的方法来设置刷新令牌。无论如何,本文都适合您。

先决条件

我将使用 NestJS(它就在标题里,希望大家看得一清二楚),并且会用到Guards,所以如果你还不了解它们是什么,强烈建议你先了解一下。别担心,我会等你。

我也不会使用PostmanInsomnia之类的 HTTP 客户端,而是使用cURL。我喜欢尽可能多地使用终端,因为它可以在终端之间提供即时反馈。您可以随意使用任何您喜欢的客户端,但代码片段将使用 curl 命令。

说到即时反馈,我还将使用tmux终端多路复用器,它允许我在同一个窗口和逻辑分组中同时运行多个终端。这样,我就可以保持一个终端窗口打开,查看服务器日志、docker-compose 实例和/或日志,以及执行 curl 命令,而无需使用 Alt-Tab 切换视图。非常方便,而且高度可定制。

最后,我将使用dockerdocker-compose file运行Redis实例进行会话存储,并允许运行 redis-cli 以便能够查询 Docker 运行的 redis 实例。

所有代码都可以在这里参考和运行。需要注意的是,克隆并安装仓库后,如果要运行代码,你需要自己cd blog-posts/nestjs-passport-sessions运行nest start --watch。这只是我为 dev.to 博客设置仓库的一个副作用。

从头开始

如果您遵循预先构建的代码,请随意跳过此部分。

要从头开始设置类似的项目,您需要先设置一个 Nest 项目,最简单的方法是通过 Nest CLI

nest new session-authentication
Enter fullscreen mode Exit fullscreen mode

选择你喜欢的包管理器,然后安装以下依赖项

pnpm i @nestjs/passport passport passport-local express-session redis connect-redis bcrypt
Enter fullscreen mode Exit fullscreen mode

以及以下对等依赖项

pnpm i -D @types/passport-local @types/express-session @types/connect-redis @types/bcrypt @types/redis
Enter fullscreen mode Exit fullscreen mode

npm 和 yarn 也很好用,我只是喜欢 pnpm 作为包管理器

现在您应该可以按照其余代码进行操作,边走边构建。

NestJS 和 Passport

AuthGuard()

与大多数@nestjs/包一样,该@nestjs/passport主要是护照的薄包装,但 Nest 确实在护照包中做了一些很酷的功能,我认为值得一提。首先是AuthGuardmixin。乍一看,这个 mixin 可能有点吓人,但让我们逐一了解一下。

export const AuthGuard: (type?: string | string[]) => Type<IAuthGuard> = memoize(createAuthGuard);
Enter fullscreen mode Exit fullscreen mode

忽略memoize调用,这createAuthGuard就是类创建的神奇之处。我们最终会将type(如果适用)传递给createAuthGuard方法,并最终将其传回@UseGuards()。从这里开始的所有内容,除非另有说明,都将成为方法的一部分createAuthGuard

class MixinAuthGuard<TUser = any> implements CanActivate {
  constructor(@Optional() protected readonly options?: AuthModuleOptions) {
    this.options = this.options || {};
    if (!type && !this.options.defaultStrategy) {
      new Logger('AuthGuard').error(NO_STRATEGY_ERROR);
    }
  }
...
Enter fullscreen mode Exit fullscreen mode

构造函数允许可选地注入AuthModuleOptions。这就是传递给 的内容PassportModule.register()。这只是为了让 Nest 弄清楚是defaultStrategy使用 还是传递给 的命名对象AuthGuard

  async canActivate(context: ExecutionContext): Promise<boolean> {
    const options = {
      ...defaultOptions,
      ...this.options,
      ...await this.getAuthenticateOptions(context)
    };
    const [request, response] = [
      this.getRequest(context),
      this.getResponse(context)
    ];
    const passportFn = createPassportContext(request, response);
    const user = await passportFn(
      type || this.options.defaultStrategy,
      options,
      (err, user, info, status) =>
        this.handleRequest(err, user, info, context, status)
    );
    request[options.property || defaultOptions.property] = user;
    return true;
  }
Enter fullscreen mode Exit fullscreen mode

这段代码读起来相当顺畅,我们自定义了获取身份验证选项(默认返回undefined)、获取requestresponse对象(默认为context.switchToHttp().getRequest()/getResponse())的方法,然后createPassportContext调用该方法,并立即使用策略名称和选项进行返回。之后,我们设置 的req.user返回值passportFn并返回true,以让请求继续。接下来的代码块不是 mixin 或类的一部分MixinAuthGuard

const createPassportContext = (request, response) => (type, options, callback: Function) =>
  new Promise<void>((resolve, reject) =>
    passport.authenticate(type, options, (err, user, info, status) => {
      try {
        request.authInfo = info;
        return resolve(callback(err, user, info, status));
      } catch (err) {
        reject(err);
      }
    })(request, response, err => (err ? reject(err) : resolve())),
  );
Enter fullscreen mode Exit fullscreen mode

这里可以看到一些神奇的事情发生:Nest 最终会passport.authenticate为我们调用,这样我们就不必自己调用它了。这样做是为了将护照包装在一个 Promise 中,以便我们能够正确地管理回调,并为authenticate函数提供它自己的处理程序。整个方法实际上是在创建一个不同的回调函数,以便我们最终可以this.handleRequest使用护照返回的erruserinfostatus进行调用。这可能需要一些时间来理解,而且并非必需,但了解一些底层代码的作用通常总是有益的。

  handleRequest(err, user, info, context, status): TUser {
    if (err || !user) {
      throw err || new UnauthorizedException();
    }
    return user;
  }
Enter fullscreen mode Exit fullscreen mode

这很简单,但知道这个方法在这里很有用。正如 Nest 文档中提到的,如果您需要调试请求失败的原因,这里是一个不错的选择。通常只需添加这一行console.log({ err, user, info, context, status })就足够了,它几乎可以帮助您找出请求护照部分中出现的所有问题。

在开始实施之前,我还想谈谈另外两个类,但我保证这是值得的!

护照策略()

所以接下来我们要看的就是 mixin PassportStrategy。这就是我们最终将策略类的validate方法注册到护照verify回调中的方式。这个 mixin 在一些高级 JS 技术方面做得更多,所以,让我们再一次逐一讲解。

export function PassportStrategy<T extends Type<any> = any>(
  Strategy: T,
  name?: string | undefined
): {
  new (...args): InstanceType<T>;
} {
  abstract class MixinStrategy extends Strategy {
Enter fullscreen mode Exit fullscreen mode

这部分非常简单,我们只是将护照策略类和可选的策略重命名传递给混合。

constructor(...args: any[]) {
  const callback = async (...params: any[]) => {
    const done = params[params.length - 1];
    try {
      const validateResult = await this.validate(...params);
      if (Array.isArray(validateResult)) {
        done(null, ...validateResult);
      } else {
        done(null, validateResult);
      }
    } catch (err) {
      done(err, null);
    }
  };
Enter fullscreen mode Exit fullscreen mode

这是构造函数的前半部分。你可能一开始就注意到了super,至少现在还没有调用。这是因为我们正在设置一个回调函数,稍后会将其传递给 Passport。所以,这里实际上是设置了一个函数,它将被调用this.validate并获取结果。如果结果是一个数组,我们会展开该数组(Passport 将使用第一个值),否则,我们最终会使用done结果调用回调函数。如果发生错误,按照传统的回调方式,错误信息将作为第一个值传递给done方法。

  super(...args, callback);
  const passportInstance = this.getPassportInstance();
  if (name) {
    passportInstance.use(name, this as any);
  } else {
    passportInstance.use(this as any);
  }
}
Enter fullscreen mode Exit fullscreen mode

现在我们最终调用了super,并在调用过程中用我们刚刚创建的新回调覆盖了原始回调verify。这将设置整个护照策略类,我们将使用它作为策略的名称。现在剩下要做的就是通过调用passportInstance.use(this)(或将自定义名称作为第一个参数传递)来告知护照。

如果其中任何一个内容有点太深入,别担心。如果你真的想,可以回过头来再看,但对于本文的其余部分来说,这并非必要。

护照序列化器

终于,一个真正的类了!这是我在开始讲解会话实现之前要讲解的最直接也是最后一点。这个类通常不会在 Nest 应用程序中使用——除非你使用会话,我们接下来会解释为什么。

因此,Passport 具有序列化和反序列化用户的概念。序列化用户只是获取用户信息并对其进行压缩/使其尽可能精简。在很多情况下,这只是使用ID用户的信息。反序列化用户则相反,获取 ID 并从中提取完整的用户信息。这通常意味着调用数据库,但如果您不想担心这一点,则没有必要。现在,Nest 有一个PassportSerializer类似这样的类:

export abstract class PassportSerializer {
  abstract serializeUser(user: any, done: Function);
  abstract deserializeUser(payload: any, done: Function);

  constructor() {
    const passportInstance = this.getPassportInstance();
    passportInstance.serializeUser((user, done) => this.serializeUser(user, done));
    passportInstance.deserializeUser((payload, done) => this.deserializeUser(payload, done));
  }

  getPassportInstance() {
    return passport;
  }
}
Enter fullscreen mode Exit fullscreen mode

您应该只使用一个扩展 的类PassportSerializer,它应该负责为会话存储设置用户的通用序列化和反序列化。user传递给 的值serializeUser通常与 的值相同req.user,而payload传递给 的值是作为deserializeUser第二个参数传递的值。这样在代码中显示时会更直观一些。doneserializeUser

休息时间

好了,以上内容一下子介绍了很多关于 NestJS 和 Passport 的信息,还有一些相当复杂的代码需要仔细阅读。如果需要的话,可以在这里休息一下。喝杯咖啡,舒展一下腿脚,去玩玩你一直想玩的手机游戏。你想做什么就做什么,或者继续往下读这篇文章。

本地运行 Redis

您可以在本地计算机上安装并运行 Redis,也可以使用docker-compose.yml文件在容器内运行 Redis。以下是我在撰写本文时使用的 compose 文件

# docker-compose.yml

version: '3'
services:
  redis:
    image: redis:latest
    ports:
      - '6379:6379'
  rcli:
    image: redis:latest
    links:
      - redis
    command: redis-cli -h redis

Enter fullscreen mode Exit fullscreen mode

然后要运行 redis,我只需使用docker compose up redis -d。当我需要运行 redis CLI 时,我过去常常docker compose run rcli通过 docker 网络连接到 redis 实例。

设置中间件

现在介绍一下我们将要使用的中间件:为了设置会话并存储它们,我将使用express-session,并使用connect-redis作为会话和会话存储,并使用redis作为 connect-redis 的客户端。我还将通过Nest 中间件(而不是使用)app.use设置我们的中间件,以便在进行端到端测试时,中间件已经设置好了(这超出了本文的讨论范围)。我还使用以下代码bootstrap将 redis 设置为自定义提供程序。

// src/redis/redis.module.ts

import { Module } from '@nestjs/common';
import * as Redis from 'redis';

import { REDIS } from './redis.constants';

@Module({
  providers: [
    {
      provide: REDIS,
      useValue: Redis.createClient({ port: 6379, host: 'localhost' }),
    },
  ],
  exports: [REDIS],
})
export class RedisModule {}

Enter fullscreen mode Exit fullscreen mode
// src/redis/redis.constants.ts

export const REDIS = Symbol('AUTH:REDIS');

Enter fullscreen mode Exit fullscreen mode

这允许我们注入@Inject(REDIS)Redis 客户端。现在我们可以像这样配置中间件:

// src/app.module.ts

import { Inject, Logger, MiddlewareConsumer, Module, NestModule } from '@nestjs/common';
import * as RedisStore from 'connect-redis';
import * as session from 'express-session';
import * as passport from 'passport';
import { RedisClient } from 'redis';

import { AppController } from './app.controller';
import { AppService } from './app.service';
import { AuthModule } from './auth';
import { REDIS, RedisModule } from './redis';

@Module({
  imports: [AuthModule, RedisModule],
  providers: [AppService, Logger],
  controllers: [AppController],
})
export class AppModule implements NestModule {
  constructor(@Inject(REDIS) private readonly redis: RedisClient) {}
  configure(consumer: MiddlewareConsumer) {
    consumer
      .apply(
        session({
          store: new (RedisStore(session))({ client: this.redis, logErrors: true }),
          saveUninitialized: false,
          secret: 'sup3rs3cr3t',
          resave: false,
          cookie: {
            sameSite: true,
            httpOnly: false,
            maxAge: 60000,
          },
        }),
        passport.initialize(),
        passport.session(),
      )
      .forRoutes('*');
  }
}

Enter fullscreen mode Exit fullscreen mode

并准备好使用护照会话。这里有两件重要的事情要注意:

  1. passport.initialize()必须在之前调用passport.session()
  2. session()必须先调用passport.initialize()

现在解决了这个问题,让我们继续讨论我们的授权模块。

AuthModule

首先,让我们定义User如下

// src/auth/models/user.interface.ts

export interface User {
  id: number;
  firstName: string;
  lastName: string;
  email: string;
  password: string;
  role: string;
}

Enter fullscreen mode Exit fullscreen mode

然后有RegisterUserDtoLoginUserDto作为

// src/auth/models/register-user.dto.ts

export class RegisterUserDto {
  firstName: string;
  lastName: string;
  email: string;
  password: string;
  confirmationPassword: string;
  role = 'user';
}

Enter fullscreen mode Exit fullscreen mode


// src/auth/models/login-user.dto.ts

export class LoginUserDto {
  email: string;
  password: string;
}

Enter fullscreen mode Exit fullscreen mode

现在我们将设置我们LocalStrategy

// src/auth/local.strategy.ts

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { Strategy } from 'passport-local';
import { AuthService } from './auth.service';

@Injectable()
export class LocalStrategy extends PassportStrategy(Strategy) {
  constructor(private readonly authService: AuthService) {
    super({
      usernameField: 'email',
    });
  }

  async validate(email: string, password: string) {
    return this.authService.validateUser({ email, password });
  }
}

Enter fullscreen mode Exit fullscreen mode

注意,这里我们传递usernameField: 'email'的是super。这是因为在我们的RegisterUserDto和中,LoginUserDto我们使用的是email字段,而不是username护照的默认字段。你也可以更改passwordField,但在本文中我没有理由这么做。现在,我们将AuthService

// src/auth/auth.service.ts

import { BadRequestException, Injectable, UnauthorizedException } from '@nestjs/common';
import { compare, hash } from 'bcrypt';

import { LoginUserDto, RegisterUserDto } from './models';
import { User } from './models/user.interface';

@Injectable()
export class AuthService {
  private users: User[] = [
    {
      id: 1,
      firstName: 'Joe',
      lastName: 'Foo',
      email: 'joefoo@test.com',
      // Passw0rd!
      password: '$2b$12$s50omJrK/N3yCM6ynZYmNeen9WERDIVTncywePc75.Ul8.9PUk0LK',
      role: 'admin',
    },
    {
      id: 2,
      firstName: 'Jen',
      lastName: 'Bar',
      email: 'jenbar@test.com',
      // P4ssword!
      password: '$2b$12$FHUV7sHexgNoBbP8HsD4Su/CeiWbuX/JCo8l2nlY1yCo2LcR3SjmC',
      role: 'user',
    },
  ];

  async validateUser(user: LoginUserDto) {
    const foundUser = this.users.find(u => u.email === user.email);
    if (!user || !(await compare(user.password, foundUser.password))) {
      throw new UnauthorizedException('Incorrect username or password');
    }
    const { password: _password, ...retUser } = foundUser;
    return retUser;
  }

  async registerUser(user: RegisterUserDto): Promise<Omit<User, 'password'>> {
    const existingUser = this.users.find(u => u.email === user.email);
    if (existingUser) {
      throw new BadRequestException('User remail must be unique');
    }
    if (user.password !== user.confirmationPassword) {
      throw new BadRequestException('Password and Confirmation Password must match');
    }
    const { confirmationPassword: _, ...newUser } = user;
    this.users.push({
      ...newUser,
      password: await hash(user.password, 12),
      id: this.users.length + 1,
    });
    return {
      id: this.users.length,
      firstName: user.firstName,
      lastName: user.lastName,
      email: user.email,
      role: user.role,
    };
  }

  findById(id: number): Omit<User, 'password'> {
    const { password: _, ...user } = this.users.find(u => u.id === id);
    if (!user) {
      throw new BadRequestException(`No user found with id ${id}`);
    }
    return user;
  }
}

Enter fullscreen mode Exit fullscreen mode

我们的控制器

// src/auth/auth.controller.ts

import { Body, Controller, Post, Req, UseGuards } from '@nestjs/common';

import { LocalGuard } from '../local.guard';
import { AuthService } from './auth.service';
import { LoginUserDto, RegisterUserDto } from './models';

@Controller('auth')
export class AuthController {
  constructor(private readonly authService: AuthService) {}

  @Post('register')
  registerUser(@Body() user: RegisterUserDto) {
    return this.authService.registerUser(user);
  }

  @UseGuards(LocalGuard)
  @Post('login')
  loginUser(@Req() req, @Body() user: LoginUserDto) {
    return req.session;
  }
}

Enter fullscreen mode Exit fullscreen mode

和我们的序列化器

// src/auth/serialization.provider.ts

import { Injectable } from '@nestjs/common';
import { PassportSerializer } from '@nestjs/passport';

import { AuthService } from './auth.service';
import { User } from './models/user.interface';

@Injectable()
export class AuthSerializer extends PassportSerializer {
  constructor(private readonly authService: AuthService) {
    super();
  }
  serializeUser(user: User, done: (err: Error, user: { id: number; role: string }) => void) {
    done(null, { id: user.id, role: user.role });
  }

  deserializeUser(payload: { id: number; role: string }, done: (err: Error, user: Omit<User, 'password'>) => void) {
    const user = this.authService.findById(payload.id);
    done(null, user);
  }
}

Enter fullscreen mode Exit fullscreen mode

以及我们的模块

// src/auth/auth.module.ts

import { Module } from '@nestjs/common';
import { PassportModule } from '@nestjs/passport';

import { AuthController } from './auth.controller';
import { AuthService } from './auth.service';
import { LocalStrategy } from './local.strategy';
import { AuthSerializer } from './serialization.provider';

@Module({
  imports: [
    PassportModule.register({
      session: true,
    }),
  ],
  providers: [AuthService, LocalStrategy, AuthSerializer],
  controllers: [AuthController],
})
export class AuthModule {}

Enter fullscreen mode Exit fullscreen mode

我们需要做的AuthSerializer就是将它添加到providers数组中。Nest 会实例化它,最终会调用passport.serializeUserpassport.deserializeUser之前告诉过你,这会很有用)。

卫兵

现在让我们开始讨论守卫,正如你会注意到的,AuthController我们没有使用AuthGuard('local'),而是使用LocalGuard。这样做的原因是,我们最终需要调用super.logIn(request),虽然AuthGuard ,但默认情况下不会使用。它最终会request.login(user, (err) => done(err ? err : null, null))为我们调用,这就是用户序列化发生的方式。这就是启动会话的方式。我再重复一遍,因为它非常重要super.logIn(request) 这就是用户获取会话的方式LocalGuard。要使用此方法,我们可以按如下所示设置

// src/local.guard.ts

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

@Injectable()
export class LocalGuard extends AuthGuard('local') {
  async canActivate(context: ExecutionContext): Promise<boolean> {
    const result = (await super.canActivate(context)) as boolean;
    await super.logIn(context.switchToHttp().getRequest());
    return result;
  }
}

Enter fullscreen mode Exit fullscreen mode

我们还有另一个守卫,即LoggedInGuard。这个守卫最终只会调用request.isAuthenticated()一个方法,当使用会话时,Passport 会将其添加到请求对象中。我们可以使用它来代替用户每次请求时都向我们传递用户名和密码,因为其中会包含一个包含用户会话 ID 的 Cookie。

// src/logged-in.guard.ts

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


@Injectable()
export class LoggedInGuard implements CanActivate {
  canActivate(context: ExecutionContext) {
    return context.switchToHttp().getRequest().isAuthenticated();
  }
}

Enter fullscreen mode Exit fullscreen mode

现在我们还有另一个警卫来检查用户是否是管理员。

// src/admin.guard.ts

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

import { LoggedInGuard } from './logged-in.guard';

@Injectable()
export class AdminGuard extends LoggedInGuard {
  canActivate(context: ExecutionContext): boolean {
    const req = context.switchToHttp().getRequest();
    return super.canActivate(context) && req.session.passport.user.role === 'admin';
  }
}

Enter fullscreen mode Exit fullscreen mode

这个守卫扩展了我们的常规功能,并通过我们之前创建的LoggedInGuard功能检查用户的角色(该角色保存在 redis 会话中) 。AuthSerializer

几节额外的课

我还用到了一些其他的类。在 GitHub 仓库中查看它们最方便,但如果你想要直接复制粘贴,我会把它们添加到这里:

// src/app.controller.ts

import { Controller, Get, UseGuards } from '@nestjs/common';
import { AdminGuard } from './admin.guard';

import { AppService } from './app.service';
import { LoggedInGuard } from './logged-in.guard';

@Controller()
export class AppController {
  constructor(private readonly appService: AppService) {}

  @Get()
  publicRoute() {
    return this.appService.getPublicMessage();
  }

  @UseGuards(LoggedInGuard)
  @Get('protected')
  guardedRoute() {
    return this.appService.getPrivateMessage();
  }

  @UseGuards(AdminGuard)
  @Get('admin')
  getAdminMessage() {
    return this.appService.getAdminMessage();
  }
}

Enter fullscreen mode Exit fullscreen mode
// src/app.service.ts

import { Injectable } from '@nestjs/common';

@Injectable()
export class AppService {
  getPublicMessage(): string {
    return 'This message is public to all!';
  }

  getPrivateMessage(): string {
    return 'You can only see this if you are authenticated';
  }

  getAdminMessage(): string {
    return 'You can only see this if you are an admin';
  }
}

Enter fullscreen mode Exit fullscreen mode
// src/main.ts

import { Logger } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';

import { AppModule } from './app.module';

const bootstrap = async () => {
  const app = await NestFactory.create(AppModule);
  const logger = app.get(Logger);
  await app.listen(3000);
  logger.log(`Application listening at ${await app.getUrl()}`);
};

bootstrap();

Enter fullscreen mode Exit fullscreen mode

测试流程

现在,我们可以一起运行所有程序并测试流程了。首先,确保 Redis 实例正在运行。否则,服务器将无法启动。运行后,运行以下命令nest start --watch以开发模式启动服务器,服务器会在文件更改时重新编译并重启。现在是时候发送一些curl数据了。

测试现有用户

那么,让我们从一些现有用户测试开始。我们将尝试以 Joe Foo 的身份登录。

curl http://localhost:3000/auth/login -d 'email=joefoo@test.com&password=Passw0rd!' -c cookie.joe.txt
Enter fullscreen mode Exit fullscreen mode

如果您不熟悉 curl,请将-d请求发送为 POST,并发送application/x-www-form-urlencodedNest 默认接受的数据。这-c会告诉 curl 启动 Cookie 引擎并将 Cookie 保存到文件中。如果一切顺利,您应该会收到类似这样的响应

{"cookie":{"originalMaxAge":60000,"expires":"2021-08-16T05:30:51.621Z","httpOnly":false,"path":"/","sameSite":true},"passport":{"user":1}}
Enter fullscreen mode Exit fullscreen mode

现在我们可以发送请求/protected并得到受保护的响应

curl http://localhost:3000/protected -b cookie.joe.txt
Enter fullscreen mode Exit fullscreen mode

我们-b告诉 curl 使用在这个文件中找到的 cookie。

现在让我们检查注册情况:

curl http://localhost:3000/auth/register -c cookie.new.txt -d 'email=new.email@test.com&password=password&confirmationPassword=password&firstName=New&lastName=Test'
Enter fullscreen mode Exit fullscreen mode

您会注意到没有为新用户创建会话,这意味着他们仍然需要登录。现在让我们发送登录请求

curl http://localhost:3000/auth/login -c cookie.new.txt -d 'email=new.email@test.com&password=password'
Enter fullscreen mode Exit fullscreen mode

并检查我们确实创建了一个会话

curl http://localhost:3000/protected -b cookie.new.txt`
Enter fullscreen mode Exit fullscreen mode

就这样,我们使用 NestJS、Redis 和 Passport 实现了会话登录。

要查看 Redis 中的会话 ID,可以使用 redis-cli 连接到正在运行的实例,然后运行KEYS *以获取所有已设置的键。默认情况下,connect-redis用作sess:会话键前缀。

结论

呼,好吧,这篇文章确实比我预想的要长,而且更深入地探讨了 Nest 与 Passport 的集成,但希望它能帮助大家了解所有组件是如何结合在一起的。有了以上内容,只要用户对象保持不变,会话应该能够与任何类型的登录(基本登录、本地登录、OAuth2.0)集成。

最后需要注意的是,使用 session 时,cookie 是必须的。客户端必须能够使用 cookie,否则 session 每次请求都会丢失。

如果您有任何疑问,请随时发表评论或在NestJS Discord 服务器上找到我

文章来源:https://dev.to/nestjs/setting-up-sessions-with-nestjs-passport-and-redis-210
PREV
这只是工作
NEXT
第一部分:微服务和传输器简介