之前在分析套件都是只有在看 Laravel 原始碼,沒有任何客製化的範例,而今天會來示範如何客製化驗證機制。
雖然 Laravel 已經實作得很完整了,但不同的情境下,可能還是需要寫些客製化程式。
舉個情境:既有的 Credentials 資料來源無法使用 DatabaseUserProvider 或 EloquentUserProvider 讀取,如,資料是 PHP Array:
$credentials = [
// 帳號 => 密碼
'admin' => 'password',
'miles' => '123456'
];
因資料的結構改變,所以 Authenticatable 也可能需要自己寫一個。先來看一下它的介面:
// 取得 ID 時,所會需要用到的 key / col 名稱等識別名稱
public function getAuthIdentifierName();
// 取得 ID
public function getAuthIdentifier();
// 取得密碼的內容
public function getAuthPassword();
// 取得 remember token
public function getRememberToken();
// 設定 remember token
public function setRememberToken($value);
// 取得 remember token 時,所會需要用到的 key / col 名稱等識別名稱
public function getRememberTokenName();
很明顯,因為例子裡的資料並沒有 remember token,所以需要來實作一下:
class User implements Authenticatable
{
private $attributes;
public function __construct($attributes)
{
$this->attributes = $attributes;
}
public function getAuthIdentifierName()
{
return 'id';
}
public function getAuthIdentifier()
{
return $this->attributes[$this->getAuthIdentifierName()];
}
public function getAuthPassword()
{
return $this->attributes['password'];
}
public function getRememberToken()
{
// 不使用 remember token,回傳空字串
return '';
}
public function setRememberToken($value)
{
// 不使用 remember token,pass
}
public function getRememberTokenName()
{
// 不使用 remember token,回傳空字串
return '';
}
}
事實上,也可以使用 GenericUser,但它的 getRememberToken()
可能會發現 key 不存在的錯誤:
public function __construct(array $attributes)
{
$this->attributes = $attributes;
}
public function getRememberToken()
{
return $this->attributes[$this->getRememberTokenName()];
}
public function getRememberTokenName()
{
return 'remember_token';
}
如果對 guard 或 controller 的原始碼不熟的話,比方說哪裡突然有呼叫到,也許就會發生非預期的結果。
當然這也是能解決的,等等下面看 provider 是如何做的就會了解。
在 UML 圖裡,有一個角色是專門提供資料的--UserProvider,它的介面如下:
// 使用 ID 取得 Authenticatable 實例
public function retrieveById($identifier);
// 透過 ID 與 remember token 取得 Authenticatable 實例
public function retrieveByToken($identifier, $token);
// 更新 remember token
public function updateRememberToken(Authenticatable $user, $token);
// 透過 credentials 取得 Authenticatable 實例
public function retrieveByCredentials(array $credentials);
// 透過 Authenticatable 來驗證 credentials
public function validateCredentials(Authenticatable $user, array $credentials);
DatabaseUserProvider 與 EloquentUserProvider 都實作了這個介面,即然這兩個實作都不能用的話,就自己寫一個:
class ArrayUserProvider extends UserProvider
{
private $credentials = [
'admin' => 'password',
'miles' => '123456'
];
public function retrieveById($identifier)
{
if (!isset($this->credentials[$identifier])) {
return null;
}
$attributes = [
'id' => $identifier,
'password' => $this->credentials[$identifier],
// 如果要用 GenericUser 的話,加下面這行即可
'remember_token' => '',
]
return new User($attributes);
}
public function retrieveByToken($identifier, $token)
{
// 不使用 remember token,所以回傳 null
return null;
}
public function updateRememberToken(Authenticatable $user, $token)
{
// 不使用 remember token,所以不做事
return null;
}
public function retrieveByCredentials(array $credentials)
{
// 如果沒有給 id 欄位的話,無法找到對應的 user
if (!isset($credentials['id']) {
return null;
}
$attributes = [
'id' => $credentials['id'],
'password' => $this->credentials[$credentials['id']],
// 如果要用 GenericUser 的話,加下面這行即可
'remember_token' => '',
]
return new User($attributes);
}
public function validateCredentials(Authenticatable $user, array $credentials)
{
// 如果沒有給 password 欄位的話,等於驗證失敗
if (!isset($credentials['password']) {
return false;
}
return $user->getAuthPassword() === $credentials['password'];
}
}
到目前為止,自定義的 Array 認證功能就算客製化完成了,只要最後設定一下即可。首先要先註冊 ArrayUserProvider:
// 在 boot 階段做即可
Auth::provider('array', function() {
return new ArrayUserProvider();
});
接著加上設定:
return [
'defaults' => [
'guard' => 'web',
'passwords' => 'users',
],
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
],
'providers' => [
'users' => [
// 這裡的 driver 使用 array 即可對應上面註冊的 ArrayUserProvider
'driver' => 'array',
],
],
];
最後,預設的 AuthenticatesUsers 是使用 email
當作登入名稱的,可以覆寫 username()
方法的值為 id
,即可運作正常。