美文网首页PHP实战PHP经验分享Laravel开发实践
Lumen5.7使用Passport【2019.02.13最新教

Lumen5.7使用Passport【2019.02.13最新教

作者: 我爱余倩 | 来源:发表于2019-02-13 17:47 被阅读7次

    一、前言

    1. 如果只是单纯需要使用 JWT,可以参考在下之前:Lumen5.7使用JWT【2019.02.11最新教程】,更新啦!或者Lumen5.6使用JWT【最新教程】,亲身失败百次的总结 的教程。
    2. 由于学院 文档 的简洁性,导致新手初学不易理解。所以本文将在学院 文档 的基础上增加少许细节,仅供新手参考。
    3. 相关指导文献: 理解OAuth 2.0RESTful API 设计指南

    二、说明

    1. 不知不觉 Lumen 已经更新到 '5.7.x' 版本,因此本文也紧跟脚步,使用最新版的 Lumen 进行讲解,最重要的是 Laravel/Lumen 5.7.x 版本只支持 'PHP7.1.3' 及以上。
    2. 本文使用 'dusterio/lumen-passport: ^0.2.9' 版本 。
    3. 操作环境:'Windows 10' + 'PHP7.2' + 'MariaDB10.3'。上述环境在下均已测试多次,现分享出本人至今 'Windows' 下正常开发使用的 整合压缩包

    三、开始

    1. 使用 'Composer' 安装最新的 Lumen 到本地。
      composer create-project laravel/lumen passport-test --prefer-dist

    2. 进入项目 'passport-test' 中,安装 'dusterio/lumen-passport: ^0.2.9' 到本地。
      composer require dusterio/lumen-passport

    3. 首先模拟 Laravel 目录结构,复制'vender/laravel/lumen-framework'下的 'config 目录到 'passport-test' 根路径。复制完成以后 'passport-test' 的根目录结构如下:

    /app
    ......others.......
    /config                                    <<<<<< 配置文件目录
    /vendor
    ......others.......
    
    1. 以后的配置文件,都只需要在根路径下的 'config'目录操作即可,修改根路径下的 '.env 文件:
    ......others.......
    APP_KEY=9TBF8FrZZgYBoM0AzKjkii/yb6TJVm11        #### Lumen默认没有设置APP_KEY
    CACHE_DRIVER=file                               #### 修改为文件缓存
    ......others (包括MySQL的配置项) .......
    

    本文使用以下指令快速启动服务。
    php -S localhost:8000 public/index.php

    1. 下面开始实现 JWT 功能。由于本人习惯在 'app' 路径下新建一个 'Api' 目录存放接口控制器类、 'Models' 目录存放模型,因此之后的项目目录结构是:
    ......others.......
    /app
    ..........others.......
    ..../Api                                   <<<<<< 接口控制器文件目录
    ..../Models                                <<<<<< 模型文件目录
    /config                                    <<<<<< 配置文件目录
    /vendor
    ......others.......
    

    5.1 修改 'bootstrap' 文件夹下的 'app.php' 如下所示,需要注意的是,我修改了最后一段代码,也即修改了 Lumen 默认的控制器命名空间和默认的路由文件:

    # Dir: @/bootstrap/app.php
    <?php
    
    require_once __DIR__.'/../vendor/autoload.php';
    
    try {
        (new Dotenv\Dotenv(__DIR__.'/../'))->load();
    } catch (Dotenv\Exception\InvalidPathException $e) {
        //
    }
    
    $app = new Laravel\Lumen\Application(
        realpath(__DIR__.'/../')
    );
    
    // 取消注释
    $app->withFacades();
    $app->withEloquent();
    
    $app->singleton(
        Illuminate\Contracts\Debug\ExceptionHandler::class,
        App\Exceptions\Handler::class
    );
    
    $app->singleton(
        Illuminate\Contracts\Console\Kernel::class,
        App\Console\Kernel::class
    );
    
    # 注意!我已经修改了默认的认证中间件的路径!!
    $app->routeMiddleware([
        'auth' => App\Api\Middlewares\Authenticate::class,
    ]);
    
    // 取消注释
    $app->register(App\Providers\AppServiceProvider::class);
    $app->register(App\Providers\AuthServiceProvider::class);
    
    // 新增Passport的注册
    $app->register(Laravel\Passport\PassportServiceProvider::class);
    $app->register(Dusterio\LumenPassport\PassportServiceProvider::class);
    
    # 注意!我已经修改了默认的命名空间!!
    $app->router->group([
        'namespace' => 'App\Api\Controllers',
    ], function ($router) {
        require __DIR__ . '/../routes/api.php'; # 注意!我已经修改了默认的路由文件!!
    });
    
    return $app;
    
    

    5.2. 修改 'config' 文件夹下的 'auth.php' 如下所示:

    # Dir: @/config/auth.php
    <?php
    
    return [
        'defaults' => [
            'guard' => env('AUTH_GUARD', 'api'),
        ],
    
        'guards' => [
            'api' => ['driver' => 'passport', 'provider' => 'passport-provider'],
        ],
    
        'providers' => [
            'passport-provider' => [
                'driver' => 'eloquent',
                'model' => \App\Models\UserModel::class
            ]
        ],
    
        'passwords' => [
            //
        ],
    
    ];
    

    5.3. 修改 'app/Providers' 文件夹下的 'AuthServiceProvider.php' 如下所示:

    # Dir: @/app/Providers/AuthServiceProvider.php
    <?php
    
    use Dusterio\LumenPassport\LumenPassport;
    use Illuminate\Support\ServiceProvider;
    
    class AuthServiceProvider extends ServiceProvider
    {
        /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            //
        }
    
        /**
         * Boot the authentication services for the application.
         *
         * @return void
         */
        public function boot()
        {
            LumenPassport::routes($this->app); # 注册Passport相关路由
            LumenPassport::allowMultipleTokens(); # 允许生成多个有效Token
        }
    }
    
    

    5.4. 修改 'app/Models' 文件夹下的 'UserModel.php' 如下所示:

    # Dir: @/app/Models/UserModel.php
    <?php
    
    namespace App\Models;
    
    use Illuminate\Auth\Authenticatable;
    use Laravel\Lumen\Auth\Authorizable;
    use Illuminate\Database\Eloquent\Model;
    use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
    use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
    use Laravel\Passport\HasApiTokens;
    
    /**
     * @author AdamTyn
     * @description <用户>数据模型
     */
    class UserModel extends Model implements AuthenticatableContract, AuthorizableContract
    {
        use Authenticatable, Authorizable, HasApiTokens;
    
        /**
         * 绑定数据表
         * @var string
         */
        protected $table = 'users';
    
        /**
         * 使用模型时可以访问的字段
         * @var array
         */
        protected $fillable = [
            'user_name', 'password',
        ];
    
        /**
         * 使用模型无法序列化为JSON时的字段
         * @var array
         */
        protected $hidden = [
            'password',
        ];
    
        /**
         * 使用Passport用户凭证字段,数据库必须保证该字段的唯一性(默认是email)
         * @var string
         */
        static private $credentials = 'user_name';
    
        /**
         * @author AdamTyn
         * @description 使用用户凭证字段查询用户
         *
         * @param $username
         * @return $this
         */
        public function findForPassport($username)
        {
            if (!isset(self::$credentials)) {
                return $this->whereEmail($username)->first();
            }
            return $this->where(self::$credentials, $username)->first();
        }
    }
    
    

    5.5. 在 'app/Api/Controllers' 文件夹下新建 'ClientController.php',内容如下所示:

    # Dir: @/app/Api/Controllers/ClientController.php
    <?php
    
    namespace App\Api\Controllers;
    
    use Illuminate\Http\Request;
    use Laravel\Passport\ClientRepository;
    use Illuminate\Support\Facades\Hash;
    use Illuminate\Support\Facades\Log;
    use Laravel\Lumen\Routing\Controller;
    
    /**
     * @author AdamTyn
     * @description <用户客户端相关>控制器
     */
    class ClientController extends Controller
    {
        /**
         * 用户客户端仓库的单例
         * @var \Laravel\Passport\ClientRepository
         */
        private static $clientRepository = null;
    
        /**
         * 用户模型
         * @var \App\Models\UserModel
         */
        private static $userModel = null;
    
        /**
         * 当前时间戳
         * @var int|string
         */
        protected $currentDateTime;
    
        /**
         * 当前时间戳
         * @var int|string
         */
        protected $grantType = 'password';
    
        /**
         * @author AdamTyn
         *
         * AuthController constructor.
         */
        public function __construct()
        {
            empty(self::$userModel) ? (self::$userModel = (new \App\Models\UserModel)) : true;
            $this->currentDateTime = time();
        }
    
        /**
         * @author AdamTyn
         * @description 用户注册(默认密码123456)
         *
         * @param \Illuminate\Http\Request;
         * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
         */
        public function register(Request $request)
        {
            $field = [
                'user_name' => $request->get('user_name') ?? time(),
                'password' => Hash::make($request->get('password') ?? '123456')
            ];
    
            $user = (self::$userModel)->create($field);
            $response['data'] = $user;
    
            return response()->json($response);
        }
    
        /**
         * @author AdamTyn
         * @description 添加使用密码认证的客户端
         *
         * @param \Illuminate\Http\Request;
         * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
         */
        public function registerPasswordClient(Request $request)
        {
            $response = array('status_code' => '2000');
    
            try {
                $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
                $this->createClient($response, $request, $user);
            } catch (\Exception $exception) {
                $response = [
                    'status_code' => '5002',
                    'msg' => '无法响应请求,服务端异常',
                ];
                Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
            }
    
            return response()->json($response);
        }
    
        /**
         * @author AdamTyn
         * @description 登录私人访问的客户端
         *
         * @param \Illuminate\Http\Request;
         * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
         */
        public function loginPersonalClient(Request $request)
        {
            $response = array('status_code' => '2000');
            $this->changeGrantType();
    
            try {
                $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
                if (Hash::check($request->input('password'), $user->getAuthPassword())) {
                    $response['data'] = $user->createToken(data_get($request, 'token_name', $user->user_name . '`s token_name'));
                } else {
                    $response = [
                        'status_code' => '5000',
                        'msg' => '系统错误',
                    ];
                }
            } catch (\Exception $exception) {
                $response = [
                    'status_code' => '5002',
                    'msg' => '无法响应请求,服务端异常',
                ];
                Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
            }
    
            return response()->json($response);
        }
    
        /**
         * @author AdamTyn
         * @description 添加私人访问的客户端
         *
         * @param \Illuminate\Http\Request;
         * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
         */
        public function registerPersonalClient(Request $request)
        {
            $response = array('status_code' => '2000');
            $this->changeGrantType();
    
            try {
                $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
                $this->createClient($response, $request, $user);
            } catch (\Exception $exception) {
                $response = [
                    'status_code' => '5002',
                    'msg' => '无法响应请求,服务端异常',
                ];
                Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
            }
    
            return response()->json($response);
        }
    
        /**
         * @author AdamTyn
         * @description 初始化用户客户端仓库的单例
         *
         * @return void
         */
        private function initialClientRepository()
        {
            empty(self::$clientRepository) ? (self::$clientRepository = (new ClientRepository)) : true;
        }
    
        /**
         * @author AdamTyn
         * @description 改变授权类型为私人访问
         *
         * @return void
         */
        private function changeGrantType()
        {
            $this->grantType = 'personal';
        }
    
        /**
         * @author AdamTyn
         * @description 新增用户客户端记录
         *
         * @param array $response
         * @param \Illuminate\Http\Request $request
         * @param \App\Models\UserModel $user
         * @return void
         */
        private function createClient(&$response, $request, $user)
        {
            $this->initialClientRepository();
    
            if (Hash::check($request->input('password'), $user->getAuthPassword())) {
                $grantType = ($this->grantType == 'password');
    
                $client = self::$clientRepository->create(
                    $user->id,
                    $user->user_name . '`s new ' . $this->grantType . ' client',
                    data_get($request, 'redirect', 'http://localhost:8000'),
                    !$grantType,
                    $grantType
                );
    
                $response['data'] = [
                    'client_id' => $client->id,
                    'client_name' => $client->name,
                    'client_grant_type' => $this->grantType,
                    'client_secret' => $grantType ? $client->secret : null,
                    'client_redirect' => $client->redirect,
                ];
            } else {
                $response = [
                    'status_code' => '5000',
                    'msg' => '系统错误',
                ];
            }
    
            return;
        }
    }
    
    

    5.6. 创建 'users' 数据表,在数据库中简单填充一条数据。需要注意的是 Lumen 默认数据库使用 'utf8mb4' 编码,如果数据库版本较低,需要修改'app/Providers/AppServiceProvider.php' 如下:

    # Dir: @/app/Providers/AppServiceProvider.php
    <?php
    
    namespace App\Providers;
    
    use Illuminate\Support\Facades\Schema;
    use Illuminate\Support\ServiceProvider;
    
    class AppServiceProvider extends ServiceProvider
    {
        /**
         * Register any application services.
         *
         * @return void
         */
        public function register()
        {
            Schema::defaultStringLength(191);
        }
    }
    

    5.7. 使用以下 Artisan 在数据库中新增 'Passport' 的相关数据表,并且填充2条测试数据:

    php artisan migrate
    php artisan passport:install             <<<在oauth_clients表中填充了2条测试数据,并且生成了加密用的Key
    
    1. 运行测试。在'routers/api.php' 中添加相应的路由规则。
    # Dir: @/routers/api.php
    <?php
    
    # 模拟用户注册路由
    $router->post('register', 'ClientController@register');
    # 模拟登录私人访问的客户端
    $router->post('loginPersonalClient', 'ClientController@loginPersonalClient');
    # 模拟添加使用密码认证的客户端
    $router->post('registerPasswordClient', 'ClientController@registerPasswordClient');
    # 模拟添加私人访问的客户端
    $router->post('registerPersonalClient', 'ClientController@registerPersonalClient');
    # Passport相关的路由已经被组件定义,无需自己定义
    

    最后,可以得出以下测试结果。

    HTTP/1.1 200 OK. POST http://127.0.0.1:8000/register
    Request: user_name='testman', password='123456'
    {
        "status_code": "2000",
        "data": {
            "user_name": "testman",
            "updated_at": "2019-02-13 09:31:14",
            "created_at": "2019-02-13 09:31:14",
            "id": 5
        }
    }
    
    HTTP/1.1 200 OK. POST http://127.0.0.1:8000/registerPasswordClient
    Request: user_name='testman', password='123456'
    {
        "status_code": "2000",
        "data": {
            "client_id": 21,
            "client_name": "testman`s new password client",
            "client_grant_type": "password",
            "client_secret": "hojrGJUQMzXAm9e9r7q1K01I1Rx8rWP3LgV5xW4a",
            "client_redirect": "http://localhost:8000"
        }
    }
    
    HTTP/1.1 200 OK. POST http://127.0.0.1:8000/registerPersonalClient
    Request: user_name='testman', password='123456'
    {
        "status_code": "2000",
        "data": {
            "client_id": 22,
            "client_name": "testman`s new personal client",
            "client_grant_type": "personal",
            "client_secret": null,
            "client_redirect": "http://localhost:8000"
        }
    }
    
    HTTP/1.1 200 OK. POST http://127.0.0.1:8000/loginPersonalClient
    Request: user_name='testman',token_name='testman_token', password='123456'
    {
        "status_code": "2000",
        "data": {
            "accessToken": "eyJ0eXiIsImp0aSI6IjRkYmQ4ZGZDM5YmVmOTVlYzk4ZW6c",
            "token": {
                "id": "4dbd8dd35a8c109a40629d9a204915e7a22db0bd6dc",
                "user_id": 2,
                "client_id": 20,
                "name": "testman_token",
                "scopes": [],
                "revoked": false,
                "created_at": "2019-02-13 09:33:50",
                "updated_at": "2019-02-13 09:33:50",
                "expires_at": "2020-02-13 09:33:50"
            }
        }
    }
    
    HTTP/1.1 200 OK. GET http://127.0.0.1:8000/oauth/tokens
    Authorization: Bearer eyJ0eXiIsImp0aSI6IjRkYmQ4ZGZDM5YmVmOTVlYzk4ZW6c
    [
        {
            "id": "5fc4cf22e2b5386660659775fc5919",
            "user_id": 2,
            "client_id": 20,
            "name": "token_name",
            "scopes": [],
            "revoked": false,
            "created_at": "2019-02-13 08:52:31",
            "updated_at": "2019-02-13 08:52:31",
            "expires_at": "2020-02-13 08:52:31",
            "client": {
                "id": 20,
                "user_id": null,
                "name": "test",
                "redirect": "http://localhost",
                "personal_access_client": true,
                "password_client": false,
                "revoked": false,
                "created_at": "2019-02-13 08:48:48",
                "updated_at": "2019-02-13 08:48:48"
            }
        }
    ]
    
    HTTP/1.1 200 OK. GET http://127.0.0.1:8000/oauth/clients
    Authorization: Bearer eyJ0eXiIsImp0aSI6IjRkYmQ4ZGZDM5YmVmOTVlYzk4ZW6c
    [
        {
            "id": 3,
            "user_id": 1,
            "name": " Password Grant Client",
            "secret": "FfA04DRjDA9iMi4RJ9ju9mBtmKDyu4HbHYXRCoKD",
            "redirect": "http://localhost",
            "personal_access_client": false,
            "password_client": true,
            "revoked": false,
            "created_at": "2019-02-13 14:43:58",
            "updated_at": "2019-02-13 14:43:58"
        }
    ]
    
    1. 放出参考的示例 Code。至此,Lumen5.7使用Passport 讲解结束,给出所有 Passport 中预设的路由规则:
    Routes URL

    四、结语

    1. 本教程面向新手,更多教程会在日后给出。
    2. 随着系统升级,软件更新,以后的配置可能有所变化,在下会第一时间测试并且更新教程;
    3. 欢迎联系在下,讨论建议都可以,之后会发布其它的教程。
    4. 如果读者使用的是旧版本 Lumen,可以参考本人之前出了 Lumen5.4使用OAuth2.0基于Passport【最新教程】 以及Lumen5.4配置OAuth2.0【强迫症,就是要用最新版本的Lumen】 教程。
    5. 后面紧锣密鼓地将会推出 Laravel业务篇 系列的教程,敬请期待。

    相关文章

      网友评论

        本文标题:Lumen5.7使用Passport【2019.02.13最新教

        本文链接:https://www.haomeiwen.com/subject/cwsreqtx.html