Laravel passport 多端使用者使用

____發表於2019-09-30

說明

使用 passport 進行 admin 端和 customer 端的使用者認證。

雖然教程很多,但是我並沒有參照其他教程完整的走下來,所以記錄了自己的開發流程,希望能對其他人有所幫助。

github

安裝專案

laravel new passport

安裝 passport

composer require laravel/passport

1240

資料遷移

首先我們需要建立 admins 和 customers 表,並填充假資料


php artisan make:migration create_admins_table --create=admins

php artisan make:migration create_customers_table --create=customers

php artisan make:seeder AdminsTableSeeder

php artisan make:seeder CustomersTableSeeder

124012401240124012401240

執行遷移

php artisan migrate --seed

1240

passport 初始化

php artisan passport:install

1240

此時在 storage 下會生成 oauth-private.key 和 oauth-public.key

1240

生成認證

目前我們只是為前後端分離的後臺使用,所以 password 模式足夠

php artisan passport:client --password --name='passport-admin'

php artisan passport:client --password --name='passport-customer'

1240

備註

原先我以為這裡採用不一樣的資料之後下面 token 不會出現複用的情況,然而和這個沒有關係

token 複用是指 admin 端生成的 1 號使用者的 token 去請求 customer 端時,依然有效

解決方法下文會有介紹

修改路由配置

找到 app/Providers/RouteServiceProvider.php, 增加如下程式碼


public function map()

{

·

·

·

// admin 路由

$this->mapAdminRoutes();

// customer 路由

$this->mapCustomerRoutes();

}

protected function mapAdminRoutes()

{

Route::prefix('admin')

->namespace($this->namespace . '\Admin')

->group(base_path('routes/admin.php'));

}

protected function mapCustomerRoutes()

{

Route::prefix('customer')

->namespace($this->namespace . '\Customer')

->group(base_path('routes/customer.php'));

}

在 routes 下新建 admin.php 和 customer.php

分別增加如下程式碼

admin.php


<?php

Route::group([

'middleware' => 'passport-guard'

], function () {

// 登入

Route::post('login', 'AuthController@login');

// 重新整理 token

Route::put('refresh', 'AuthController@refresh');

Route::group([

'middleware' => ['auth:api', 'scopes:admin']

], function () {

// 退出

Route::delete('logout', 'AuthController@logout');

// 詳情

Route::get('admins/current', 'AdminsController@current');

});

});

customer.php


<?php

Route::group([

'middleware' => 'passport-guard'

], function () {

// 登入

Route::post('login', 'AuthController@login');

// 重新整理 token

Route::put('refresh', 'AuthController@refresh');

Route::group([

'middleware' => ['auth:api', 'scopes:customer']

], function () {

// 退出

Route::delete('logout', 'AuthController@logout');

// 詳情

Route::get('customers/current', 'CustomersController@current');

});

});

備註

此處 auth:api 是檢驗 token

scopes:admin, scopes:customer 是給 token 指定作用域,即防止上文 token 複用的情況出現

建立中介軟體

php artisan make:middleware PassportGuard

1240

增加如下程式碼

因為 passport 預設使用的是 api 守衛,並且不支援傳參修改,所以需要通過中介軟體修改 provider


public function handle($request, Closure $next)

{

try {

if ($request->is('admin/*')) {// 如果是 admin 路由

config(['auth.guards.api.provider' => 'admins']);

} elseif ($request->is('customer/*')) { // 如果是 customer 路由

config(['auth.guards.api.provider' => 'customers']);

}

} catch (\Exception $exception) {

throw new $exception;

}

return $next($request);

}

找到 app/Http/Kernel.php,註冊中介軟體


protected $routeMiddleware = [

·

·

·

// passport 認證路由

'passport-guard' => \App\Http\Middleware\PassportGuard::class

// token 作用域

'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class,

'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,

];

修改 Providers

找到 app/Http/Providers

增加如下程式碼


use Laravel\Passport\Passport;

use Laravel\Passport\RouteRegistrar;

public function boot()

{

·

·

·

// Passport 路由註冊

$prefix = '';

if (request()->is('admin/*')) {

$prefix = 'admin';

} elseif (request()->is('customer/*')) {

$prefix = 'customer';

}

// 我們只需要前後端分離的形式, 而不需要認證

Passport::routes(function (RouteRegistrar $router) {

$router->forAccessTokens();

}, ['prefix' => $prefix . '/oauth', 'middleware' => 'passport-guard']);

// token 作用域

Passport::tokensCan([

'admin' => 'admin',

'customer' => 'customer'

]);

// access_token 過期時間

Passport::tokensExpireIn(Carbon::now()->addDays(15));

// refreshTokens 過期時間

Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));

}

此時還需修改 config/auth.php, 修改為如下程式碼


'guards' => [

·

·

·

'api' => [

'driver' => 'passport',

'provider' => 'users',

'hash' => false,

],

'admin' => [

'driver' => 'passport',

'provider' => 'admins',

],

'customer' => [

'driver' => 'passport',

'provider' => 'customers',

],

],

'providers' => [

'users' => [

'driver' => 'eloquent',

'model' => App\User::class,

],

'admins' => [

'driver' => 'eloquent',

'model' => App\Admin::class,

],

'customers' => [

'driver' => 'eloquent',

'model' => App\Customer::class,

],

],

建立 Model

php artisan make:model Admin
php artisan make:model Customer

1240

分別修改為如下程式碼

Admin.php


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Foundation\Auth\User as Authenticatable;

use Laravel\Passport\HasApiTokens;

class Admin extends Authenticatable

{

use HasApiTokens;

/**

* Passport 多認證欄位

*/

public function findForPassport($username)

{

return self::orWhere('email', $username)->orWhere('username', $username)->first();

}

}

Customer.php


<?php

namespace App;

use Illuminate\Database\Eloquent\Model;

use Illuminate\Foundation\Auth\User as Authenticatable;

use Laravel\Passport\HasApiTokens;

class Customer extends Authenticatable

{

use HasApiTokens;

/**

* Passport 多認證欄位

*/

public function findForPassport($username)

{

return self::orWhere('email', $username)->orWhere('username', $username)->first();

}

}

建立控制器


php artisan make:controller Admin/AuthController

php artisan make:controller Admin/AdminsController

php artisan make:controller Customer/AuthController

php artisan make:controller Customer/CustomersController

1240

安裝 guzzle 擴充套件包

composer require guzzlehttp/guzzle

在 app/Http/Controllers/Admin 下新建 Traits/TokenTrait,新增如下程式碼


<?php

namespace App\Http\Controllers\Admin\Traits;

use GuzzleHttp\Client;

use GuzzleHttp\Exception\RequestException;

trait TokenTrait

{

public function authenticate()

{

$client = new Client();

try {

// 請求本地的 passport token

$url = request()->root() . '/admin/oauth/token';

$password_client = \DB::table('oauth_clients')->where('name', 'passport-admin')->first();

$params = [

'grant_type' => 'password', // 認證型別 passport

'client_id' => $password_client->id,

'client_secret' => $password_client->secret,

'scope' => 'admin', // 設定 token 作用域

'username' => request('username'),

'password' => request('password'),

];

$respond = $client->request('POST', $url, ['form_params' => $params]);

} catch (RequestException $exception) {

abort(401, '系統異常');

}

if ($respond->getStatusCode() !== 401) {

return json_decode($respond->getBody()->getContents(), true);

}

abort(401, '賬號或密碼錯誤');

}

public function getRefreshToken()

{

$client = new Client();

try {

// 請求本地的 passport token

$url = request()->root() . '/admin/oauth/token';

$password_client = \DB::table('oauth_clients')->where('name', 'passport-admin')->first();

$params = [

'grant_type' => 'refresh_token',// 認證型別 refresh_token

'client_id' => $password_client->id,

'client_secret' => $password_client->secret,

'scope' => 'admin', // 設定 token 作用域

'refresh_token' => request('refresh_token')

];

$respond = $client->request('POST', $url, ['form_params' => $params]);

} catch (RequestException $exception) {

abort(401, '系統異常');

}

if ($respond->getStatusCode() !== 401) {

return json_decode($respond->getBody()->getContents(), true);

}

abort(401, 'refresh token 錯誤');

}

}

在 app/Http/Controllers/Customer 下新建 Traits/TokenTrait,新增如下程式碼


<?php

namespace App\Http\Controllers\Customer\Traits;

use GuzzleHttp\Client;

use GuzzleHttp\Exception\RequestException;

trait TokenTrait

{

public function authenticate()

{

$client = new Client();

try {

$url = request()->root() . '/customer/oauth/token';

$password_client = \DB::table('oauth_clients')->where('name', 'passport-customer')->first();

$params = [

'grant_type' => 'password', // 認證型別 passport

'client_id' => $password_client->id,

'client_secret' => $password_client->secret,

'scope' => 'customer', // 設定 token 作用域

'username' => request('username'),

'password' => request('password'),

];

$respond = $client->request('POST', $url, ['form_params' => $params]);

} catch (RequestException $exception) {

abort(401, '系統異常');

}

if ($respond->getStatusCode() !== 401) {

return json_decode($respond->getBody()->getContents(), true);

}

abort(401, '賬號或密碼錯誤');

}

public function getRefreshToken()

{

$client = new Client();

try {

// 請求本地的 passport token

$url = request()->root() . '/customer/oauth/token';

$password_client = \DB::table('oauth_clients')->where('name', 'passport-customer')->first();

$params = [

'grant_type' => 'refresh_token',// 認證型別 refresh_token

'client_id' => $password_client->id,

'client_secret' => $password_client->secret,

'scope' => 'customer', // 設定 token 作用域

'refresh_token' => request('refresh_token')

];

$respond = $client->request('POST', $url, ['form_params' => $params]);

} catch (RequestException $exception) {

abort(401, '系統異常');

}

if ($respond->getStatusCode() !== 401) {

return json_decode($respond->getBody()->getContents(), true);

}

abort(401, 'refresh token 錯誤');

}

}

備註

其實此處請求的時候是有點問題的,用 guzzle 請求時如果不正確的引數是會返回 http 401 狀態碼以及報錯,然後 guzzle 如果不是 http 200 的返回,都是會丟擲異常的

所以此處丟擲的異常更準確的是 賬號密碼或 refresh_token 錯誤,最下面的 abort() 也是不會執行的。

在 app/Http/Controllers/Admin 下新建 Controller.php,新增如下程式碼


<?php

namespace App\Http\Controllers\Admin;

use App\Http\Controllers\Controller as BaseController;

class Controller extends BaseController

{

}

在 app/Http/Controllers/Customer 下新建 Controller.php,新增如下程式碼


<?php

namespace App\Http\Controllers\Customer;

use App\Http\Controllers\Controller as BaseController;

class Controller extends BaseController

{

}

修改 app/Http/Controller/Admin/AuthController.php 為如下程式碼


<?php

namespace App\Http\Controllers\Admin;

use Illuminate\Http\Request;

use App\Admin;

use App\Http\Controllers\Admin\Traits\TokenTrait;

use Auth;

use Illuminate\Support\Facades\Hash;

class AuthController extends Controller

{

use TokenTrait;

public function login(Request $request)

{

// 根據使用者名稱或者郵箱登入

$admin = Admin::orWhere('username', $request->username)

->orwhere('email', $request->username)

->firstOrFail();

// 檢驗密碼是否正確,錯誤返回 401 和報錯資訊

if (!Hash::check($request->password, $admin->password)) {

return response()->json([

'message' => '使用者名稱或密碼錯誤'

], 401);

}

$token = $this->authenticate();

return response()->json($token);

}

public function refresh()

{

// 獲取 token

$token = $this->getRefreshToken();

return response()->json($token);

}

public function logout()

{

if (Auth::guard('admin')->check()) {

Auth::guard('admin')->user()->token()->delete();

}

return response()->noContent();

}

}

修改 app/Http/Controller/Customer/AuthController.php 為如下程式碼


<?php
namespace App\Http\Controllers\Customer;

use App\Http\Controllers\Controller;

use Illuminate\Http\Request;

use App\Customer;

use App\Http\Controllers\Customer\Traits\TokenTrait;

use Auth;

use Illuminate\Support\Facades\Hash;

class AuthController extends Controller

{

use TokenTrait;

public function login(Request $request)

{

// 根據使用者名稱或者郵箱登入

$customer = Customer::orWhere('username', $request->username)

->orwhere('email', $request->username)

->firstOrFail();

// 檢驗密碼是否正確,錯誤返回 401 和報錯資訊

if (!Hash::check($request->password, $customer->password)) {

return response()->json([

'message' => '使用者名稱或密碼錯誤'

], 401);

}

$token = $this->authenticate();

return response()->json($token);

}

public function refresh()

{

// 獲取 token

$token = $this->getRefreshToken();

return response()->json($token);

}

public function logout()

{

if (Auth::guard('customer')->check()) {

Auth::guard('customer')->user()->token()->delete();

}

return response()->noContent();

}

}

好了,激動人心的時刻到了!

開啟 postman 測試, 我分別建立了 6 個請求,具體看 url 和引數應該能明白

1240124012401240124012401240

ok,按照預期的該返回的返回,不通過的也沒通過

接下來我們用這些 token 獲取使用者資訊

124012401240124012401240

圖 5 為我用 admin1 的 token 請求 customer 的介面

圖 6 為我用 customer1 的 token 請求 admin 的介面

都是無效的。

接下來驗證重新整理 token

admin1 的 refresh_token

1240

再請求一下

1240

customer1 的 refersh_token

12401240

ok, 完工!

總結

  • 雖然 refresh_token 不能重新刷出來,但是之前沒過期的 access_token 其實依然會有效

  • 個人覺得這個並不如 dingoapi + jwt (也就是第三本 api 的教程)好用,本文只說明怎麼使用 passport 進行多端驗證。像丟擲異常,沒有自定義返回碼等還有一大堆未完善的東西。

參考資料

部落格:Laravel5.5+passport 放棄 dingo 開發 API 實戰,讓 API 開發更省心 重點感謝

部落格:Laravel Passport API 認證使用小結

部落格:Laravel Passport 多表使用者認證踩坑

部落格:passport API 認證 -- 多表登入

部落格:Laravel 5.5 使用 Passport 實現 Auth 認證

Passport OAuth 認證

相關文章