【问题标题】:Using 'nestjs/jwt' signing with dynamic/user-related secret使用带有动态/用户相关秘密的“nestjs/jwt”签名
【发布时间】: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 或者更抽象的问题,您将如何解决它:我处理用户机密的方式在这里有意义吗?谢谢。

【问题讨论】:

    标签: javascript node.js typescript jwt nestjs


    【解决方案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 };
            }
           
        }
    

    【讨论】:

      猜你喜欢
      • 2016-10-23
      • 1970-01-01
      • 2021-06-13
      • 2020-07-08
      • 2018-01-09
      • 1970-01-01
      • 2020-10-27
      • 2019-05-09
      • 2020-08-12
      相关资源
      最近更新 更多