1

我们在 play 应用程序中使用 play-pac4j 进行身份验证。

我们希望拥有相同的路由/控制器端点,但具有取决于用户角色的不同行为。

从概念上讲,这将执行以下操作:

 val ACTION_ONE: ActionBuilder[Request, AnyContent] = Secure(
    JWT_CLIENT,  Authorizers.Role1
 )(anyContentLarge)

 val ACTION_TWO: ActionBuilder[Request, AnyContent] = Secure(
    JWT_CLIENT,  Authorizers.Role2
 )(anyContentLarge)

 def index = ACTION_ONE.async{ req => index1(req) } orElse ACTION_TWO.async{ req => index2(req) }

 def index1(req: Request[AnyContent]) = //... behavior with role1

 def index2(req: Request[AnyContent]) = //... behavior with role2

但是 Play 的组成Actions只提供了andThen,没有orElse。有没有办法做到这一点?

4

2 回答 2

1

我不认为你能以 s 的orElse方式作曲Action

但是,您应该能够创建一个ActionBuilder使用您现有的 2 个ActionBuilders 并执行orElse逻辑的“组合”。虽然你只能提供一个身体来运行。而这个机构将不得不依靠类似的东西AuthenticatedRequest#profiles来确定要做什么。

就像是:

def index = ACTION_COMBINED.async{ req: AuthenticatedRequest =>
  // Check something on req.profiles
  if (...) index1(req) else index2(req)
}

更准确地说,我不熟悉 play-pac4j

于 2021-10-15T11:48:03.653 回答
0

所以最后我实现了它:)

它使用'fallBackToNext',这是我们在代码库中已经拥有的一种方法,它的行为类似于fallBackTo,但带有一个异步lambda参数,因此只有在第一个future已经失败时才会执行下一个future(防止在不需要时发生大计算,但减少并行度)。

以下是大部分逻辑:


/**
 * This combination of action is the implementation class of the "orElse" operator,
 * allowing to have one and only one action to be executed within the given actions
 */
class EitherActions[A](actions: Seq[Action[A]]) extends Action[A] {

  require(actions.nonEmpty, "The actions to combine should not be empty")

  override def parser: BodyParser[A] = actions.head.parser
  override def executionContext: ExecutionContext = actions.head.executionContext

  /**
   * @param request
   * @return either the first result to be successful, or the first to be failure
   */
  override def apply(
      request: Request[A]
  ): Future[Result] = {

    // as we know actions is nonEmpty, we can start with actions.head and directly fold on actions.tail
    // this removes the need to manage an awkward "zero" value in the fold
    val firstResult = actions.head.apply(request)

    // we wrap all apply() calls into changeUnauthorizedIntoFailure to be able to use fallbackToNext on 403
    val finalResult = actions.tail.foldLeft( changeUnauthorizedIntoFailure(firstResult) ) {
      ( previousResult, nextAction ) =>

        RichFuture(previousResult).fallbackToNext{ () =>
          changeUnauthorizedIntoFailure(nextAction.apply(request))
        }(executionContext)
    }

    // restore the original message
    changeUnauthorizedIntoSuccess(finalResult)
  }


  /**
   * to use fallBackToNext, we need to have failed Future, thus we change the Success(403) into a Failure(403)
   * we keep the original result to be able to restore it at the end if none of the combined actions did success
   */
  private def changeUnauthorizedIntoFailure(
      before: Future[Result]
  ): Future[Result] = {
    val after = before.transform {
      case Success(originalResult) if originalResult.header.status == Unauthorized =>
        Failure(EitherActions.UnauthorizedWrappedException(originalResult = originalResult))
      case Success(originalResult) if originalResult.header.status == Forbidden =>
        Failure(EitherActions.UnauthorizedWrappedException(originalResult = originalResult))
      case keepResult@_ => keepResult
    }(executionContext)
    after
  }

  /**
   * after the last call, if we still have a UnauthorizedWrappedException, we change it back to a Success(403)
   * to restore the original message
   */
  private def changeUnauthorizedIntoSuccess(
      before: Future[Result]
  ): Future[Result] = {
    val after = before.transform {
      case Failure(EitherActions.UnauthorizedWrappedException(_, _, result)) => Success(result)
      case keepResult@_ => keepResult
    }(executionContext)
    after
  }

  def orElse( other: Action[A]): EitherActions[A] = {
    new EitherActions[A]( actions :+ other)
  }

}

object EitherActions {

  private case class UnauthorizedWrappedException(
      private val message: String = "",
      private val cause: Throwable = None.orNull,
      val originalResult: Result,
  ) extends Exception(message, cause)
}
于 2021-10-19T16:46:02.053 回答