我正在尝试找到一种干净的方法来覆盖AuthorizationException以获取一个动态字符串,该字符串可以在策略失败时传回。
我知道我能做的事情是:
1)使用try-catch将策略包装在控制器中,然后重新抛出一个带有特定字符串的自定义异常,这看起来有点冗长
2)abort(403, '...')
在回归之前的政策,这似乎有点hacky,因为政策已经在做工作
然后在/ Exceptions / Handler :: render我可以将响应作为JSON发回
有没有更好的方法来在策略失败的响应中获取消息?或者是1或2我最好的选择。
我注意到如果你在使用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
中删除了。
Laravel确实可以选择传递参数来自定义
authorize()
类的Controller
方法中的错误,该类通过Gate
类的GateContract
Facade提供的Gate
实现访问。然而,似乎他们忘记将这些参数传递给负责返回错误消息的
allow()
/deny()
方法,这些方法在HandlesAuthorization
Trait中实现。
您需要按照以下步骤传递这些参数:
authorize
文件中的vendor/laravel/framework/src/Illuminate/Auth/Access/Gate.php
方法
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来解决这个问题,希望有人会尽快合并。
我认为考虑策略的最佳方式是它们只是一种分离控制器逻辑的方法,并将所有与授权相关的逻辑移动到单独的文件中。因此,在大多数情况下,abort(403, 'message')
是正确的方法。
唯一的缺点是您可能希望某些策略是仅用于业务逻辑的“纯”逻辑,因此不具有任何响应控制。它们可以分开,并且可以使用评论系统来区分它们。
我发现的不是“传递”自定义消息进行授权,只是在自己的策略中定义自定义消息,例如,如果您在UserPolicy中使用方法“canUseIt”,如下所示:
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错误代码,其中包含“抱歉男人,你不是查克诺里斯”的消息。