日韩黑丝制服一区视频播放|日韩欧美人妻丝袜视频在线观看|九九影院一级蜜桃|亚洲中文在线导航|青草草视频在线观看|婷婷五月色伊人网站|日本一区二区在线|国产AV一二三四区毛片|正在播放久草视频|亚洲色图精品一区

分享

【Laravel系列7.4】安全相關(guān)

 硬核項(xiàng)目經(jīng)理 2022-06-06 發(fā)布于湖南

安全相關(guān)

對于一個框架來說,安全體系是非常重要的一環(huán)。如果一個框架沒有好的安全措施及功能的話,那么這個框架在線上運(yùn)行的時候多多少少還是會讓人不放心的,畢竟各路大佬可能隨時都在掃描各個網(wǎng)站的漏洞。之前的各種安全事件可能你不一定經(jīng)歷過,但一定聽說過。今天,我們就來看看 Laravel 中的安全相關(guān)功能。

認(rèn)證體系

在 Laravel 中,自帶了一套用戶登錄認(rèn)證體系,這一套體系原來是直接框架自帶的,現(xiàn)在剝離出來通過 laravel/jetstream 組件實(shí)現(xiàn)了。默認(rèn)情況下,我們安裝 Laravel 框架后,會自帶一個默認(rèn)的 User Model ,這個 Model 就是這個默認(rèn)用戶表的模型類。

composer require laravel/jetstream

// 使用 Livewire 棧安裝 Jetstream...
php artisan jetstream:install livewire

// 使用 Inertia 棧安裝 Jetstream...
php artisan jetstream:install inertia

composer require laravel/sanctum

npm install && npm run dev

通過這三個命令行代碼,我們就可以安裝好 Jetstream 相關(guān)的組件,安裝完成后,將會自帶路由以及 view 界面,我們可以訪問 /register 路徑,返回的界面是這個樣子的。


這就是系統(tǒng)為我們生成的界面,這個時候如果我們查看 route/web.php 的話,是看不到任何路由信息的,那么它的路由是在哪里定義的呢?其實(shí)它是通過 /vendor/laravel/fortify/src/FortifyServiceProvider.php 中的 configureRoutes() 方法添加的,這個服務(wù)提供者是 vendor/laravel/framework/src/Illuminate/Foundation/Application.php 容器類在 registerConfiguredProviders() 中加載的。最后實(shí)際加載的是 vendor/laravel/fortify/routes/routes.php 這個路由文件。
// ……
if (Features::enabled(Features::registration())) {
    if ($enableViews) {
        Route::get('/register', [RegisteredUserController::class, 'create'])
            ->middleware(['guest:'.config('fortify.guard')])
            ->name('register');
    }

    Route::post('/register', [RegisteredUserController::class, 'store'])
        ->middleware(['guest:'.config('fortify.guard')]);
}
// ……

在這個文件中,我們還可以看到 login、logout、user 相關(guān)的操作路由。那么它的模板在哪里呢?其實(shí)上面的 npm run dev 操作就是編譯了 Laravel 框架自帶的 Vue 框架,而模板走的正是 Vue ,文件在 resource/js/Pages 中,在這里我們可以找到 Auth/Register.vue 這個文件,隨便修改一點(diǎn)然后再次執(zhí)行 npm run dev 重新編譯,就可以看到修改之后的內(nèi)容了。或許還有別的方法,可以走普通的直接輸出的頁面形式,因?yàn)?view/auth 下面也生成了一些文件,一開始我還以為是走的這里的前端文件,但結(jié)果并不是。我們也不深究了。

費(fèi)勁嗎?其實(shí)挺費(fèi)勁的,如果是正式的公司團(tuán)隊(duì)開發(fā)的話,前端小哥哥小姐姐們才不會來你的 Laravel 框架中進(jìn)行編譯或者寫代碼呢。所以這個功能更適合的是我們自己一個人承擔(dān)一整套后臺頁面開發(fā)的情況。說白了,做私活的時候很方便。

不過,更多情況下其實(shí)我們還是寧愿自己使用 vue 腳手架去讓前后端完全分離,所以這一塊的功能,大家了解一下就好。接下來我們看看怎么自己實(shí)現(xiàn)這些注冊登錄操作,以接口形式。(網(wǎng)頁形式也是同理的)

自已實(shí)現(xiàn)的注冊、登錄

要自己實(shí)現(xiàn)登錄注冊其實(shí)非常簡單,如果只是網(wǎng)頁的登錄,同樣我們還是使用 Laravel 自帶的那個 users 數(shù)據(jù)表,然后自定義幾個路由和控制器。

class LoginController extends \App\Http\Controllers\Controller
{
    public function register(){
        return User::create([
            'name' => request()->input('name'''),
            'password' => Hash::make(request()->input('password''')),
        ]);
    }

    public function login(){
        $name = request()->input('name''');
        $password = request()->input('password''');

        $attempt = Auth::attempt(['name' => $name, 'password' => $password]);
        $user = Auth::user();

        dd($user, $attempt, $user->api_token);
    }

    public function info(){
        dd(Auth::user());
    }
}

在這個控制器中,我們在 Login 方法中使用了 attempt() 方法來實(shí)現(xiàn)登錄功能,只需要將原始的用戶名和密碼傳遞進(jìn)去,方法內(nèi)部會查詢用戶并進(jìn)行比對,它默認(rèn)走的是 User 這個 Model ,調(diào)用的數(shù)據(jù)表就是 users 表。登錄成功后會直接種下 Session 和 Cookie ,大家可以自行查看請求返回的 Cookie 信息以及查找你系統(tǒng)保存的 Session 數(shù)據(jù)。

Route::get('/custom/login', [\App\Http\Controllers\Auth\LoginController::class, 'login']);
Route::get('/custom/register', [\App\Http\Controllers\Auth\LoginController::class, 'register']);
Route::get('/custom/info', [\App\Http\Controllers\Auth\LoginController::class, 'info'])->middleware('auth');

在路由中,我們給最后的這個 info 添加了一個中間件,如果請求它的時候沒有 Cookie 信息,那么它就會返回 403 未認(rèn)證的信息。大家可以自己嘗試一下,接下來我們要看一下如何使用 token 來進(jìn)行 api 的登錄和認(rèn)證控制。一般情況下,我們可能會使用 jwt 或者 passport 之類的插件來做這種 api 的認(rèn)證功能。不過這些內(nèi)容不在我們今天討論的范圍內(nèi),我們只是看一下默認(rèn)情況下 Laravel 自帶的認(rèn)證是如何使用的。

默認(rèn)情況下,Laravel 框架雖然提供了 Api 的驗(yàn)證功能,但還需要我們手動的添加一些內(nèi)容,比如說數(shù)據(jù)庫需要添加一個 api_token 的 varchar 字段,給個 80 左右的長度即可。然后我們改造一下登錄和路由驗(yàn)證中間件。

public function login(){
    $name = request()->input('name''');
    $password = request()->input('password''');

    $attempt = Auth::attempt(['name' => $name, 'password' => $password]);
    $user = Auth::user();

    $user->api_token = Str::random(60);
    $user->save();

    // dd($user, $attempt, $user->api_token);
}

在登錄這塊,我們只需要在每次登錄的時候去新建一個 api_token 并保存到數(shù)據(jù)中就可以了。然后將生成的這個 api_token 返回交給前端保存。

Route::get('/custom/info', [\App\Http\Controllers\Auth\LoginController::class, 'info'])->middleware('auth:api');

接著將 info 這個測試接口的中間件換成框架自帶的 auth:api 就可以了。接下來你可以自己測試一下效果,在訪問 /custom/info 這個接口時,你可以用兩種方式來傳遞 api_token 。

  • 直接在請求中添加 api_token 比如 get 方式的 /custom/info?api_token=xxxxx(POST也沒問題)

  • 在請求頭中添加 Authorization ,內(nèi)容格式為 Bearer XXXXX ,這里的 XXXXX 就是 token 的內(nèi)容。

中間件守護(hù)

在 Laravel 的認(rèn)證體系中,中間件有守衛(wèi)的職責(zé),包括在配置文件和 Auth 的常用方法中都有 guard 這個單詞的出現(xiàn)。我們在源碼中主要就來看一下它的中間件是如何進(jìn)行認(rèn)證守護(hù)的。

框架中的 app\Middleware\Authenticate 繼承自 vendor/laravel/framework/src/Illuminate/Auth/Middleware/Authenticate.php 方法,其中 handle() 方法最終調(diào)用的是 authenticate() 方法。

protected function authenticate($request, array $guards)
{
    if (empty($guards)) {
        $guards = [null];
    }

    foreach ($guards as $guard) {
        if ($this->auth->guard($guard)->check()) {
            return $this->auth->shouldUse($guard);
        }
    }

    $this->unauthenticated($request, $guards);
}

這個方法內(nèi)部會調(diào)用 auth 對象的 grard() 方法并鏈?zhǔn)嚼^續(xù)調(diào)用 check() 方法來判斷用戶是否登錄。這個 auth 對象實(shí)際上是 vendor/laravel/framework/src/Illuminate/Auth/AuthManager.php 對象。我們可以進(jìn)入這個類模板中查看 guard() 方法。

public function guard($name = null)
{
    $name = $name ?: $this->getDefaultDriver();

    return $this->guards[$name] ?? $this->guards[$name] = $this->resolve($name);
}

在創(chuàng)建驅(qū)動時,會根據(jù)我們在 config/auth.php 中的配置,調(diào)用指定的驅(qū)動,比如 web 調(diào)用的是 SessionGuard ,而 api 則會調(diào)用 TokenGuard ,這里的驅(qū)動生成和我們之前看過的緩存驅(qū)動非常類似,大家可以自己看一下,最后其實(shí)就是通過配置文件的內(nèi)容拼接成類名并獲得實(shí)例化對象。我們繼續(xù)以 TokenGuard 為例往下看。

public function __construct(
    UserProvider $provider,
    Request $request,
    $inputKey = 'api_token',
    $storageKey = 'api_token',
    $hash = false)

{
    $this->hash = $hash;
    $this->request = $request;
    $this->provider = $provider;
    $this->inputKey = $inputKey;
    $this->storageKey = $storageKey;
}

vendor/laravel/framework/src/Illuminate/Auth/TokenGuard.php 的構(gòu)造函數(shù)默認(rèn)指定的 key 就是 api_token ,這下明白為什么我們在數(shù)據(jù)庫添加的字段必須是 api_token 這個字段了吧。check() 方法在 TokenGuard 所使用的那個 GuardHelpers 特性對象中,它會再調(diào)用 user() 方法。

public function user()
{
    // If we've already retrieved the user for the current request we can just
    // return it back immediately. We do not want to fetch the user data on
    // every call to this method because that would be tremendously slow.
    if (! is_null($this->user)) {
        return $this->user;
    }

    $user = null;

    $token = $this->getTokenForRequest();

    if (! empty($token)) {
        $user = $this->provider->retrieveByCredentials([
            $this->storageKey => $this->hash ? hash('sha256', $token) : $token,
        ]);
    }

    return $this->user = $user;
}

public function getTokenForRequest()
{
    $token = $this->request->query($this->inputKey);

    if (empty($token)) {
        $token = $this->request->input($this->inputKey);
    }

    if (empty($token)) {
        $token = $this->request->bearerToken();
    }

    if (empty($token)) {
        $token = $this->request->getPassword();
    }

    return $token;
}

在這個方法內(nèi)部,又會調(diào)用 getTokenForRequest() 來獲得請求參數(shù)中的 api_token 參數(shù),如果不存在的話,則會使用 request 的 bearerToekn() 方法來獲得在頭信息中的 Authorization 數(shù)據(jù)。這也是我們使用 api 方式可以用兩種方式傳遞 token 的原因。最后,通過獲得的 token 調(diào)用 UserProvider 服務(wù)提供者獲得用戶信息完成登錄認(rèn)證的判斷。整個認(rèn)證守衛(wèi)的過程就完成了。

加密解密

對于加密來說, Laravel 框架直接使用的就是 OpenSSL 提供的 AES-256 和 AES-128 加密。也就是說,這個默認(rèn)的加密功能使用的是 對稱加密 的形式。在之前我們已經(jīng)學(xué)習(xí)過 PHP 中的加密以及 OpenSSL 的加密,對稱加密是需要一個密鑰的,這個密鑰其實(shí)就是我們在安裝框架之后使用 php artisan key:generate 生成的那個密鑰,它被保存在 .env 文件中。這個命令是我們最開始第一篇文章搭建 Laravel 框架時就見過的。

所有 Laravel 加密之后的結(jié)果都會使用消息認(rèn)證碼 (MAC) 簽名,使其底層值不能在加密后再次修改。因此,最好建議是使用 Laravel 內(nèi)建的加密工具。

Route::get('crypt'function(){
    $crypt =  \Illuminate\Support\Facades\Crypt::encrypt("aaa");
    echo $crypt, "<br/>"// eyJpdiI6IjhqWUthVWZ2TFVYU0NCa2JxMlFMTXc9PSIsInZhbHVlIjoiUHYwdlhidEhINW9mOE5qMk1pTDg2QT09IiwibWFjIjoiYzVkZDQ4NjgxNDY5YWUwNTU4Yzk4NGZkYjRmMzI5MTIxNDU3M2MxMmNlODAwMjAzOGEzMmU0MjFhNThiYzdmNyJ9
    echo \Illuminate\Support\Facades\Crypt::decrypt($crypt); // aaa
});

測試代碼很簡單,也沒什么多說的,主要就是 encrypt() 加密和 decrypt() 解密這兩個函數(shù)。它們的實(shí)現(xiàn)在 vendor/laravel/framework/src/Illuminate/Encryption/Encrypter.php 中,具體如何通過門面找到這個實(shí)現(xiàn)類想必也不用我多說了。具體實(shí)現(xiàn)的內(nèi)容大家可以自己去看這兩個方法,如果有疑問,可以查看之前我們學(xué)習(xí)過的 PHP的OpenSSL加密擴(kuò)展學(xué)習(xí)(一):對稱加密https://mp.weixin.qq.com/s/mkUjW5MFQyJDFS4YyMdyaA 。

哈希

和上面的 Crypt 加密一樣,Hash 門面使用的其實(shí)就是 password_hash() 的加密方式,Laravel 也只是對它進(jìn)行了一個簡單的封裝。

Route::get('hash'function(){
    $hash1 = \Illuminate\Support\Facades\Hash::make("aaa");
    $hash2 = \Illuminate\Support\Facades\Hash::make("aaa", [
        'rounds' => 7,
        'memory' => 1024,
        'time' => 2,
        'threads' => 2,
    ]);

    echo $hash1, "<br/>", $hash2, "<br/>";
    // $2y$10$Ga3mtVuosSEkMztnA6TRleJZL6JqNCnT.sQHbw.jdUrmg1o.NPqDO
    // $2y$07$B1wLnF/5gjMH/GGY/KaYbu7WVdWIvswBcuORAQRsyfxJ46xyOVTOW
    echo \Illuminate\Support\Facades\Hash::check('aaa', $hash1), "<br/>"// 1
    echo \Illuminate\Support\Facades\Hash::check('aaa1', $hash1), "<br/>"//

    echo \Illuminate\Support\Facades\Hash::needsRehash($hash1), "<br/>"//
});

關(guān)于測試結(jié)果和參數(shù)我也不多說了,源碼大家也自己去翻一翻吧,非常好找,make() 底層就是 password_hash() ,check() 底層就是 password_verify() ,而 needsRehash() 底層就是 password_needs_rehash() 。對于 password_hash() 有疑問的同學(xué)也可以移步我們之前學(xué)習(xí)過的 PHP密碼散列算法的學(xué)習(xí) https://mp.weixin.qq.com/s/d_qI3GKB-DoNrBNb7r_LaA 再好好復(fù)習(xí)一下。

防注入

對于注入來說,我們最關(guān)心的無非就是兩種注入問題,一個是 SQL 注入,一個是 XSS 注入。對于 SQL 注入,只要你使用框架的 查詢構(gòu)造器 或者 模型 ?;静粫刑蟮淖⑷雴栴}。當(dāng)然,前提是不要直接去用 DB::select() 這樣的寫自己拼的 SQL 語句。

而對于 XSS 來說呢?雖然在模板輸出的時候已經(jīng)默認(rèn)做了一些安全防護(hù)的操作,但我們接收到的參數(shù)如果入庫了,可能會有存儲型 XSS 的潛在風(fēng)險(xiǎn)。這個東西框架沒有提供直接的解決功能,大家可以使用 HtmlPurifier 來解決,直接 Composer 安裝就可以了。

總結(jié)

今天的內(nèi)容主要是探討了一下 Laravel 框架中自帶的認(rèn)證功能和加密相關(guān)的內(nèi)容。其實(shí)更多情況下,我們會自己去做 api 形式的接口或者自己去寫登錄頁面和驗(yàn)證的邏輯。畢竟對于大多數(shù)項(xiàng)目來說,用戶表的情況可能并不和框架所提供的完全一樣,可能很多字段也不相同。不過原始的認(rèn)證模塊還是非常好用的,大家可以多多嘗試。至于加密相關(guān)的和注入安全相關(guān)的知識可以查閱我們之前的文章以及自己去搜索相關(guān)的資料。這些東西都是通用的,框架能做的其實(shí)也就這么多。

參考文檔:

https:///docs/laravel/8.5/authentication/10397

    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多