Laravel 基于角色的权限
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/47821342/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Role based permission to Laravel
提问by UselesssCat
I am trying to do a role based permission control in a Laravel application. I want to check what actions can some user do, but i can't figure out how to implement gates and policies in my model (the permission description is in the database and are booleans asociated to a table that stores the resource's ids).
我正在尝试在 Laravel 应用程序中进行基于角色的权限控制。我想检查一些用户可以执行哪些操作,但我无法弄清楚如何在我的模型中实现门和策略(权限描述在数据库中,并且是与存储资源 ID 的表相关联的布尔值)。
This is the database model that im using:
这是我使用的数据库模型:
I would like to know if laravel gatesis useful in my case, and how can i implement it, if not, how to make a basic middleware that take care of permission control to protect routes (or controllers).
我想知道laravel 门在我的情况下是否有用,我该如何实现它,如果没有,如何制作一个基本的中间件来处理权限控制以保护路由(或控制器)。
In the table resource i have a uuid that identifies the resources, the alias is the name of the resource and has dot notation values of actions or context of the resource (eg. 'mysystem.users.create', 'mysystem.roles.delete', 'mysystem.users.images.view'). The policy tables has a boolean 'allow' field that describes the permission of users.
在表资源中,我有一个标识资源的 uuid,别名是资源的名称,并且具有资源的操作或上下文的点符号值(例如,'mysystem.users.create'、'mysystem.roles.delete ', 'mysystem.users.images.view')。策略表有一个布尔值“允许”字段,用于描述用户的权限。
Thanks in advance.
提前致谢。
回答by kerrin
This is the way that I implement role based permissions in Laravel using Policies.
这是我使用策略在 Laravel 中实现基于角色的权限的方式。
Users can have multiple roles. Roles have associated permissions. Each permission allows a specific action on a specific model.
用户可以有多个角色。角色具有关联的权限。每个权限都允许对特定模型执行特定操作。
Migrations
迁移
Roles table
角色表
class CreateRolesTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('roles', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permissions table
权限表
class CreatePermissionsTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('permissions', function (Blueprint $table) {
$table->increments('id');
$table->string('name')->unique();
$table->string('label');
$table->text('description');
$table->timestamps();
});
}
// rest of migration file
Permission Role Pivot Table
权限角色数据透视表
class CreatePermissionRolePivotTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('permission_role', function (Blueprint $table) {
$table->integer('permission_id')->unsigned()->index();
$table->foreign('permission_id')->references('id')->on('permissions')->onDelete('cascade');
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->primary(['permission_id', 'role_id']);
});
}
// rest of migration file
Role User Pivot Table
角色用户数据透视表
class CreateRoleUserPivotTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('role_user', function (Blueprint $table) {
$table->integer('role_id')->unsigned()->index();
$table->foreign('role_id')->references('id')->on('roles')->onDelete('cascade');
$table->integer('user_id')->unsigned()->index();
$table->foreign('user_id')->references('id')->on('users')->onDelete('cascade');
$table->primary(['role_id', 'user_id']);
});
}
// rest of migration file
Models
楷模
User
用户
public function roles()
{
return $this->belongsToMany(Role::class);
}
public function assignRole(Role $role)
{
return $this->roles()->save($role);
}
public function hasRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
Role
角色
class Role extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function permissions()
{
return $this->belongsToMany(Permission::class);
}
public function givePermissionTo(Permission $permission)
{
return $this->permissions()->save($permission);
}
/**
* Determine if the user may perform the given permission.
*
* @param Permission $permission
* @return boolean
*/
public function hasPermission(Permission $permission, User $user)
{
return $this->hasRole($permission->roles);
}
/**
* Determine if the role has the given permission.
*
* @param mixed $permission
* @return boolean
*/
public function inRole($permission)
{
if (is_string($permission)) {
return $this->permissions->contains('name', $permission);
}
return !! $permission->intersect($this->permissions)->count();
}
}
Permission
允许
class Permission extends Model
{
protected $guarded = ['id'];
protected $fillable = array('name', 'label', 'description');
public function roles()
{
return $this->belongsToMany(Role::class);
}
/**
* Determine if the permission belongs to the role.
*
* @param mixed $role
* @return boolean
*/
public function inRole($role)
{
if (is_string($role)) {
return $this->roles->contains('name', $role);
}
return !! $role->intersect($this->roles)->count();
}
}
Policies
政策
A policy is required for each model. Here is an example policy for a model item
. The policy defines the 'rules' for the four actions 'view, create, update, delete.
每个模型都需要一个策略。这是模型的示例策略item
。该策略定义了四个操作“查看、创建、更新、删除”的“规则”。
class ItemPolicy
{
use HandlesAuthorization;
/**
* Determine whether the user can view the item.
*
* @param \App\User $user
* @return mixed
*/
public function view(User $user)
{
$permission = Permission::where('name', 'items-view')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can create items.
*
* @param \App\User $user
* @return mixed
*/
public function create(User $user)
{
$permission = Permission::where('name', 'items-create')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can update the item.
*
* @param \App\User $user
* @return mixed
*/
public function update(User $user)
{
$permission = Permission::where('name', 'items-update')->first();
return $user->hasRole($permission->roles);
}
/**
* Determine whether the user can delete the item.
*
* @param \App\User $user
* @return mixed
*/
public function delete(User $user)
{
$permission = Permission::where('name', 'items-delete')->first();
return $user->hasRole($permission->roles);
}
}
Register each policy in AuthServiceProvider.php
将每个政策注册在 AuthServiceProvider.php
use App\Item;
use App\Policies\ItemPolicy;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
Item::class => ItemPolicy::class,
];
// rest of file
Controllers
控制器
In each controller, refer to the corresponding authorisation action from the policy.
在每个控制器中,从策略中引用相应的授权操作。
For example, in the index
method of ItemController
:
例如,在index
方法中ItemController
:
public function index()
{
$this->authorize('view', Item::class);
$items = Item::orderBy('name', 'asc')->get();
return view('items', ['items' => $items]);
}
Views
观看次数
In your views, you can check if the user has a specific role:
在您的视图中,您可以检查用户是否具有特定角色:
@if (Auth::user()->hasRole('item-administrator'))
// do stuff
@endif
or if a specific permission is required:
或者如果需要特定许可:
@can('create', App\User::class)
// do stuff
@endcan
回答by webDev
Answer for your Question:how to make a basic middleware that take care of permission control to protect routes (or controllers)?.
回答你的问题:如何制作一个基本的中间件来处理权限控制以保护路由(或控制器)?.
Just an Example:
Here is the simple role middleware for your routes
AdminRole
只是一个例子:
这是你的路由AdminRole的简单角色中间件
namespace App\Http\Middleware;
use Illuminate\Support\Facades\Auth;
use Closure;
class AdminRole
{
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
if(Auth::user()->role->name!=="admin"){ //Check your users' role or permission, in my case only admin role for routes
return redirect('/access-denied');
}
return $next($request);
}
}
After defining this middleware Update your kernel.phpfile as
定义此中间件后 将您的kernel.php文件更新为
protected $routeMiddleware = [
..............
'admin' =>\App\Http\Middleware\AdminRole::class,
...................
];
And to use this route middleware:
There are different way to use route middleware but following is one example
并使用此路由中间件:使用路由中间件
有不同的方法,但以下是一个示例
Route::group(['middleware' => ['auth','admin']], function () {
Route::get('/', 'AdminController@index')->name('admin');
});
Note:There are some tools and libraries for roles and permission on laravel but above is the example of creating basic route middle-ware.
注意:laravel 上有一些用于角色和权限的工具和库,但上面是创建基本路由中间件的示例。
回答by UselesssCat
Because the laravel model did not fit my database so much, I did almost everything again. This is a functional draft in which some functions are missing, the code is not optimized and it may be a bit dirty, but here it is:
因为 laravel 模型不太适合我的数据库,所以我几乎把所有东西都重新做了一遍。这是一个功能草案,其中一些功能缺失,代码没有优化,可能有点脏,但这里是:
proyect/app/Components/Contracts/Gate.phpThis interface is used to create singleton in AuthServiceProvider.
proyect/app/Components/Contracts/Gate.php这个接口用于在AuthServiceProvider中创建singleton。
<?php
namespace App\Components\Contracts;
interface Gate
{
public function check($resources, $arguments = []);
public function authorize($resource, $arguments = []);
}
proyect/app/Components/Security/Gate.phpThis file loads the permissions from the database. This could be improved a lot :(
proyect/app/Components/Security/Gate.php这个文件从数据库加载权限。这可以改进很多:(
<?php
namespace App\Components\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Auth\Access\HandlesAuthorization;
use Illuminate\Contracts\Container\Container;
use Illuminate\Support\Arr;
use Illuminate\Support\Str;
class Gate implements GateContract
{
use HandlesAuthorization;
protected $container;
protected $userResolver;
protected $policies = [];
public function __construct(Container $container, callable $userResolver)
{
$this->container = $container;
$this->userResolver = $userResolver;
}
public function permissionsForUser(User $user)
{
$result = User::with(['roles.resources', 'groups.resources', 'policies'])->where('id', $user->id)->first();
$list = [];
//role-specific ... the order is important role < group < user permissions
foreach ($result->roles as $role) {
foreach ($role->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::ROLE_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::ROLE_POLICY;
$list[$permission->uuid]['id'] = $role->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::ROLE_POLICY,
'id' => $role->id];
}
}
}
// group-specific
foreach ($result->groups as $group) {
foreach ($group->resources as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::GROUP_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::GROUP_POLICY;
$list[$permission->uuid]['id'] = $group->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::GROUP_POLICY,
'id' => $group->id];
}
}
}
// user-specific policies
foreach ($result->policies as $permission) {
if (isset($list[$permission->uuid])) {
if ($list[$permission->uuid]['on'] == User::USER_POLICY) {
if ($permission->pivot->allow == false) {
$list[$permission->uuid]['allow'] = false;
}
} else {
$list[$permission->uuid]['allow'] = $permission->pivot->allow ? true : false;
$list[$permission->uuid]['on'] = User::USER_POLICY;
$list[$permission->uuid]['id'] = $result->id;
}
} else {
$list[$permission->uuid] = [
'allow' => ($permission->pivot->allow ? true : false),
'on' => User::USER_POLICY,
'id' => $result->id,
];
}
}
return $list;
}
public function check($resources, $arguments = [])
{
$user = $this->resolveUser();
return collect($resources)->every(function ($resource) use ($user, $arguments) {
return $this->raw($user, $resource, $arguments);
});
}
protected function raw(User $user, $resource, $arguments = [])
{
$list = $user->getPermissionList();
if (!Resource::isUUID($resource)) {
if (empty($resource = Resource::byAlias($resource))) {
return false;
}
}
if (empty($list[$resource->uuid]['allow'])) {
return false;
} else {
return $list[$resource->uuid]['allow'];
}
}
public function authorize($resource, $arguments = [])
{
$theUser = $this->resolveUser();
return $this->raw($this->resolveUser(), $resource, $arguments) ? $this->allow() : $this->deny();
}
protected function resolveUser()
{
return call_user_func($this->userResolver);
}
}
proyect/app/Traits/Security/AuthorizesRequests.phpThis file is added to controller. Allows to use $this->authorize('stuff');
in a controller when is added.
proyect/app/Traits/Security/AuthorizesRequests.php这个文件被添加到控制器中。$this->authorize('stuff');
添加时允许在控制器中使用。
<?php
namespace App\Traits\Security;
use App\Components\Contracts\Gate;
trait AuthorizesRequests
{
public function authorize($ability, $arguments = [])
{
list($ability, $arguments) = $this->parseAbilityAndArguments($ability, $arguments);
return app(Gate::class)->authorize($ability, $arguments);
}
}
proyect/app/Providers/AuthServiceProvider.phpThis file is the same that can be found on proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php
, but i changed some parts to add new classe. Here are the important methods:
proyect/app/Providers/AuthServiceProvider.php该文件与 上的相同proyect/vendor/laravel/framework/src/Illuminate/Auth/AuthServiceProvider.php
,但我更改了一些部分以添加新类。以下是重要的方法:
<?php
namespace App\Providers;
use App\Components\Contracts\Gate as GateContract;
use App\Components\Security\Gate;
use Illuminate\Auth\AuthManager;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Support\ServiceProvider;
class AuthServiceProvider extends ServiceProvider
{
/* function register() ... */
/* other methods () */
protected function registerAccessGate()
{
$this->app->singleton(GateContract::class, function ($app) {
return new Gate($app, function () use ($app) {
return call_user_func($app['auth']->userResolver());
});
});
}
/* ... */
}
proyect /app/Http/Middleware/AuthorizeRequest.phpThis file is used to allow add the 'can' middleware to routes, eg: Route::get('users/', 'Security\UserController@index')->name('users.index')->middleware('can:inet.user.list')
;
proyect /app/Http/Middleware/AuthorizeRequest.php此文件用于以允许“可以”中间件添加途径,例如:Route::get('users/', 'Security\UserController@index')->name('users.index')->middleware('can:inet.user.list')
;
<?php
namespace App\Http\Middleware;
use App\Components\Contracts\Gate;
use Closure;
use Illuminate\Contracts\Auth\Factory as Auth;
class AuthorizeRequest
{
protected $auth;
protected $gate;
public function __construct(Auth $auth, Gate $gate)
{
$this->auth = $auth;
$this->gate = $gate;
}
public function handle($request, Closure $next, $resource, ...$params)
{
$this->auth->authenticate();
$this->gate->authorize($resource, $params);
return $next($request);
}
}
but you must overwrite the default value in proyect/app/Http/Kernel.php
:
但您必须覆盖 中的默认值proyect/app/Http/Kernel.php
:
/* ... */
protected $routeMiddleware = [
'can' => \App\Http\Middleware\AuthorizeRequest::class,
/* ... */
];
To use @can('inet.user.list')
in a blade template you have to add this lines to proyect/app/Providers/AppServiceProvider.php
:
要@can('inet.user.list')
在刀片模板中使用,您必须将此行添加到proyect/app/Providers/AppServiceProvider.php
:
class AppServiceProvider extends ServiceProvider
{
public function boot()
Blade::if ('can', function ($resource, ...$params) {
return app(\App\Components\Contracts\Gate::class)->check($resource, $params);
});
}
/* ... */
User model at proyect/app/Models/Security/User.php
用户模型位于 proyect/app/Models/Security/User.php
<?php
namespace App\Models\Security;
use App\Components\Contracts\Gate as GateContract;
use App\Models\Security\Group;
use App\Models\Security\Resource;
use App\Models\Security\Role;
use Illuminate\Database\Eloquent\SoftDeletes;
use Illuminate\Foundation\Auth\User as Authenticatable;
use Illuminate\Notifications\Notifiable;
use Illuminate\Support\Facades\Hash;
class User extends Authenticatable
{
use SoftDeletes;
use Notifiable;
public $table = 'user';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
// tipos de politicas
const GROUP_POLICY = 'group_policy';
const ROLE_POLICY = 'role_policy';
const USER_POLICY = 'user_policy';
protected $dates = ['deleted_at'];
public $fillable = [
];
public function policies()
{
return $this->belongsToMany(Resource::class, 'user_policy', 'user_id', 'resource_id')
->whereNull('user_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
public function groups()
{
return $this->belongsToMany(Group::class, 'user_group', 'user_id', 'group_id')
->whereNull('user_group.deleted_at')
->withTimestamps();
}
public function roles()
{
return $this->belongsToMany(Role::class, 'user_role', 'user_id', 'role_id')
->whereNull('user_role.deleted_at')
->withTimestamps();
}
public function getPermissionList()
{
return app(GateContract::class)->permissionsForUser($this);
}
}
Group model at proyect/app/Models/Security/Group.phpTHis is the same than Role, change only names
组模型在 proyect/app/Models/Security/Group.php这与角色相同,只更改名称
<?php
namespace App\Models\Security;
use App\Models\Security\Resource;
use App\Models\Security\User;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Group extends Model
{
use SoftDeletes;
public $table = 'group';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'name',
];
public static $rules = [
];
public function users()
{
return $this->hasMany(User::class);
}
public function resources()
{
return $this->belongsToMany(Resource::class, 'group_policy', 'group_id', 'resource_id')
->whereNull('group_policy.deleted_at')
->withPivot('allow')
->withTimestamps();
}
}
Resource Model proyect/app/Models/Security/Resource.php
资源模型 proyect/app/Models/Security/Resource.php
<?php
namespace App\Models\Security;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\SoftDeletes;
class Resource extends Model
{
use SoftDeletes;
public $table = 'resource';
const CREATED_AT = 'created_at';
const UPDATED_AT = 'updated_at';
protected $dates = ['deleted_at'];
public $fillable = [
'alias',
'uuid',
'type',
];
public static $rules = [
];
public static function isUUID($value)
{
$UUIDv4 = '/^[0-9A-F]{8}-[0-9A-F]{4}-4[0-9A-F]{3}-[0-9A-F]{4}-[0-9A-F]{12}$/i';
return preg_match($UUIDv4, $value);
}
public static function byAlias($value)
{
return Resource::where('alias', $value)->first();
}
}
There are a lot of things that I have not put here, but this is what I have so far
有很多东西我还没有放在这里,但这是我迄今为止所拥有的
回答by omarjebari
The problem i find with trying to combine permissions from a db with policies is when it comes to the ownership of a record.
我在尝试将来自数据库的权限与策略相结合时发现的问题在于记录的所有权。
Ultimately in our code we would like to check access to a resource using permission only. This is because as the list of roles grows we don't want to have to keep adding checks for these roles to the codebase.
最终在我们的代码中,我们希望仅使用权限检查对资源的访问。这是因为随着角色列表的增长,我们不想继续向代码库添加对这些角色的检查。
If we have a users table we may want 'admin' (role) to be able to update all user records but a 'basic' user to only be able to update their own user record. We would like to be able to control this access SOLELYusing the database.
如果我们有一个用户表,我们可能希望“管理员”(角色)能够更新所有用户记录,但“基本”用户只能更新他们自己的用户记录。我们希望能够使用数据库单独控制此访问。
However, if you have an 'update_user' permission then do you give it to both roles? If you don't give it to the basic user role then the request won't get as far as the policy to check ownership.
但是,如果您拥有“update_user”权限,那么您是否将其授予两个角色?如果您不将它提供给基本用户角色,那么该请求将无法达到检查所有权的策略。
Hence, you cannot revoke access for a basic user to update their record from the db alone. Also the meaning of 'update_user' in the permissions table now implies the ability to update ANYuser.
因此,您不能撤销基本用户的访问权限以仅从数据库更新他们的记录。此外,权限表中“update_user”的含义现在意味着可以更新任何用户。
SOLUTION?
解决方案?
Add extra permissions to cater for the case where a user owns the record.
添加额外权限以满足用户拥有记录的情况。
So you could have permissions to 'update_user' AND 'update_own_user'.
所以你可以有权限'update_user' AND 'update_own_user'。
The 'admin' user would have the first permission whilst the 'basic' user would have the second one.
'admin' 用户拥有第一个权限,而'basic' 用户拥有第二个权限。
Then in the policy we check for the 'update_user' permission first and if it's not present we check for the 'update_own_user'.
然后在策略中,我们首先检查“update_user”权限,如果不存在,我们检查“update_own_user”。
If the 'update_own_user' permission is present then we check ownership. Otherwise we return false.
如果存在“update_own_user”权限,则我们检查所有权。否则我们返回false。
The solution will work but it seems ugly to have to have manage 'own' permissions in the db.
该解决方案将起作用,但必须在数据库中管理“自己的”权限似乎很难看。