[ Laravel 5.3 文檔 ] 安全 ―― API認證(Passport)保障安全性。


1、簡介

Laravel通過傳統的登錄表單已經讓用戶認證變得很簡單,但是API怎么辦?API通常使用token進行認證並且在請求之間不維護session狀態。Laravel使用LaravelPassport讓API認證變得輕而易舉,Passport基於Alex Bilbie維護的 League OAuth2 server ,可以在數分鍾內為Laravel應用提供完整的OAuth2服務器實現。

注:本文檔假設你已經很熟悉OAuth2,如果你對OAuth2一無所知,那么在開始學習文文檔之前,先要去熟悉OAuth2的一些術語和特性(參考阮一峰博客: 理解OAuth 2.0 )。

2、安裝

使用Composer包管理器安裝Passport:

composer require laravel/passport

接下來,在配置文件 config/app.php 的 providers 數組中注冊Passport服務提供者:

Laravel\Passport\PassportServiceProvider::class,

Passport服務提供着為框架注冊了自己的數據庫遷移目錄,所以在注冊之后需要遷移數據庫,Passport遷移將會為應用生成用於存放客戶端和訪問令牌的數據表:

php artisan migrate

接下來,需要運行 passport:install 命令,該命令將會創建生成安全訪問令牌(token)所需的加密鍵,此外,該命令還會創建“personal access”和“password grant”客戶端用於生成訪問令牌:

php artisan passport:install

運行完這個命令后,添加 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的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')
);
Vue.component(
'passport-authorized-clients',
require('./components/passport/AuthorizedClients.vue')
);
Vue.component(
'passport-personal-access-tokens',
require('./components/passport/PersonalAccessTokens.vue')
);

注冊完成后,就可以將它們放到應用的某個模板中以便創建客戶端和私人訪問令牌:

<passport-clients></passport-clients>
<passport-authorized-clients></passport-authorized-clients>
<passport-personal-access-tokens></passport-personal-access-tokens> 3、配置 令牌生命周期

默認情況下,Passport頒發的訪問令牌(access token)是長期有效的,如果你想要配置更短的令牌生命周期,可以使用 tokensExpireIn 和 refreshTokensExpireIn 方法,這些方法需要在 AuthServiceProvider 的 boot 方法中調用:

/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
Passport::routes();
Passport::tokensExpireIn(Carbon::now()->addDays(15));
Passport::refreshTokensExpireIn(Carbon::now()->addDays(30));
精簡撤銷令牌

默認情況下,Passport不會從數據庫刪除撤銷的訪問令牌,隨着時間的推移,在數據庫中會累積大量的撤銷令牌,如果你希望Passport可以自動刪除撤銷的令牌,可以在 AuthServiceProvider 的 boot 方法中調用 pruneRevokedTokens 方法:

use Laravel\Passport\Passport;
Passport::pruneRevokedTokens();

該方法不會立即刪除所有的撤銷令牌,而是當用戶請求一個新的訪問令牌或者刷新已存在令牌的時候才會刪除撤銷的令牌。

4、頒發訪問令牌

通過授權碼使用OAuth2是大多數開發者屬性的方式。使用授權碼的時候,客戶端應用會將用戶重定向到你的服務器,服務器將會通過或決絕頒發訪問令牌到客戶端的請求。

管理客戶端

首先,開發構建和你的應用API交互的應用時,需要通過創建一個“客戶端”以便將他們的應用注冊到你的應用。通常,這一過程包括提供應用的名稱以及用戶授權請求通過后重定向到的URL。

passport:client命令

創建客戶端最簡單的方式就是使用Artisan命令 passport:client ,該命令可用於創建你自己的客戶端以方便測試OAuth2功能。當你運行 client 命令時,Passport會提示你關於客戶端的更多信息,並且為你提供client ID和secret:

php artisan passport:client JSON API

由於用戶不能使用client命令,Passport提供了一個JSON API用於創建客戶端,這省去了你手動編寫控制器用於創建、更新以及刪除客戶端的麻煩。

不過,你需要配對Passport的JSON API和自己的前端以便為用戶提供一個可以管理他們自己客戶端的后台,下面,我們來回顧下所有用於管理客戶端的API,為了方便,我們將會使用Vue來演示發送HTTP請求到API:

注:如果你不想要自己實現整個客戶端管理前端,可以使用前端快速上手教程在數分鍾內擁有完整功能的前端。

GET /oauth/clients

這個路由為認證用戶返回所有客戶端,這在展示用戶客戶端列表時很有用,可以讓用戶很容易編輯或刪除客戶端:

this.$http.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'
};
this.$http.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'
};
this.$http.put('/oauth/clients/' + clientId, data)
.then(response => {
console.log(response.data);
})
.catch (response => {
// List errors on response...
});

DELETE /oauth/clients/{client-id}

這個路由用於刪除客戶端:

this.$http.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 方法定義了,不需要手動定義這個路由。

通過請求

接收授權請求的時候,Passport會自動顯示一個視圖模板給用戶從而允許他們通過或拒絕授權請求,如果用戶通過請求,就會被重定向回消費者應用指定的 redirect_uri ,這個 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('/callback', function (Request $request) {
$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => ['grant_type' => 'authorization_code','client_id' => 'client-id','client_secret' => 'client-secret','redirect_uri' => 'http://example.com/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 方法定義過了,不需要手動定義這個路由。

刷新令牌

如果應用頒發的是短期有效的訪問令牌,那么用戶需要通過訪問令牌頒發時提供的 refresh_token 刷新訪問令牌,在這個例子中,我們使用Guzzle HTTP庫來刷新令牌:

$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/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)。

5、密碼發放令牌

OAuth2 密碼發放允許你的其他第一方客戶端,例如移動應用,使用郵箱地址/用戶名+密碼獲取訪問令牌。這使得你可以安全地頒發訪問令牌給第一方客戶端而不必要求你的用戶走整個OAuth2授權碼重定向流程。

創建一個密碼發放客戶端

在應用可以通過密碼發放頒發令牌之前,需要創建一個密碼發放客戶端,你可以通過使用帶 --password 選項的 passport:client 命令來實現。如果你已經運行了 passport:install 命令,則不必再運行這個命令:

php artisan passport:client --password 請求令牌

創建完密碼發放客戶端后,可以通過發送POST請求到 /oauth/token 路由(帶上用戶郵箱地址和密碼參數)獲取訪問令牌。這個路由已經通過 Passport::routes 方法注冊過了,不需要手動定義。如果請求成功,就可以從服務器返回的JSON響應中獲取 access_token 和 refresh_token :

$http = new GuzzleHttp\Client;
$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'client_secret' => 'client-secret',
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '',
],
]);
return json_decode((string) $response->getBody(), true);

注:記住,訪問令牌默認長期有效,不過,如果需要的話你也可以配置訪問令牌的最長生命周期。

請求所有域

使用密碼發放的時候,你可能想要授權應用所支持的所有域的令牌,這可以通過請求 * 域來實現。如果你請求的是 * 域,則令牌實例上的 can 方法總是返回 true ,這個域只會分配給使用 password 發放的令牌:

$response = $http->post('http://your-app.com/oauth/token', [
'form_params' => [
'grant_type' => 'password',
'client_id' => 'client-id',
'username' => 'taylor@laravel.com',
'password' => 'my-password',
'scope' => '*',
],
]); 6、私人訪問令牌

有時候,你的用戶可能想要頒發訪問令牌給自己而不走典型的授權碼重定向流程。允許用戶通過應用的UI頒發令牌給自己在用戶體驗你的API或者作為更簡單的頒發訪問令牌方式時會很有用。

注:私人訪問令牌總是一直有效的,它們的生命周期在使用 tokensExpireIn 或 refreshTokensExpireIn 方法時不會修改。

創建一個私人訪問客戶端

在你的應用可以頒發私人訪問令牌之前,需要創建一個私人的訪問客戶端。你可以通過帶 --personal 選項的 passport:client 命令來實現,如果你已經運行過了 passport:install 命令,則不必再運行此命令:

php artisan passport:client --personal 管理私人訪問令牌

創建好私人訪問客戶端之后,就可以使用 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。為了方便起見,我們使用Vue來演示發送HTTP請求到API。

注:如果你不想要實現自己的私人訪問令牌前端,可以使用前端快速上手教程在數分鍾內打造擁有完整功能的前端。

GET /oauth/scopes

這個路由會返回應用所定義的所有域。你可以使用這個路由來列出用戶可以分配給私人訪問令牌的所有域:

this.$http.get('/oauth/scopes')
.then(response => {
console.log(response.data);
});

GET /oauth/personal-access-tokens

這個路由會返回該認證用戶所創建的所有私人訪問令牌,這在列出用戶的所有令牌以便編輯或刪除時很有用:

this.$http.get('/oauth/personal-access-tokens')
.then(response => {
console.log(response.data);
});

POST /oauth/personal-access-tokens

這個路由會創建一個新的私人訪問令牌,該路由要求傳入兩個參數:令牌的 name 和需要分配到這個令牌的 scopes :

const data = {
name: 'Token Name',
scopes: []
};
this.$http.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}

這個路由可以用於刪除私人訪問令牌:

this.$http.delete('/oauth/personal-access-tokens/' + tokenId); 7、路由保護 通過中間件

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,
],


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM