Как вызвать авторизацию при каждом вызове REST в loopback 4? - PullRequest
0 голосов
/ 29 января 2020

В loopback4 я создал собственные обработчики аутентификации и авторизации и добавил их в приложение. Но обработчик авторизации называется только , если функция аутентификации возвращает объект UserProfile и пропускает авторизацию для пользователя undefined.

Я хочу, чтобы мой обработчик авторизации вызывался каждый раз , независимо от того, каков результат аутентификации. Я хочу разрешить неаутентифицированному вызову (не знаю пользователя) все еще проходить через обработчик авторизации, чтобы он мог судить, разрешать ли вызов на основе других факторов, помимо личности конечного пользователя.

Как мне сделать так, чтобы обработчик авторизации вызывался каждый раз?

export class MySequence implements SequenceHandler {
  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 authenticateRequest: AuthenticateFn,
  ) {}

// see: https://loopback.io/doc/en/lb4/Loopback-component-authentication.html#adding-an-authentication-action-to-a-custom-sequence
  async handle(context: RequestContext) {
    try {
      const {request, response} = context;
      const route = this.findRoute(request);

      //call authentication action
      console.log(`request path = ${request.path}`);
      await this.authenticateRequest(request); // HOW DO I CONTROL AUTHORIZATION CALL THAT FOLLOWS?

      // Authentication step done, proceed to invoke controller
      const args = await this.parseParams(request, route);
      const result = await this.invoke(route, args);
      this.send(response, result);
    } catch (error) {
      if (
        error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
        error.code === USER_PROFILE_NOT_FOUND
      ) {
        Object.assign(error, {statusCode: 401 /* Unauthorized */});
      }

      this.reject(context, error);
    }
  }
}

Полный пример кода длинен, поэтому я разместил его в гистограмме здесь .

1 Ответ

0 голосов
/ 30 января 2020

Я нашел один способ вызывать обработчик авторизации для каждого запроса. Это все еще не совсем правильно, поэтому, возможно, есть лучшее решение.

В application.ts вы можете настроить метаданные авторизации по умолчанию и предоставить более простой voter, который всегда голосует DENY. После этого все вызовы контроллера вызовут обработчики авторизации, независимо от того, присутствует ли декоратор @authorize() или нет. Вот настройка:

    // setup authorization
    const noWayJose = (): Promise<AuthorizationDecision> => {
      return new Promise(resolve => {
        resolve(AuthorizationDecision.DENY);
      });
    };
    this.component(AuthorizationComponent);
    this.configure(AuthorizationBindings.COMPONENT).to({
      defaultDecision: AuthorizationDecision.DENY,
      precedence: AuthorizationDecision.ALLOW,
      defaultMetadata: {
        voters: [noWayJose],
      },
    });
    this.bind('authorizationProviders.my-authorization-provider')
      .toProvider(MyAuthorizationProvider)
      .tag(AuthorizationTags.AUTHORIZER);

Теперь конечная точка /nope в контроллере будет обрабатываться обработчиками авторизации даже без декоратора.

export class YoController {
  constructor() {}

  @authorize({scopes: ['IS_COOL', 'IS_OKAY']})
  @get('/yo')
  yo(@inject(SecurityBindings.USER) user: UserProfile): string {
    return `yo, ${user.name}!`;
  }

  @authorize({allowedRoles: [EVERYONE]})
  @get('/sup')
  sup(): string {
    return `sup, dude.`;
  }

  @get('/nope')
  nope(): string {
    return `sorry dude.`;
  }

  @authorize({allowedRoles: [EVERYONE]})
  @get('/yay')
  yay(
    @inject(SecurityBindings.USER, {optional: true}) user: UserProfile,
  ): string {
    if (user) {
      return `yay ${user.name}!`;
    }
    return `yay!`;
  }
}

Другая вещь, которую вам нужно сделать, это не выдает ошибку, когда аутентификация не может найти пользователя. Это происходит потому, что авторизация не выполняется до тех пор, пока функция invoke() не вызовет все перехватчики. Поэтому вы должны проглотить эту ошибку и позволить авторизации сказать:

  // from sequence.ts
  async handle(context: RequestContext) {
    try {
      const {request, response} = context;
      const route = this.findRoute(request);

      //call authentication action
      console.log(`request path = ${request.path}`);
      try {
        await this.authenticateRequest(request);
      } catch (authenticationError) {
        if (authenticationError.code === USER_PROFILE_NOT_FOUND) {
          console.log(
            "didn't find  user. let's wait and see what authorization says.",
          );
        } else {
          throw authenticationError;
        }
      }

      // Authentication step done, proceed to invoke controller
      const args = await this.parseParams(request, route);

      // Authorization happens within invoke()
      const result = await this.invoke(route, args);
      this.send(response, result);
    } catch (error) {
      if (
        error.code === AUTHENTICATION_STRATEGY_NOT_FOUND ||
        error.code === USER_PROFILE_NOT_FOUND
      ) {
        Object.assign(error, {statusCode: 401 /* Unauthorized */});
      }

      this.reject(context, error);
    }
  }

Это все подходит для моего варианта использования. Я хотел, чтобы в глобальных значениях по умолчанию все конечные точки были заблокированы при наличии декораторов @authenticate и @authorize(). Я планирую добавить @authorize() только в те места, где я хочу открыть вещи. Это потому, что я собираюсь автоматически сгенерировать тонну контроллеров и захочу выставить только часть конечных точек вручную.

...