好久没写blog了!大家好,我是stefan,中断了半年多的blog今天开始重新运营啦!,废话不多说,开始laravel 5.1 授权的学习吧!
介绍
除了提供了认证服务,Laravel还提供了一个组织授权逻辑和资源访问控制的简单方法。它提供了非常多的方法和辅助函数来帮助你组织认证逻辑,这篇文章将会一一阐述。
(备注:授权服务是5.1.11补充进来的,在整合之前请参考你对应的的框架升级指南)
定义功能
判断用户是否可以执行给定的接口最简单的方法就是使用定义
Illuminate\Auth\Access\Gate
类来定义一个功能。Laravel服务中附带的
AuthServiceProvider可以方便的定义应用需要的所有能力。例如,定义一个update-post能力来接受当前用户和一个Post模型,在能力的定义中,我们可以判断当前用户的id是否匹配post过来的user_id:
<?php
namespace App\Providers;
use Illuminate\Contracts\Auth\Access\Gate as GateContract;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
$gate->define('update-post', function ($user, $post) {
return $user->id === $post->user_id;
});
}
}
注意,我们不会检查$user是否不为空,Gate会自动检查所有能力是否有未认证用户或者没有特殊声明的特殊用户使用forUser方法,如果有就返回false。
类基本能力
处理注册闭包作为回调,你还可以通过包含类名和方法名的字符串注册类方法,当需要用的时候,会将其取出服务容器取出。
$gate->define('update-post', 'Class@method');
拦截授权检查
有时,你可能希望授权向一个特殊的用户授权所有的能力。在这种情况下,使用before方法来定义一个回调,这个回调将先于所有的授权检查执行:
$gate->before(function ($user, $ability) {
if ($user->isSuperAdmin()) {
return true;
}
});
如果before回调返回一个非空的结果,这个结果将会被认为是授权检查的结果。
(备注;before方法其实就是超级管理员授权,覆盖所有的授权检查)
你也可能会使用after防范来定义一个回调,这个回调将在所有授权检查结束后执行。然而,你无法通过after方法来改变之前的授权检查:
$gate->after(function ($user, $ability, $result, $arguments) {
//
});
检查能力
通过Gate 外观件
一旦能力被定义好了,我们可以通过许多方法来检查它。首先,我们可以通过Gate外观件上的 check、allows、denies 方法
。所有的这些方法接收能力的名称和应该传递给能力回调的参数。Gate外观件会自动的将当前用户传递给回调,因此你不必传递当前的用户给这些方法。所有当我们检查我们早先定义的update-post能力时,我们只需要传递post实例给denies方法:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update-post', $post)) {
abort(403);
}
// Update Post...
}
}
当然,allows方法只是简单的将denies方法反转,如果接口授权成功返回true。check方法是allows方法的别名。
检查特殊用户的能力
如果你想要使用Gate外观件来检查非当前用户的其他用户是否含有给定的能力,你可以使用foruser方法:
if (Gate::forUser($user)->allows('update-post', $post)) {
//
}
传递多个参数
当然,能力回调可以接收多个参数:
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
Gate::define('delete-comment', function ($user, $post, $comment) {
//
});
如果你的能力需要多个参数,直接用参数数组传递给Gate方法:
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
if (Gate::allows('delete-comment', [$post, $comment])) {
//
}
通过用户模型
另一方面,你也可以通过User模型实例来检查能力,默认情况下,Laravel的App\User模型使用一个Authorizable的trait提供了两个方法:can和cannot,
这些方法的使用和Gate外观件提供的allows以及denies类似。因此,应用我们之前的例子,代码可以做如下修改:
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return Response
*/
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
// Update Post...
}
}
<?php
namespace App\Http\Controllers;
use App\Post;
use Illuminate\Http\Request;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param \Illuminate\Http\Request $request
* @param int $id
* @return Response
*/
public function update(Request $request, $id)
{
$post = Post::findOrFail($id);
if ($request->user()->cannot('update-post', $post)) {
abort(403);
}
// Update Post...
}
}
当然,can方法简单的反转了cannot方法:
if ($request->user()->can('update-post', $post)) {
// Update Post...
}
if ($request->user()->can('update-post', $post)) {
// Update Post...
}
使用blade模板
为了方便起见,Laravel提供了@can blade可以直接的检查当前已认证用户是否有给定能力,例如:
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
<a href="/post/{{ $post->id }}">View Post</a>
@can('update-post', $post)
<a href="/post/{{ $post->id }}/edit">Edit Post</a>
@endcan
你可能会配合@else来使用@can:
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
@can('update-post', $post)
<!-- The Current User Can Update The Post -->
@else
<!-- The Current User Can't Update The Post -->
@endcan
使用表单请求
你还可以选择使用来自表单请求认证方法中的Gate外观件定义的能力。(不是很清楚)。例如:
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
/**
* Determine if the user is authorized to make this request.
*
* @return bool
*/
public function authorize()
{
$postId = $this->route('post');
return Gate::allows('update', Post::findOrFail($postId));
}
策略
创建策略
如果将你所有的授权逻辑都放在AuthServiceProvider里面,那复杂的应用得写多少行代码呀!Laravel提供了让你分离这些授权逻辑的防范:通过将授权逻辑分离岛”Policy“类中。policies是一些平滑的PHP类,它们基于它们的授权组织起了整个授权逻辑。
首先,让我们来生成一个策略来管理Post模型中的授权。你可以通过make:policy 的 artisan命令行来生成策略类。生成的策略文件将存放在app/Policies目录下面:
php artisan make:policy PostPolicy
php artisan make:policy PostPolicy
注册Policies
一旦策略存在,我们需要在Gate类中注册它。AuthServiceProvider包含一个Policies属性,这个属性指向了多个管理策略的实例子
。因此,我们将PostPolicy类注册为Post模型的策略类:
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
<?php
namespace App\Providers;
use App\Post;
use App\Policies\PostPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Post::class => PostPolicy::class,
];
/**
* Register any application authentication / authorization services.
*
* @param \Illuminate\Contracts\Auth\Access\Gate $gate
* @return void
*/
public function boot(GateContract $gate)
{
$this->registerPolicies($gate);
}
}
撰写策略
一旦策略被生成和注册,你就可以为每个需要授权的能力来添加方法。例如,在PostPolicy类上定义update方法,决定是否给定的用于可以更新一个Post:
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
<?php
namespace App\Policies;
use App\User;
use App\Post;
class PostPolicy
{
/**
* Determine if the given post can be updated by the user.
*
* @param \App\User $user
* @param \App\Post $post
* @return bool
*/
public function update(User $user, Post $post)
{
return $user->id === $post->user_id;
}
}
你可以继续在策略中定义自定义的方法来处理多个需授权的能力。例如,你可以定义诸如show、destroy、addComment等方法来授权多个Post接口。
备注:所有的策略都是通过Laravel的服务容器来取得的,这意味着框架会将自动诸如需要的依赖到你的policy构造函数中。
拦截所有的检查
上文提到了需要授权所有能力给特殊用户的情况,使用策略同样可以达到这个目的;未使用时,我们通过Gate外观件来调用before方法来实现,现在,我们可以直接在policy类中重写before方法,通过它来拦截所有的检查:
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
public function before($user, $ability)
{
if ($user->isSuperAdmin()) {
return true;
}
}
如果before返回一个非空的结果,那么将被认为是检查的最终结果。
检查策略
策略方法方法其实和闭包回调授权方法是同样的调用方式。你可以使用Gate外观件,或者User模型,或者直接@can blade模板,或policy辅助函数。
通过Gate外观件
Gate外观件将通过检查参数中的类来自动决定使用哪个策略。因此,如果我们传递一个Post实力给denies方法,Gate外观件将使用相对于的PostPolicy策略来授权接口:
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// Update Post...
}
}
<?php
namespace App\Http\Controllers;
use Gate;
use App\User;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
if (Gate::denies('update', $post)) {
abort(403);
}
// Update Post...
}
}
通过用户模型
User模型的can和cannot方法当给定参数是可用的情况下会自动使用策略类。这些方法提供了一个简单方便的方法去授权应用程序中任何用户的接口。
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
if ($user->can('update', $post)) {
//
}
if ($user->cannot('update', $post)) {
//
}
使用Blade模板
通上文,Blade将会在参数可用的情况下自动使用策略类。
@can('update', $post)
<!-- The Current User Can Update The Post -->
@endcan
@can('update', $post)
<!-- The Current User Can Update The Post -->
@endcan
通过策略辅助方法
全局的polity辅助函数可以被用来为给定类实例获取Policy类。例如,我们可以通过传递一个Post的实例给policy辅助函数来获取相对应的Postpolicy
类:
if (policy($post)->update($user, $post)) {
//
}
if (policy($post)->update($user, $post)) {
//
}
控制器授权
默认的,控制器基类App\Http\Controllers\Controller类中就包含了AuthorizeRequests的Trait。这个trait提供了authorize方法,这个方法可以被用来快速的授权一个给定的接口并在接口未通过授权时抛出HttpException异常。
authorize方法其实和上文提到的Gate::allows以及$user->can()等方法类似,因此我们来利用authorize方法快速的授权一个请求来更新Post:
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize('update', $post);
// Update Post...
}
}
<?php
namespace App\Http\Controllers;
use App\Post;
use App\Http\Controllers\Controller;
class PostController extends Controller
{
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize('update', $post);
// Update Post...
}
}
如果这个接口被授权通过,那么控制器将会继续执行,如果authorize这个方法判断没有授权通过,将会抛出一个HttpException异常,这个异常将生成一个状态码味403 not authorized的HTTP响应。这样看来,这个authorize方法是挺方便的,能够快速的授权一个接口或者抛出一个单行状态码的异常。
AuthorizeRequests trait同样提供authorizeForUser方法来授权非当前授权用户的的请求:
$this->authorizeForUser($user, 'update', $post);
$this->authorizeForUser($user, 'update', $post);
自动判断策略方法(不常用)
经常性的,一个策略方法对应一个控制器上的方法。例如,在update方法上,控制器方法和策略方法拥有同样的名字:update。
正是由于这个原因,Laravel允许你去简单的传递一个实例参数给authorize方法,检测授权的能力将会自动的判断调用函数的名字,
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// Update Post...
}
/**
* Update the given post.
*
* @param int $id
* @return Response
*/
public function update($id)
{
$post = Post::findOrFail($id);
$this->authorize($post);
// Update Post...
}