一、前言

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

    二、说明

  4. 切不知不觉 Lumen 已经更新到 ‘5.7.x’ 版本,因此本文也紧跟脚步,使用最新版的 Lumen 进行讲解,最重要的是 Laravel/Lumen 5.7.x 版本只支持 ‘PHP7.1.3’ 及以上。

  5. 本文使用 ‘dusterio/lumen-passport: ^0.2.9’ 版本 。
  6. 操作环境:‘Windows 10’ + ‘PHP7.2’ + ‘MariaDB10.3’。上述环境在下均已测试多次,现分享出本人至今 ‘Windows’ 下正常开发使用的 整合压缩包

    三、开始

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

  8. 进入项目 ‘passport-test’ 中,安装 ‘dusterio/lumen-passport: ^0.2.9’ 到本地。
    composer require dusterio/lumen-passport
  9. 首先模拟 Laravel 目录结构,复制‘vender/laravel/lumen-framework’下的 ‘config 目录到 ‘passport-test’ 根路径。复制完成以后 ‘passport-test’ 的根目录结构如下:

    1. /app
    2. ......others.......
    3. /config <<<<<< 配置文件目录
    4. /vendor
    5. ......others.......
  10. 以后的配置文件,都只需要在根路径下的 ‘config’目录操作即可,修改根路径下的 ‘.env 文件:

    1. ......others.......
    2. APP_KEY=9TBF8FrZZgYBoM0AzKjkii/yb6TJVm11 #### Lumen默认没有设置APP_KEY
    3. CACHE_DRIVER=file #### 修改为文件缓存
    4. ......others (包括MySQL的配置项) .......

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

  11. 下面开始实现 JWT 功能。由于本人习惯在 ‘app’ 路径下新建一个 ‘Api’ 目录存放接口控制器类、 ‘Models’ 目录存放模型,因此之后的项目目录结构是:

    1. ......others.......
    2. /app
    3. ..........others.......
    4. ..../Api <<<<<< 接口控制器文件目录
    5. ..../Models <<<<<< 模型文件目录
    6. /config <<<<<< 配置文件目录
    7. /vendor
    8. ......others.......

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

    Dir: @/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;

  1. 5.2. 修改 **'config'** 文件夹下的 **'auth.php'** 如下所示:<br />
  2. ```php
  3. # Dir: @/config/auth.php
  4. <?php
  5. return [
  6. 'defaults' => [
  7. 'guard' => env('AUTH_GUARD', 'api'),
  8. ],
  9. 'guards' => [
  10. 'api' => ['driver' => 'passport', 'provider' => 'passport-provider'],
  11. ],
  12. 'providers' => [
  13. 'passport-provider' => [
  14. 'driver' => 'eloquent',
  15. 'model' => \App\Models\UserModel::class
  16. ]
  17. ],
  18. 'passwords' => [
  19. //
  20. ],
  21. ];

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

  1. # Dir: @/app/Providers/AuthServiceProvider.php
  2. <?php
  3. use Dusterio\LumenPassport\LumenPassport;
  4. use Illuminate\Support\ServiceProvider;
  5. class AuthServiceProvider extends ServiceProvider
  6. {
  7. /**
  8. * Register any application services.
  9. *
  10. * @return void
  11. */
  12. public function register()
  13. {
  14. //
  15. }
  16. /**
  17. * Boot the authentication services for the application.
  18. *
  19. * @return void
  20. */
  21. public function boot()
  22. {
  23. LumenPassport::routes($this->app); # 注册Passport相关路由
  24. LumenPassport::allowMultipleTokens(); # 允许生成多个有效Token
  25. }
  26. }

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

  1. # Dir: @/app/Models/UserModel.php
  2. <?php
  3. namespace App\Models;
  4. use Illuminate\Auth\Authenticatable;
  5. use Laravel\Lumen\Auth\Authorizable;
  6. use Illuminate\Database\Eloquent\Model;
  7. use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
  8. use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
  9. use Laravel\Passport\HasApiTokens;
  10. /**
  11. * @author AdamTyn
  12. * @description <用户>数据模型
  13. */
  14. class UserModel extends Model implements AuthenticatableContract, AuthorizableContract
  15. {
  16. use Authenticatable, Authorizable, HasApiTokens;
  17. /**
  18. * 绑定数据表
  19. * @var string
  20. */
  21. protected $table = 'users';
  22. /**
  23. * 使用模型时可以访问的字段
  24. * @var array
  25. */
  26. protected $fillable = [
  27. 'user_name', 'password',
  28. ];
  29. /**
  30. * 使用模型无法序列化为JSON时的字段
  31. * @var array
  32. */
  33. protected $hidden = [
  34. 'password',
  35. ];
  36. /**
  37. * 使用Passport用户凭证字段,数据库必须保证该字段的唯一性(默认是email)
  38. * @var string
  39. */
  40. static private $credentials = 'user_name';
  41. /**
  42. * @author AdamTyn
  43. * @description 使用用户凭证字段查询用户
  44. *
  45. * @param $username
  46. * @return $this
  47. */
  48. public function findForPassport($username)
  49. {
  50. if (!isset(self::$credentials)) {
  51. return $this->whereEmail($username)->first();
  52. }
  53. return $this->where(self::$credentials, $username)->first();
  54. }
  55. }

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

  1. # Dir: @/app/Api/Controllers/ClientController.php
  2. <?php
  3. namespace App\Api\Controllers;
  4. use Illuminate\Http\Request;
  5. use Laravel\Passport\ClientRepository;
  6. use Illuminate\Support\Facades\Hash;
  7. use Illuminate\Support\Facades\Log;
  8. use Laravel\Lumen\Routing\Controller;
  9. /**
  10. * @author AdamTyn
  11. * @description <用户客户端相关>控制器
  12. */
  13. class ClientController extends Controller
  14. {
  15. /**
  16. * 用户客户端仓库的单例
  17. * @var \Laravel\Passport\ClientRepository
  18. */
  19. private static $clientRepository = null;
  20. /**
  21. * 用户模型
  22. * @var \App\Models\UserModel
  23. */
  24. private static $userModel = null;
  25. /**
  26. * 当前时间戳
  27. * @var int
  28. */
  29. protected $currentDateTime;
  30. /**
  31. * 授权方式:password=密码授权的令牌,personal=私人授权的令牌
  32. * @var string
  33. */
  34. protected $grantType = 'password';
  35. /**
  36. * @author AdamTyn
  37. *
  38. * AuthController constructor.
  39. */
  40. public function __construct()
  41. {
  42. empty(self::$userModel) ? (self::$userModel = (new \App\Models\UserModel)) : true;
  43. $this->currentDateTime = time();
  44. }
  45. /**
  46. * @author AdamTyn
  47. * @description 用户注册(默认密码123456)
  48. *
  49. * @param \Illuminate\Http\Request;
  50. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  51. */
  52. public function register(Request $request)
  53. {
  54. $field = [
  55. 'user_name' => $request->get('user_name') ?? time(),
  56. 'password' => Hash::make($request->get('password') ?? '123456')
  57. ];
  58. $user = (self::$userModel)->create($field);
  59. $response['data'] = $user;
  60. return response()->json($response);
  61. }
  62. /**
  63. * @author AdamTyn
  64. * @description 添加使用密码认证的客户端
  65. *
  66. * @param \Illuminate\Http\Request;
  67. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  68. */
  69. public function registerPasswordClient(Request $request)
  70. {
  71. $response = array('status_code' => '2000');
  72. try {
  73. $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
  74. $this->createClient($response, $request, $user);
  75. } catch (\Exception $exception) {
  76. $response = [
  77. 'status_code' => '5002',
  78. 'msg' => '无法响应请求,服务端异常',
  79. ];
  80. Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
  81. }
  82. return response()->json($response);
  83. }
  84. /**
  85. * @author AdamTyn
  86. * @description 登录私人访问的客户端
  87. *
  88. * @param \Illuminate\Http\Request;
  89. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  90. */
  91. public function loginPersonalClient(Request $request)
  92. {
  93. $response = array('status_code' => '2000');
  94. $this->changeGrantType();
  95. try {
  96. $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
  97. if (Hash::check($request->input('password'), $user->getAuthPassword())) {
  98. $response['data'] = $user->createToken(data_get($request, 'token_name', $user->user_name . '`s token_name'));
  99. } else {
  100. $response = [
  101. 'status_code' => '5000',
  102. 'msg' => '系统错误',
  103. ];
  104. }
  105. } catch (\Exception $exception) {
  106. $response = [
  107. 'status_code' => '5002',
  108. 'msg' => '无法响应请求,服务端异常',
  109. ];
  110. Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
  111. }
  112. return response()->json($response);
  113. }
  114. /**
  115. * @author AdamTyn
  116. * @description 添加私人访问的客户端
  117. *
  118. * @param \Illuminate\Http\Request;
  119. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  120. */
  121. public function registerPersonalClient(Request $request)
  122. {
  123. $response = array('status_code' => '2000');
  124. $this->changeGrantType();
  125. try {
  126. $user = (self::$userModel)->whereUserName($request->input('user_name'))->firstOrFail();
  127. $this->createClient($response, $request, $user);
  128. } catch (\Exception $exception) {
  129. $response = [
  130. 'status_code' => '5002',
  131. 'msg' => '无法响应请求,服务端异常',
  132. ];
  133. Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
  134. }
  135. return response()->json($response);
  136. }
  137. /**
  138. * @author AdamTyn
  139. * @description 初始化用户客户端仓库的单例
  140. *
  141. * @return void
  142. */
  143. private function initialClientRepository()
  144. {
  145. empty(self::$clientRepository) ? (self::$clientRepository = (new ClientRepository)) : true;
  146. }
  147. /**
  148. * @author AdamTyn
  149. * @description 改变授权类型为私人访问
  150. *
  151. * @return void
  152. */
  153. private function changeGrantType()
  154. {
  155. $this->grantType = 'personal';
  156. }
  157. /**
  158. * @author AdamTyn
  159. * @description 新增用户客户端记录
  160. *
  161. * @param array $response
  162. * @param \Illuminate\Http\Request $request
  163. * @param \App\Models\UserModel $user
  164. * @return void
  165. */
  166. private function createClient(&$response, $request, $user)
  167. {
  168. $this->initialClientRepository();
  169. if (Hash::check($request->input('password'), $user->getAuthPassword())) {
  170. $grantType = ($this->grantType == 'password');
  171. $client = self::$clientRepository->create(
  172. $user->id,
  173. $user->user_name . '`s new ' . $this->grantType . ' client',
  174. data_get($request, 'redirect', 'http://localhost:8000'),
  175. !$grantType,
  176. $grantType
  177. );
  178. $response['data'] = [
  179. 'client_id' => $client->id,
  180. 'client_name' => $client->name,
  181. 'client_grant_type' => $this->grantType,
  182. 'client_secret' => $grantType ? $client->secret : null,
  183. 'client_redirect' => $client->redirect,
  184. ];
  185. } else {
  186. $response = [
  187. 'status_code' => '5000',
  188. 'msg' => '系统错误',
  189. ];
  190. }
  191. return;
  192. }
  193. }

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

  1. # Dir: @/app/Providers/AppServiceProvider.php
  2. <?php
  3. namespace App\Providers;
  4. use Illuminate\Support\Facades\Schema;
  5. use Illuminate\Support\ServiceProvider;
  6. class AppServiceProvider extends ServiceProvider
  7. {
  8. /**
  9. * Register any application services.
  10. *
  11. * @return void
  12. */
  13. public function register()
  14. {
  15. Schema::defaultStringLength(191);
  16. }
  17. }

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

  1. php artisan migrate
  2. php artisan passport:install <<<在oauth_clients表中填充了2条测试数据,并且生成了加密用的Key
  1. 运行测试。在‘routers/api.php’ 中添加相应的路由规则。 ```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相关的路由已经被组件定义,无需自己定义

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

Lumen5.7使用Passport【2019.02.13最新教程】,更新啦 - 图1

四、结语

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