目录
登录
参考:[ Laravel 5.8 文档 ] 安全系列 —— 登录认证
在底层代码中,Laravel 的认证组件由“guards”和“providers”组成,Guard 定义了用户在每个请求中如何实现认证,例如,Laravel 通过 session
guard 来维护 Session 存储的状态和 Cookie。
Provider 定义了如何从持久化存储中获取用户信息,Laravel 底层支持通过 Eloquent 和数据库查询构建器两种方式来获取用户,如果需要的话,你还可以定义额外的 Provider。
现在我要搭建一个后台。
假如,目前我有一张表,admins表(借用laravel已有的user表,改一下表名即可),用来存储用户。
先在.env
中配置好数据库信息。顺便看一下APP_KEY
有没有值,没有的话,在项目的根目录是用命令行生成:php artisan key:generate
。
创建admins表的model:php artisan make:model Models/Admin
,注意的是Models文件夹是自己建立的,laravel取消了model文件夹。
admin的model默认是继承Model的,现在需要使用这个model来做权限验证,所以admin不是一般的Model,它需要继承laravel提供的带有权限验证的父类:Illuminate\Foundation\Auth\User
:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
<?php namespace App\Models; //使用laravel提供的权限父类 use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; class Admin extends Authenticatable { //使用laravel提供的消息通知trait use Notifiable; } |
其它的和普通model没什么区别。
因为流程是在登录页面填写正确的账号密码,验证,成功转跳到首页,所以,views应该有两个页面文件,这个自己准备,登录的关键不涉及页面,而在登录的控制器以及配置。
转跳后的首页也要controller,很简单,我叫它为IndexController:php artisan make:controller Admin/IndexController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<?php namespace App\Http\Controllers\Admin; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class IndexController extends Controller { public function index() { //显示后台首页 return view('admin.index'); } } |
创建登录controller。
因为是后台,所以后台controller都用一个admin文件夹来保存。使用命令创建controller:php artisan make:controller Admin/LoginController
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
<?php namespace App\Http\Controllers\Admin; use Illuminate\Foundation\Auth\AuthenticatesUsers; use Illuminate\Http\Request; use App\Http\Controllers\Controller; class LoginController extends Controller { use AuthenticatesUsers; /** * 一般来说,需要重写AuthenticatesUsers的方法有: * showLoginForm()、username()、 * redirectTo()、logout(Request $request)、guard() * */ //登录页面显示 public function showLoginForm() { return view('admin.login_register.login'); } //用于登录的字段 public function username() { return 'email';//使用表的email字段作为验证字段 } //登录成功后的跳转地址 public function redirectTo() { return route('admin'); } //登出 public function logout(Request $request) { $this->guard()->logout();//守护登出 $request->session()->invalidate();//清除session return redirect(route('admin.showLoginForm'));//转跳到指定路由 } //当前登录使用那个守护(guard) //这个要重点说一下 protected function guard() { return Auth::guard('admin'); } } |
因为这是后台,我们想有前台和后台这两个角色,如果使用Auth::guard();
默认使用的guard(守护)是web。这是由config/auth.php
权限配置文件所决定的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<?php return [ //默认使用 'defaults' => [ 'guard' => 'web',//默认使用的guard是web 'passwords' => 'users',//默认使用的guard是web ], //Laravel 的认证组件由“guards”和“providers”组成, //这里定义guard 'guards' => [ 'web' => [ 'driver' => 'session',//使用的驱动,可以自定义驱动,具体参考文档 'provider' => 'users',//使用的provider ], //新增名为admin的guard 'admin' => [ 'driver' => 'session', 'provider' => 'admins', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', 'hash' => false, ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent',//providers使用的驱动 'model' => App\User::class,//使用的模型 ], //为admin新增的providers,key值要与guard中的admin的provider的值相同 'admins' => [ 'driver' => 'eloquent', 'model' => App\Models\Admin::class,//这个使用的模型,是admin特有的模型 ], // 'users' => [ // 'driver' => 'database', // 'table' => 'users', // ], ], 'passwords' => [ 'users' => [ 'provider' => 'users', 'table' => 'password_resets', 'expire' => 60, ], ], ]; |
上面的代码,已经定义了admin的guard,以及provider。
现在开始写路由:
我现在需要一个独立的路由文件来管理后台路由,所以在routes文件夹下新建admin.php文件。
1 2 3 4 5 |
<?php Route::get('admin', function () { return 'this is admin'; }); |
访问:http://你的域名/admin
,按理来说应该会输出this is admin,但却是404。原来还需要到Providers
文件夹的RouteServiceProvider.php
注册一下自定得路由文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php namespace App\Providers; use Illuminate\Support\Facades\Route; use Illuminate\Foundation\Support\Providers\RouteServiceProvider as ServiceProvider; class RouteServiceProvider extends ServiceProvider { ... 省略 ... //主要是新增一个路由方法,并把新增的方法到map()方法中注册一下 public function map() { $this->mapApiRoutes(); $this->mapWebRoutes(); //注册一下路由 $this->mapAdminRoutes(); // } //自定义路由方法 protected function mapAdminRoutes() { Route::prefix('admin')//路由前缀 ->middleware('admin')//使用的中间件 ->namespace($this->namespace) ->group(base_path('routes/admin.php'));//路由文件的路径 } ... 省略 ... } |
这个问文件还有一个问题需要解决,那就是名字叫admin的middleware还没有。接着来到app/Http/Kernel.php文件,找到$middlewareGroups,新增有一个key为admin的数组,数组里面是各种middleware,因为和web差不多,所以可以复制web的配置,再进行加减。
回到路由文件,admin.php路由文件如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
//后台登录、注册、登出路由, Route::group(['namespace'=>'Admin','prefer'=>'admin'],function (){ Route::get('login','LoginController@showLoginForm')->name('admin.showLoginForm'); Route::post('login','LoginController@login')->name('admin.login'); Route::get('logout','LoginController@logout')->name('admin.logout'); }); //后台路由, Route::group(['namespace'=>'Admin','prefer'=>'admin'], function () { Route::get('/','IndexController@index')->name('admin'); //面板 Route::get('dashboard','DashboardController@index')->name('admin.dashboard'); Route::get('dashboard/one','DashboardController@indexOne')->name('admin.dashboard.index_one'); Route::get('dashboard/two','DashboardController@indexTwo')->name('admin.dashboard.index_two'); }); |
使用权限验证:
权限验证可以在控制器中,也可以在路由等其他地方使用,这里说的是路由验证。
在路由里添加middleware=>['auth']
即可路由权限验证,这只是使用默认的guard进行路由验证,比如说,我这里是后台,也定义了admin的guard,那么可以在auth的冒号后面指定guard,即:middleware=>['auth:admin']
1 2 3 4 5 6 7 8 9 |
//后台路由 //这里加路由验证 Route::group(['namespace'=>'Admin','prefer'=>'admin','middleware'=>['auth:admin']], function () { Route::get('/','IndexController@index')->name('admin'); //面板 Route::get('dashboard','DashboardController@index')->name('admin.dashboard'); Route::get('dashboard/one','DashboardController@indexOne')->name('admin.dashboard.index_one'); Route::get('dashboard/two','DashboardController@indexTwo')->name('admin.dashboard.index_two'); }); |
路由守护的转跳地址:
当没有登录,直接访问路由,会被重定向到指定的位置,那么这个位置可以在app\Http\Middleware\
的 Authenticate.php文件中修改:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
<?php namespace App\Http\Middleware; use Illuminate\Auth\Middleware\Authenticate as Middleware; class Authenticate extends Middleware { protected function redirectTo($request) { if (! $request->expectsJson()) { //转跳的地址 return route('admin.showLoginForm'); } } } |
可以参考:Laravel 登陆认证 访问指定的 Guard 实例 Admin认证
laravel-permission的使用
参考:
安装
安装laravel-permission:
1 |
composer require spatie/laravel-permission |
可选:provider将自动注册。或者,您可以在config/app.php文件中手动添加provider:
1 2 3 4 |
'providers' => [ // ... Spatie\Permission\PermissionServiceProvider::class, ]; |
发布 migration:
1 |
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations" |
migration发布后,您可以通过运行以下命令来创建角色和权限表:
1 |
php artisan migrate |
迁移后会有 model_has_permissions(用户-权限表)、model_has_roles(用户-角色表)、role_has_permission(角色-权限表)、spermissions(权限表)、roles(角色表)还有一个users(用户表),laravel自带的。RBAC需要的6张表就齐了。
发布配置文件:
1 |
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config" |
当发布了配置文件后,就可以看到 config/permission.php
配置看注释自己配置,一般不需要配置。配置可以改表名、改对应的model、缓存配置等等。
表说明
用户表(users):不用说了都是一些基本信息包括名字,邮箱什么的!
权限表(permissions): 权限表就是存放所有权限的一张表,权限可以是控制器访问权限,接口访问权限,model访问权限,在这里我们只讨论接口访问权限!
角色表(roles): 角色表就是存放你所有的角色,角色的名字为索引!
基本的表有了,那他们是怎么关联的呢?请继续往下看!
用户拥有权限表(model_has_permissions): 這张表记录的就是user_id,permission_id的多对多的关系表,用户直接获取权限。
用户拥有角色表(model_has_roles): 这张表记录的用户拥有的权限,表里有user_id,role_id这个也是记录用户和角色多对多的关系表,也可以理解为中间表!
角色拥有权限表(role_has_permissions): 這张表记录的是角色拥有哪些权限,表里就2个字段role_id,permission_id!也可以根据需要进行拓展!
修改users模型(model)
需要创建三个模型:用户,角色,权限。其中角色和权限模型laravel-permission已提供给我们,可以直接使用,分别是\Spatie\Permission\Models\Role
和\Spatie\Permission\Models\Permission
,如果想使用自定义的角色和权限模型,那么需要分别继承对应的laravel-permission提供的模型。
1 2 3 4 5 6 7 8 9 10 |
<?php namespace App\Models; use Illuminate\Database\Eloquent\Model; class Permission extends \Spatie\Permission\Models\Permission { // } |
最后,是用户模型了。
首先,添加 Spatie\Permission\Traits\HasRoles trait 到 用户 模型(我这里的用户模型是admin):
1 2 3 4 5 6 7 8 9 10 |
use Illuminate\Foundation\Auth\User as Authenticatable; use Spatie\Permission\Traits\HasRoles; class Adminextends Authenticatable//这个是laravel的 { use HasRoles;//这个是Spatie的 protected $guard_name = 'web'; // 可选,自定义守卫 // ... } |
用户模型必须有,且要自己创建。
使用middle(中间件)或构造函数 做权限过滤
该软件包随附RoleMiddleware,PermissionMiddleware和RoleOrPermissionMiddleware中间件。您可以将它们添加到app/Http/Kernel.php文件中。
1 2 3 4 5 6 |
protected $routeMiddleware = [ // ... 'role' => \Spatie\Permission\Middlewares\RoleMiddleware::class, 'permission' => \Spatie\Permission\Middlewares\PermissionMiddleware::class, 'role_or_permission' => \Spatie\Permission\Middlewares\RoleOrPermissionMiddleware::class, ]; |
然后,您可以在路由配置中,使用中间件规则保护路由:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
Route::group(['middleware' => ['role:super-admin']], function () { // }); Route::group(['middleware' => ['permission:publish articles']], function () { // }); Route::group(['middleware' => ['role:super-admin','permission:publish articles']], function () { // }); Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], function () { // }); Route::group(['middleware' => ['role_or_permission:publish articles']], function () { // }); |
或者,您可以使用|
(竖线)字符分隔多个角色或权限:
1 2 3 4 5 6 7 8 9 10 11 |
Route::group(['middleware' => ['role:super-admin|writer']], function () { // }); Route::group(['middleware' => ['permission:publish articles|edit articles']], function () { // }); Route::group(['middleware' => ['role_or_permission:super-admin|edit articles']], function () { // }); |
您可以通过在构造函数中设置所需的中间件来类似地保护控制器:
1 2 3 4 |
public function __construct() { $this->middleware(['role:super-admin','permission:publish articles|edit articles']); } |
1 2 3 4 |
public function __construct() { $this->middleware(['role_or_permission:super-admin|edit articles']); } |
定义超级管理员
我们强烈建议通过设置检查所需角色的全局Gate::before或Gate::after规则来处理超级管理员。
Gate::before
如果希望“超级管理员”角色响应true所有权限,而无需将所有这些权限分配给角色,则可以使用Laravel的Gate::before()方法。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
use Illuminate\Support\Facades\Gate; class AuthServiceProvider extends ServiceProvider { public function boot() { $this->registerPolicies(); // Implicitly grant "Super Admin" role all permissions // This works in the app by using gate-related functions like auth()->user->can() and @can() Gate::before(function ($user, $ability) { return $user->hasRole('Super Admin') ? true : null; }); } } |
注意:Gate::before
规则需要返回null而不是返回false,否则将干扰正常的策略操作。
Gate::after
或者,您可能希望将超级管理员检查移到该Gate::after
阶段,尤其是在不允许超级管理员执行您的应用不希望“任何人”执行的事情时,例如写超过1条评论或绕过退订规则等
1 2 3 4 5 |
// somewhere in a service provider Gate::after(function ($user, $ability) { return $user->hasRole('Super Admin'); // note this returns boolean }); |
Gate::before
和Gate::after
根据情况二选一即可,一般选Gate::before
。
seed(播种)超级管理员数据
生成seed(播种)文件:
1 |
php artisan make:seed PermissionsSeeder |
写seed(播种)规则与数据:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 |
<?php namespace Database\Seeders; use Illuminate\Database\Seeder; use Spatie\Permission\Models\Role; use Spatie\Permission\PermissionRegistrar; class PermissionsSeeder extends Seeder { /** * Run the database seeds. * * @return void */ public function run() { // 重置角色和权限的缓存 app()[PermissionRegistrar::class]->forgetCachedPermissions(); // 创建超级管理员角色 // gets all permissions via Gate::before rule; see AuthServiceProvider $role = Role::create(['name' => 'super_admin']); // 创建超级管理员 $user = \App\Models\User::factory()->create([ 'name' => 'suadim', 'email' => 'superadmin@admin.com', 'password' => bcrypt("13068114sbz"), ]); // 授权超级管理员 $user->assignRole($role); } } |
执行seed(播种):
1 |
php artisan migrate:fresh --seed --seeder=PermissionsSeeder |
操作方法
新增角色/权限:
1 2 3 |
$role = Role::create(['name' => 'writer']); $permission = Permission::create(['name' => 'edit articles']); |
权限–角色:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// $role 必须是具体的角色实例,$role = (new Role())->find(角色id); // 单权限 // role_has_permissions表,以追加的方式创建 $role->givePermissionTo($permission); $permission->assignRole($role); // 多个权限 // role_has_permissions表,以覆盖的方式创建 $role->syncPermissions($permissions); $permission->syncRoles($roles); // 取消权限授予角色 $role->revokePermissionTo($permission); $permission->removeRole($role); |
其他相关查询:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// get a list of all permissions directly assigned to the user $permissionNames = $user->getPermissionNames(); // collection of name strings $permissions = $user->permissions; // collection of permission objects // get all permissions for the user,either directly, or from roles, or from both $permissions = $user->getDirectPermissions(); $permissions = $user->getPermissionsViaRoles(); $permissions = $user->getAllPermissions(); // get the names of the user's roles $roles = $user->getRoleNames(); // Returns a collection |
使用HasRoles trait的模型,被赋予了名为role和permission的scope方法:
1 2 3 |
$users = User::role('writer')->get(); // Returns only users with the role 'writer' $users = User::permission('edit articles')->get(); // Returns only users with the permission 'edit articles' (inherited or directly) |
由于Role和Permission模型继承了Eloquent,可以Eloquent相关方法属性:
1 2 3 4 5 6 7 8 9 |
$all_users_with_all_their_roles = User::with('roles')->get(); $all_users_with_all_direct_permissions = User::with('permissions')->get(); $all_roles_in_database = Role::all()->pluck('name'); $users_without_any_roles = User::doesntHave('roles')->get(); $all_roles_except_a_and_b = Role::whereNotIn('name', ['role A','role B'])->get(); |
权限–用户:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
// 赋予权限 $user->givePermissionTo('edit articles'); $user->givePermissionTo('edit articles', 'delete articles'); $user->givePermissionTo(['edit articles', 'delete articles']); // 收回权限 $user->revokePermissionTo('edit articles'); // 检测是否具有权限 $user->hasPermissionTo('edit articles'); $user->can('edit articles'); // laravel 默认方法 // 是否具有其中任意权限 $user->hasAnyPermission(['edit articles', 'publish articles', 'unpublish articles']); // 是否拥有全部权限 $user->hasAllPermissions(['edit articles', 'publish articles', 'unpublish articles']); $user->hasDirectPermission('edit articles'); $user->hasAllDirectPermissions(['edit articles', 'delete articles']); $user->hasAnyDirectPermission(['create articles', 'delete articles']); // 返回通过赋予权限获取的权限 $user->getDirectPermissions(); // Or $user->permissions; // 仅返回通过角色获取的权限 $user->getPermissionsViaRoles(); // 所有权限(直接赋予或角色赋予) $user->getAllPermissions(); |
角色–用户:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
// 授予单个角色 $user->assignRole('writer'); // 授予多个角色 $user->assignRole('writer', 'admin'); $user->assignRole(['writer', 'admin']); // 收回授予角色 $user->removeRole('writer'); // All current roles will be removed from the user and replaced by the array given // 收回当前角色并授予新角色 $user->syncRoles(['writer', 'admin']); // 是否具有指定角色 $user->hasRole('writer'); // 是否具有至少其中一个角色 $user->hasRole(['editor', 'moderator']); $user->hasAnyRole(['writer', 'reader']); $user->hasAnyRole('writer', 'reader'); // 是否具有所有角色 $user->hasAllRoles(Role::all()); |
角色–权限
1 2 3 4 5 |
$role->givePermissionTo('edit articles'); $role->hasPermissionTo('edit articles'); $role->revokePermissionTo('edit articles'); |
多字段登录
https://learnku.com/articles/18588(评论是正解)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
class LoginController extends Controller { use AuthenticatesUsers; public function showLoginForm() { return view('admin.login_register.login'); } //用于登录的字段 public function username() { return 'account';//对应表单字段 } //重写登录 protected function attemptLogin(Request $request) { //多字段登录核心代码 //collect里的是那些字段可以用来登录的 return collect(['name', 'email', 'mobile'])->contains(function ($value) use ($request) { return $this->guard()->attempt([ $value => $request->input($this->username()), 'password' => $request->input('password') ]); }); } } |