[ Laravel 5.8 文件 ] 官方擴充套件包 —— API 認證解決方案:Laravel Passport
簡介
Laravel 通過傳統的登入表單已經讓使用者認證變得很簡單,但是 API 認證怎麼實現?API 通常使用令牌(token)進行認證並且在請求之間不維護會話(Session)狀態。Laravel 官方擴充套件包 Laravel Passport 讓 API 認證變得輕而易舉,Passport 基於 Alex Bilbie 維護的 League OAuth2 server ,可以在數分鐘內為 Laravel 應用提供完整的 OAuth2 伺服器實現。
OAuth2 概述
正式開始之前我們先簡單瞭解下 OAuth2。
什麼是 OAuth 協議
OAuth 是 Open Authorization 的簡寫,OAuth 協議為使用者資源的授權提供了一個安全的、開放而又簡易的標準。與以往的授權方式不同之處是 OAuth 的授權不會使第三方觸及到使用者的帳號資訊(如使用者名稱與密碼),即第三方無需使用使用者的使用者名稱與密碼就可以申請獲得該使用者資源的授權,因此 OAuth 是安全的。
OAuth 本身不存在一個標準的實現,後端開發者自己根據實際的需求和標準的規定實現。其步驟一般如下:
- 客戶端要求使用者給予授權
- 使用者同意給予授權
- 根據上一步獲得的授權,向認證伺服器請求令牌(token)
- 認證伺服器對授權進行認證,確認無誤後發放令牌
- 客戶端使用令牌向資源伺服器請求資源
- 資源伺服器使用令牌向認證伺服器確認令牌的正確性,確認無誤後提供資源
OAuth2 解決什麼問題
任何身份認證,本質上都是基於對請求方的不信任所產生的。同時,請求方是信任被請求方的,例如使用者請求服務時,會信任服務方。所以,身份認證就是為了解決身份的可信任問題。
在 OAuth 中,簡單來說有三方:使用者(這裡是指屬於服務方的使用者)、服務方、第三方應用(客戶端)。
服務方不信任使用者,所以需要使用者提供密碼或其他可信憑據;
服務方不信任第三方,所以需要第三方提供自已交給它的憑據(通常的一些安全簽名之類的就是);
使用者部分信任第三方,所以使用者願意把自已在服務方里的某些服務交給第三方使用,但不願意把自已在服務方的密碼交給第三方;
在 OAuth 的流程中,使用者登入了第三方的系統後,會先跳去服務方獲取一次性使用者授權憑據,再跳回來把它交給第三方,第三方的伺服器會把授權憑據以及服務方給它的的身份憑據一起交給服務方,這樣,服務方一可以確定第三方得到了使用者對此次服務的授權(根據使用者授權憑據),二可以確定第三方的身份是可以信任的(根據身份憑據),所以,最終的結果就是,第三方順利地從服務方獲取到了此次所請求的服務。
從上面的流程中可以看出,OAuth 完整地解決掉了使用者、服務方、第三方 在某次服務時這三者之間的信任問題。
OAuth 基本流程
涉及成員:
- Resource Owner(資源擁有者:使用者)
- Client (第三方接入平臺:請求者)
- Resource Server (伺服器資源:資料中心)
- Authorization Server (認證伺服器)
注:Passport 需要你對 OAuth2 非常熟悉才能自如使用,上面關於 OAuth2 的概述轉自 理解 OAuth2.0 認證 一文,更多關於 OAuth2 模式的探討,還可以參考阮一峰部落格: 理解OAuth 2.0 )。
安裝
首先通過 Composer 包管理器安裝 Passport:
composer require laravel/passport
Passport 服務提供者為框架註冊了自己的資料庫遷移目錄,所以在註冊服務提供者之後(Laravel 5.5之後會自動註冊服務提供者)需要遷移資料庫,Passport 遷移將會為應用生成用於存放客戶端和訪問令牌的資料表:
php artisan migrate
接下來,需要執行 passport:install
命令,該命令將會建立生成安全訪問令牌(token)所需的加密鍵,此外,該命令還會建立“personal access”和“password grant”客戶端用於生成訪問令牌:
php artisan passport:install
生成記錄存放在資料表 oauth_clients
:
執行完這個命令後,新增 Laravel\Passport\HasApiTokens
trait 到 App\User
模型,該 trait 將會為模型類提供一些輔助函式用於檢查認證使用者的 token 和 scope:
<?php namespace App; use Laravel\Passport\HasApiTokens; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use HasApiTokens, Notifiable; }
接下來,你需要在 AuthServiceProvider
的 boot
方法中呼叫 Passport::routes
方法,該方法將會為頒發訪問令牌、撤銷訪問令牌、客戶端以及私人訪問令牌註冊必要的路由:
<?php namespace App\Providers; use Laravel\Passport\Passport; use Illuminate\Support\Facades\Gate; use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider; class AuthServiceProvider extends ServiceProvider { /** * The policy mappings for the application. * * @var array */ protected $policies = [ 'App\Model' => 'App\Policies\ModelPolicy', ]; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); } }
最後,在配置檔案 config/auth.php
中,需要設定 api
認證 guard 的 driver
選項為 passport
。這將告知應用在認證輸入的 API 請求時使用 Passport 的 TokenGuard
:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ],
自定義遷移
如果你不想使用 Passport 的預設遷移,需要在 AppServiceProvider
的 register
方法中呼叫 Passport::ignoreMigrations
方法。你可以使用 php artisan vendor:publish --tag=passport-migrations
匯出預設遷移。
預設情況下,Passport 使用整型列來儲存 user_id
,如果你的應用使用了不同的列型別來標識使用者(例如 UUID),需要在釋出預設遷移之後對其進行修改。
前端快速入門
注:如果要使用 Passport Vue 元件,前端 JavaScript 必須使用 Vue 框架,這些元件同時也使用了 Bootstrap CSS 框架。不過,即使你不使用這些工具,這些元件同樣可以為你實現自己的前端元件提供有價值的參考。
Passport 附帶了 JSON API 以便使用者建立客戶端和私人訪問令牌(access token)。不過,考慮到編寫前端程式碼與這些 API 互動是一件很花費時間的事,Passport 還預置了 Vue 元件作為示例以供使用(或者作為自己實現的參考)。
要釋出 Passport Vue 元件,可以使用 vendor:publish
命令:
php artisan vendor:publish --tag=passport-components
釋出後的元件位於 resources/assets/js/components
目錄下,元件釋出之後,還需要將它們註冊到 resources/assets/js/app.js
檔案:
Vue.component( 'passport-clients', require('./components/passport/Clients.vue').default ); Vue.component( 'passport-authorized-clients', require('./components/passport/AuthorizedClients.vue').default ); Vue.component( 'passport-personal-access-tokens', require('./components/passport/PersonalAccessTokens.vue').default );
注:在 Laravel 5.7.19 之前的版本,在註冊元件時附加 .default
字尾會導致控制檯錯誤。有關此次更改的說明,請參考 Laravel Mix v4.0.0 版本發行說明 。
註冊完元件後,確保執行 npm run dev
來重新編譯前端資源。重新編譯前端資源後,就可以將這些元件放到應用的某個模板中以便建立客戶端和私人訪問令牌:
<passport-clients></passport-clients> <passport-authorized-clients></passport-authorized-clients> <passport-personal-access-tokens></passport-personal-access-tokens>
部署 Passport
第一次部署 Passport 到生產伺服器時,可能需要執行 passport:keys
命令。這個命令生成 Passport 需要的金鑰以便生成訪問令牌,生成的金鑰將不會存放在原始碼控制中:
php artisan passport:keys
如果必要的話,可以定義 Passport 金鑰的載入路徑,這可以通過使用 Passport::loadKeysFrom
方法來實現:
/** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::loadKeysFrom('/secret-keys/oauth'); }
配置
令牌生命週期
預設情況下,Passport 頒發的訪問令牌(access token)是長期有效的,如果你想要配置生命週期短一點的令牌,可以使用 tokensExpireIn
和 refreshTokensExpireIn
方法,這些方法需要在 AuthServiceProvider
的 boot
方法中呼叫:
/** * 註冊任意認證/授權服務 * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::tokensExpireIn(now()->addDays(15)); Passport::refreshTokensExpireIn(now()->addDays(30)); }
覆蓋預設方法
你可以按需擴充套件 Passport 底層使用的方法,然後,通過 Passport
類告知 Passport 使用你自定義的方法:
use App\Models\Passport\Client; use App\Models\Passport\Token; use App\Models\Passport\AuthCode; use App\Models\Passport\PersonalAccessClient; /** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::useTokenModel(Token::class); Passport::useClientModel(Client::class); Passport::useAuthCodeModel(AuthCode::class); Passport::usePersonalAccessClientModel(PersonalAccessClient::class); }
頒發訪問令牌
通過授權碼使用 OAuth2 是大多數開發者熟悉的方式。使用授權碼的時候,客戶端應用會將使用者重定向到你的伺服器,伺服器可以通過或拒絕頒發訪問令牌到客戶端的請求。
管理客戶端
約定:我們參考 OAuth2 認證流程,這裡將 Laravel 應用約定為服務方,開發者開發應用作為第三方客戶端。
首先,開發者構建和 Laravel 應用 API 互動的應用時,需要通過建立一個“客戶端”將他們的應用註冊到 Laravel 應用。通常,這包括提供應用的名稱以及使用者授權請求通過後重定向到的 URL。(想想你是怎麼使用微博、微信、QQ第三方登入API的,就明白這裡的流程了)
passport:client
命令
建立客戶端最簡單的方式就是使用 Artisan 命令 passport:client
,該命令可用於建立你自己的客戶端以方便測試 OAuth2 功能。當你執行 client
命令時,Passport 會提示你輸入更多關於客戶端的資訊,並且為你生成 client ID 和 secret:
php artisan passport:client
新生成記錄存放在 oauth_clients
:
重定向 URL
如果你想要將客戶端的多個重定向網址列入白名單,可以在 passport:client
命令互動提示中使用一個通過逗號分隔的列表來指定它們:
http://example.com/callback,http://examplefoo.com/callback
注:任何包含逗號的 URL 都需要被編碼。
JSON API
由於第三方應用開發者不能直接使用 Laravel 服務端提供的 client
命令,為此,Passport 提供了一個 JSON API 用於建立客戶端,這省去了你手動編寫控制器用於建立、更新以及刪除客戶端的麻煩。
不過,你需要配對 Passport 的 JSON API 和自己的前端以便為第三方開發者提供一個可以管理他們自己客戶端的後臺,下面,我們來概覽下所有用於管理客戶端的 API,為了方便起見,我們將會使用 Axios 來演示傳送 HTTP 請求到 API:
注:如果你不想要自己實現整個客戶端管理前端,可以使用前端快速上手教程在數分鐘內搭建擁有完整功能的前端。
GET /oauth/clients
這個路由為認證使用者返回所有客戶端,這在展示使用者客戶端列表時很有用,可以讓使用者很容易編輯或刪除客戶端:
axios.get('/oauth/clients') .then(response => { console.log(response.data); });
POST /oauth/clients
這個路由用於建立新的客戶端,要求傳入兩個資料:客戶端的 name
和 redirect
URL, redirect
URL 是使用者授權請求通過或拒絕後重定向到的位置。
當客戶端被建立後,會附帶一個 client ID 和 secret,這兩個值會在請求訪問令牌時用到。客戶端建立路由會返回新的客戶端例項:
const data = { name: 'Client Name', redirect: 'http://example.com/callback' }; axios.post('/oauth/clients', data) .then(response => { console.log(response.data); }) .catch (response => { // List errors on response... });
PUT /oauth/clients/{client-id}
這個路由用於更新客戶端,要求傳入兩個引數:客戶端的 name
和 redirect
URL。 redirect
URL 是使用者授權請求通過或拒絕後重定向到的位置。該路由將會返回更新後的客戶端例項:
const data = { name: 'New Client Name', redirect: 'http://example.com/callback' }; axios.put('/oauth/clients/' + clientId, data) .then(response => { console.log(response.data); }) .catch (response => { // List errors on response... });
DELETE /oauth/clients/{client-id}
這個路由用於刪除客戶端:
axios.delete('/oauth/clients/' + clientId) .then(response => { // });
請求令牌
授權重定向
客戶端被建立後,開發者就可以使用相應的 client ID 和 secret 從應用請求授權碼和訪問令牌。首先,客戶端應用要生成一個重定向請求到服務端應用的 /oauth/authorize
路由:
Route::get('/redirect', function () { $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://example.com/callback', 'response_type' => 'code', 'scope' => '', ]); return redirect('http://your-app.com/oauth/authorize?'.$query); });
注: /oauth/authorize
路由已經通過 Passport::routes
方法定義了,不需要手動定義這個路由。
我們使用上面建立的 client ID 等於 3 的測試客戶端做測試,改寫上面的測試程式碼如下(路由定義在 routes/api.php
中):
Route::get('/redirect', function (){ $query = http_build_query([ 'client_id' => '3', 'redirect_uri' => 'http://laravel58.test/auth/callback', 'response_type' => 'code', 'scope' => '', ]); return redirect('http://laravel58.test/oauth/authorize?' . $query); });
同時在 routes/web.php
註冊 auth/callback
路由:
Route::get('/auth/callback', function (\Illuminate\Http\Request $request){ if ($request->get('code')) { return 'Login Success'; } else { return 'Access Denied'; } });
然後在瀏覽器中訪問 http://blog.test/api/redirect
,如果使用者尚未在 laravel58
應用中登入,首選會重定向到表單登入頁面,登入成功之後就會跳轉到第三方授權登入頁面 http://blog.test/oauth/authorize?client_id=3&redirect_uri=http%3A%2F%2Flaravel58.test%2Fauth%2Fcallback&response_type=code&scope=
:
通過授權請求
接收授權請求的時候,Passport 會自動顯示一個檢視模板給使用者從而允許他們通過或拒絕授權請求(如上圖所示),如果使用者通過請求,就會被重定向回第三方應用指定的 redirect_uri
(本例中是 http://blog.test/auth/callback
),這個 redirect_uri
必須和客戶端建立時指定的 redirect
URL 一致。
如果你想要自定義授權通過介面,可以使用 Artisan 命令 vendor:publish
釋出Passport 的檢視模板,釋出的檢視位於 resources/views/vendor/passport
:
php artisan vendor:publish --tag=passport-views
將授權碼轉化為訪問令牌
如果使用者通過了授權請求,會被重定向回第三方應用。第三方應用接下來會發送一個 POST
請求到服務端應用來請求訪問令牌。這個請求應該包含使用者通過授權請求時指定的授權碼。在這個例子中,我們會使用 Guzzle HTTP 庫來生成 POST
請求:
Route::get('/auth/callback', function (Request $request) { $http = new GuzzleHttp\Client; $response = $http->post('http://blog.test/oauth/token', [ 'form_params' => [ 'grant_type' => 'authorization_code', 'client_id' => '3',// your client id 'client_secret' => 'tBxbskNg9fJTIh0Ufk4eKdpneSkLx1H5HxGy2VTk',// your client secret 'redirect_uri' => 'http://blog.test/auth/callback', 'code' => $request->code, ], ]); return json_decode((string) $response->getBody(), true); });
/oauth/token
路由會返回一個包含 access_token
、 refresh_token
和 expires_in
屬性的 JSON 響應。 expires_in
屬性包含訪問令牌的過期時間(s):
注:和 /oauth/authorize
路由一樣, /oauth/token
路由已經通過 Passport::routes
方法定義過了,不需要手動定義這個路由。
拿到有效的 access_token
就可以通過它去服務端獲取其他需要的資源資訊了。至此就完成了通過授權碼方式實現 API 認證的流程。實際開發中,99% 的 API 認證都是通過這種方式實現的。
重新整理令牌
如果應用頒發的是短期有效的訪問令牌,那麼使用者需要通過訪問令牌頒發時提供的 refresh_token
重新整理訪問令牌,在本例中,我們使用 Guzzle HTTP 庫來重新整理令牌:
$http = new GuzzleHttp\Client; $response = $http->post('http://blog.test/oauth/token', [ 'form_params' => [ 'grant_type' => 'refresh_token', 'refresh_token' => 'the-refresh-token', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'scope' => '', ], ]); return json_decode((string) $response->getBody(), true);
/oauth/token
路由會返回一個包含 access_token
、 refresh_token
和 expires_in
屬性的 JSON 響應,同樣, expires_in
屬性包含訪問令牌過期時間(s)。
密碼授權令牌
OAuth2 密碼授權允許你的其他第一方客戶端,例如移動應用,使用郵箱地址/使用者名稱+密碼獲取訪問令牌。這使得你可以安全地頒發訪問令牌給第一方客戶端而不必要求你的使用者走整個 OAuth2 授權碼重定向流程。
建立一個密碼發放客戶端
在應用可以通過密碼授權頒發令牌之前,需要建立一個密碼授權客戶端,你可以通過使用帶 --password
選項的 passport:client
命令來實現。如果你已經運行了 passport:install
命令,則不必再執行這個命令:
php artisan passport:client --password
這裡我們使用一開始通過 passport:install
命令建立的記錄作為測試記錄。
請求令牌
建立完密碼授權客戶端後,可以通過傳送 POST
請求到 /oauth/token
路由(帶上使用者郵箱地址和密碼)獲取訪問令牌。這個路由已經通過 Passport::routes
方法註冊過了,不需要手動定義。如果請求成功,就可以從伺服器返回的 JSON 響應中獲取 access_token
和 refresh_token
:
Route::get('/auth/password', function (\Illuminate\Http\Request $request){ $http = new \GuzzleHttp\Client(); $response = $http->post('http://blog.test/oauth/token', [ 'form_params' => [ 'grant_type' => 'password', 'client_id' => '2', 'client_secret' => 'XtkyWdevgTnqbVtWTd8l7ASx76VtBBuZHzlAbCvm', 'username' => '[email protected]', 'password' => 'test123', 'scope' => '', ], ]); return json_decode((string)$response->getBody(), true); });
請求的時候直接訪問 http://blog.test/auth/password
即可。返回資料如下:
和通過授權碼返回資料格式一致。
注:記住,訪問令牌預設長期有效,不過,如果需要的話你也可以 配置訪問令牌的最長生命週期 。
請求所有域
使用密碼授權的時候,你可能想要對應用所支援的所有域進行令牌授權,這可以通過請求 *
域來實現。如果你請求的是 *
域,則令牌例項上的 can
方法總是返回 true
,這個域只會分配給使用 password
授權的令牌:
$response = $http->post('http://your-app.com/oauth/token', [ 'form_params' => [ 'grant_type' => 'password', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'username' => '[email protected]', 'password' => 'my-password', 'scope' => '*', ], ]);
自定義使用者名稱欄位
當使用密碼授權進行認證的時候,Passport 會使用模型的 email
屬性作為「使用者名稱」。不過,你可以通過在模型上定義 findForPassport
方法來自定義這一預設行為:
<?php namespace App; use Laravel\Passport\HasApiTokens; use Illuminate\Notifications\Notifiable; use Illuminate\Foundation\Auth\User as Authenticatable; class User extends Authenticatable { use HasApiTokens, Notifiable; /** * Find the user instance for the given username. * * @paramstring$username * @return \App\User */ public function findForPassport($username) { return $this->where('username', $username)->first(); } }
隱式授權令牌
隱式授權和授權碼授權有點相似,不過,無需獲取授權碼,令牌就會返回給客戶端。這種授權通常應用於 JavaScript 或移動應用這些客戶端憑證不能被安全儲存的地方。要啟用該授權,在 AuthServiceProvider
中呼叫 enableImplicitGrant
方法即可:
/** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::enableImplicitGrant(); }
授權啟用後,開發者就可以使用他們的 client ID 從應用中請求訪問令牌,第三方應用需要像這樣傳送重定向請求到應用的 /oauth/authorize
路由:
Route::get('/redirect', function () { $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://example.com/callback', 'response_type' => 'token', 'scope' => '', ]); return redirect('http://your-app.com/oauth/authorize?'.$query); });
注: /oauth/authorize
路由已經在 Passport::routes
方法中定義過了,無需再手動定義這個路由。
客戶端憑證授權令牌
客戶端憑證授權適用於機器對機器的認證,例如,你可以在排程任務中使用這種授權來通過 API 執行維護任務。
在應用可以通過客戶端憑證授權頒發令牌之前,需要先建立一個客戶端憑證授權客戶端,這可以通過在執行 passport:client
命令時新增 --client
選項來實現:
php artisan passport:client --client
要使用這個方法,需要在 app/Http/Kernel.php
中新增新的中介軟體 CheckClientCredentials
到 $routeMiddleware
:
use Laravel\Passport\Http\Middleware\CheckClientCredentials; protected $routeMiddleware = [ 'client' => CheckClientCredentials::class, ];
然後將這個中介軟體應用到路由:
Route::get('/orders', function(Request $request) { ... })->middleware('client');
要限定對特定路由域的訪問,可以在新增 client
中介軟體到路由時提供一個以逗號分隔的域列表:
Route::get('/orders', function (Request $request) { ... })->middleware('client:check-status,your-scope');
獲取令牌
要獲取令牌,傳送請求到 oauth/token
:
$guzzle = new GuzzleHttp\Client; $response = $guzzle->post('http://your-app.com/oauth/token', [ 'form_params' => [ 'grant_type' => 'client_credentials', 'client_id' => 'client-id', 'client_secret' => 'client-secret', 'scope' => 'your-scope', ], ]); return json_decode((string) $response->getBody(), true)['access_token'];
私人訪問令牌
有時候,你的使用者可能想要頒發訪問令牌給自己而不走典型的授權碼重定向流程。允許使用者通過應用的 UI 頒發令牌給自己在使用者體驗你的 API 或者作為更簡單的頒發訪問令牌方式時會很有用。
注:私人訪問令牌總是一直有效的,它們的生命週期在使用 tokensExpireIn
或 refreshTokensExpireIn
方法時不會修改。
建立一個私人訪問客戶端
在你的應用可以頒發私人訪問令牌之前,需要建立一個私人訪問客戶端。你可以通過帶 --personal
選項的 passport:client
命令來實現,如果你已經執行過了 passport:install
命令,則不必再執行此命令:
php artisan passport:client --personal
如果已經定義了一個私人訪問客戶端,可以通過 personalAccessClientId
方法告知 Passport 直接使用它。通常,這個方法在 AuthServiceProvider
的 boot
方法中呼叫:
/** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::personalAccessClientId('client-id'); }
管理私人訪問令牌
建立好私人訪問客戶端之後,就可以使用 User
模型例項上的 createToken
方法為給定使用者頒發令牌。 createToken
方法接收令牌名稱作為第一個引數,以及一個可選的域陣列作為第二個引數:
$user = App\User::find(1); // Creating a token without scopes... $token = $user->createToken('Token Name')->accessToken; // Creating a token with scopes... $token = $user->createToken('My Token', ['place-orders'])->accessToken;
JSON API
Passport 還提供了一個 JSON API 用於管理私人訪問令牌,你可以將其與自己的前端配對以便為使用者提供管理私人訪問令牌的後臺。下面,我們來概覽用於管理私人訪問令牌的所有 API。為了方便起見,我們使用 Axios 來演示傳送 HTTP 請求到 API。
JSON API 由 web
和 auth
中介軟體守衛,因此,只能在自己的應用中呼叫,不能從外部應用呼叫。
注:如果你不想要實現自己的私人訪問令牌前端,可以使用前端快速上手教程在數分鐘內打造擁有完整功能的前端。
GET /oauth/scopes
這個路由會返回應用所定義的所有域。你可以使用這個路由來列出使用者可以分配給私人訪問令牌的所有域:
axios.get('/oauth/scopes') .then(response => { console.log(response.data); });
GET /oauth/personal-access-tokens
這個路由會返回該認證使用者所建立的所有私人訪問令牌,這在列出使用者的所有令牌以便編輯或刪除時很有用:
axios.get('/oauth/personal-access-tokens') .then(response => { console.log(response.data); });
POST /oauth/personal-access-tokens
這個路由會建立一個新的私人訪問令牌,該路由要求傳入兩個引數:令牌的 name
和需要分配到這個令牌的 scopes
:
const data = { name: 'Token Name', scopes: [] }; axios.post('/oauth/personal-access-tokens', data) .then(response => { console.log(response.data.accessToken); }) .catch (response => { // List errors on response... });
DELETE /oauth/personal-access-tokens/{token-id}
這個路由可以用於刪除私人訪問令牌:
axios.delete('/oauth/personal-access-tokens/' + tokenId);
路由保護
通過中介軟體
Passport 提供了一個認證 guard 用於驗證輸入請求的訪問令牌,當你使用 passport
驅動配置好 api
guard 後,只需要在所有路由上指定需要傳入有效訪問令牌的 auth:api
中介軟體即可:
Route::get('/user', function () { // })->middleware('auth:api');
傳遞訪問令牌
呼叫被 Passport 保護的路由時,應用 API 的消費者需要在請求的 Authorization
頭中指定它們的訪問令牌作為 Bearer
令牌。例如:
$response = $client->request('GET', '/api/user', [ 'headers' => [ 'Accept' => 'application/json', 'Authorization' => 'Bearer '.$accessToken, ], ]);
令牌作用域
定義作用域
作用域(Scope)允許 API 客戶端在請求賬戶授權的時候請求特定的許可權集合。例如,如果你在構建一個電子商務應用,不是所有的 API 消費者都需要下訂單的能力,取而代之地,你可以讓這些消費者只請求訪問訂單物流狀態的許可權,換句話說,作用域允許你的應用使用者限制第三方應用自身可以執行的操作。
你可以在 AuthServiceProvider
的 boot
方法中使用 Passport::tokensCan
方法定義 API 的作用域。 tokensCan
方法接收作用域名稱陣列和作用域描述,作用域描述可以是任何你想要在授權通過頁面展示給使用者的東西:
use Laravel\Passport\Passport; Passport::tokensCan([ 'place-orders' => 'Place orders', 'check-status' => 'Check order status', ]);
預設作用域
如果一個客戶端沒有請求任何指定域,可以通過 setDefaultScope
方法配置 Passport 服務端新增一個預設的域到令牌。通常,我們在 AuthServiceProvider
的 boot
方法中呼叫這個方法:
use Laravel\Passport\Passport; Passport::setDefaultScope([ 'check-status', 'place-orders', ]);
分配作用域到令牌
請求授權碼
當使用授權碼請求訪問令牌時,消費者應該指定他們期望的作用域作為 scope
查詢字串引數, scope
引數是通過空格分隔的作用域列表:
Route::get('/redirect', function () { $query = http_build_query([ 'client_id' => 'client-id', 'redirect_uri' => 'http://example.com/callback', 'response_type' => 'code', 'scope' => 'place-orders check-status', ]); return redirect('http://your-app.com/oauth/authorize?'.$query); });
頒發私人訪問令牌
如果你使用 User
模型的 createToken
方法頒發私人訪問令牌,可以傳遞期望的作用域陣列作為該方法的第二個引數:
$token = $user->createToken('My Token', ['place-orders'])->accessToken;
檢查作用域
Passport 提供了兩個可用於驗證輸入請求是否經過已發放作用域的令牌認證的中介軟體。開始使用之前,新增如下中介軟體到 app/Http/Kernel.php
檔案的 $routeMiddleware
屬性:
'scopes' => \Laravel\Passport\Http\Middleware\CheckScopes::class, 'scope' => \Laravel\Passport\Http\Middleware\CheckForAnyScope::class,
檢查所有作用域
scopes
中介軟體會分配給一個用於驗證輸入請求的訪問令牌擁有所有列出作用域的路由:
Route::get('/orders', function () { // Access token has both "check-status" and "place-orders" scopes... })->middleware('scopes:check-status,place-orders');
檢查任意作用域
scope
中介軟體會分配給一個用於驗證輸入請求的訪問令牌擁有至少一個列出作用域的路由:
Route::get('/orders', function () { // Access token has either "check-status" or "place-orders" scope... })->middleware('scope:check-status,place-orders');
檢查令牌例項上的作用域
當一個訪問令牌認證過的請求進入應用後,你仍然可以使用經過認證的 User
例項上的 tokenCan
方法來檢查這個令牌是否擁有給定作用域:
use Illuminate\Http\Request; Route::get('/orders', function (Request $request) { if ($request->user()->tokenCan('place-orders')) { // } });
額外的域方法
scopeIds
方法將會返回所有已定義的 ID/名字陣列:
Laravel\Passport\Passport::scopeIds();
scopes
方法將會返回所有已定義的 Laravel\Passport\Scope
域例項陣列:
Laravel\Passport\Passport::scopes();
scopesFor
方法將會返回匹配給定 ID/名字的 Laravel\Passport\Scope
例項陣列:
Laravel\Passport\Passport::scopesFor(['place-orders', 'check-status']);
你可以使用 hasScope
方法判斷給定域是否被定義過:
Laravel\Passport\Passport::hasScope('place-orders');
使用 JavaScript 消費 API
構建 API 時,能夠從你的 JavaScript 應用消費你自己的 API 非常有用。這種 API 開發方式允許你自己的應用消費你和其他人分享的同一個 API,這個 API 可以被你的 Web 應用消費,也可以被你的移動應用消費,還可以被第三方應用消費,以及任何你可能釋出在多個包管理器上的 SDK 消費。
通常,如果你想要從你的 JavaScript 應用消費自己的 API,需要手動傳送訪問令牌到應用並在應用的每一個請求中傳遞它。不過,Passport 提供了一箇中間件用於處理這一操作。你所需要做的只是在 app/Http/Kernel.php
檔案中新增這個中介軟體 CreateFreshApiToken
到 web
中介軟體組:
'web' => [ // Other middleware... \Laravel\Passport\Http\Middleware\CreateFreshApiToken::class, ],
注:你需要確保 EncryptCookies
中介軟體位於 CreateFreshApiToken
中介軟體之前執行。
這個 Passport 中介軟體將會附加 laravel_token
Cookie 到輸出響應,這個 Cookie 包含加密過的JWT,Passport 將使用這個 JWT 來認證來自 JavaScript 應用的 API 請求,現在,你可以傳送請求到應用的 API,而不必顯示傳遞訪問令牌:
axios.get('/user') .then(response => { console.log(response.data); });
自定義 Cookie 名稱
如果需要的話,你可以使用 Passport::cookie
方法自定義 laravel_token
Cookie 的名稱。通常,我們在 AuthServiceProvider
的 boot
方法中呼叫相應的方法:
/** * Register any authentication / authorization services. * * @return void */ public function boot() { $this->registerPolicies(); Passport::routes(); Passport::cookie('custom_name'); }
CSRF 保護
使用這種認證方法時,Laravel 預設 JavaScript 腳手架程式碼會告知 Axios 傳送 X-CSRF-TOKEN
和 X-Requested-With
請求頭。不過,你需要確保在HTML meta 標籤中引入了 CSRF 令牌:
// In your application layout... <meta name="csrf-token" content="{{ csrf_token() }}"> // Laravel's JavaScript scaffolding... window.axios.defaults.headers.common = { 'X-Requested-With': 'XMLHttpRequest', };
事件
Passport 會在頒發訪問令牌和重新整理令牌時觸發事件,你可以使用這些事件來處理或撤銷資料庫中的其它訪問令牌,你可以在應用的 EventServiceProvider
中新增監聽器到這些事件:
/** * The event listener mappings for the application. * * @var array */ protected $listen = [ 'Laravel\Passport\Events\AccessTokenCreated' => [ 'App\Listeners\RevokeOldTokens', ], 'Laravel\Passport\Events\RefreshTokenCreated' => [ 'App\Listeners\PruneOldTokens', ], ];
測試
Passport 的 actingAs
方法可用於指定當前認證使用者及其作用域,傳遞給 actingAs
方法的第一個引數是使用者例項,第二個引數是授權給使用者令牌的作用域陣列:
public function testServerCreation() { Passport::actingAs( factory(User::class)->create(), ['create-servers'] ); $response = $this->post('/api/create-server'); $response->assertStatus(201); }