我正在尝试找到一种干净的方法来覆盖AuthorizationException
,以获取可以在Policy
失败时传回的动态字符串。
我知道我能做的事情是:
-
使用 try-catch 将
Policy
包装在Controller
中,然后重新引发采用特定字符串的自定义异常,这似乎有点冗长 -
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确实可以选择传递参数来自定义
Controller
类的authorize()
方法中的错误,该方法是通过Gate
类对Gate
外观提供的GateContract
的实现访问的。但是,他们似乎忘记将这些参数传递给负责返回错误消息的
allow()
/deny()
方法,这些方法在HandlesAuthorization
Trait中实现。
您需要按照以下步骤传递这些参数:
-
修改
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);
我已经提出了一个拉取请求来解决这个问题,希望有人能尽快合并它。
我认为考虑策略的最佳方式是它们只是拆分控制器逻辑的一种方式,并将所有与授权相关的逻辑移动到单独的文件中。因此,在大多数情况下,abort(403, 'message')
是正确的方法。
唯一的缺点是您可能希望某些策略是仅用于业务逻辑的"纯"逻辑,因此没有任何响应控制。它们可以分开,并且可以使用评论系统来区分它们。