Laravel 5.3 传递授权策略失败时异常消息



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

我知道我能做的事情是:

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

  2. abort(403, '...')在返回之前Policy,这似乎有点笨拙,因为政策已经在做这项工作

然后在/Exceptions/Handler::render我可以将响应作为 JSON 发送回 JSON

有没有更好的方法来执行此操作以在策略失败的响应中获取消息?或者 1 或 2 是我的最佳选择。

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

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

use AppExceptionsAuthorizationException;
// ... 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 AppExceptionsAuthorizationException($exceptionMessage);
}
return $hasPermission;
}

仅在AppExceptions中抛出策略的新例外:

namespace AppExceptions;
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;
}
}

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

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.

我发现不是"传递"自定义消息进行授权,只是在它自己的策略中定义自定义消息,因此,例如,如果您有方法"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');
}

它使用HandlesAuthorizationtrait 中的deny()方法。 然后,当您像$this->authorize('canUseIt', $user)一样使用它并且失败时,它将返回一个 403 HTTP 错误代码,并带有消息"对不起,伙计,你不是查克诺里斯"。

Laravel确实可以选择传递参数来自定义Controllerauthorize()方法中的错误,该方法是通过Gate类对Gate外观提供的GateContract的实现访问的

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


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

  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);
    

我已经提出了一个拉取请求来解决这个问题,希望有人能尽快合并它。

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

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

最新更新