【问题标题】:Role based permission to Laravel基于角色的 Laravel 权限
【发布时间】:2018-05-29 00:20:52
【问题描述】:

我正在尝试在 Laravel 应用程序中进行基于角色的权限控制。我想检查某些用户可以执行哪些操作,但我不知道如何在我的模型中实现门和策略(权限描述在数据库中,并且是与存储资源 ID 的表相关联的布尔值)。

这是我使用的数据库模型:

我想知道laravel gates 在我的情况下是否有用,我该如何实现它,如果没有,如何制作一个基本的中间件来处理权限控制以保护路由(或控制器)。

在表资源中,我有一个标识资源的 uuid,别名是资源的名称,并且具有资源的操作或上下文的点表示法值(例如,'mysystem.users.create'、'mysystem.角色.删除','mysystem.users.images.view')。策略表有一个布尔“允许”字段,用于描述用户的权限。

提前致谢。

【问题讨论】:

  • 我认为你应该看看政策。对于整个组和角色部分,可能使用一些可重用的代码作为特征或基类。
  • 角色和组部分具有相同的行为,但优先级是:用户特定权限(最重要)、组权限和角色权限(次要)。我在想我可以生成权限列表,从所有表中添加策略列表(保持重要性)

标签: php laravel


【解决方案1】:

我发现尝试将数据库的权限与策略结合起来的问题在于记录的所有权。

最终,在我们的代码中,我们希望仅使用权限来检查对资源的访问。这是因为随着角色列表的增加,我们不希望继续在代码库中添加对这些角色的检查。

如果我们有一个用户表,我们可能希望“管理员”(角色)能够更新所有用户记录,但“基本”用户只能更新他们自己的用户记录。我们希望能够使用数据库仅控制此访问。

但是,如果您拥有“update_user”权限,那么您是否将其授予两个角色? 如果您不将其提供给基本用户角色,则该请求将无法达到检查所有权的政策。

因此,您不能撤销基本用户的访问权限,以仅从数据库更新他们的记录。 此外,权限表中“update_user”的含义现在意味着能够更新 ANY 用户。

解决方案?

添加额外权限以满足用户拥有记录的情况。

因此您可以拥有 'update_user' AND 'update_own_user' 的权限。

“admin”用户拥有第一个权限,而“basic”用户拥有第二个权限。

然后在策略中,我们首先检查“update_user”权限,如果不存在,我们检查“update_own_user”。

如果存在“update_own_user”权限,那么我们会检查所有权。否则我们返回 false。

该解决方案可行,但必须在数据库中管理“自己的”权限似乎很难看。

【讨论】:

    【解决方案2】:

    这是我在 Laravel 中使用策略实现基于角色的权限的方式。

    用户可以有多个角色。 角色具有关联的权限。 每个权限都允许对特定模型执行特定操作。

    迁移

    角色表

    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
    

    权限表

    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
    

    权限角色数据透视表

    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
    

    角色用户数据透视表

    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
    

    型号

    用户

    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();
        }
    

    角色

    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();
        }
    }
    

    权限

    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();
        }
    }
    

    政策

    每个模型都需要一个策略。这是模型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);
        }
    }
    

    在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
    

    控制器

    在每个控制器中,参考策略中相应的授权操作。

    例如在ItemController的index方法中:

    public function index()
    {
        $this->authorize('view', Item::class);
    
        $items = Item::orderBy('name', 'asc')->get();
    
        return view('items', ['items' => $items]);
    }
    

    观看次数

    在您的视图中,您可以检查用户是否具有特定角色:

    @if (Auth::user()->hasRole('item-administrator'))
    // do stuff
    @endif
    

    或者如果需要特定权限:

    @can('create', App\User::class)
    // do stuff
    @endcan
    

    【讨论】:

    • 嗨。我如何动态地定义动作?用户可以执行的所有操作(能力)都在我的资源表中定义。但是文件Illuminate\Auth\Access\Gate中的resolvePolicyCallback方法调用了策略文件中声明的函数,所以我不能在ResourcePolicy类中定义函数:(
    • 能否提供用户模型的关系? hasRole() 目前缺少。
    • 很好的答案,请更新到 Laravel 7,以免失去相关性
    • @kerrin 所以不应该是$user->hasRole 而不是$this->hasRole
    【解决方案3】:

    因为 laravel 模型不太适合我的数据库,所以我几乎又做了一遍。这是一个功能草案,其中缺少一些功能,代码没有优化,可能有点脏,但这里是:

    proyect/app/Components/Contracts/Gate.php 该接口用于在AuthServiceProvider中创建单例。

    <?php
    
    namespace App\Components\Contracts;
    
    interface Gate
    {
        public function check($resources, $arguments = []);
        public function authorize($resource, $arguments = []);
    }
    

    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.php 此文件已添加到控制器。允许在添加时在控制器中使用$this-&gt;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.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.php 该文件用于允许将“can”中间件添加到路由中,例如:Route::get('users/', 'Security\UserController@index')-&gt;name('users.index')-&gt;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);
        }
    }
    

    但你必须覆盖proyect/app/Http/Kernel.php中的默认值:

    /* ... */
    protected $routeMiddleware = [
        'can'        => \App\Http\Middleware\AuthorizeRequest::class,
        /* ... */
    ];
    

    要在刀片模板中使用@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);
            });
        }
        /* ... */
    

    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);
        }
    }
    

    proyect/app/Models/Security/Group.php 中的组模型这与 Role 相同,仅更改名称

    <?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();
        }
    }
    

    资源模型 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();
        }
    }
    

    有很多东西我没有放在这里,但这是我目前所拥有的

    【讨论】:

      【解决方案4】:

      回答您的问题:如何制作一个基本的中间件来处理权限控制以保护路由(或控制器)?。

      只是一个例子:
      这是您的路由的简单角色中间件
      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);
          }
      }
      

      定义此中间件后 将您的 kernel.php 文件更新为

      protected $routeMiddleware = [
          ..............
          'admin' =>\App\Http\Middleware\AdminRole::class,
          ...................
      ];
      

      并使用此路由中间件:
      路由中间件有多种使用方式,以下是一个示例

      Route::group(['middleware' => ['auth','admin']], function () {
          Route::get('/', 'AdminController@index')->name('admin');
      });
      

      注意: laravel 上有一些角色和权限的工具和库,但以上是创建基本路由中间件的示例。

      【讨论】:

        猜你喜欢
        • 2020-12-17
        • 2023-03-15
        • 2017-01-16
        • 2021-09-16
        • 1970-01-01
        • 2019-12-08
        • 1970-01-01
        • 1970-01-01
        • 2020-03-05
        相关资源
        最近更新 更多