Laravel5.5原始碼詳解 -- Auth中介軟體
Laravel5.5原始碼詳解 – Auth中介軟體
為了體現全貌,前面的程式碼部分沒有做太多刪減,重點關注特別加入的註釋部分。原文的註釋刪除,以減少閱讀篇幅。本文重點在後面的流程講解,這些比較詳細。
如果光看官方的文件,碰到問題的時候往往還是不知所云。所以,熟練的運用,應該建立在對原始碼的深刻了解的基礎上。而其流程是瞭解原始碼的第一步。瞭解這些,開發時才能遊刃有餘。
在App\Http\Kernel中註冊的
<?php
namespace App\Http;
use Illuminate\Foundation\Http\Kernel as HttpKernel;
class Kernel extends HttpKernel
{
// 全域性Http中介軟體
protected $middleware = [
\Illuminate\Foundation\Http\Middleware\CheckForMaintenanceMode::class,
\Illuminate\Foundation\Http\Middleware\ValidatePostSize::class,
\App\Http\Middleware\TrimStrings::class,
\Illuminate\Foundation\Http\Middleware\ConvertEmptyStringsToNull ::class,
\App\Http\Middleware\TrustProxies::class,
];
// 路由中介軟體
protected $middlewareGroups = [
'web' => [
\App\Http\Middleware\EncryptCookies::class,
\Illuminate\Cookie\Middleware\AddQueuedCookiesToResponse::class,
\Illuminate\Session \Middleware\StartSession::class,
// \Illuminate\Session\Middleware\AuthenticateSession::class,
\Illuminate\View\Middleware\ShareErrorsFromSession::class,
\App\Http\Middleware\VerifyCsrfToken::class,
\Illuminate\Routing\Middleware\SubstituteBindings::class,
],
'api' => [
'throttle:60,1',
'bindings',
],
];
// 可能需要分組或單獨使用的中介軟體
protected $routeMiddleware = [
'auth' => \Illuminate\Auth\Middleware\Authenticate::class,
'auth.basic' => \Illuminate\Auth\Middleware\AuthenticateWithBasicAuth::class,
'bindings' => \Illuminate\Routing\Middleware\SubstituteBindings::class,
'can' => \Illuminate\Auth\Middleware\Authorize::class,
'guest' => \App\Http\Middleware\RedirectIfAuthenticated::class,
'throttle' => \Illuminate\Routing\Middleware\ThrottleRequests::class,
];
}
可見,Auth中介軟體屬於可能需要分組或單獨使用的中介軟體,當然你也可以根據需要調整。
'auth' => \Illuminate\Auth\Middleware\Authenticate::class
關於中介軟體的執行機理,請參考另文,
開啟\Illuminate\Auth\Middleware\Authenticate,
<?php
namespace Illuminate\Auth\Middleware;
use Closure;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Auth\Factory as Auth;
class Authenticate
{
protected $auth;
public function __construct(Auth $auth)
{
// 注入Illuminate\Auth\AuthManager工廠類
$this->auth = $auth;
}
public function handle($request, Closure $next, ...$guards)
{
$this->authenticate($guards);
return $next($request);
}
protected function authenticate(array $guards)
{
// 如果沒有指定guard 則使用預設的設定
if (empty($guards)) {
// 使用Illuminate\Auth\AuthManager\__call()獲取SessionGuard 例項,
// 並呼叫其(實際是GuardHelpers的) authenticate() 方法進行認證
return $this->auth->authenticate();
}
// 如果有指定guard,則使用其中第一個能成功認證的
foreach ($guards as $guard) {
if ($this->auth->guard($guard)->check()) {
// 獲得本次請求guard,(後續請求也都會通過這個guard)
return $this->auth->shouldUse($guard);
}
}
throw new AuthenticationException('Unauthenticated.', $guards);
}
}
註釋中說明,$this->auth
在構造時傳入的是Illuminate\Contracts\Auth\Factory
介面,其實現類在Illuminate\Auth\AuthManager
,接下來,我們看一下這個類,特別注意resolve
函式中的註釋,
<?php
namespace Illuminate\Auth;
use Closure;
use InvalidArgumentException;
use Illuminate\Contracts\Auth\Factory as FactoryContract;
class AuthManager implements FactoryContract
{
use CreatesUserProviders;
protected $app;
protected $customCreators = [];
protected $guards = [];
protected $userResolver;
public function __construct($app)
{
$this->app = $app;
$this->userResolver = function ($guard = null) {
return $this->guard($guard)->user();
};
}
public function guard($name = null)
{
// getDefaultDriver() 返回來的就是 "web"
$name = $name ?: $this->getDefaultDriver();
return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}
protected function resolve($name)
{
// array:2 ["driver" => "session", "provider" => "users"]
// array:2 ["driver" => "token","provider" => "users"]
$config = $this->getConfig($name);
if (is_null($config)) {
throw new InvalidArgumentException("Auth guard [{$name}] is not defined.");
}
if (isset($this->customCreators[$config['driver']])) {
return $this->callCustomCreator($name, $config);
}
// "web" => "createSessionDriver"
// "api" => "createTokenDriver"
$driverMethod = 'create'.ucfirst($config['driver']).'Driver';
if (method_exists($this, $driverMethod)) {
// SessionGuard("web", ["driver" => "session", "provider" => "users"])
// TokenGuard("api", ["driver" => "token","provider" => "users"])
return $this->{$driverMethod}($name, $config);
}
throw new InvalidArgumentException("Auth guard driver [{$name}] is not defined.");
}
protected function callCustomCreator($name, array $config)
{
return $this->customCreators[$config['driver']]($this->app, $name, $config);
}
public function createSessionDriver($name, $config)
{
$provider = $this->createUserProvider($config['provider'] ?? null);
// 在這裡建立SessionGuard,
$guard = new SessionGuard($name, $provider, $this->app['session.store']);
if (method_exists($guard, 'setCookieJar')) {
$guard->setCookieJar($this->app['cookie']);
}
// 在這裡呼叫SessionGuard的setDispatcher()函式,關聯了Dispatcher,後面再述。
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;
}
public function createTokenDriver($name, $config)
{
$guard = new TokenGuard(
$this->createUserProvider($config['provider'] ?? null),
$this->app['request']
);
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
}
protected function getConfig($name)
{
return $this->app['config']["auth.guards.{$name}"];
}
public function getDefaultDriver()
{
return $this->app['config']['auth.defaults.guard'];
}
public function shouldUse($name)
{
$name = $name ?: $this->getDefaultDriver();
$this->setDefaultDriver($name);
$this->userResolver = function ($name = null) {
return $this->guard($name)->user();
};
}
public function setDefaultDriver($name)
{
$this->app['config']['auth.defaults.guard'] = $name;
}
public function viaRequest($driver, callable $callback)
{
return $this->extend($driver, function () use ($callback) {
$guard = new RequestGuard($callback, $this->app['request'], $this->createUserProvider());
$this->app->refresh('request', $guard, 'setRequest');
return $guard;
});
}
public function userResolver()
{
return $this->userResolver;
}
public function resolveUsersUsing(Closure $userResolver)
{
$this->userResolver = $userResolver;
return $this;
}
public function extend($driver, Closure $callback)
{
$this->customCreators[$driver] = $callback;
return $this;
}
public function provider($name, Closure $callback)
{
$this->customProviderCreators[$name] = $callback;
return $this;
}
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
}
在本例中,相當於__call("authenticate", [])
,這裡,$this->guard()
就是SessionGuard
,它是通過AuthManager
的函式resolve('web')
得到,
簡單說,在AuthManager裡面,函式guard()
呼叫了函式resolve('web')
,其中又呼叫了createSessionDriver
例項化了SessionGuard
.
SessionGuard {#329 ▼
#name: "web"
#lastAttempted: null
#viaRemember: false
#session: Store {#314 ▶}
#cookie: CookieJar {#302 ▶}
#request: Request {#42 ▶}
#events: Dispatcher {#26 ▶}
#loggedOut: false
#recallAttempted: false
#user: null
#provider: EloquentUserProvider {#326 ▶}
}
對這個過程深入一點的話,就是在 Illuminate\Auth\AuthManager
中,createSessionDriver()
呼叫了SessionGuard
的setDispatcher()
函式,進行了一次注入(關聯),
// 在這裡呼叫SessionGuard的setDispatcher()函式
if (method_exists($guard, 'setDispatcher')) {
$guard->setDispatcher($this->app['events']);
}
在SessionGuard
的setDispatcher
裡,
public function setDispatcher(Dispatcher $events)
{
$this->events = $events;
}
這裡\$events
的原型是 Illuminate\Contracts\Events\Dispatcher
這個介面,其例項是
\Illuminate\Events\Dispatcher
, 這種關聯在Illuminate\Foundation\Application
中可以看得很明白,
'events' => [ \Illuminate\Events\Dispatcher::class, \Illuminate\Contracts\Events\Dispatcher::class ],
AuthManager並沒有authenticate()
,所以\Illuminate\Auth\Middleware\Authenticate
裡面的這一句
$this->guard()->authenticate()
在執行時,實際找不到AuthManager的authenticate()
,便會通過AuthManager的
public function __call($method, $parameters)
{
return $this->guard()->{$method}(...$parameters);
}
來呼叫SessionGuard
裡的authenticate()
函式,SessionGuard
也沒有authenticate
,它是通過巨集use GuardHelpers, Macroable;
呼叫了Illuminate\Auth\GuardHelpers
中的authenticate()
,
<?php
namespace Illuminate\Auth;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
trait GuardHelpers
{
protected $user;
protected $provider;
public function authenticate()
{
if (! is_null($user = $this->user())) {
return $user;
}
throw new AuthenticationException;
}
從$this->user()
這裡進入了真正進行身份驗證的地方,其他Auth驗證函式也是同樣的原理,比如你在使用Auth::check()判斷一個使用者是否已經登入時,實際呼叫的函式也是Illuminate\Auth\GuardHelpers
中的check()
,
public function check()
{
return ! is_null($this->user());
}
同樣,這個GuardHelpers
中還有Auth::id()
, Auth::guest()
等,
所以,最關鍵的,還是看在Illuminate\Auth\SessionGuard
中的user()
函式,如下,
public function user()
{
if ($this->loggedOut) {
return;
}
if (! is_null($this->user)) {
return $this->user;
}
// 從session中獲取使用者ID。已登入使用者的使用者ID會被儲存在Session內。
$id = $this->session->get($this->getName());
if (! is_null($id)) {
// 通過Illuminate\Auth\EloquentUserProvider 的retrieveById() 方法,
// 獲取使用者資訊,並返回User Model
// 換句話說:在這裡SessionGuard拿到了使用者資訊
if ($this->user = $this->provider->retrieveById($id)) {
$this->fireAuthenticatedEvent($this->user);
}
}
// 獲取認證cookie(登入時選中記住我,服務端會向瀏覽器設定一個認證cookie,
// 其中包括 remember_token,下次可以直接使用這個認證cookie進行登入)
$recaller = $this->recaller();
if (is_null($this->user) && ! is_null($recaller)) {
// 使用 remember_token 從資料庫中獲取使用者資訊
// 最終委託 Illuminate\Auth\EloquentUserProvider::retrieveByToken()方法取資料
$this->user = $this->userFromRecaller($recaller);
if ($this->user) {
//將使用者ID儲存到Session
$this->updateSession($this->user->getAuthIdentifier());
$this->fireLoginEvent($this->user, true);
}
}
return $this->user;
}
第一步,如何拿到使用者id
先看如何從session獲取使用者id,session介面在Illuminate\Contracts\Session\Session
,其門面在Illuminate\Foundation\Application.php
中有定義
'session' => [\Illuminate\Session\SessionManager::class],
'session.store' => [\Illuminate\Session\Store::class, \Illuminate\Contracts\Session\Session::class],
當然, 真正的session是\Illuminate\Session\Store
,這個在session的原始碼詳解中已經講過,
它實現了session的介面,如下
...
namespace Illuminate\Session;
use Illuminate\Contracts\Session\Session;
class Store implements Session
{...}
先是SessionGuard得到一個當前的登陸相關的資訊,這裡還沒有使用者的資訊
public function getName()
{
return 'login_'.$this->name.'_'.sha1(static::class);
}
打印出來是如下的結果,
"login_web_59ba36addc2b2f9401580f014c7f58ea4e30989d"
翻譯開來就是login_web_Illuminate\Auth\SessionGuard
,後面會用作登陸使用者資訊的識別。
前面說過,session就是\Illuminate\Session\Store,得到使用者id在其get函式中,
public function get($key, $default = null)
{
return Arr::get($this->attributes, $key, $default);
}
進一步追蹤,發現使用者資訊在session載入時就已經放入attributes屬性中,
protected function loadSession()
{
$this->attributes = array_merge($this->attributes, $this->readFromHandler());
}
其中$this->readFromHandler()又是資訊來源,
protected function readFromHandler()
{
if ($data = $this->handler->read($this->getId())) {
$data = @unserialize($this->prepareForUnserialize($data));
if ($data !== false && ! is_null($data) && is_array($data)) {
return $data;
}
}
return [];
}
再id是在這裡拿到的,
public function getId()
{
return $this->id;
}
public function setId($id)
{
$this->id = $this->isValidId($id) ? $id : $this->generateSessionId();
}
那麼又是誰呼叫了setId來設定使用者id呢?是Illuminate\Session\Middleware\StartSession
,
public function handle($request, Closure $next)
{
$this->sessionHandled = true;
if ($this->sessionConfigured()) {
$request->setLaravelSession(
$session = $this->startSession($request)
);
$this->collectGarbage($session);
}
$response = $next($request);
if ($this->sessionConfigured()) {
$this->storeCurrentUrl($request, $session);
$this->addCookieToResponse($response, $session);
}
return $response;
}
protected function startSession(Request $request)
{
return tap($this->getSession($request), function ($session) use ($request) {
$session->setRequestOnHandler($request);
$session->start();
});
}
public function getSession(Request $request)
{
return tap($this->manager->driver(), function ($session) use ($request) {
$session->setId($request->cookies->get($session->getName()));
});
}
可以看到,這個函式呼叫鏈的關係為handle() ==> startSession() ==> getSession()
,handle是中介軟體的主函式,對所有經過的request進行處理。所以,當Request建立的時候,中介軟體例項化後,會首先在這裡拿到使用者的某個特徵資訊,並記錄相關詳情。
第二步,根據使用者id獲取使用者資訊
上面拿到了使用者id。再來看如何根據這個id獲取使用者資訊。前面註釋中提到,$this->provider
是這個
Illuminate\Contracts\Auth\UserProvider
介面,其實現類是
Illuminate\Auth\EloquentUserProvider
,它的retrieveById()
函式如下,
namespace Illuminate\Auth;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Hashing\Hasher as HasherContract;
use Illuminate\Contracts\Auth\Authenticatable as UserContract;
class EloquentUserProvider implements UserProvider
{
protected $hasher;
protected $model;
public function __construct(HasherContract $hasher, $model)
{
$this->model = $model;
$this->hasher = $hasher;
}
public function retrieveById($identifier)
{
$model = $this->createModel();
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
}
可見,這裡實質上是對模型相關的資料庫進行了一次查詢,然後獲取了所有相關屬性。要特別注意的是,重點進行查詢的函式是這裡的first(),上面那三句,
return $model->newQuery()
->where($model->getAuthIdentifierName(), $identifier)
->first();
重新寫一下,相當於下面的執行語句,
// 例項化一個QueryBuilder
$query = $model->newQuery();
// 執行一些準備工作
$query = $query->where($model->getAuthIdentifierName(), $identifier);
// 查詢資料庫,獲取當前使用者屬性及相關資訊 ---- 這裡才是重點
$user = $query->first();
// 獲得使用者資訊之後,返回查詢結果
return $user;
關於資料庫的查詢,詳情可查閱另文,裡面對來龍去脈進行了非常詳盡的描述。
Laravel5.5原始碼詳解 – 一次查詢的詳細執行:從Auth-Login-web中介軟體到資料庫查詢結果的全過程
附:laravel自帶的Auth路由參考
如果使用的是laravel預設的Auth路由,那麼就是下面的情況,可以根據需要選擇。
Illuminate\Routing\Router
public function auth()
{
// Authentication Routes...
$this->get('login', 'Auth\[email protected]')->name('login');
$this->post('login', 'Auth\[email protected]');
$this->post('logout', 'Auth\[email protected]')->name('logout');
// Registration Routes...
$this->get('register', 'Auth\[email protected]')->name('register');
$this->post('register', 'Auth\[email protected]');
// Password Reset Routes...
$this->get('password/reset', 'Auth\[email protected]')->name('password.request');
$this->post('password/email', 'Auth\[email protected]')->name('password.email');
$this->get('password/reset/{token}', 'Auth\[email protected]')->name('password.reset');
$this->post('password/reset', 'Auth\[email protected]');
}
App\Http\Controllers\Auth\LonginController裡面幾乎沒有有用的資訊
class LoginController extends Controller
{
use AuthenticatesUsers;
其父類App\Http\Controllers\Controller 繼承了 Illuminate\Routing\Controller
<?php
namespace App\Http\Controllers;
use Illuminate\Foundation\Bus\DispatchesJobs;
use Illuminate\Routing\Controller as BaseController;
use Illuminate\Foundation\Validation\ValidatesRequests;
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
class Controller extends BaseController
{
use AuthorizesRequests, DispatchesJobs, ValidatesRequests;
}
在Illuminate\Foundation\Auth\AuthenticatesUsers.php:
public function showLoginForm()
{
return view('auth.login');
}
相關推薦
Laravel5.5原始碼詳解 -- Auth中介軟體
Laravel5.5原始碼詳解 – Auth中介軟體 為了體現全貌,前面的程式碼部分沒有做太多刪減,重點關注特別加入的註釋部分。原文的註釋刪除,以減少閱讀篇幅。本文重點在後面的流程講解,這些比較詳細。 如果光看官方的文件,碰到問題的時候往往還是不知所云。所以
Laravel5.5原始碼詳解 -- 資料庫的啟動與連線過程
Laravel5.5原始碼詳解 – 資料庫的啟動與連線過程 整個laravel的操作,一般情況下,資料庫的處理會佔掉很大一部分。所以對資料 庫處理的理解,顯得尤為重要。關於其原始碼解析,網上有非常多的文獻,但流程一般都含糊其辭,讀完來龍去脈甚為不解。所以,我自
Laravel5.5原始碼詳解 -- Laravel-debugbar及使用elementUI-ajax的注意事項
Laravel5.5原始碼詳解 – Laravel-debugbar 及使用elementUI - ajax的注意事項 關於laravel對中介軟體的處理,請參中介軟體考另文, Laravel5.5原始碼詳解 – 中介軟體MiddleWare分析 這裡只是
ASP.NET Core管道詳解[4]: 中介軟體委託鏈
ASP.NET Core應用預設的請求處理管道是由註冊的IServer物件和HostingApplication物件組成的,後者利用一個在建立時提供的RequestDelegate物件來處理IServer物件分發給它的請求。而RequestDelegate物件實際上是由所有的中介軟體按照註冊順序建立的。換句話
redux的中介軟體applyMiddleware原始碼詳解
原文: redux的applyMiddleware原始碼 – 歡迎 watch || star 記得之前第一次看redux原始碼的時候是很懵逼的,尤其是看到applyMiddleware函式的時候,更是懵逼。當然那也是半年前的事情了,前幾天把redux原始碼看了下,並且實現了個簡單的
5.3 以太坊原始碼詳解3
一、轉賬的概念和交易的基本流程 使用者輸入轉入的地址和金額 系統用轉賬地址的私鑰對交易進行簽名(確保這筆交易是由發起交易的所有人) 對交易進行驗證 存入交易快取池 廣播交易 二、交易的資料 type
5.6 以太坊原始碼詳解6
1、概念 以太坊地址:代表一個賬戶 形式:0x97FBAb0a865fb81A8A22dA3798424398387413D8 特點:全網唯一 2、如何生成全網唯一的地址 1)、 建立賬戶 /
java集合類原始碼詳解-ArrayList(5)
上次,測試了java集合類支援遍歷方式的效率比較,今天再通過斷電除錯,去ArrayList底層的迭代器做了什麼。 首先在迭代器這裡打上斷電,(在實際中變數ArrayList最後別用迭代器,因為他很慢) 可以看到這個iterator()方法返回值是一個迭代器,函式體是r
5.API詳解
ces 沒有 pen update 源文件 open 成了 delete 關閉 Dao 中需要通過 SqlSession 對象來操作 DB。而 SqlSession 對象的創建, 需要其工廠對象 SqlSessionFactory。SqlSessionFactory 對象,
【轉】JDK的Parser來解析Java原始碼詳解
轉自:https://www.jb51.net/article/92989.htm 這篇文章主要介紹了JDK的Parser來解析Java原始碼的相關資料,需要的朋友可以參考下 在JDK中,自帶了一套相關的編譯API,可以在Java中發起編譯流程,解析Java原始檔然後獲取其語法樹,在JDK的
Map容器家族(HashMap原始碼詳解)
一、在Map集合家族的位置及描述 HashMap子類繼承自AbstractMap抽象類,實現了Map,Serializable,Cloneable介面,AbstractMap實現了Map介面的一部分方法,減輕了其子類的負擔。
zxing開源庫工作流程原始碼詳解
程式碼獲取 作為移動客戶端開發者來說,對二維碼識別或二維碼生成相關的開發需求肯定並不陌生,Android開發二維碼相關的功能通常都會使用或參考大名鼎鼎的zxing庫。而本文則主要是通過原始碼分析一下該開源庫掃描二維碼的工作流程,對這塊能有個更深的瞭解。 首先使用git將專案程式碼clone到本地,新建專案
Collection容器家族(LinkedHashSet原始碼詳解)
一、在Collection集合體系中的位置及概述 LinkedHashSet 是非同步的有序的,分別是插入順序和訪問順序,LinkedHashSet的有序性可參考LinkedHashMap的有序性,繼承於HashSet,內部基
Collection容器家族(HashSet原始碼詳解)
一、在Collection集合體系中的位置及概述 HashSet繼承自AbstractSet抽象類,實現了Cloneable、Serializable介面,顯示的實現了Set介面。至於為什麼顯示的實現Set介面,我前面的文章講過。
Map容器家族(TreeMap原始碼詳解)
一、在Map集合家族的位置及概述 TreeMap是一個有序的key-value集合,它內部是通過紅-黑樹實現的。TreeMap繼承與AbstractMap,實現了NavigableMap介面,意味著它支援一系列的導航方法
Map容器家族(LinkedHashMap原始碼詳解)
一、在Map集合家族的位置及描述 Li
OkHttp原始碼詳解之二完結篇
1. 請大家思考幾個問題 在開始本文之前,請大家思考如下幾個問題。並請大家帶著這幾個問題,去本文尋找答案。如果你對下面幾個問題的答案瞭如指掌那本文可以略過不看 在瀏覽器中輸入一個網址,按回車後發生了什麼? Okhttp的TCP連線建立發生在什麼時候? Okht
OkHttp原始碼詳解之Okio原始碼詳解
請在電腦上閱讀,效果更佳 本文將從兩個技術點講解OkHttp 1. 講解Okio,因為Okhttp的IO操作都是基於Okio,拋開Okio的OkHttp講解是不完美的 2. 講解OkHttp原始碼 Okio 1. Okio簡介 引用官方的一段介紹 Okio是一個補
wordcount 原始碼詳解
1.原始碼解釋 package org.apache.hadoop.examples;import java.io.IOException;import java.util.StringTokenizer;import org.apache.hadoop.conf.Configuration;import
openTSDB原始碼詳解之rowKey生成
openTSDB原始碼詳解之rowKey生成 openTSDB的一個非常好的設計就是其rowKey的生成。下面詳細介紹一下。 1.相關處理類 openTSDB往hbase中寫入資料的處理過程,我之前就已經分析過,主要涉及的類有: addPointInternal(