摘要:通過裝載看守器和用戶提供器裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個過程中用到的方法來看具體的實現細節。
用戶認證系統的實現細節
上一節我們介紹來Laravel Auth系統的基礎知識,說了他的核心組件都有哪些構成,這一節我們會專注Laravel Auth系統的實現細節,主要關注Auth也就是AuthManager是如何裝載認證用的看守器(Guard)和用戶提供器(UserProvider)以及默認的用戶注冊和登錄的實現細節,通過梳理這些實現細節我們也就能知道應該如何定制Auth認證來滿足我們自己項目中用戶認證的需求的。
通過AuthManager裝載看守器和用戶提供器AuthManager裝載看守器和用戶提供器用到的方法比較多,用文字描述不太清楚,我們通過注解這個過程中用到的方法來看具體的實現細節。
namespace IlluminateAuth; class AuthManager implements FactoryContract { /** * 嘗試從$guards屬性中獲取指定的Guard * * @param string $name * @return IlluminateContractsAuthGuard|IlluminateContractsAuthStatefulGuard */ 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 IlluminateContractsAuthGuard|IlluminateContractsAuthStatefulGuard * * @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 IlluminateAuthSessionGuard */ 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對象,它實現了IlluminateContractsAuth 接口 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", "AuthRegisterController@register");
所以用戶注冊的邏輯是由RegisterController的register方法來完成的
class RegisterController extends Controller { //方法定義在IlluminateFoundationAuthRegisterUsers中 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", "AuthLoginController@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系統進行擴展。
本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/28951.html
摘要:系統的核心是由的認證組件的看守器和提供器組成。使用的認證系統,幾乎所有東西都已經為你配置好了。其配置文件位于,其中包含了用于調整認證服務行為的注釋清晰的選項配置。 用戶認證系統(基礎介紹) 使用過Laravel的開發者都知道,Laravel自帶了一個認證系統來提供基本的用戶注冊、登錄、認證、找回密碼,如果Auth系統里提供的基礎功能不滿足需求還可以很方便的在這些基礎功能上進行擴展。這篇...
摘要:擴展用戶認證系統上一節我們介紹了系統實現的一些細節知道了是如何應用看守器和用戶提供器來進行用戶認證的,但是針對我們自己開發的項目或多或少地我們都會需要在自帶的看守器和用戶提供器基礎之上做一些定制化來適應項目,本節我會列舉一個在做項目時遇到的 擴展用戶認證系統 上一節我們介紹了Laravel Auth系統實現的一些細節知道了Laravel是如何應用看守器和用戶提供器來進行用戶認證的,但是...
摘要:過去一年時間寫了多篇文章來探討了我認為的框架最核心部分的設計思路代碼實現。為了大家閱讀方便,我把這些源碼學習的文章匯總到這里。數據庫算法和數據結構這些都是編程的內功,只有內功深厚了才能解決遇到的復雜問題。 過去一年時間寫了20多篇文章來探討了我認為的Larave框架最核心部分的設計思路、代碼實現。通過更新文章自己在軟件設計、文字表達方面都有所提高,在剛開始決定寫Laravel源碼分析地...
摘要:的契約是一組定義框架提供的核心服務的接口,例如我們在介紹用戶認證的章節中到的用戶看守器契約和用戶提供器契約以及框架自帶的模型所實現的契約。接口與團隊開發當你的團隊在開發大型應用時,不同的部分有著不同的開發速度。 Contracts Laravel 的契約是一組定義框架提供的核心服務的接口, 例如我們在介紹用戶認證的章節中到的用戶看守器契約IllumninateContractsAuth...
摘要:如何做用戶認證根據文檔描述,提供用戶認證的接口,他的核心是看守器和提供器,看守器定義怎么認證用戶,提供器定義怎么檢索用戶。 最近的一個PHP項目,上一個項目是采用ThinkPHP來弄的,因為很早就聽說過Laravel的大名,所以進了Laravel的官網,意外發現了Lumen,正好我項目是提供API的,所以選擇了Lumen,因為是Laravel的精簡版,看了幾天的Laravel文檔,也總...
閱讀 4913·2023-04-25 18:47
閱讀 2673·2021-11-19 11:33
閱讀 3445·2021-11-11 16:54
閱讀 3101·2021-10-26 09:50
閱讀 2540·2021-10-14 09:43
閱讀 665·2021-09-03 10:47
閱讀 671·2019-08-30 15:54
閱讀 1498·2019-08-30 15:44