- 一、前言
- 二、说明
- 三、开始
- Dir: @/bootstrap/app.php
- 注意!我已经修改了默认的认证中间件的路径!!
- 注意!我已经修改了默认的命名空间!!
- Dir: @/app/Providers/AuthServiceProvider.php
- Dir: @/app/Api/Controllers/ClientController.php
- Dir: @/app/Providers/AppServiceProvider.php
- Dir: @/routers/api.php
- 模拟用户注册路由
- 模拟登录私人访问的客户端
- 模拟添加使用密码认证的客户端
- 模拟添加私人访问的客户端
- Passport相关的路由已经被组件定义,无需自己定义
一、前言
如果只是单纯需要使用 JWT,可以参考在下之前:Lumen5.8使用JWT【2019.06.22最新教程】,更新啦! 的教程
二、说明
不知不觉 Lumen 已经更新到 ‘5.8.x’ 版本,因此本文也紧跟脚步,使用最新版的 Lumen 进行讲解,最重要的是 Laravel/Lumen 5.8.x 版本只支持 ‘PHP7.1.3’ 及以上
- 本文使用 ‘dusterio/lumen-passport’ 最新 (0.2.10) 版本
操作环境:‘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_KEY
CACHE_DRIVER=file #### 修改为文件缓存
......others (包括MySQL的配置项) .......
本文是使用
php -S localhost:8000 public/index.php
指令快速启动服务下面开始实现 JWT 功能。由于本人习惯在 ‘app’ 路径下新建一个 ‘Api’ 目录存放接口控制器类、 ‘Models’ 目录存放模型,因此之后的项目目录结构是:
......others.......
/app
..........others.......
..../Api <<<<<< 接口控制器文件目录
..../Models <<<<<< 模型文件目录
/config <<<<<< 配置文件目录
/vendor
......others.......
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;
- 修改 **'config'** 文件夹下的 **'auth.php'** 如下所示:<br />
```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' => [
//
],
];
- 修改 ‘app/Providers’ 文件夹下的 ‘AuthServiceProvider.php’ 如下所示:
```phpDir: @/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->router); # 注册Passport相关路由
LumenPassport::allowMultipleTokens(); # 允许生成多个有效Token
}
}
- 修改 **'app/Models'** 文件夹下的 **'UserModel.php'** 如下所示:<br />
```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();
}
}
- 在 ‘app/Api/Controllers’ 文件夹下新建 ‘ClientController.php’,内容如下所示:
```phpDir: @/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 */ 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 初始化用户客户端仓库的单例 */ private function initialClientRepository() { empty(self::$clientRepository) ? (self::$clientRepository = (new ClientRepository)) : true; }
/**
- @author AdamTyn
@description 改变授权类型为私人访问 */ private function changeGrantType() { $this->grantType = ‘personal’; }
/**
- @author AdamTyn
- @description 新增用户客户端记录 *
- @param array $response
- @param \Illuminate\Http\Request $request
@param \App\Models\UserModel $user */ private function createClient(&$response, $request, $user) { $this->initialClientRepository();
if (Hash::check($request->input(‘password’), $user->getAuthPassword())) {
# createPasswordGrantClient: 创建密码获取token访问的客户端记录
# createPersonalAccessClient: 创建私人访问的客户端记录
$func = ($this->grantType === 'password')? 'createPasswordGrantClient':'createPersonalAccessClient';
$client = self::$clientRepository->$func(
$user->id,
$user->user_name . '`s new ' . $this->grantType . ' client',
data_get($request, 'redirect', 'http://localhost:8000')
);
$response['data'] = [
'client_id' => $client->id,
'client_name' => $client->name,
'client_grant_type' => $this->grantType,
'client_secret' => $this->grantType ? $client->secret : null,
'client_redirect' => $client->redirect,
];
} else {
$response = [
'status_code' => '5000',
'msg' => '系统错误',
];
} } } ```
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);
}
}
- 使用以下 _Artisan_ 在数据库中新增 **'Passport'** 的相关数据表,并且填充2条测试数据:<br />
```php
php artisan migrate
php 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相关的路由已经被组件定义,无需自己定义
7. 最后,可以得出以下测试结果
```php
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"
}
]
- 放出参考的示例 代码仓库。至此,Lumen5.8使用Passport 讲解结束,给出所有 Passport 中预设的路由规则:
四、附录
针对
dusterio/lumen-passport >= 0.2.10
的扩展包,改动位置:\Dusterio\LumenPassport\LumenPassport::routes()
public static function routes($callback = null, array $options = [])
{
# 这一行代码导致Lumen5.8之后注册路由时,要特别指定 $this->app->router
if ($callback instanceof Application && preg_match('/5\.[5-7]\..*/', $callback->version())) $callback = $callback->router;
# ......
# *********************** others ***********************
}
所以本文示例代码文件 ‘AuthServiceProvider.php’ 中的
boot
方法做出了相应的调整:public function boot()
{
LumenPassport::routes($this->app->router); # 注册Passport相关路由
LumenPassport::allowMultipleTokens(); # 允许生成多个有效Token
}
改动位置:
\Laravel\Passport\ClientRepository->create()
public function create($userId, $name, $redirect, $personalAccess = false, $password = false)
{
# ......
# ****************** 除了保留create方法以外,还特意区分了 [密码获取token访问的客户端] 和 [私人访问的客户端] ******************
}
public function createPersonalAccessClient($userId, $name, $redirect)
{
return tap($this->create($userId, $name, $redirect, true), function ($client) {
$accessClient = Passport::personalAccessClient();
$accessClient->client_id = $client->id;
$accessClient->save();
});
}
public function createPasswordGrantClient($userId, $name, $redirect)
{
return $this->create($userId, $name, $redirect, false, true);
}
所以本文示例代码文件 ‘ClientController.php’ 中的
createClient
方法做出了相应的调整:private function createClient(&$response, $request, $user)
{
# ......
# *********************** others ***********************
# createPasswordGrantClient: 创建密码获取token访问的客户端记录
# createPersonalAccessClient: 创建私人访问的客户端记录
$func = ($this->grantType === 'password')? 'createPasswordGrantClient':'createPersonalAccessClient';
$client = self::$clientRepository->$func(
$user->id,
$user->user_name . '`s new ' . $this->grantType . ' client',
data_get($request, 'redirect', 'http://localhost:8000')
);
# ......
# *********************** others ***********************
}
五、结语
本教程面向新手,更多教程会在日后给出。
- 随着系统升级,软件更新,以后的配置可能有所变化,在下会第一时间测试并且更新教程。
- 欢迎联系在下,讨论建议都可以,之后会发布其它的教程。
- 如果读者使用的是旧版本 Lumen,可以参考本人之前出了 Lumen5.7使用Passport【2019.02.13最新教程】,更新啦 教程。
- 后面紧锣密鼓地将会推出 Laravel业务篇 系列的教程,敬请期待。