一、前言

  1. 如果需要使用 Passport,可以参考在下之前的教程: ‘Lumen5.7使用Passport【2019.02.13最新教程】,更新啦’
  2. 本人之前出了 Lumen5.6使用JWT【最新教程】,亲身失败百次的总结 教程,收到不少读者的反馈,因而在前文的基础之上,赶忙跟进了最新 Lumen5.7 版本的JWT。

    二、说明

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

  4. 本文使用 ‘tymon/jwt-auth: ^1.0.0-rc.3’ 版本(Lumen5.4 之后,不再使用该扩展包的 0.5 版本)的扩展包,搭配 Laravel 开箱即用的 ‘Auth’ 组件实现 JWT 认证。
  5. 操作环境:‘Windows 7’ + ‘PHP7.2’ + ‘MariaDB10.3’。上述环境在下均已测试多次,现分享出本人至今 ‘Windows’ 下正常开发使用的 整合压缩包

    三、准备部分

  6. 检查 ‘Windows’ 上的开发环境是否正常。
    1.1. 查看 ‘PHP7.2’ 环境:
    Lumen5.8使用JWT【2019.06.20最新教程】 - 图1
    1.2. 查看 ‘MariaDB10.3’ 环境:
    Lumen5.8使用JWT【2019.06.20最新教程】 - 图2

  7. 安装 ‘PostMan’ 以及 ‘Navicat Premium’ ,其他类似软件产品亦可,根据个人喜好就行。
  8. 操作之前查看 ‘JWT的介绍’ ,对理解后文大有裨益。

    四、实现部分

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

  10. 进入项目 ‘jwt-test’ 中,安装 ‘tymon/jwt-auth: ^1.0.0-rc.3’ 到本地。
    composer require tymon/jwt-auth ^1.0.0-rc.3
  11. 首先模拟 Laravel 目录结构,复制‘vender/laravel/lumen-framework’下的 ‘config 目录到 ‘jwt-test’ 根路径。复制完成以后 ‘jwt-test’ 的根目录结构如下:

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

    1. ......others.......
    2. APP_KEY=9TBF8FrZZgYBoM0AzKjkii/yb6TJVm11 #### Lumen默认没有设置APP_KEY
    3. CACHE_DRIVER=file #### 修改为文件缓存
    4. ......others (包括MySQL的配置项) .......
    5. JWT_SECRET=Bi43uQQTHxLSnUaIOgTEUT1SkGHiOc1o #### JWT编码时需要的Key
    6. JWT_TTL=1440 #### Token过期时间,这里的单位是minute

    其中,上面的JWTSECRET字段值,可以通过以下 _Artisan 指令快捷设置:
    php artisan jwt:secret
    本文使用以下指令快速启动服务。
    php -S localhost:8000 public/index.php

  13. 下面开始实现 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);

// 新增JWT的注册 $app->register(Tymon\JWTAuth\Providers\LumenServiceProvider::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' => 'jwt', 'provider' => 'jwt-provider'],
  11. ],
  12. 'providers' => [
  13. 'jwt-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. namespace App\Providers;
  4. use App\Models\User;
  5. use Illuminate\Support\Facades\Gate;
  6. use Illuminate\Support\ServiceProvider;
  7. class AuthServiceProvider extends ServiceProvider
  8. {
  9. /**
  10. * Register any application services.
  11. *
  12. * @return void
  13. */
  14. public function register()
  15. {
  16. //
  17. }
  18. /**
  19. * Boot the authentication services for the application.
  20. *
  21. * @return void
  22. */
  23. public function boot()
  24. {
  25. // 当使用auth中间件的api门卫的时候验证请求体
  26. $this->app['auth']->viaRequest('api', function ($request)
  27. {
  28. return app('auth')->setRequest($request)->user();
  29. });
  30. }
  31. }

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 Tymon\JWTAuth\Contracts\JWTSubject;
  10. /**
  11. * @author AdamTyn
  12. * @description <用户>数据模型
  13. */
  14. class UserModel extends Model implements AuthenticatableContract, AuthorizableContract, JWTSubject
  15. {
  16. use Authenticatable, Authorizable;
  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. * @author AdamTyn
  38. * @description 获取JWT中用户标识
  39. *
  40. * @return mixed
  41. */
  42. public function getJWTIdentifier()
  43. {
  44. return $this->getKey();
  45. }
  46. /**
  47. * @author AdamTyn
  48. * @description 获取JWT中用户自定义字段
  49. *
  50. * @return array
  51. */
  52. public function getJWTCustomClaims()
  53. {
  54. return [];
  55. }
  56. }

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

  1. # Dir: @/app/Api/Controllers/AuthController.php
  2. <?php
  3. namespace App\Api\Controllers;
  4. use Illuminate\Support\Facades\Log;
  5. use Laravel\Lumen\Routing\Controller;
  6. use Illuminate\Http\Request;
  7. /**
  8. * @author AdamTyn
  9. * @description <用户认证>控制器
  10. */
  11. class AuthController extends Controller
  12. {
  13. /**
  14. * 用户模型
  15. * @var \App\Models\UserModel
  16. */
  17. private static $userModel = null;
  18. /**
  19. * 认证器
  20. * @var mixed
  21. */
  22. private $auth = null;
  23. /**
  24. * 当前时间戳
  25. * @var int|string
  26. */
  27. protected $currentDateTime;
  28. /**
  29. * @author AdamTyn
  30. *
  31. * AuthController constructor.
  32. */
  33. public function __construct()
  34. {
  35. empty(self::$userModel) ? (self::$userModel = (new \App\Models\UserModel)) : true;
  36. $this->currentDateTime = time();
  37. }
  38. /**
  39. * @author AdamTyn
  40. * @description 用户登录
  41. *
  42. * @param \Illuminate\Http\Request;
  43. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  44. */
  45. public function login(Request $request)
  46. {
  47. $response = array('status_code' => '2000');
  48. try {
  49. $user = (self::$userModel)->whereUserName($request->input('user_name'))# 魔术方法查询user_name字段
  50. ->wherePassword($request->input('password'))# 魔术方法查询password字段
  51. ->firstOrFail();
  52. $this->initialAuth(); # 数据库查询成功再初始化认证器
  53. if ($token = $this->auth->login($user)) {
  54. $response['data'] = [
  55. 'user_id' => $user->id,
  56. 'access_token' => $token,
  57. 'expires_in' => $this->currentDateTime + 86400 # 24小时过期
  58. ];
  59. } else {
  60. $response = [
  61. 'status_code' => '5000',
  62. 'msg' => '系统错误,无法生成令牌'
  63. ];
  64. }
  65. } catch (\Exception $exception) {
  66. $response = [
  67. 'status_code' => '5002',
  68. 'msg' => '无法响应请求,服务端异常'
  69. ];
  70. Log::error($exception->getMessage() . ' at' . $this->currentDateTime);
  71. }
  72. return response()->json($response);
  73. }
  74. /**
  75. * @author AdamTyn
  76. * @description 查询用户信息
  77. *
  78. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  79. */
  80. public function userInfo()
  81. {
  82. $response = array('status_code' => '2000');
  83. $this->initialAuth();
  84. if ($this->auth->check()) { # JWT同样可以使用Auth门面的check方法
  85. $response['data'] = $this->auth->user(); # JWT同样可以使用Auth门面的user方法
  86. } else {
  87. $response = [
  88. 'status_code' => '4004',
  89. 'msg' => '系统错误,无法查询用户信息'
  90. ];
  91. }
  92. return response()->json($response);
  93. }
  94. /**
  95. * @author AdamTyn
  96. * @description 用户退出
  97. *
  98. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  99. */
  100. public function logout()
  101. {
  102. $response = array('status_code' => '2000', 'msg' => '退出成功!');
  103. $this->initialAuth();
  104. $this->auth->invalidate(true);
  105. return response()->json($response);
  106. }
  107. /**
  108. * @author AdamTyn
  109. * @description 用户刷新Token
  110. *
  111. * @return \Illuminate\Contracts\Routing\ResponseFactory|\Symfony\Component\HttpFoundation\Response
  112. */
  113. public function refreshToken()
  114. {
  115. $response = array('status_code' => '2000');
  116. $this->initialAuth();
  117. if ($token = $this->auth->refresh(true, true)) {
  118. $response['data'] = [
  119. 'access_token' => $token,
  120. 'expires_in' => $this->currentDateTime + 86400 # 24小时过期
  121. ];
  122. } else {
  123. $response = [
  124. 'status_code' => '5000',
  125. 'msg' => '系统错误,无法生成令牌'
  126. ];
  127. }
  128. return response()->json($response);
  129. }
  130. /**
  131. * @author AdamTyn
  132. * @description 初始化认证器
  133. *
  134. * @return void
  135. */
  136. private function initialAuth()
  137. {
  138. $this->auth = app('auth');
  139. return;
  140. }
  141. }

5.6. 最后,我们要利用 ‘auth’ 中间件的 ‘api’ 门卫,修改 ‘app/Api/Middlewares’ 文件夹下的 ‘Authenticate.php’,内容如下所示:

  1. # Dir: @/app/Api/Middlewares/Authenticate.php
  2. <?php
  3. namespace App\Api\Middlewares;
  4. use Closure;
  5. use Illuminate\Contracts\Auth\Factory as Auth;
  6. class Authenticate
  7. {
  8. /**
  9. * The authentication guard factory instance.
  10. *
  11. * @var \Illuminate\Contracts\Auth\Factory
  12. */
  13. protected $auth;
  14. /**
  15. * Create a new middleware instance.
  16. *
  17. * @param \Illuminate\Contracts\Auth\Factory $auth
  18. * @return void
  19. */
  20. public function __construct(Auth $auth)
  21. {
  22. $this->auth = $auth;
  23. }
  24. /**
  25. * 在进入控制器之前,判断并处理请求体
  26. *
  27. * @param \Illuminate\Http\Request $request
  28. * @param \Closure $next
  29. * @param string|null $guard
  30. * @return mixed
  31. */
  32. public function handle($request, Closure $next, $guard = null)
  33. {
  34. if ($this->auth->guard($guard)->guest()) {
  35. $response['code'] = '4001';
  36. $response['errorMsg'] = '无效令牌,需要重新获取';
  37. return response()->json($response);
  38. }
  39. return $next($request);
  40. }
  41. }

5.7. 创建 ‘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. }
  1. 运行测试。在‘routers/api.php’ 中添加相应的路由规则。 ```php

    Dir: @/routers/api.php

    <?php

$router->post(‘login’, ‘AuthController@login’); $router->group([‘prefix’ => ‘/‘, ‘middleware’ => ‘auth:api’], function () use ($router) { $router->post(‘logout’, ‘AuthController@logout’); $router->get(‘userInfo’, ‘AuthController@userInfo’); $router->post(‘refresh’, ‘AuthController@refreshToken’); });

  1. 最后,可以得出以下测试结果。<br />
  2. ```php
  3. HTTP/1.1 200 OK. POST http://127.0.0.1:8000/login
  4. Request: user_name='test', password='123456'
  5. {
  6. "status_code": "2000",
  7. "data": {
  8. "user_id": 1,
  9. "access_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NgXOuXfn421r8",
  10. "expires_in": 1549966383
  11. }
  12. }
  1. HTTP/1.1 200 OK. GET http://127.0.0.1:8000/userInfo
  2. Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NgXOuXfn421r8
  3. {
  4. "status_code": "2000",
  5. "data": {
  6. "id": 1,
  7. "user_name": "test",
  8. "created_at": "2019-02-11 09:50:08",
  9. "updated_at": null
  10. }
  11. }
  1. HTTP/1.1 200 OK. POST http://127.0.0.1:8000/logout
  2. Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NgXOuXfn421r8
  3. {
  4. "status_code": "2000",
  5. "msg": "退出成功!"
  6. }
  1. HTTP/1.1 200 OK. POST http://127.0.0.1:8000/refresh
  2. Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NgXOuXfn421r8
  3. {
  4. "status_code": "2000",
  5. "data": {
  6. "access_token": "eyJ0eXAiOiJK8679704746ciOiJIUzI1fajsfg42louhnhtm",
  7. "expires_in": 1549966383
  8. }
  9. }
  1. 放出参考的示例 Code

    五、结语

  2. 本教程面向新手,更多教程会在日后给出。

  3. 随着系统升级,软件更新,以后的配置可能有所变化,在下会第一时间测试并且更新教程;
  4. 欢迎联系在下,讨论建议都可以,之后会发布其它的教程。
  5. 如果读者使用的是旧版本 Lumen,可以参考本人之前出了 Lumen5.6使用JWT【最新教程】,亲身失败百次的总结 教程。
  6. 后面紧锣密鼓地将会推出 Laravel业务篇 系列的教程,敬请期待。