Skip to content

通行证

passport 结合 jwt 可以为 RESTful API 服务器实现一个完整的端到端身份验证解决方案

为 authGurad 定义 passport 内置的策略或自定义策略,来对请求进行身份验证

安装扩展包

bash
$ pnpm add @nestjs/passport passport passport-local
$ pnpm add --save @types/passport-local

passport strategy

这里使用了 passport 内置的 jwt 策略

typescript
import { ExtractJwt, Strategy } from "passport-jwt";

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    @InjectRepository(UsersRepository)
    private usersRepository: UsersRepository, // 这里使用了 typeorm 注入的数据库实体
    private configService: ConfigService
  ) {
    super({
      secretOrKey: configService.get("JWT_SECRET"), // 使用 config 注入 jwt 秘钥
      // 提供从 Request 中提取 JWT 的方法。我们将使用标准方法在 API 请求的授权标头中提供不记名令牌
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
    });
  }

  async validate(payload: JwtPayload): Promise<User> {
    const { username } = payload; // jwt 载荷
    const user: User = await this.usersRepository.findOne({ username });

    if (!user) {
      throw new UnauthorizedException();
    }

    return user;
  }
}

注册 passportModule

typescript
@Module({
  imports: [
    ConfigModule,
    // 这里也可以使用默认策略 { defaultStrategy: 'jwt' }
    PassportModule.register({}),
    JwtModule.registerAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: async (configService: ConfigService) => ({
        secret: configService.get("JWT_SECRET"),
        signOptions: {
          expiresIn: 3600,
        },
      }),
    }),
    TypeOrmModule.forFeature([UsersRepository]),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
  exports: [JwtStrategy, PassportModule],
});
export class AuthModule {}

登录时生成 jwt 载荷

typescript
export class AuthService {
  constructor(
    @InjectRepository(UsersRepository)
    private usersRepository: UsersRepository,
    private jwtService: JwtService // from '@nestjs/jwt'
  ) {}

  async signIn(
    authCredentialsDto: AuthCredentialsDto
  ): Promise<{ accessToken: string }> {
    const { username, password } = authCredentialsDto;
    const user = await this.usersRepository.findOne({ username });

    // 这里比较了加密下的密码数据
    if (user && (await bcrypt.compare(password, user.password))) {
      // 生成 jwt 载荷
      const payload: JwtPayload = { username };
      const accessToken: string = await this.jwtService.sign(payload);
      return { accessToken };
    } else {
      throw new UnauthorizedException("Please check your login credentials");
    }
  }
}

在需要用户认证的模块进行守卫

typescript
// 如果 passportModule 注册时指定了默认的策略名,这里可以不填写:AuthGuard()
@UseGuards(AuthGuard("jwt"))
export class TasksController {}

tip: 可以定义一个装饰器用来快速提取策略返回的数据

typescript
export const CurrentUser = createParamDecorator(
  (_data, ctx: ExecutionContext): User => {
    const req = ctx.switchToHttp().getRequest();
    return req.user;
  }
);

// 使用
@Controller("tasks")
@UseGuards(AuthGuard())
export class TasksController {
  @Get("/:id")
  getTaskById(
    @Param("id") id: string,
    @CurrentUser() user: User
  ): Promise<Task> {
    return this.tasksService.getTaskById(id, user);
  }
}
2025( )
今日 29.17%
本周 57.14%
本月 32.26%
本年 52.33%
Powered by Snowinlu | Copyright © 2024- | MIT License