Laravel 5.3 在策略失败时传递 AuthorizationException 消息

Posted

技术标签:

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

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

我知道我能做的事情是:

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

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

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

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

【问题讨论】:

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


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

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

    使用额外参数从控制器调用 authorize,即:您的自定义 $message -

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

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

【讨论】:

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

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

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

【讨论】:

以上是关于Laravel 5.3 在策略失败时传递 AuthorizationException 消息的主要内容,如果未能解决你的问题,请参考以下文章

从 curl 使用 Auth0 调用 Laravel 5.3 API 时未经授权的用户

为啥 Auth::attempt 在 Laravel 5.3 中总是返回 false?

从 5.1 迁移到 5.3 时急切加载关系的 Laravel 错误

Laravel 5.3 auth 检查构造函数返回 false

验证失败时,Laravel 5.3 表单不保留旧输入

Laravel 5.3 Auth 阻止用户