基于角色的Laravel许可

时间:2017-12-14 20:13:35

标签: php laravel

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

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

Diagram

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

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

提前致谢。

4 个答案:

答案 0 :(得分:10)

这是我使用策略在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

控制器

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

例如,在index的{​​{1}}方法中:

ItemController

浏览

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

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

答案 1 :(得分:1)

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

只是一个例子:
以下是您的路线的简单角色中间件
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上有一些角色和权限的工具和库,但上面是创建基本路由中间件的示例。

答案 2 :(得分:0)

因为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->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上的文件相同,但我更改了一些部分以添加新的classe。以下是重要的方法:

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

但您必须覆盖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中的群组模型这与角色相同,仅更改名称

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

我有很多事情没有放在这里,但这是我到目前为止所做的事情

答案 3 :(得分:0)

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

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

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

但是,如果您具有“ update_user”权限,那么您是否将其授予这两个角色? 如果您不将其分配给基本用户角色,则请求将不会达到检查所有权的策略。

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

解决方案?

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

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

“管理员”用户将具有第一个权限,而“基本”用户将具有第二个权限。

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

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

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