将'nestjs / jwt'签名与动态/用户相关的机密结合使用

时间:2019-03-26 09:50:23

标签: javascript node.js typescript jwt nestjs

我正在尝试根据尝试登录的用户的秘密创建用户令牌。但是,我不想使用环境中的秘密,而是要使用分配给数据库内部用户对象的秘密。

import { Injectable } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UserService } from '@src/modules/user/services';

@Injectable()
export class AuthService {
  public constructor(private readonly jwtService: JwtService,
                     private readonly userService: UserService) {}

  public async createToken(email: string): Promise<JwtReply> {
    const expiresIn = 60 * 60 * 24;
    const user = await this.userService.user({ where: { email } });
    const accessToken = await this.jwtService.signAsync({ email: user.email },
                                                        /* user.secret ,*/
                                                        { expiresIn });

    return {
      accessToken,
      expiresIn,
    };
  }
}

我是Nestjs的新手,也许我错过了一些东西。 node-jsonwebtoken确实在sign(...)函数中提供了必要的参数。 nestjs/jwt缺少此参数(请参见代码)。在不使用node-jsonwebtoken或更抽象的问题的情况下,您将如何解决此问题:在这里,我处理用户机密的方式是否有意义?谢谢。

3 个答案:

答案 0 :(得分:1)

仅使用nest的JwtModule还不可能,但是您可以自己轻松实现缺失的部分。

实时演示

Edit Nest Dynamic JWT Secrets

您可以通过调用以下路由来创建令牌:

user1(机密:“ 123”):https://yw7wz99zv1.sse.codesandbox.io/login/1
user2(机密:“ 456”):https://yw7wz99zv1.sse.codesandbox.io/login/2

然后用您的令牌呼叫受保护的路由'/',并接收您的用户:

curl -X GET https://yw7wz99zv1.sse.codesandbox.io/ \
      -H 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VySWQiOiIxIiwiaWF0IjoxNTUzNjQwMjc5fQ.E5o3djesqWVHNGe-Hi3KODp0aTiQU9X_H3Murht1R5U'

它如何工作?

AuthService中,我只是使用标准的jsonwebtoken库来创建令牌。然后,您可以从登录路线致电createToken

import * as jwt from 'jsonwebtoken';

export class AuthService {
  constructor(private readonly userService: UserService) {}

  createToken(userId: string) {
    const user = this.userService.getUser(userId);
    return jwt.sign({ userId: user.userId }, user.secret, { expiresIn: 3600 });
  }

  // ...
}

JwtStrategy中,您使用secretOrKeyProvider而不是secretOrKey,后者可以异步访问UserService以动态获取用户密码:

export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor(
    private readonly authService: AuthService,
    private readonly userService: UserService,
  ) {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKeyProvider: (request, jwtToken, done) => {
        const decodedToken: any = jwt.decode(jwtToken);
        const user = this.userService.getUser(decodedToken.userId);
        done(null, user.secret);
      },
    });
  }

  // ...
}

请注意,将不会使用您传递给JwtModule之类的选项,例如expiresIn,而是直接将您的选项传递给AuthService。导入JwtModule,不带任何选项:

JwtModule.register({})

常规

  

我在这里处理用户秘密的方式有意义吗?

如果不知道您的确切要求,很难回答。我猜jwt有动态秘密的用例,但是随着它您将失去jwt的一个重要特性:它们是无状态的。这意味着您的AuthService可以发出jwt令牌,而某些需要身份验证的ProductService可以信任jwt(它知道秘密),而无需调用其他服务(即UserService)必须查询数据库。

如果与用户相关的键不是硬性要求,请考虑频繁旋转键,而不要使用jwt的kid属性。

答案 1 :(得分:1)

postDelayed版本 7.1.0 中添加了将secret添加到JwtSignOptions的选项。

这样,示例将是:

nestjs/jwt

答案 2 :(得分:0)

我还需要使用不同的密钥对访问和刷新令牌进行签名。 如果你遵循 nestjs 文档,你会看到 JwtModule 是使用单个配置注册的,并且令牌是在没有选项的情况下签名的(使用默认配置)。要使用带有选项的 jwtService 签名函数,请使用空对象导入 JwtModule.register

import { JwtModule } from '@nestjs/jwt';
@Module({
  imports: [JwtModule.register({})],
  providers: [],
  controllers: []
})
export class AuthModule {}

并制作具有不同标志选项的配置文件

@Injectable()
export class ApiConfigService {
    constructor(private configService: ConfigService) {   
    }

    get accessTokenConfig(): any {
        return {
            secret: this.configService.get('JWT_ACCESS_TOKEN_KEY'),
            expiresIn: eval(this.configService.get('JWT_ACCESS_TOKEN_LIFETIME'))
        }
    }
    get refreshTokenConfig(): any {
        return {
            secret: this.configService.get('JWT_REFRESH_TOKEN_KEY'),
            expiresIn: eval(this.configService.get('JWT_REFRESH_TOKEN_LIFETIME'))
        }
    }
}

您可以使用所需的配置签署令牌

@Injectable()
export class AuthService {

    constructor(private jwtService: JwtService, private apiConfigService: ApiConfigService ) {}

    login(user: any) {
            let payload = {username: user.username, id: user.id};
            let jwt = this.jwtService.sign(payload, this.apiConfigService.accessTokenConfig);
            //
            return { token: jwt };
        }
       
    }