iT邦幫忙

2021 iThome 鐵人賽

DAY 24
0
Modern Web

Laravel 實務筆記系列 第 24

身分驗證

Laravel 提供了一整套身分驗證相關的功能,並且當我們安裝 Breeze 的時候就已經幫我們實作了大部分機能,不過為了客製或調整其中的功能,還是來了解一下運作的機制。

Auth Facade

Auth Facade 幫助快速取用驗證的相關功能,包含登入登出等等。這邊先介紹直接使用這些功能的方法,底下再來解說背後的機制。

登入

Auth::attempt

當使用者試圖登入的時候,在請求中帶上驗證身分所需的欄位值,在 Laravel 中預設的就是 email , password 。

/app/Http/Requests/Auth/LoginRequest.php

public function rules()
{
    return [
        'email' => ['required', 'string', 'email'],
        'password' => ['required', 'string'],
    ];
}

這邊還沒介紹過資料檢查( Validation )的功能,不過上面的程式碼應該很好理解,email 跟 password 都是必須( required )欄位。

取得必須的欄位值之後就能用 Auth::attempt 進行檢查。

/app/Http/Requests/Auth/LoginRequest.php

use Illuminate\Support\Facades\Auth;

//...

public function authenticate()
{
    $this->ensureIsNotRateLimited();

    // 如果 attempt 檢查失敗的話就拋出錯誤
    if (! Auth::attempt($this->only('email', 'password'), $this->boolean('remember'))) {
        RateLimiter::hit($this->throttleKey());

        throw ValidationException::withMessages([
            'email' => __('auth.failed'),
        ]);
    }

    RateLimiter::clear($this->throttleKey());
}

Auth::attempt 的第一個參數是要驗證的資料陣列,寫得直接點的話會像

Auth::attempt([
    'email' => 'your_email',
    'password' => 'your_password'
]);

第二個參數則用於指定是否要記憶這個使用者,該值為 true 的話就記憶。

Auth::attempt([
    'email' => 'your_email',
    'password' => 'your_password'
    ],
    $rememberMe
);

而記憶的方式則是在 user 的 remember_token 欄位存入 token ,之後使用者用網頁請求時就會用 cookie 內的資訊來對照這個 token 確認是否讓使用者快速登入。而當使用者登出的時候這個欄位就會清空,幫助防止 cookie 被劫持的安全問題

為了驗證身分所必須的欄位其實只有 password ,除 password 外添加的欄位都是用來檢索使用者,這些額外的欄位會應用於 where 的搜尋,只有被搜尋到的使用者會進行密碼驗證。

// 信箱相符且被啟用(active)的用戶才進行密碼驗證
if (Auth::attempt(['email' => $email, 'password' => $password, 'active' => 1])) {
 
}

Auth::attempt 僅用於判定登入資訊是否正確,判定後是要導向首頁或是使用者原本嘗試拜訪的頁面、產生 session id 等行為都是另外定義的。

if (Auth::attempt(['email' => $email, 'password' => $password])) {

    $request->session()->regenerate();

    return redirect()->intended(RouteServiceProvider::HOME);
}

Auth::login

那如果使用者才剛申請完帳號怎麼辦呢? 如果是在已經有 User 實例的情況下,可以直接用 login 方法指定該 User 為目前登入的使用者,並更新 session 。

/app/Http/Controllers/Auth/RegisteredUserController.php

public function store(Request $request)
{
    $request->validate([
        'name' => 'required|string|max:255',
        'email' => 'required|string|email|max:255|unique:users',
        'password' => ['required', 'confirmed', Rules\Password::defaults()],
    ]);

    $user = User::create([
        'name' => $request->name,
        'email' => $request->email,
        'password' => Hash::make($request->password),
    ]);

    $user->setting()->create();

    event(new Registered($user));

    Auth::login($user);  // 指定新申請的用戶為登入的 user

    return redirect(RouteServiceProvider::HOME);
}

跟 attempt 一樣,可以指定是否記憶使用者

Auth::login($user,$remeberMe);

登出

Auth::logout

使用 Auth::logout() 能將更新 user session ,移除其中的登入資訊。

不過官方建議除了登出外也要完全清除 session 資訊,並且重產 csrf_token。

public function logout(Request $request)
{
    Auth::logout();

    $request->session()->invalidate();

    $request->session()->regenerateToken();

    return redirect('/');
}

auth config

Laravel 的身分驗證主要有兩個設定, guards 跟 providers

providers

跟身分驗證有關的系統設定都在 config/auth.php 中。

首先可以找到 providers 的資料。

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
],

providers 可以理解為發行身分證的來源,主要就是指定要去哪裡取得身分相關的資料,像這邊定義 users 使用 eloquent 來查詢 App\Models\User 的資料,並利用查詢出的資料進行身分驗證。我們也可追加不同的身分發行類別。

'providers' => [
    'users' => [
        'driver' => 'eloquent',
        'model' => App\Models\User::class,
    ],
    'admins' => [
        'driver' => 'eloquent',
        'model' => App\Models\Admin::class,
    ], 
],

用在這邊的 model 需要經過特殊處理,需要有對應的欄位好進行身分驗證,最直接的方法就是繼承框架的 User Model

<?php
  
use Illuminate\Foundation\Auth\User as Authenticatable; 

class Admin extends Authenticatable  
{
    //...
}

再打開 Illuminate\Foundation\Auth\User 可以看到也是很多介面或屬性的組合,可以參考來自製想要的 User Model ,比如說不想要信箱驗證功能的話,就不要引入 MustVerifyEmail 屬性。

/vendor/laravel/framework/src/Illuminate/Foundation/Auth/User.php

<?php

namespace Illuminate\Foundation\Auth;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Auth\Passwords\CanResetPassword;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Foundation\Auth\Access\Authorizable;

class User extends Model implements
    AuthenticatableContract,
    AuthorizableContract,
    CanResetPasswordContract
{
    use Authenticatable, Authorizable, CanResetPassword, MustVerifyEmail;
}

guards

guards 用於定義不同的"關口",各個關口會察看不同的身分證,或是用不同的方法來檢查身分證。

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
],

像這邊定義了 web 這個關口,只檢查 providers 中的 users 身分證,並且用 session 的方式進行檢查。

檢查的方式有許多種,除了 session 外 Laravel 預設的套件中有 passport 跟 sanctum ,或是第三方套件的 jwt。

可以定義多個關口

'guards' => [
    'web' => [
        'driver' => 'session',
        'provider' => 'users',
    ],
    'admin' => [
        'driver' => 'session',
        'provider' => 'admins',
    ],
    'api' => [
        'driver' => 'token',
        'provider' => 'users',
    ],      
],

當要進行身分檢查時,可以先指定要用哪個 guard 進行檢查,如果沒有指定就是使用 config/auth.php 設定的 default 值。

// 在 Admin 的資料中找的到的話才能驗證成功
Auth::guard('admin')->login($user);
Auth::guard('admin')->attempt($crednetials);

auth middleware

我們可以在路由中加上 auth 這個中介層,檢查使用者的身分,如果身分符合的話才放行。

/routes/auth.php

Route::post('/logout', [AuthenticatedSessionController::class, 'destroy'])
                ->middleware('auth')
                ->name('logout');

auth 只是個快捷鍵,在 app/Http/Kernel.php 中定義了 auth 對應的中介層類別。

app/Http/Kernel.php

protected $routeMiddleware = [
    'auth' => \App\Http\Middleware\Authenticate::class,
    //...
];

再仔細看看這個 Authenticate 類別,也只是繼承了框架底層的類別,詳細進行身分驗證的檢查都在 Illuminate\Auth\Middleware\Authenticate 中。

在專案的 app 目錄底下的 Authenticate 目前只用來指定當身分檢查失敗時要導向哪個頁面。

/app/Http/Middleware/Authenticate.php

<?php

namespace App\Http\Middleware;

use Illuminate\Auth\Middleware\Authenticate as Middleware;

class Authenticate extends Middleware
{
    protected function redirectTo($request)
    {
        if (! $request->expectsJson()) {
            return route('login'); // 身分驗證失敗就導向登入頁面
        }
    }
}

auth:guard

當加入 auth 中介層時如果沒有額外指定,就是用預設的 guard 進行檢查。

要指定不同的 guard 話,加上 : 後指定

Route::get('/flights', function () {
    // 通過 admin 檢查的用戶才能進入路由
})->middleware('auth:admin');

驗證方法

Laravel 預設的身分檢查是用基礎的 session-cookie 方式,如果想要改成其他方法的話可以利用套件。

這邊簡單介紹幾個可以用的套件,各自的安裝跟使用又都是大長篇就先不細說。

Passport

Laravel 預設包含的套件之一,可以用來進行 Outh2 驗證。

Sanctum

Laravel 預設包含的套件之一,以簡易的 token 進行驗證,可以應用於 SPA 跟行動裝置的 API 驗證。

jwt-auth

顧名思義登入後就產出 jwt ,讓客戶端存好後使用。

laravel-keyable

幫 Model 產出 API key 並用來驗證,可以用於 S2S 的 API 驗證。


上一篇
Facades
下一篇
權限
系列文
Laravel 實務筆記30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言