FORMA

JWT 与 Passport 授权

归档:完整、可维护的写法见 new/auth。下文为早期项目中的 JWT + Redis 校验片段。

身份验证常见流程:客户端用用户名密码登录 → 服务端签发 JWT → 后续请求在 Authorization: Bearer <token> 中携带 → Guard 校验并挂载 request.user

安装依赖

bash
pnpm add @nestjs/jwt @nestjs/passport passport passport-jwt
pnpm add -D @types/passport-jwt

注册 JwtModule

AuthModule(或根模块)中注册:

ts
import { Module } from "@nestjs/common";
import { JwtModule } from "@nestjs/jwt";

@Module({
  imports: [
    JwtModule.register({
      global: true,
      secret: process.env.JWT_SECRET, // 生产环境勿硬编码
      signOptions: { expiresIn: "1h" },
    }),
  ],
})
export class AuthModule {}

使用 @nestjs/config 时推荐 registerAsync

ts
import { ConfigModule, ConfigService } from "@nestjs/config";

JwtModule.registerAsync({
  imports: [ConfigModule],
  useFactory: (config: ConfigService) => ({
    secret: config.getOrThrow<string>("JWT_SECRET"),
    signOptions: {
      expiresIn: config.get<string>("JWT_EXPIRES_IN", "1h"),
    },
  }),
  inject: [ConfigService],
}),

Passport JWT 策略

ts
import { ExtractJwt, Strategy } from "passport-jwt";
import { PassportStrategy } from "@nestjs/passport";
import { Injectable } from "@nestjs/common";

export interface JwtPayload {
  sub: string;
  username: string;
}

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: JwtPayload) {
    // 返回的对象会挂载到 request.user
    return { userId: payload.sub, username: payload.username };
  }
}

AuthModuleproviders 中注册 JwtStrategy,并 imports: [PassportModule.register({ defaultStrategy: "jwt" })]

全局守卫与 @Public()

结合 路由守卫 中的 JwtAuthGuard@Public() 装饰器,可对大部分路由默认鉴权、对登录/注册等接口放行。

若需在服务端使 token 失效(登出、踢人),可将签发后的 token 存入 Redis,在 validate 中与请求头中的 token 比对(见下方历史片段)。

可选:Redis 校验 token(历史片段)

ts
async validateToken(payload: JwtPayload, requestToken: string) {
  const cached = await this.cacheService.get(`${REDIS_TOKEN_KEY}${payload.sub}`);
  if (requestToken !== cached) {
    throw new UnauthorizedException("登录状态已过期");
  }
  const userJson = await this.cacheService.get(`${REDIS_USER_INFO_KEY}${payload.sub}`);
  return userJson ? JSON.parse(userJson) : { userId: payload.sub };
}

参考文献

资料说明
AuthenticationNest 官方认证
JWT@nestjs/jwt
passport-jwt策略实现

Series

before

8 / 11