使用 jwt 和私钥和公钥的 NestJs 身份验证

Tom*_*mer 4 authentication jwt passport.js jwt-auth nestjs

我正在尝试使用 nestJS 了解 jwt 和身份验证。我创建了两个单独的微服务,其中一个是身份验证服务,成功登录后,客户端获得 jwt 令牌,使用此令牌他可以访问另一个微服务。

这是 auth 服务的 JwtStrategy 和 AuthModule 的代码:

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

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

  async validate(payload: any) {
    return payload;
  }
}
Run Code Online (Sandbox Code Playgroud)
import { Module } from '@nestjs/common';
import { AuthService } from './auth.service';
import { UsersModule } from '../users/users.module';
import { PassportModule } from '@nestjs/passport';
import { LocalStrategy } from './local.strategy';
import { JwtStrategy } from './jwt.strategy';
import { JwtModule } from '@nestjs/jwt';
import { jwtConstants } from './constants';
import { AuthController } from './auth.controller';
import * as fs from 'fs';

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: 'secretKey',
      signOptions: { expiresIn: '1h' },
    }),
  ],
  providers: [AuthService, LocalStrategy, JwtStrategy],
  exports: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}
Run Code Online (Sandbox Code Playgroud)

这是其他服务的代码:

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

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

  async validate(payload: any) {
    return payload;
  }
}
Run Code Online (Sandbox Code Playgroud)

我发现对这两个服务使用相同的密钥是没有意义的(因为如果我要创建 10 个微服务,我不会对所有服务使用相同的密钥)所以我使用 openssl 创建了一个私钥和公钥. 在 AuthModule 中,我复制了私钥而不是 'secretKey' 字符串,而在其他服务中,我复制了公钥而不是 'secretKey' 字符串,但我收到了 401 未经授权的错误。我在这里错过了什么?为什么 JwtStrategy 不验证公钥?

Avi*_*kar 5

由于已经过去了几天,我猜这已经解决了。我只是在这里为未来的读者添加我的两分钱。

问题在于 JwtModule 和 JwtStrategy 实例化。它们没有正确配置。您需要传入用于签名和验证令牌的算法以及密钥。要验证令牌是否确实是使用 RS256 算法生成的,请在https://jwt.io/检查令牌中的标头。它可能会显示 HS256,因为您的代码没有使用正确的算法来签署令牌。并且在使用公钥验证令牌时失败。

要使用 RSA 密钥对正确生成签名令牌:

  • 您需要在 signOptions 中添加算法为RS256并在 JwtModule 配置中传递公钥私钥
  • 然后在您的服务中,您将在签名时使用PRIVATE_KEY生成令牌。
  • JwtStrategy 用作 Guard。它所做的只是根据配置验证 JWT。它需要对称密钥“秘密”或非对称密钥“公共部分”进行验证。我们必须使用 PUBLIC_KEY。您还必须在此处指定要检查验证的算法。我们也必须在这里使用RS256,因为我们使用它来生成令牌。

认证模块

@Module({
  imports: [
    ConfigModule,
    JwtModule.registerAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => {
        const options: JwtModuleOptions = {
          privateKey: configService.get('JWT_PRIVATE_KEY'),
          publicKey: configService.get('JWT_PUB LIC_KEY'),
          signOptions: {
            expiresIn: '3h',
            issuer: '<Your Auth Service here>',
            algorithm: 'RS256',
          },
        };
        return options;
      },
      inject: [ConfigService],
    }),
  ],
  providers: [AuthService, JwtStrategy],
  exports: [AuthService],
  controllers: [AuthController],
})
export class AuthModule {}
Run Code Online (Sandbox Code Playgroud)

认证服务

@Injectable()
export class AuthService {
  constructor(
    private jwtService: JwtService,
  ) {}

  async generateToken(
    user: User,
    signOptions: jwt.SignOptions = {},
  ): Promise<AuthJwtToken> {
    const payload = { sub: user.id, email: user.email, scopes: user.roles };
    return {
      accessToken: this.jwtService.sign(payload, signOptions),
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

策略

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(private configService: ConfigService) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      ignoreExpiration: false,
      secretOrKey: configService.get('JWT_PUBLIC_KEY'),
      algorithms: ['RS256'],
    });
  }

  async validate(payload: any) {
    const { sub: userId, email, scopes: roles } = payload;
    return {
      id: userId,
      email,
      roles,
    };
  }
}
Run Code Online (Sandbox Code Playgroud)

在您的其他微服务中,您可以使用我们在 Auth 模块中使用的相同 JwtStrategy。

由于您正在创建分布式应用程序,您需要通过手动添加密钥或使用某些 API 端点公开它来与其他微服务共享 PUBLIC_KEY。无论哪种方式,您都必须使用PUBLIC_KEY进行其他服务的验证。您不得共享或公开PRIVATE_KEY

注意:以下代码假定 ConfigService 将提供 RSA 密钥对形式 env。强烈建议不要签入代码中的密钥。