【问题标题】:How can I add parameters to a Loopback 4 JWT payload?如何向 Loopback 4 JWT 有效负载添加参数?
【发布时间】:2020-08-21 02:32:46
【问题描述】:

我有一个loopback 4 项目,我正在使用@loopback/authentication: ^6.0.0 添加 JWT 身份验证。我关注了official documentaion,并将其连接到我的 MongoDB。所有这一切都很顺利,我可以保护端点。然而,进展已经戛然而止。当用户登录系统时会生成一个 JWT 令牌,我需要在 payload 中添加一个userId。我只是不知道如何向有效负载添加任何内容。

在登录时创建 JWT 的代码是:

  @post('/users/login', {
    responses: {
      '200': {
        description: 'Token',
        content: {
          'application/json': {
            schema: {
              type: 'object',
              properties: {
                token: {
                  type: 'string',
                },
              },
            },
          },
        },
      },
    },
  })
  async login(
    @requestBody(CredentialsRequestBody) credentials: Credentials,
  ): Promise<{token: string}> {
    // ensure the user exists, and the password is correct
    const user = await this.userService.verifyCredentials(credentials);
    // convert a User object into a UserProfile object (reduced set of properties)
    const userProfile = this.userService.convertToUserProfile(user);
    // create a JSON Web Token based on the user profile
    const token = await this.jwtService.generateToken(userProfile);
    return {token};
  }

我首先尝试将userId 从用户对象手动添加到 userProfile 对象,但没有成功。当我检查generateToken 方法时,我发现它被输入到UserProfile,其定义为:

export interface UserProfile extends Principal {
    email?: string;
    name?: string;
}

只是为了测试,我尝试添加我的 userID: number; 参数,但这不起作用。生成的有效载荷始终是:

{
  "id": "84f0106e-3d47-4af5-8ea3-f8d41194be87",
  "name": "chrisloughnane",
  "email": "test@gmail.com",
  "iat": 1597973078,
  "exp": 1597994678
}

name 参数也令人困惑,因为我在 User 对象定义中没有 name。它确实有一个username,它是怎么绑定这个的,我找不到代码。

如何向生成的 JWT 有效负载添加额外参数?

好奇 当向安全端点发出请求时,此 payload 将被处理,因此userId 用于获取正确的数据。我可以编写一个函数来手动解码 payload,是否有内置函数可以做到这一点?

更新:我想出的访问有效负载的解决方案是将SecurityBindings.USER 注入我的控制器的构造函数,然后将其分配给可在任何端点中使用的变量。

constructor(
    @repository(IconsRepository)
    public iconsRepository: IconsRepository,
    @inject(SecurityBindings.USER, {optional: true})
    public user: UserProfile,
  ) {
    this.userId = this.user[securityId];
  }

【问题讨论】:

    标签: jwt loopback4


    【解决方案1】:

    您需要扩展 UserProfile 接口。

    例子:

    export interface UserJWT extends UserProfile {
       someProperty: string;
    }
    

    完整示例如下:

    第 1 步:重构 JWTService

    @bind({scope: BindingScope.TRANSIENT})
    export class JWTService implements TokenService {
        constructor() {}
    
        async verifyToken(token: string): Promise<UserJWT> {
            try {
                return await jwt.verify(token, 'your secret') as UserJWT;
            } catch (err) {
                throw new HttpErrors.Unauthorized('Invalid token');
            }
        }
    
        async generateToken(user: UserJWT): Promise<string> {
            return await jwt.sign(user, 'secret', {
                expiresIn: 'exp timer'
            });
        }
    }
    

    第 2 步:将服务绑定到 keys.ts

    export namespace TokenServiceConstants {
        export const TOKEN_SECRET = 'somevalue';
        export const TOKEN_EXP = '600000';
    }
    
    export namespace TokenServiceBindings {
        export const TOKEN_SECRET = BindingKey.create<string> ('authentication.jwt.secret');
        export const TOKEN_EXP = BindingKey.create<string> ('authentication.jwt.expires.in');
    
        export const TOKEN_SERVICE = BindingKey.create<TokenService> ('services.authentication.jwt.tokenservice'); // TokenService imported from @loopback/authentication !!
    }
    

    第 3 步:您可以按如下方式绑定新的 JWTAuthenticationComponet

    export class JWTAuthenticationComponent implements Component {
        bindings: Binding[] = [
            Binding.bind(TokenServiceBindings.TOKEN_SECRET).to(TokenServiceConstants.TOKEN_SECRET),
            Binding.bind(TokenServiceBindings.TOKEN_EXP).to(TokenServiceConstants.TOKEN_EXP),
            Binding.bind(TokenServiceBindings.TOKEN_SERVICE).toClass(JWTService),
            Binding.bind(UserServiceBindings.USER_SERVICE).toClass(MyUserService)
        ]
    
        constructor(
            @inject(CoreBindings.APPLICATION_INSTANCE) app: Application
        ) {
            registerAuthenticationStrategy(app, JWTAuthenticationStrategy);
        }
    }
    

    第 4 步:将组件添加到 application.ts

    this.component(AuthenticationComponent);
    this.component(JWTAuthenticationComponent); // imported from previous step
    

    第 5 步:将 AuthenticationFn 添加到 sequence.ts

    constructor(
        @inject(SequenceActions.FIND_ROUTE) protected findRoute: FindRoute,
        @inject(SequenceActions.PARSE_PARAMS) protected parseParams: ParseParams,
        @inject(SequenceActions.INVOKE_METHOD) protected invoke: InvokeMethod,
        @inject(SequenceActions.SEND) public send: Send,
        @inject(SequenceActions.REJECT) public reject: Reject,
        @inject(AuthenticationBindings.AUTH_ACTION) protected authRequest: AuthenticateFn // imported from @loopback/authentication
      ) {}
    
      async handle(context: RequestContext) {
        try {
          const {request, response} = context;
          const finished = await this.invokeMiddleware(context);
          if (finished) return;
          const route = this.findRoute(request);
    
          await this.authRequest(request);
    
          const args = await this.parseParams(request, route);
          const result = await this.invoke(route, args);
          this.send(response, result);
        } catch (err) {
          if (err.code === AUTHENTICATION_STRATEGY_NOT_FOUND || err.code === USER_PROFILE_NOT_FOUND) Object.assign(err, {statusCode: 401});
          
          this.reject(context, err);
        }
      }
    

    第 6 步:使用 @authenticate('jwt)

    @post('/route', {})
    @authenticate('jwt')
    async someFunc(): Promise<AnyObject> {}
    

    额外:检索 id

    @post('/route', {})
    @authenticate('jwt')
    async someFunc(
        @inject(SecurityBindings.USER) currentUser: UserJWT
    ): Promise<AnyObject> {
        return { userId: currentUser.id } // NEVER user securityId !!
    }
    

    额外2:使用generateToken

    constructor(
       @inject(TokenServiceBindings.TOKEN_SERVICE) public tokenService: TokenService
    ) {}
    
    @post('/route', {})
    @authenticate('jwt')
    async someFunc(): Promise<AnyObject> {
       return { token: this.tokenService.generateToken(userProfile) }
    }
    

    希望我能帮到你。

    【讨论】:

    • 感谢 lorenzoli 的详细回答,我会在一两天内完成。
    猜你喜欢
    • 2020-09-18
    • 2021-07-09
    • 2016-03-12
    • 2012-02-27
    • 2020-01-10
    • 2020-08-15
    • 2020-08-03
    • 1970-01-01
    • 2019-11-17
    相关资源
    最近更新 更多