起
最近研究 laravel 自帶的認證,發現使用token認證方式的話,預設傳值欄位和資料庫中欄位名都是api_token
,這讓我很不爽,laravel 這樣的框架一定有什麼自定義的途徑,網上查詢無果,那就自己看看原始碼吧。
尋
我們知道,認證其實就是呼叫Auth::guard()
來執行的,那我們不妨從這裡開始看起。
自定義dirver配置項
我們知道Auth
是Illuminate/Auth/AuthManager.php
的alias,那我們開啟這個檔案,找到guard
後,發現判斷用實際哪個guard的程式碼在方法resolve
中,其中有一段程式碼:
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
發現這個類中有一個陣列customCreators
,這說明,我們是可以自定義driver的。也就是說,雖然config/auth.php
的guards
中說driver
只支援session
和token
,但實際上是可以自己擴充套件的。
那怎麼擴充套件呢,順藤摸瓜,發現class AuthManager
中有一個方法extend
:
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
這個方法就是給Auth新增自定義driver用的,那我們直接找個provider擴充套件一下Auth,把自己的driver新增進去就好。
我們可以先看一下預設的token driver是怎麼寫的:
public function createTokenDriver($name, $config)
{
$guard = new TokenGuard(
$this->createUserProvider($config['provider'] ?? null),
$this->app['request']
);
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
}
就這麼幾行程式碼,在5.6以後TokenGuard
的建構函式是有4個引數的,原型如下
public function __construct(UserProvider $provider, Request $request, $inputKey = 'api_token', $storageKey = 'api_token');
也就是說,我們把後兩個引數傳進去就可以了。那就開始搞起,例如在AppServiceProvider
的boot
方法中新增:
Auth::extend('web-token', function ($app, $name, $config) {
$guard = new WebTokenGuard(
$this->createUserProvider($config['provider'] ?? null),
$app['request'],
'token',
'token'
);
$app->refresh('request', $guard, 'setRequest');
return $guard;
});
這樣我們就有了一個新的driver——web-token
,config中我們就可以寫:
'guards' => [
'web-api' => [
'driver' => 'web-token',
'provider' => 'tokens',
],
],
這裡發現我的provider也不是users,是因為我新建了一張表專門存token用,配置起來很方便,在同檔案的providers
中新增如下配置即可:
'tokens' => [
'driver' => 'eloquent',
'model' => App\Models\Token::class,
],
但是這樣一來,就產生了新的問題:我們在使用laravel的時候,總會使用類似Auth::id()
,Auth::user()
這樣的方法,因為確實很方便,而我們的認證現在雖然沒有問題了,但是因為跟user不是同表,這些方法現在不能用了,那怎麼辦呢?我們首先來解決第一個問題:
實現 Auth:id()
我們可以看一下框架帶的User Model,發現其繼承自Illuminate\Foundation\Auth\User
,順藤摸瓜,我們發現他實現了三個介面:
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
這三個介面各有作用,比方說第一個AuthenticatableContract
,就是我們目前要實現的,因為其中有一個方法public function getAuthIdentifier();
,這個方法就是執行Auth::id()
時候會呼叫的。
第二個介面是授權時候用的,第三個是重置密碼時候用的,這兩個我們暫時不需要,那就等以後用到時候再說。
我們現在讓Token Model實現第一個介面,方法的實現根據各自的情況有所不同,例如我的token表中,使用者的id列名為user_id
,那我的方法就是這樣子的:
public function getAuthIdentifierName()
{
return 'user_id';
}
而因為我實現了實體關係:
public function user()
{
return $this->belongsTo('App\User');
}
其他的一些方法可以這樣寫:
public function getAuthPassword()
{
return $this->user->password;
}
這樣大體上第一個問題我們就解決了,接下來解決第二個問題。
實現 Auth:user()
首先,我們來找一下這個user()方法在哪裡,看了AuthManager
以後發現,這個user()原來是呼叫的Guard中的方法,那怎麼辦呢?我們擴充套件一下TokenGuard吧。
新建一個php類繼承於TokenGuard
,並重寫user()
方法:
class WebTokenGuard extends TokenGuard
{
public function user()
{
$token = parent::user();
if (!$token) {
return null;
}
return $token->user;
}
}
return null
並不會產生問題,這樣會使Guard的check()
方法返回false,丟擲AuthenticationException
異常,這與正常的邏輯是一致的。
這樣我們就解決了Auth::user()
的問題,擴充套件類還可以解決一些其他的問題,例如在5.4版本中,$inputKey
和$storageKey
是直接寫在建構函式中的,而不是引數穿進去的,擴充套件類也可以解決這個問題。
最佳化
因為使用用了新的Guard類,我們在provider中的程式碼也需要做一些修改,而同時,我們把欄位名也寫到config中,便於以後修改,最佳化後的程式碼如下:
# auth.config
'web-api' => [
'driver' => 'web-token',
'input_key' => 'token',
'storage_key' => 'token',
'provider' => 'tokens',
],
# AppServiceProvider 的 boot
Auth::extend('web-token', function ($app, $name, $config) {
$guard = new WebTokenGuard(
$this->createUserProvider($config['provider'] ?? null),
$app['request'],
$config['input_key'],
$config['storage_key']
);
$app->refresh('request', $guard, 'setRequest');
return $guard;
});
這樣基本就趨近於最終版本了。
尾
laravel 確實是一個龐大的框架,這次只涉及到了認證的問題,而未談及授權,例如Auth::can()
這樣的方法,現在也是不能使用的。
授權也是一個大頭的問題,不同的需求採用的方式不盡相同,laravel也自帶一套授權的機制,希望本篇文章可以達到一個拋磚引玉的作用,給小夥伴們一些啟發。
本作品採用《CC 協議》,轉載必須註明作者和本文連結