基于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

发布了13 篇原创文章 · 获赞 9 · 访问量 3518

猜你喜欢

转载自blog.csdn.net/zdy_lyq/article/details/104366621