一、前言
- 如果只是单纯需要使用 JWT,可以参考在下之前:Lumen5.7使用JWT【2019.02.11最新教程】,更新啦!或者Lumen5.6使用JWT【最新教程】,亲身失败百次的总结 的教程。
- 由于学院 文档 的简洁性,导致新手初学不易理解。所以本文将在学院 文档 的基础上增加少许细节,仅供新手参考。
相关指导文献: 理解OAuth 2.0、RESTful API 设计指南 。
二、说明
切不知不觉 Lumen 已经更新到 ‘5.7.x’ 版本,因此本文也紧跟脚步,使用最新版的 Lumen 进行讲解,最重要的是 Laravel/Lumen 5.7.x 版本只支持 ‘PHP7.1.3’ 及以上。
- 本文使用 ‘dusterio/lumen-passport: ^0.2.9’ 版本 。
操作环境:‘Windows 10’ + ‘PHP7.2’ + ‘MariaDB10.3’。上述环境在下均已测试多次,现分享出本人至今 ‘Windows’ 下正常开发使用的 整合压缩包 。
三、开始
使用 ‘Composer’ 安装最新的 Lumen 到本地。
composer create-project laravel/lumen passport-test --prefer-dist- 进入项目 ‘passport-test’ 中,安装 ‘dusterio/lumen-passport: ^0.2.9’ 到本地。
composer require dusterio/lumen-passport 首先模拟 Laravel 目录结构,复制‘vender/laravel/lumen-framework’下的 ‘config 目录到 ‘passport-test’ 根路径。复制完成以后 ‘passport-test’ 的根目录结构如下:
/app......others......./config <<<<<< 配置文件目录/vendor......others.......
以后的配置文件,都只需要在根路径下的 ‘config’目录操作即可,修改根路径下的 ‘.env 文件:
......others.......APP_KEY=9TBF8FrZZgYBoM0AzKjkii/yb6TJVm11 #### Lumen默认没有设置APP_KEYCACHE_DRIVER=file #### 修改为文件缓存......others (包括MySQL的配置项) .......
本文使用以下指令快速启动服务。
php -S localhost:8000 public/index.php下面开始实现 JWT 功能。由于本人习惯在 ‘app’ 路径下新建一个 ‘Api’ 目录存放接口控制器类、 ‘Models’ 目录存放模型,因此之后的项目目录结构是:
......others......./app..........others.........../Api <<<<<< 接口控制器文件目录..../Models <<<<<< 模型文件目录/config <<<<<< 配置文件目录/vendor......others.......
5.1 修改 ‘bootstrap’ 文件夹下的 ‘app.php’ 如下所示,需要注意的是,我修改了最后一段代码,也即修改了 Lumen 默认的控制器命名空间和默认的路由文件:
```phpDir: @/bootstrap/app.php
<?php
requireonce _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'** 如下所示:<br />```php# Dir: @/config/auth.php<?phpreturn ['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<?phpuse 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<?phpnamespace 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<?phpnamespace 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*/protected $currentDateTime;/*** 授权方式:password=密码授权的令牌,personal=私人授权的令牌* @var 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<?phpnamespace 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 migratephp artisan passport:install <<<在oauth_clients表中填充了2条测试数据,并且生成了加密用的Key
模拟用户注册路由
$router->post(‘register’, ‘ClientController@register’);
模拟登录私人访问的客户端
$router->post(‘loginPersonalClient’, ‘ClientController@loginPersonalClient’);
模拟添加使用密码认证的客户端
$router->post(‘registerPasswordClient’, ‘ClientController@registerPasswordClient’);
模拟添加私人访问的客户端
$router->post(‘registerPersonalClient’, ‘ClientController@registerPersonalClient’);
Passport相关的路由已经被组件定义,无需自己定义
最后,可以得出以下测试结果。<br />```phpHTTP/1.1 200 OK. POST http://127.0.0.1:8000/registerRequest: 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/registerPasswordClientRequest: 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/registerPersonalClientRequest: 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/loginPersonalClientRequest: 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/tokensAuthorization: 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/clientsAuthorization: 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"}]
- 放出参考的示例 Code。至此,Lumen5.7使用Passport 讲解结束,给出所有 Passport 中预设的路由规则:
四、结语
- 本教程面向新手,更多教程会在日后给出。
- 随着系统升级,软件更新,以后的配置可能有所变化,在下会第一时间测试并且更新教程。
- 欢迎联系在下,讨论建议都可以,之后会发布其它的教程。
- 如果读者使用的是旧版本 Lumen,可以参考本人之前出了 Lumen5.4使用OAuth2.0基于Passport【最新教程】 以及Lumen5.4配置OAuth2.0【强迫症,就是要用最新版本的Lumen】 教程。
- 后面紧锣密鼓地将会推出 Laravel业务篇 系列的教程,敬请期待。
