這篇文章主要介紹了關於Laravel使用者認證系統的實現細節,有著一定的參考價值,現在分享給大家,有需要的朋友可以參考一下
使用者認證系統的實現細節
上一節我們介紹來Laravel Auth系統的基礎知識,說了他的核心組件都有哪些構成,這一節我們會專註Laravel Auth系統的實現細節,主要關注Auth
也就是AuthManager
是如何裝載認證用的看守器(Guard)和使用者提供器(UserProvider)以及預設的使用者註冊和登入的實現細節,通過梳理這些實現細節我們也就能知道應該如何定製Auth認證來滿足我們自己項目中使用者認證的需求的。
通過AuthManager裝載看守器和使用者提供器
AuthManager裝載看守器和使用者提供器用到的方法比較多,用文字描述不太清楚,我們通過註解這個過程中用到的方法來看具體的實現細節。
namespace Illuminate\Auth;class AuthManager implements FactoryContract{ /** * 嘗試從$guards屬性中擷取指定的Guard * * @param string $name * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard */ public function guard($name = null) { $name = $name ?: $this->getDefaultDriver(); return isset($this->guards[$name]) ? $this->guards[$name] : $this->guards[$name] = $this->resolve($name); } /** * 解析出給定name的Guard * * @param string $name * @return \Illuminate\Contracts\Auth\Guard|\Illuminate\Contracts\Auth\StatefulGuard * * @throws \InvalidArgumentException */ protected function resolve($name) { //擷取Guard的配置 //$config = ['driver' => 'session', 'provider' => 'users'] $config = $this->getConfig($name); if (is_null($config)) { throw new InvalidArgumentException("Auth guard [{$name}] is not defined."); } //如果通過extend方法為guard定義了磁碟機,這裡去調用自訂的Guard磁碟機 if (isset($this->customCreators[$config['driver']])) { return $this->callCustomCreator($name, $config); } //Laravel auth預設的配置這裡是執行createSessionDriver $driverMethod = 'create'.ucfirst($config['driver']).'Driver'; if (method_exists($this, $driverMethod)) { return $this->{$driverMethod}($name, $config); } throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined."); } /** * 從config/auth.php中擷取給定名稱的Guard的配置 * * @param string $name * @return array */ 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'token', 'provider' => 'users', ], ], protected function getConfig($name) { //'guards' => [ // 'web' => [ // 'driver' => 'session', // 'provider' => 'users', // ], // 'api' => [ // 'driver' => 'token', // 'provider' => 'users', // ], //], // 根據Laravel預設的auth配置, 這個方法會擷取key "web"對應的數組 return $this->app['config']["auth.guards.{$name}"]; } /** * 調用自訂的Guard磁碟機 * * @param string $name * @param array $config * @return mixed */ protected function callCustomCreator($name, array $config) { return $this->customCreators[$config['driver']]($this->app, $name, $config); } /** * 註冊一個自訂的閉包Guard 磁碟機 到customCreators屬性中 * * @param string $driver * @param \Closure $callback * @return $this */ public function extend($driver, Closure $callback) { $this->customCreators[$driver] = $callback; return $this; } /** * 註冊一個自訂的使用者提供器建立器到 customProviderCreators屬性中 * * @param string $name * @param \Closure $callback * @return $this */ public function provider($name, Closure $callback) { $this->customProviderCreators[$name] = $callback; return $this; } /** * 建立基於session的認證看守器 SessionGuard * * @param string $name * @param array $config * @return \Illuminate\Auth\SessionGuard */ public function createSessionDriver($name, $config) { //$config['provider'] == 'users' $provider = $this->createUserProvider($config['provider'] ?? null); $guard = new SessionGuard($name, $provider, $this->app['session.store']); if (method_exists($guard, 'setCookieJar')) { $guard->setCookieJar($this->app['cookie']); } if (method_exists($guard, 'setDispatcher')) { $guard->setDispatcher($this->app['events']); } if (method_exists($guard, 'setRequest')) { $guard->setRequest($this->app->refresh('request', $guard, 'setRequest')); } return $guard; } //建立Guard驅動依賴的使用者提供器對象 public function createUserProvider($provider = null) { if (is_null($config = $this->getProviderConfiguration($provider))) { return; } //如果通過Auth::provider方法註冊了自訂的使用者提供器creator閉包則去調用閉包擷取使用者提供器對象 if (isset($this->customProviderCreators[$driver = ($config['driver'] ?? null)])) { return call_user_func( $this->customProviderCreators[$driver], $this->app, $config ); } switch ($driver) { case 'database': return $this->createDatabaseProvider($config); case 'eloquent': //通過預設的auth配置這裡會返回EloquentUserProvider對象,它實現了Illuminate\Contracts\Auth 介面 return $this->createEloquentProvider($config); default: throw new InvalidArgumentException( "Authentication user provider [{$driver}] is not defined." ); } } /** * 會通過__call去動態地調用AuthManager代理的Guard的使用者認證相關方法 * 根據預設配置,這裡__call會去調用SessionGuard裡的方法 * @param string $method * @param array $parameters * @return mixed */ public function __call($method, $parameters) { return $this->guard()->{$method}(...$parameters); }}
註冊使用者
Laravel Auth系統中預設的註冊路由如下:
$this->post('register', 'Auth\RegisterController@register');
所以使用者註冊的邏輯是由RegisterController的register方法來完成的
class RegisterController extends Controller{ //方法定義在Illuminate\Foundation\Auth\RegisterUsers中 public function register(Request $request) { $this->validator($request->all())->validate(); event(new Registered($user = $this->create($request->all()))); $this->guard()->login($user); return $this->registered($request, $user) } protected function validator(array $data) { return Validator::make($data, [ 'name' => 'required|string|max:255', 'email' => 'required|string|email|max:255|unique:users', 'password' => 'required|string|min:6|confirmed', ]); } protected function create(array $data) { return User::create([ 'name' => $data['name'], 'email' => $data['email'], 'password' => bcrypt($data['password']), ]); } }
register的流程很簡單,就是驗證使用者輸入的資料沒問題後將這些資料寫入資料庫產生使用者,其中密碼加密採用的是bcrypt演算法,如果你需要改成常用的salt加密碼明文做雜湊的密碼加密方法可以在create方法中對這部分邏輯變更,註冊完使用者後會調用SessionGuard的login方法把使用者資料裝載到應用中,注意這個login方法沒有登入認證,只是把認證後的使用者裝載到應用中這樣在應用裡任何地方我們都能夠通過Auth::user()
來擷取使用者資料啦。
使用者登入認證
Laravel Auth系統的登入路由如下
$this->post('login', 'Auth\LoginController@login');
我們看一下LoginController裡的登入邏輯
class LoginController extends Controller{ /** * 處理登入請求 */ public function login(Request $request) { //驗證登入欄位 $this->validateLogin($request); //防止惡意的多次登入嘗試 if ($this->hasTooManyLoginAttempts($request)) { $this->fireLockoutEvent($request); return $this->sendLockoutResponse($request); } //進行登入認證 if ($this->attemptLogin($request)) { return $this->sendLoginResponse($request); } $this->incrementLoginAttempts($request); return $this->sendFailedLoginResponse($request); } //嘗試進行登入認證 protected function attemptLogin(Request $request) { return $this->guard()->attempt( $this->credentials($request), $request->filled('remember') ); } //擷取登入用的欄位值 protected function credentials(Request $request) { return $request->only($this->username(), 'password'); }}
可以看到,登入認證的邏輯是通過SessionGuard
的attempt
方法來實現的,其實就是Auth::attempt()
, 下面我們來看看attempt
方法裡的邏輯:
class SessionGuard implements StatefulGuard, SupportsBasicAuth{ public function attempt(array $credentials = [], $remember = false) { $this->fireAttemptEvent($credentials, $remember); $this->lastAttempted = $user = $this->provider->retrieveByCredentials($credentials); //如果登入認證通過,通過login方法將使用者物件裝載到應用裡去 if ($this->hasValidCredentials($user, $credentials)) { $this->login($user, $remember); return true; } //登入失敗的話,可以觸發事件通知使用者有可疑的登入嘗試(需要自己定義listener來實現) $this->fireFailedEvent($user, $credentials); return false; } protected function hasValidCredentials($user, $credentials) { return ! is_null($user) && $this->provider->validateCredentials($user, $credentials); }}
SessionGuard
的attempt
方法首先通過使用者提供器的retriveBycredentials
方法通過使用者名稱從使用者表中查詢出使用者資料,認證使用者資訊是通過使用者提供器的validateCredentials
來實現的,所有使用者提供器的實作類別都會實現UserProvider契約(interface)中定義的方法,通過上面的分析我們知道預設的使用者提供器是EloquentUserProvider
class EloquentUserProvider implements UserProvider{ 從資料庫中取出使用者執行個體 public function retrieveByCredentials(array $credentials) { if (empty($credentials) || (count($credentials) === 1 && array_key_exists('password', $credentials))) { return; } $query = $this->createModel()->newQuery(); foreach ($credentials as $key => $value) { if (! Str::contains($key, 'password')) { $query->where($key, $value); } } return $query->first(); } //通過給定使用者認證資料來驗證使用者 public function validateCredentials(UserContract $user, array $credentials) { $plain = $credentials['password']; return $this->hasher->check($plain, $user->getAuthPassword()); }}class BcryptHasher implements HasherContract{ //通過bcrypt演算法計算給定value的散列值 public function make($value, array $options = []) { $hash = password_hash($value, PASSWORD_BCRYPT, [ 'cost' => $this->cost($options), ]); if ($hash === false) { throw new RuntimeException('Bcrypt hashing not supported.'); } return $hash; } //驗證散列值是否給定明文值通過bcrypt演算法計算得到的 public function check($value, $hashedValue, array $options = []) { if (strlen($hashedValue) === 0) { return false; } return password_verify($value, $hashedValue); }}
使用者密碼的驗證是通過EloquentUserProvider
依賴的hasher
雜湊器來完成的,Laravel認證系統預設採用bcrypt演算法來加密使用者提供的純文字密碼然後儲存到使用者表裡的,驗證時haser
雜湊器的check
方法會通過PHP內建方法password_verify
來驗證純文字密碼是否是儲存的密文密碼的原值。
使用者認證系統的主要細節梳理完後我們就知道如何定義我們自己的看守器(Guard)或使用者提供器(UserProvider)了,首先他們必須實現各自遵守的契約裡的方法才能夠無縫接入到Laravel的Auth系統中,然後還需要將自己定義的Guard或Provider通過Auth::extend
、Auth::provider
方法註冊返回Guard或者Provider執行個體的閉包到Laravel中去,Guard和UserProvider的自訂不是必須成套的,我們可以單獨自訂Guard仍使用預設的EloquentUserProvider,或者讓預設的SessionGuard使用自訂的UserProvider。
下一節我會給出一個我們以前項目開發中用到的一個案例來更好地講解應該如何對Laravel Auth系統進行擴充。
以上就是本文的全部內容,希望對大家的學習有所協助,更多相關內容請關注topic.alibabacloud.com!