使用带有动态/用户相关秘密的“nestjs/jwt”签名

Posted

技术标签:

【中文标题】使用带有动态/用户相关秘密的“nestjs/jwt”签名【英文标题】:Using 'nestjs/jwt' signing with dynamic/user-related secret 【发布时间】:2019-08-16 15:45:28 【问题描述】:

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

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 或者更抽象的问题,您将如何解决它:我处理用户机密的方式在这里有意义吗?谢谢。

【问题讨论】:

【参考方案1】:

这还不能单独使用 Nest 的 JwtModule,但您可以自己轻松实现缺失的部分。

现场演示

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

user1(秘密:'123'):https://yw7wz99zv1.sse.codesandbox.io/login/1 用户2(秘密:'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 属性来频繁轮换密钥。

【讨论】:

这正是我最终要做的(nestjs/jwt 没有其他选择)。除了我将令牌存储在用户本身中而不是解码令牌以使令牌无效(在secretOrKeyProvider中)。谢谢你的一般解释。过时的令牌争论迫使我更多地考虑它。好点子。 很高兴听到。 :) 如果不是硬性要求,请考虑通过使用 jwt 的 child 属性来频繁轮换密钥 这是一个非常好的主意。您是否有任何资源在实践中如何使用我可以查找?我认为这需要使用secretOrKeyProvider 来临时接受以前使用的秘密? 是的,至少在到期时您必须接受旧密钥。我自己还没有实现它,所以我不能真正指出一个好的资源,对不起 谢谢,没问题。这个小提示可能已经足够了。我会尝试提出一个优雅的解决方案。干杯【参考方案2】:

nestjs/jwt 版本7.1.0中添加了将secret 添加到JwtSignOptions 中的选项。

这样,示例将是:

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 ,
         expiresIn,
          secret: user.secret,
        );

    return 
      accessToken,
      expiresIn,
    ;
 


【讨论】:

正确:github.com/nestjs/jwt/pull/336。你可以对我很久以前贡献的secretOrKeyProvider 做同样的事情。唯一的区别是它不用于密钥管理,而仅用于秘密处理。 很好的澄清@TomSiwik。这让我想知道定义例如的惯用方式是什么。如果使用secretOrKeyProvider(在具有不同令牌的用例中),是否要使用刷新令牌密码或访问令牌密码进行签名/验证。 jwt.SignOptionsjwt.VerifyOptions 似乎不是为了这个目的。 好问题可能值得回答而不是评论。不知道。我在端点上使用它,端点本身定义了如何签名/refresh vs /verify。您可能可以通过 sub 声明 tools.ietf.org/html/rfc7519#section-4.1.2 来解决它。再次......不知道。【参考方案3】:

我还曾使用不同的密钥签署访问权和刷新令牌。 如果您遵循 nestjs 文档,您会看到 JwtModule 是使用单个配置注册的,并且令牌是在没有选项的情况下签名的(使用默认配置)。使用带有选项的 jwtService 符号函数 import JwtModule.register with empty object

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 ;
        
       
    

【讨论】:

以上是关于使用带有动态/用户相关秘密的“nestjs/jwt”签名的主要内容,如果未能解决你的问题,请参考以下文章

NestJS JWT 策略认证。有效载荷对所有用户使用相同的安全密钥进行签名

@@ nestjs / jwt-无法读取未定义的属性'挑战'

NestJS jwt-passport 身份验证

NestJs JWT 身份验证返回 401

Nestjs 身份验证

使用带有秘密的 github 操作反应应用程序构建/部署