從 0 搭建用戶認證和權限管理
文章目錄
用戶認證
用戶註冊
1.新增路由
本項目使用DingoAPI作爲路由,未安裝請參考並進行安裝配置:
添加用戶註冊路由
routes/api.php
.
.
.
$api->version('v1', [
"prefix" => "api",
'namespace' => 'App\Http\Controllers\Api',
], function (routesr $api) {
// 用戶註冊
$api->post('users', 'UsersController@store')
->name('api.users.store');
.
.
.
2.創建請求類和控制器類
php artisan make:controller Api/UsersController
php artisan make:request Api/UserRequest
修改文件
app/Http/Controllers/UsersController.php
.
.
.
class UsersController extends Controller
{
use ApiTraits;
public function store(UserRequest $request)
{
$user = User::create([
'name' => $request->username,
'password' => bcrypt($request->password),
]);
return $this->apiReturn($user,CodeEnum::INC_SUCCESS);
}
}
app/Http/Requests/Api/Request.php
<?php
namespace App\Http\Requests\Api;
use Dingo\Api\Http\FormRequest;
class UserRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'name' => 'required|between:3,25|regex:/^[A-Za-z0-9\-\_]+$/|unique:users,name',
'password' => 'required|string|min:5',
];
}
public function messages()
{
return [
'username.required' => '用戶名 必須',
'username.between' => '用戶名 長度過短或過長',
'name.regex' => '用戶名 只支持英文,數字,橫杆和下劃線',
'username.unique' => '用戶名 已存在',
'password.required' => '密碼 必須',
'password.min' => '密碼 長度最小爲5',
];
}
}
其中引用Trait類-ApiTraits,統一接口返回
CodeEnum統一存放枚舉值
app/Traits/ApiTraits.php
<?php
namespace App\Traits;
/**
* Trait ApiTraits:api處理
* @package App\Traits
* @author zhaodayuan
* @date 2020/2/3
*/
Trait ApiTraits
{
public function apiReturn($data = [], $codeEnum)
{
return [
'status' => (int) $codeEnum[0],
'message' => (string) $codeEnum[1],
'data' => $data
];
}
}
app/Enum/CodeEnum.php
<?php
namespace App\Enum;
/**
* 響應碼錶
* 所有接口在使用務必在此定義
* 將所有的響應碼中心化,同時也便於查詢
* Class CodeEnum
* @package App\Enums
*/
class CodeEnum
{
const SUCCESS = [200, '操作成功'];
const INC_SUCCESS = [201, '新增成功'];
const FAIL = [999, '操作失敗'];
const NON_EXISTENT = [500, '信息不存在'];
const DATA_EMPTY = [998, '信息爲空'];
const PARAMS_MISS = [997, '參數缺失'];
const ERR_NAME_OR_PASSWORD = [996, '用戶名或密碼錯誤'];
const NOT_PERMISSION = [401, '權限不足'];
}
3.Postman運行結果
用戶登陸
本項目使用JWT作爲用戶登陸憑證,如果沒有了解過,請參考:
1.安裝JWT
首先來安裝一下,Laravel 5.5 的適配版本爲 1.0.0-rc.2
composer require tymon/jwt-auth:1.0.0-rc.2
安裝完成後,我們需要設置一下 JWT 的 secret,這個 secret 很重要,用於最後的簽名,更換這個 secret 會導致之前生成的所有 token 無效。
php artisan jwt:secret
可以看到在 .env 文件中,增加了一行 JWT_SECRET
修改 config/auth.php,將 api guard 的 driver 改爲 jwt
config/auth.php
.
.
.
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'jwt',
'provider' => 'users',
],
],
.
.
.
修改 config/api.php,auth 中增加 JWT 相關的配置
config/api.php
.
.
.
'auth' => [
'jwt' => 'Dingo\Api\Auth\Provider\JWT',
],
.
.
.
user 模型需要繼承 Tymon\JWTAuth\Contracts\JWTSubject
接口,並實現接口的兩個方法 getJWTIdentifier () 和 getJWTCustomClaims ()
app\Model\User.php
<?php
namespace App\Model;
use Tymon\JWTAuth\Contracts\JWTSubject;
use Illuminate\Foundation\Auth\User as Authenticatable;
class User extends Authenticatable implements JWTSubject
{
protected $table = 'users';
public $timestamps = true;
protected $fillable = [
'name', 'password'
];
protected $hidden = [
'password', 'remember_token'
];
public function getJWTIdentifier()
{
return $this->getKey();
}
public function getJWTCustomClaims()
{
return [];
}
}
2.新增路由
.
.
.
$api->version('v1', [
"prefix" => "api",
'namespace' => 'App\Http\Controllers\Api',
], function (routesr $api) {
// 獲取token
$api->post('authorizations', 'AuthorizationsController@login')
->name('api.authorizations.login');
.
.
.
3.創建請求類和控制器類
php artisan make:controller Api/AuthorizationsController
php artisan make:request Api/AuthorizationsRequest
修改app/Http/Requests/Api/AuthorizationsRequest.php
<?php
namespace App\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
class AuthorizationRequest extends FormRequest
{
public function authorize()
{
return true;
}
public function rules()
{
return [
'username' => 'required|string',
'password' => 'required|string|min:5',
];
}
public function messages()
{
return [
'username.required' => '用戶名 必須',
'username.string' => '用戶名 必須爲字符串',
'password.required' => '密碼 必須',
'username.string' => '密碼 必須爲字符串',
'password.min' => '密碼 長度最小爲5',
];
}
}
修改app/Http/Controller/Api/AuthorizationsController.php
<?php
namespace App\Http\Controllers\Api;
use App\Enum\CodeEnum;
use \App\Traits\ApiTraits;
use App\Http\Controllers\Controller;
use App\Http\Requests\Api\AuthorizationRequest;
class AuthorizationsController extends Controller
{
use ApiTraits;
public function login(AuthorizationRequest $request)
{
$credentials['name'] = $request->username;
$credentials['password'] = $request->password;
if (!$token = \Auth::guard('api')->attempt($credentials)) {
return $this->apiReturn('',CodeEnum::ERR_NAME_OR_PASSWORD);
}
return $this->apiReturn([
'access_token' => $token,
'token_type' => 'Bearer',
'expires_in' => \Auth::guard('api')->factory()->getTTL() * 60
],CodeEnum::SUCCESS);
}
}
4.Postman返回結果
獲取用戶信息
登錄獲取了 token 之後,第一件事就是需要換取用戶信息,先來實現 獲取登錄用戶信息
接口。
1.增加路由
routes/api.php
$api->version('v1', [
"prefix" => "api",
'namespace' => 'App\Http\Controllers\Api',
], function (Router $api) {
.
.
.
// 需要 token 驗證的接口
$api->group(['middleware' => 'auth:api'], function($api) {
// 當前登錄用戶信息
$api->get('user', 'UsersController@me')
->name('api.user.show');
.
.
.
2.修改控制器文件
app/Http/Controllers/Api/UsersController.php
.
.
.
public function me()
{
$user = \Auth::guard('api')->user();
$user->mate = [
'access_token' => \Auth::guard('api')->fromUser(\Auth::guard('api')->user()),
'token_type' => 'Bearer',
'expires_in' => \Auth::guard('api')->factory()->getTTL() * 60
];
return $this->apiReturn($user,CodeEnum::SUCCESS);
}
.
.
.
3.Postman返回結果
編輯用戶信息
1.新增路由
routes/api.php
$api->version('v1', [
"prefix" => "api",
'namespace' => 'App\Http\Controllers\Api',
], function (Router $api) {
.
.
.
// 需要 token 驗證的接口
$api->group(['middleware' => 'auth:api'], function($api) {
// 編輯登錄用戶信息
$api->patch('user', 'UsersController@update')
->name('api.user.update');
.
.
.
2.修改請求類
修改 UserRequest
app/Http/Requests/Api/UserRequest.php
.
.
.
public function rules()
{
switch($this->method()) {
case 'POST':
return [
'name' => 'between:3,25|regex:/^[A-Za-z0-9\-\_]+$/|unique:users,name',
'password' => 'required|string|min:5',
];
break;
case 'PATCH':
$userId = \Auth::guard('api')->id();
return [
'name' => 'between:3,25|regex:/^[A-Za-z0-9\-\_]+$/|unique:users,name,' .$userId,
];
break;
}
}
.
.
.
3.修改控制器文件
app/Http/Controllers/Api/UserController.php
.
.
.
.
.
public function update(UserRequest $request)
{
$user = \Auth::guard('api')->user();
$attributes = $request->only(['name']);
$user->update($attributes);
return $this->apiReturn($user,CodeEnum::SUCCESS);
}
.
.
.
4.Postman返回結果
記得添加 Authorization
至此所有用戶認證模塊搭建已完成!!!
權限管理
1.laravel_permission
安裝包的安裝
- 安裝命令
composer require "spatie/laravel-permission:~2.7"
- 安裝完成後不能直接用,還需要
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="migrations"
這段命令生成遷移文件,然後執行php artisan migrate
進行文件遷移(創建 5 張數據表:權限表,角色表,權限角色關係表,角色用戶關係表,權限用戶關係表) - 生成配置文件用命令
php artisan vendor:publish --provider="Spatie\Permission\PermissionServiceProvider" --tag="config"
=> config/permisson.php
2.運行生成權限表遷移文件
database/migrations/2020_02_15_130314_create_permission_tables.php
可能名字會有些不同,跟當前日期有關,這些都沒有關係
- (permission)權限表 -> 配置權限名稱,比如 manage_contents (管理內容)
- (roles)角色表 -> 配置角色名稱,比如 Founder (站長)
- (role_has_permissions)權限角色關係表 -> 這張表存儲角色和權限的關係,一個角色有多個關係,比如 Founder 站長有所有權限。
- (model_has_roles)角色用戶關係表 -> 這張表存儲用戶和角色的關係,一個用戶有多個角色,比如 1 號用戶可以扮演 Founder。
- (model_has_permissions)權限用戶關係表 -> 這張表存儲權限和用戶的關係,即跳過上一張表,直接給用戶權限而不是給用戶角色。不建議使用,而是用戶通過扮演不同角色來獲得權限
運行遷移文件
php artisan migrate
3.修改User 模型文件
app/Model/User.php
.
.
.
use Spatie\Permission\Traits\HasRoles;
class User extends Authenticatable implements JWTSubject
{
use HasRoles;
.
.
.
4.創建權限表初始化數據
可以根據當前項目進行自主添加權限和角色
<?php
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\Migrations\Migration;
use Spatie\Permission\Models\Role;
use Spatie\Permission\Models\Permission;
class SeedRolesAndPermissionsData extends Migration
{
public function up()
{
// 清除緩存
app()['cache']->forget('spatie.permission.cache');
// 創建權限 Persisson::create(['name' => '權限名'])
Permission::create(['name' => 'material-management']);
// 創建角色 Role::create(['name' => '角色名'])
$founder = Role::create(['name' => 'Founder']); //站長
// 賦予權限 $角色->givePermessionTo('權限名')
$founder->givePermissionTo('material-management');
}
public function down()
{
// 清除緩存
app()['cache']->forget('spatie.permission.cache');
// 清空所有數據表數據
Model::unguard(); //記得解除模型保護
DB::table('role_has_permissions')->delete();
DB::table('model_has_roles')->delete();
DB::table('model_has_permissions')->delete();
DB::table('roles')->delete();
DB::table('permissions')->delete();
Model::reguard(); //最後重新開啓模型保護
}
}
運行遷移文件
php artisan migrate
5.新增路由
routes/api.php
.
.
.
// 需要 token 驗證的接口
$api->group(['middleware' => 'auth:api'], function($api) {
// 當前登錄用戶信息
$api->get('user', 'UsersController@me')
->name('api.user.show');
// 編輯登錄用戶信息
$api->patch('user', 'UsersController@update')
->name('api.user.update');
// 圖片資源
$api->post('images', 'ImagesController@store')
->name('api.images.store');
//角色權限
$api->group(['prefix' => 'role-permission'], function ($api){
// 所有權限列表
$api->get('list','RolePermissionController@list');
// 賦予角色權限
$api->post('givepermisstorole','RolePermissionController@givePermissionToRole');
// 取消角色權限
$api->post('revokepermisstorole','RolePermissionController@revokePermissionToRole');
// 獲取當前角色權限
$api->post('rolehavepermisson','RolePermissionController@roleHavePermisson');
// 添加角色
$api->post('addrole','RolePermissionController@addRole');
});
.
.
.
6.修改控制器文件
<?php
namespace App\Http\Controllers\Api;
use App\Enum\CodeEnum;
use App\Traits\ApiTraits;
use Dingo\Api\Http\Request;
use Spatie\Permission\Models\Role;
use App\Http\Controllers\Controller;
use Spatie\Permission\Models\Permission;
class RolePermissionController extends Controller
{
use ApiTraits;
/**
* function 添加角色
* describe 添加角色
* @param Request $request
* @param Role $role
* @return array
* @author ZhaoDaYuan
* 2020/2/16 下午9:40
*/
public function addRole(Request $request,Role $role)
{
$role_name = $request->all()['name'];
$result = $role::create(['name' => $role_name,'guard_name' => "web"]);
return $this->apiReturn($result,CodeEnum::INC_SUCCESS);
}
/**
* function 所有權限列表
* describe 所有權限列表
* @param Permission $permission
* @return array
* @author ZhaoDaYuan
* 2020/2/16 下午9:41
*/
public function list(Permission $permission)
{
return $this->apiReturn($permission::all(),CodeEnum::SUCCESS);
}
/**
* function 賦予角色權限
* describe 賦予角色權限
* @param Request $request
* @param Role $role
* @return array
* @author ZhaoDaYuan
* 2020/2/16 下午9:41
*/
public function givePermissionToRole(Request $request,Role $role)
{
$role_id = $request->all()['id'];
$permission = $request->all()['permission'];
$role = $role::findById($role_id);
$result = $role->givePermissionTo($permission);
return $this->apiReturn($result,CodeEnum::SUCCESS);
}
/**
* function 取消角色權限
* describe 取消角色權限
* @param Request $request
* @param Role $role
* @return array
* @author ZhaoDaYuan
* 2020/2/16 下午9:41
*/
public function revokePermissionToRole(Request $request,Role $role)
{
$role_id = $request->all()['id'];
$permission = $request->all()['permission'];
$role = $role::findById($role_id);
$result = $role->revokePermissionTo($permission);
return $this->apiReturn($result,CodeEnum::SUCCESS);
}
/**
* function 獲取當前角色權限
* describe 獲取當前角色權限
* @param Request $request
* @param Role $role
* @return array
* @author ZhaoDaYuan
* 2020/2/16 下午9:42
*/
public function roleHavePermisson(Request $request,Role $role)
{
$role_id = $request->all()['id'];
$role = $role::findById($role_id);
$permisson = $role->getAllPermissions();;
return $this->apiReturn($permisson,CodeEnum::SUCCESS);
}
}
7.使用權限對路由進行控制
自定義中間件
app/Http/Middleware/Permission.php
<?php
namespace App\Http\Middleware;
use Closure;
use App\Enum\CodeEnum;
use App\Traits\ApiTraits;
use Illuminate\Contracts\Auth\Access\Gate;
class Permission
{
use ApiTraits;
/**
* Handle an incoming request.
*
* @param \Illuminate\Http\Request $request
* @param \Closure $next
* @return mixed
*/
public function handle($request, Closure $next)
{
// 獲取當前路徑中的模塊部分
$path = $request->segments()[1];
// 獲取當前用戶信息
$user = \Auth::guard('api')->user();
// 對用戶權限檢測
$permisson = app(Gate::class)->forUser($user)->check($path);
// 用戶檢測通過可以繼續路由
if ($permisson) {
return $next($request);
}else{
// 用戶檢測不通過則返回`權限不足`
return $this->apiReturn('',CodeEnum::NOT_PERMISSION);
}
}
}
註冊中間件
app/Http/Kernel.php
protected $routeMiddleware = [
.
.
.
'permission' =>\App\Http\Middleware\Permission::class,
];
修改路由文件
routes/api.php
.
.
.
$api->group(['middleware' => 'permission'],function ($api) {
.
.
.
8.Postman返回結果
獲取角色權限
賦予角色權限
移除角色權限
獲取當前用戶權限
\Auth::guard('api')->user()->getAllPermissions();
到此爲止,已經完成了權限管理模塊的所有代碼!!!
美中不足
由於現在一個項目都是由多個開發人員進行協作開發,包括前端和後端,所以
-
前端如何獲得當前用戶可查看的模塊
修改用戶獲取個人信息模塊
app/Http/Controllers/Api/UserController.php
public function me() { // 獲取當前登陸用戶信息 $user = \Auth::guard('api')->user(); // 獲取當前用戶所有權限 $models_permission = $user->getAllPermissions(); foreach ($models_permission as $key=>$value) { $models_permission[$key]['cn_name'] = config("module.".$value['name']); } $user->mate = [ 'access_token' => \Auth::guard('api')->fromUser(\Auth::guard('api')->user()), 'token_type' => 'Bearer', 'expires_in' => \Auth::guard('api')->factory()->getTTL() * 60 ]; $user->permission = $models_permission; unset($user['permissions'],$user['roles']); return $this->apiReturn($user,CodeEnum::SUCCESS); }
這樣每當用戶登陸後,將返回可以訪問的模塊,前端根據返回做相應的顯示判斷
-
後端人員增加模塊,怎麼自動將模塊信息註冊到數據庫權限表
能夠自動添加信息的方式有很多種,在這裏最想想到使用的是定時任務
php artisan make:command AddModule
修改命令行文件
app/Console/Commands/AddModule.php
<?php namespace App\Console\Commands; use Illuminate\Console\Command; use Spatie\Permission\Contracts\Permission; use Log; class AddModule extends Command { protected $signature = 'p_o:add-module'; protected $description = '將所有模塊註冊到數據庫'; public function __construct() { parent::__construct(); } public function handle() { $module = config('module'); $module_names = array_keys($module); foreach ($module_names as $module_name) { /** * @var $permission Permission */ $permission = app()->make(Permission::class); $permission::findOrCreate($module_name,'web'); Log::info('向數據庫添加模塊:'.$module_name); } } }
這個時候運行
php artisan p_o:add-module
居然顯示Command "p_o:add-module" is not defined.
O(∩_∩)O哈哈~ 這是自然,因爲自定義的命令需要進行註冊
app/Console/Commands/Kernel.php
. . . protected $commands = [ \App\Console\Commands\AddModule::class, ]; . . .
此時已經可以使用命令了,不妨試一下,可以在日誌文件中看見入下記錄:
[2020-02-16 14:00:01] local.INFO: 向數據庫添加模塊:xxx [2020-02-16 14:00:01] local.INFO: 向數據庫添加模塊:xxx [2020-02-16 14:00:01] local.INFO: 向數據庫添加模塊:xxx
最後一步,開啓定時任務,這裏設置的是每隔30分鐘調用一次
app/Console/Commands/Kernel.php
protected function schedule(Schedule $schedule) { $schedule->command('p_o:add-module')->everyThirtyMinutes(); }
配置Linux定時任務
crontab -e #添加定時任務,/path/to/artisan爲項目根目錄 * * * * * php /path/to/artisan schedule:run #保存退出
參考
定時任務 https://learnku.com/laravel/t/1402/laravel-timing-task
L03 Laravel 教程 - 實戰構架 API 服務器 ( Laravel 5.5 ) https://learnku.com/courses/laravel-advance-training/6.x
L02 Laravel 教程 - Web 開發實戰進階 ( Laravel 5.5 ) https://learnku.com/laravel/t/15264/the-summing-up-of-the-two-courses