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');}
}
復制代碼

可以看到,登錄認證的邏輯是通過SessionGuardattempt方法來實現的,其實就是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);}
}
復制代碼

SessionGuardattempt方法首先通過用戶提供器的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::extendAuth::provider方法注冊返回Guard或者Provider實例的閉包到Laravel中去,Guard和UserProvider的自定義不是必須成套的,我們可以單獨自定義Guard仍使用默認的EloquentUserProvider,或者讓默認的SessionGuard使用自定義的UserProvider。

下一節我會給出一個我們以前項目開發中用到的一個案例來更好地講解應該如何對Laravel Auth系統進行擴展。

本文已經收錄在系列文章Laravel源碼學習里,歡迎訪問閱讀。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/538351.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/538351.shtml
英文地址,請注明出處:http://en.pswp.cn/news/538351.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

matlab 光譜噪聲,環境小衛星高光譜影像條紋噪聲去除程序IDL版

前言源代碼PRO stripe_remove1ENVI,/Restore_Base_Save_FilesENVI_Batch_initfile DIALOG_PICKFILE(/READ, FILTER *.img)print,fileENVI_OPEN_FILE,file,r_fidfidENVI_FILE_QUERY, fid, dimsdims, nsns, nlnl, nbnbprint,fid,dims,ns,nl,nbfdata fltarr(ns,nl,nb)datafltar…

啟動TOMCAT報錯 java.util.zip.ZipException: invalid LOC header (bad signature)

報錯信息大致如下所示: at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)at java.lang.reflect.Method.invoke(Unknown Source)at org.apache.catalina.startup.Bootstrap.start(Bootstrap.java:303)at org.apache.catalina.startup.Bootstrap.…

快速乘模板

描述 求 a 乘 b 對 p 取模的值&#xff0c;其中 1≤a,b,p≤10^18。 輸入格式 第一行a&#xff0c;第二行b&#xff0c;第三行p。 輸出格式 一個整數&#xff0c;表示a*b mod p的值。 樣例輸入 2 3 9 樣例輸出 6 #include <bits/stdc.h> using namespace std; const int M…

結構體怎么賦值_c語言學習之基礎知識點介紹:結構體的介紹

一、結構體的介紹/* 語法&#xff1a;struct 結構體名{成員列表;};切記切記有分號&#xff01;說明&#xff1a;成員列表就是指你要保存哪些類型的數據。注意&#xff1a;上面的語法只是定義一個新的類型&#xff0c;而這個類型叫做結構體類型。因為類型不能保存數據&#xff0…

php 生成excel空白,phpexcel庫在localhost上運行良好,但在服務器中生成空白的excel文件...

這是我的代碼,在本地主機上可以很好地使用數據庫中的數據生成一個excel文件,但在托管服務器中它會生成一個空白的excel文件&#xff1a;// Starting the PHPExcel library$this->load->library(PHPExcel);//$this->load->library(PHPExcel/IOFactory);$objPHPExcel…

阿里云Maven倉庫地址

<默認情況下配置多個mirror的情況下&#xff0c;只有第一個生效&#xff0c;只有當前一個mirror 無法連接的時候&#xff0c;才會去找后一個&#xff1b;而我們想要的效果是&#xff1a;當a.jar在第一個mirror中不存在的時候&#xff0c;maven會去第二個mirror中查詢下載&a…

python發短信腳本_python腳本發送短信

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":4,"count":4}]},"card":[{"des":"阿里技術人對外發布原創技術內容的最大平臺&…

國內遠程醫療市場快速增長

目前&#xff0c;遠程醫療技術已經從最初的電視監護、電話遠程診斷發展到利用高速網絡進行數字、圖像、語音的綜合傳輸&#xff0c;并且實現了實時的語音和高清晰圖像的交流&#xff0c;為現代醫學的應用提供了更廣闊的發展空間。 健康一體機 遠程醫療是指通過計算機技術、遙感…

php怎么使得字體滾動,滾動文字+字體特效代碼(全集)

收集了幾天&#xff0c;終于把滾動文字*字體特效差不多收集完了&#xff0c;這里與大家一同分享&#xff0c;期待您的博客越做越漂亮&#xff01;感謝您的光臨&#xff01;1.陰影滾動字循環滾動:歡迎光臨彌勒內院看門人博客&#xff0c;看門人歡迎您代碼:歡迎光臨彌勒內院看門人…

Eclipse集成svn后出現Failed to load JavaHL Library的解決辦法

在win10 64位上eclipsex64位 集成svn插件 在使用 Team-share project &#xff0c;選擇svn后&#xff0c;報了&#xff1a;Failed to load JavaHL Library錯誤 解決方法&#xff1a; winodws--perference--svn。設置如下圖

python寫進程_將數據作為后臺進程在Python中寫入磁盤

您可以像這樣嘗試using multiple processes&#xff1a;import multiprocessing as mpdef compute(j):# compute a bunch of datareturn datadef write(data):# write data to diskif __name__ __main__:pool mp.Pool()for j in xrange(200):pool.apply_async(compute, args(…

Unity快捷鍵

1 飛越模式 使用飛越模式通過第一人稱飛行來導航場景視圖&#xff0c;類似于在許多游戲中導航。 單擊并按住鼠標右鍵。 使用鼠標移動視圖&#xff0c;WASD鍵向左/右/前/后移動&#xff0c;Q和E鍵可上下移動。 按住Shift鍵可以更快地移動。 2 攝像機對準當前我Sceen的屏幕 Ctrl …

php如何打出的正方形行列,javascript實現輸出指定行數正方形圖案的方法

本文實例講述了javascript實現輸出指定行數正方形圖案的方法。分享給大家供大家參考。具體如下&#xff1a;javascript實現輸出指定行數的正方形圖案&#xff1a;點擊生成圖案&#xff0c;會有2個提示框&#xff0c;1&#xff0c;輸入圖案的組成字符&#xff0c;只能是1個字符哦…

eclipse的SVN插件設置忽略文件

windows--preference--Team-ignore resource 這里我新增了maven項目常不需要提交應該忽略的文件和文件夾 文件&#xff1a; .setting .project .classpath 文件夾&#xff1a; */target/* */target */settings */settings/*

風變python怎么樣_Python取代Excel?風變編程帶你了解如何更好地學Python!

當前最簡單、最流行的編程語言是什么&#xff1f;是Python。最近&#xff0c;谷歌公布的編程語言流行指數顯示&#xff0c;Python目前仍然是全球范圍內最受歡迎的技術語言。而得益于簡潔、易讀、易維護等特點&#xff0c;Python可廣泛運用于數據分析、人工智能、爬蟲、運維、測…

android 開發書簽大全,一站式的導航分享!

#一、描述 此資源是轉載而來&#xff0c;只為需要而用。 #二、書簽大全 ###Android大神 android-dev-cn Trinea 郭神 任玉剛 鴻洋 夏安明 徐醫生 daimajia stormzhang 農民伯伯 胡凱 郝錫強 張興業 老羅 Mr.Simple(源碼設計模式) 咪當系歐巴(非常有天賦的Coder) android_tutor…

php 正則匹配 %3e,在shell腳本中使用正則表達式

1)在Linuxshell腳本中使用正則表達式解析字符串的正確方法是什么?包括正則表達式功能的工具包括sed、grep、awk、perl、python等等。即使是更新版本的bash也具有regex功能。你所要做的就是查找關于如何使用它們的文檔。2)在這里使用SED是正確的嗎?可以,但不是必須的。3)這可以…

python排大小函數_python numpy 一些函數 大小排序和統計

排序大小函數import numpy as npsize 100a np.random.randint(0, 1000, sizesize)print(a)# 排序之后的前k個元素# 返回的值不一定是按照順序排好的# [ 5 0 15 22 32]# [ 0 5 15 22 32]print(np.partition(a, 3)[:5])print(np.sort(a)[:5])# 百分位數a np.abs(np.random.ran…

逸管家:把握不同行業生命周期,選擇正確商業模式

</P><P>  原標題&#xff1a;把握不同行業生命周期&#xff0c;選擇正確商業模式</P><P>  現今中小企業已進入理性商業模式選擇期&#xff0c;大量中小企業在拿到天使投資后進入融資瓶頸期。與此同時&#xff0c;“共享”一詞在各地出現&#xff0…

dubbo是如何“插入”到spring框架中的

原文鏈接&#xff1a;http://blog.csdn.net/achilles12345/article/details/41789527 ---------------------------------------------------------------------------------------------- 作為一個分布式服務治理框架&#xff0c;dubbo做的非常好&#xff0c;在業界使用很廣&…