基於Laravel 從 0 搭建用戶認證和權限管理

從 0 搭建用戶認證和權限管理

用戶認證

用戶註冊

1.新增路由

本項目使用DingoAPI作爲路由,未安裝請參考並進行安裝配置:

https://learnku.com/docs/dingo-api/2.0.0

添加用戶註冊路由

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作爲用戶登陸憑證,如果沒有了解過,請參考:

https://www.cnblogs.com/skaarl/p/9508628.html

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章