【问题标题】:Laravel 5.3 Passing AuthorizationException a message when a Policy failsLaravel 5.3 在策略失败时传递 AuthorizationException 消息
【发布时间】:2016-11-29 18:59:13
【问题描述】:

我正在尝试找到一种干净的方法来覆盖 AuthorizationException 以获取可以在 Policy 失败时传回的动态字符串。

我知道我能做的事情是:

  1. 使用 try-catch 将 Policy 包装在 Controller 中,然后重新抛出一个采用特定字符串的自定义异常,这似乎有点冗长

  2. abort(403, '...') 在返回之前在Policy 中,这似乎有点老套,因为政策已经在起作用了

然后在/Exceptions/Handler::render 中,我可以将响应以 JSON 格式发回

有没有更好的方法来获得响应策略失败的消息?还是 1 或 2 是我最好的选择。

【问题讨论】:

  • 那么如果引发了 AuthorizationException,您想生成自定义错误吗?
  • 嗨@atefth yah有点像验证失败并且您收到错误包时,但在这种情况下,您将收到一个失败的策略包,其中包含一条消息,该消息会根据失败的策略而有所不同JSON 响应。

标签: laravel laravel-5


【解决方案1】:

我注意到,如果您在使用 Laravel 异常的策略中 throw AuthorizationException($message),它会将您跳出策略,但会继续在控制器中执行,并且不会前进到 Handler::render。我假设这是他们以某种方式处理异常,但我找不到他们在哪里做这件事......所以如果有人找到发生这种情况的地方,我仍然想知道。

如果您创建自己的 AuthorizationException 并抛出它,它将按预期停止执行,并放入 Handler::render 所以我最终将此方法添加到我的策略中:

use App\Exceptions\AuthorizationException;

// ... removed for brevity

private function throwExceptionIfNotPermitted(bool $hasPermission = false, bool $allowExceptions = false, $exceptionMessage = null): bool
{
    // Only throw when a message is provided, or use the default 
    // behaviour provided by policies
    if (!$hasPermission && $allowExceptions && !is_null($exceptionMessage)) {

        throw new \App\Exceptions\AuthorizationException($exceptionMessage);
    }

    return $hasPermission;
}

仅在 \App\Exceptions 中抛出策略的新例外:

namespace App\Exceptions;

use Exception;

/**
 * The AuthorizationException class is used by policies where authorization has
 * failed, and a message is required to indicate the type of failure.
 * ---
 * NOTE: For consistency and clarity with the framework the exception was named
 * for the similarly named exception provided by Laravel that does not stop
 * execution when thrown in a policy due to internal handling of the
 * exception.
 */
class AuthorizationException extends Exception
{
    private $statusCode = 403;

    public function __construct($message = null, \Exception $previous = null, $code = 0)
    {
        parent::__construct($message, $code, $previous);
    }

    public function getStatusCode()
    {
        return $this->statusCode;
    }
}

处理异常并在 Handler::render() 的 JSON 响应中提供消息:

public function render($request, Exception $exception)
{
    if ($exception instanceof AuthorizationException && $request->expectsJson()) {

        return response()->json([
            'message' => $exception->getMessage()
        ], $exception->getStatusCode());
    }

    return parent::render($request, $exception);
}

我也将其从登录Handler::report中删除。

【讨论】:

    【解决方案2】:

    我发现并没有将自定义消息“传递”给 authorize,只是在它自己的策略中定义自定义消息,因此,例如,如果您有方法“canUseIt”,在您的 UserPolicy,如下所示:

    public function canUseIt(User $user, MachineGun $machineGun)
        {
            if ($user->isChuckNorris()) {
                return true;
            }
            return false;
        }
    

    您可以更改它并执行以下操作:

    public function canUseIt(User $user, MachineGun $machineGun)
        {
            if ($user->isChuckNorris()) {
                return true;
            }
            $this->deny('Sorry man, you are not Chuck Norris');
        }
    

    它使用 HandlesAuthorization 特征中的 deny() 方法。 然后当你像$this->authorize('canUseIt', $user) 一样使用它并且它失败时,它会返回一个 403 HTTP 错误代码和消息“对不起,你不是 Chuck Norris”。

    【讨论】:

      【解决方案3】:

      Laravel 确实有一个选项可以传递参数来自定义 authorize() 方法中的错误,该方法是通过 Gate 访问的 Controller Gate Facade 提供的 GateContract 类的实现。

      但是,他们似乎忘记将这些参数传递给 allow()/deny() 负责返回错误消息的方法,在 中实现HandlesAuthorization 特质.


      您需要按照以下步骤传递这些参数:

      1. 修改vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php文件中的authorize方法

        public function authorize($ability, $arguments = []) {
          $result = $this->raw($ability, $arguments);
          if ($result instanceof Response) {
              return $result;
          }
        
          return $result ? $this->allow() : $this->deny($arguments);
        }
        
      2. 使用额外参数从控制器调用 authorize,即:您的自定义 $message -

        $message = "You can not delete this comment!";
        $response = $this->authorize('delete', $message);
        

      我已经创建了一个pull request 来解决这个问题,希望有人能尽快合并它。

      【讨论】:

      • 很好,我们正在使用模型策略$user->can(...);,所以我会寻找这种相同类型的解决方案,看看它是否有效。此外,您的 PR 似乎已被暂时/永久保留 :(
      【解决方案4】:

      我认为考虑策略的最佳方式是它们只是一种拆分控制器逻辑的方法,并将所有与授权相关的逻辑移动到单独的文件中。因此,在大多数情况下,abort(403, 'message') 正确的方法。

      唯一的缺点是您可能希望某些策略是“纯”逻辑,仅用于业务逻辑,因此没有任何响应控制。它们可以分开保存,并且可以使用评论系统来区分它们。

      【讨论】:

        猜你喜欢
        • 2015-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-28
        • 2017-08-02
        • 2017-01-18
        相关资源
        最近更新 更多