面臨的問題:
- 登入欄位小於或等於2個的
- 登入欄位大於或等於2個的
登入欄位不超過兩個
我在網上看到一種相對簡單解決方案,但是不能解決所有兩個欄位的驗證:
filter_var($request->input('username'), FILTER_VALIDATE_EMAIL) ? 'email' : 'name'
過濾請求中的表單內容,實現區分 username。弊端顯而易見,如果另一個不是 email 就抓瞎了……,下面是另一種通用的解決方案,在 LoginController 中重寫 login 方法:
public function login(Requests $request) {
//假設欄位是 email
if ($this->guard()->attempt(['email' =>$request->only('username'), 'password' => $request->only('password')]))) {
return $this->sendLoginResponse($request);
}
//假設欄位是 mobile
if ($this->guard()->attempt(['mobile' =>$request->only('username'), 'password' => $request->only('password')])) {
return $this->sendLoginResponse($request);
}
//假設欄位是 username
if ($this->guard()->attempt(['username' =>$request->only('username'), 'password' => $request->only('password')])) {
return $this->sendLoginResponse($request);
}
return $this->sendFailedLoginResponse($request);
}
可以看到雖然能解決問題,但是顯然有悖於 Laravel 的優雅風格!你也可以參照medz 在 1樓回覆的 方案。賣了這麼多關子,下面跟大家分享一下我的解決方案。
登入欄位大於或等於三個的(相對複雜一些)
為了方便理解我畫了個大致的流程,只畫了我認為重要的部分
-
首先需要自己實現一個
Illuminate\Contracts\Auth\UserProvider
的實現,具體可以參考 新增自定義使用者提供器 但是我喜歡偷懶,就直接繼承了EloquentUserProvider
,並重寫了retrieveByCredentials
方法:public function retrieveByCredentials(array $credentials) { if (empty($credentials)) { return; } $query = $this->createModel()->newQuery(); foreach ($credentials as $key => $value) { if (! Str::contains($key, 'password')) { $query->orWhere($key, $value); } } return $query->first(); }
注意
: 框架源生的是$query->where($key, $value);
,多個欄位同時驗證,也就是說欄位之間是and 的關係
;將$query->where($key, $value);
改為$query->orWhere($key, $value);
就可以了! -
緊接著需要註冊自定義的 UserProvider:
class AuthServiceProvider extends ServiceProvider { /** * 註冊任何應用認證/授權服務。 * * @return void */ public function boot() { $this->registerPolicies(); Auth::provider('custom', function ($app, array $config) { // 返回 Illuminate\Contracts\Auth\UserProvider 例項... return new CustomUserProvider(new BcryptHasher(), config('auth.providers.custom.model')); }); } }
-
最後我們修改一下 auth.php 的配置就搞定了:
'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'custom', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], 'providers' => [ 'users' => [ 'driver' => 'eloquent', 'model' => App\Models\User::class, ], 'custom' => [ 'driver' => 'custom', 'model' => App\Models\User::class, ], ],
-
最後看一下 LoginController 的程式碼:
public function login(LoginRequest $request)
{
$username = $request->get('username');
$result = $this->guard()->attempt([
'username' => $username,
'email' => $username,
'mobile' => $username,
'password' => $request->get('password')]);
if ($result) {
return $this->sendLoginResponse($request);
}
$this->incrementLoginAttempts($request);
return $this->sendFailedLoginResponse($request);
}
現在哪怕你有在多個欄位都妥妥的……??
最後得得承認一下,在與medz討論的過程中有些上頭了
,在這裡表示歉意。
後來我重新審視了一遍,覺得確實存在許多問題,於是繪製了認證流程圖,還請多多指點??
感謝medz的認真評論和回覆 :beers:
以下是 NicolaBonelli 給出的解決方案
我這裡按照他的思路只實現了login,其他細節還需自己根據實際需求進行修改,更多討論請看這裡20樓
- 首先需要在已有的
users
表上移除使用者憑證的欄位,如email、name等等,然後再建立另一個用於儲存使用者憑證的Certificate模型和表,大致結構如下圖所示:users
表定義如下:Schema::create('users', function (Blueprint $table) { $table->increments('id'); //使用者暱稱 $table->string('nickname')->nullable(); $table->string('password'); //使用者狀態 $table->boolean('status')->default(true); $table->rememberToken(); $table->timestamps(); });
certificates
表定義如下:
Schema::create('certificates', function (Blueprint $table) {
$table->increments('id');
//關聯使用者表
$table->integer('user_id')->unsigned();
//使用者驗證憑據
$table->string('account')->unique();
//憑據型別
$table->string('type');
$table->timestamps();
});
- 修改
config/auth.php
內容如下:
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'certificate',
],
],
'providers' => [
'certificate' => [
'driver' => 'eloquent',
'model' => App\Certificate::class,
],
],
- 緊接著修改
Certificate
模型,讓他繼承Illuminate\Foundation\Auth\User
這個類,並重寫getAuthPassword
方法:
/**
* 定義模型關聯
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function user()
{
return $this->belongsTo(User::class, 'user_id');
}
/**
* 獲取使用者憑證所對應的密碼
* @return string
* @throws AuthenticationException
*/
public function getAuthPassword()
{
if ($this->user()) {
return $this->user->password;
}
throw new AuthenticationException();
}
到這裡多欄位登入功能算是實現了,但是
登出
時會因為certificates
表中沒有定義remember_token
欄位而導致丟擲異常。要解決這個問題,我們還要重寫logout
方法,甚至重新實現一個自定義Guard
……這裡就不做分析了,有興趣的可以自行ReviewIlluminate\Auth
的原始碼!
最後感謝 NicolaBonelli 的分享 :beers:
本作品採用《CC 協議》,轉載必須註明作者和本文連結