首頁 >php框架 >Laravel >一文徹底弄清楚Laravel Guards的使用

一文徹底弄清楚Laravel Guards的使用

藏色散人
藏色散人轉載
2021-12-07 15:56:513130瀏覽

Laravel有一個最常見的功能,即對持久用戶進行身份驗證,在這種情況下, 這些功能存儲在任何受支持的數據庫(MySQL, SQLite 等),當你考慮到你可以在幾分鐘內設定一個web應用程式並使用忘記的密碼和所有的調整進行用戶身份驗證時,這個東西是令人驚嘆的。當你需要更複雜的東西時會發生什麼?

例如,如果您需要擁有自己的身份驗證入口網站的多個使用者類型,該怎麼辦?這些不同類型的使用者可以是客戶、銷售商和管理員。這樣的機制可能很快就會變得非常混亂,我知道,因為我曾經經歷過。我們通常會預先規劃資料庫的結構,但不會建構我們的身份驗證流程和機制的樣子。

推薦:《laravel教學

在本文中,我希望分解並解釋如何處理這些場景。

什麼是 Guards?

Laravel 中的 Guards 是一種機制,您的應用程式可以透過它知道某人甚至某事是否經過身份驗證。當我們查看 Laravel 的預設安裝時,我們通常會看到一個守衛,即 web。當訪客透過此防護進行身份驗證時,任何使用 auth 中間件都將允許使用者透過查看頁面,這是因為開箱即用的預設防護始終是 web。如果一個人正在瀏覽並且在任何時候都沒有經過身份驗證,那麼他們在該守衛中被稱為訪客。

通常,當在 Web 應用程式中新增額外的防護時,它會為 API 提供不同的驗證機制,例如使用者令牌。不過,這個預設守衛不必是您應用程式中唯一的 Web 守衛。事實上,我們可以為不同的使用者類型設定一個 Guard,即使是那些不使用傳統使用者名稱和密碼進行身份驗證的使用者。

如何為 Eloquent 提供者建立新的 Authenticatable?

為了示範如何建立一個新的 Authenticatable,我們將使用一個頁面範例,訂單的客戶可以透過該頁面進行驗證。客戶只能使用簽名 URL 對應用程式進行身份驗證,一旦通過身份驗證,他們就可以執行其他操作,例如取消訂單。

首先,我們建立一個新模型:

php artisan make:model Order

現在,我們需要修改app/Models/Order.php 中的Order 模型,加入一些interfaces traits。這滿足 Order 模型可用於守衛和 Eloquent 提供者類型。

Order.php

<?php

namespace App\Models;

use Illuminate\Auth\Authenticatable;
use Illuminate\Auth\MustVerifyEmail;
use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Foundation\Auth\Access\Authorizable;

class Order extends Model implements
    AuthenticatableContract,
    AuthorizableContract
{
    use Authenticatable;
    use Authorizable;
    use MustVerifyEmail;

    public function user(): BelongsTo
    {
        return $this->belongsTo(User::class);
    }
}

請注意,與開箱即用的User 模型相比,我們可以簡單地擴展框架的User類,但由於我們不打算使用密碼,所以我們將忽略能夠重置其密碼的模型。

完成此操作後,我們需要將我們的保護加入到 configs/auth.php 中的 auth 配置中。因為我們也使用不同的模型,所以我們需要實現一個附加提供程序,我們稱之為訂單提供程序,由客戶守衛使用。

auth.php

<?php
return [
    // auth.php 配置的其余部分

    &#39;guards&#39; => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'customer' => [
            'driver' => 'session',
            'provider' => 'orders',
        ],
    ],

    // auth.php 配置的其余部分
    'providers' => [
        'users' => [
            'driver' => 'eloquent',
            'model' => App\Models\User::class,
        ],

        'orders' => [
            'driver' => 'eloquent',
            'model' => App\Models\Order::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],
];

就是這樣,我們的新守衛現在已經通過了身份驗證,但是我們需要一個機制來驗證訪問者,而不需要輸入密碼。

身份驗證是否需要密碼?

從技術上講,是的, 身份驗證需要密碼,因為它是Lighmate\Contracts\Auth\Authenticatable介面的一部分,此介面需要getAuthPassword()的實作。在前面的範例中,我們使用了Authenticatable特徵來提供實作。不過,只有當我們嘗試使用守衛的嘗試方法時,才會使用此程式碼,而我們不會使用此方法。

在這種情況下,我們沒有計劃透過電子郵件和密碼來驗證我們的訂單,所以我們不必擔心這一點。取而代之的是,我們將簡單地創建一個新的中間件 元件,它將處理來自簽署的URL的身份驗證,只有我們的應用程式才能產生該URL供賣家提供給客戶。

首先,我們將在routes/web.php中為我們的訂單設定一個範例路由。

web.php

<?php

use Illuminate\Support\Facades\Route;

Route::get(&#39;order/{order}&#39;, function (\App\Models\Order $order) {
    return view(&#39;order.view&#39;, [&#39;order&#39; => $order]);
})
    ->name('order.view')
    ->middleware([
        'auth.signed:order,customer',
        'auth:customer,seller',
    ]);

請注意,我們已經新增了一個經過驗證的中間件。它還不存在,所以我們必須創建一個並將其添加到http內核。我們可以使用以下命令建立中間件:

php artisan make:middleware AuthenticateWhenRequestIsSigned

这将创建app/Http/Middleware/AuthenticateWhenRequestIsSigned.php文件,我们可以编辑该文件。我们将向Handle方法添加两个参数,这两个参数将是要从路由和我们想要进行身份验证的守卫中使用的参数名称。然后,Handle方法的代码非常简单,如果请求已签名,则使用Order参数中的ID值对客户进行身份验证。

AuthenticateWhenRequestIsSigned.php

<?php

namespace App\Http\Middleware;

use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;

class AuthenticateWhenRequestIsSigned
{
    public function handle(Request $request, Closure $next, string $parameterName, string $guard = null)
    {
        if ($request->hasValidSignature()) {
            if (Auth::guard($guard)->check()) {
                Auth::guard($guard)->logout();
            }
            Auth::guard($guard)->loginUsingId($request->route()->parameter($parameterName));
        }

        return $next($request);
    }
}

现在我们已经创建了中间件,我们需要在内核中注册它。

Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // Http内核的其余部分

    /**
     * 应用程序的路由中间件。
     *
     * 这些中间件可以分配到组中,也可以单独使用。
     *
     * @var array
     */
    protected $routeMiddleware = [
        // 数组的其余部分
        &#39;auth.signed&#39; => \App\Http\Middleware\AuthenticateWhenRequestIsSigned::class,
        // 数组的其余部分
    ];

    // Http内核的其余部分
}

这样做不会使中间件工作,因为我们还将身份验证中间件用于我们的路由,这意味着身份验证签名的中间件永远不会执行,因为身份验证中间件具有优先级,并且将在签名中间件有机会对客户进行身份验证之前终止请求。

要解决这个问题,我们只需要向内核添加一个额外的数组,以设置在会话启动中间件之后运行的身份验证签名中间件的优先级。

Kernel.php

<?php

namespace App\Http;

use Illuminate\Foundation\Http\Kernel as HttpKernel;

class Kernel extends HttpKernel
{
    // HTTP内核的其余部分

    /**
     * 中间件的优先级排序列表。
     *
     * 强制非全局中间件始终处于给定顺序。
     *
     * @var string[]
     */
    protected $middlewarePriority = [
        \Illuminate\Cookie\Middleware\EncryptCookies::class,
        \Illuminate\Session\Middleware\StartSession::class,
        \Illuminate\View\Middleware\ShareErrorsFromSession::class,
        \App\Http\Middleware\AuthenticateWhenRequestIsSigned::class,
        \Illuminate\Contracts\Auth\Middleware\AuthenticatesRequests::class,
        \Illuminate\Routing\Middleware\ThrottleRequests::class,
        \Illuminate\Routing\Middleware\ThrottleRequestsWithRedis::class,
        \Illuminate\Session\Middleware\AuthenticateSession::class,
        \Illuminate\Routing\Middleware\SubstituteBindings::class,
        \Illuminate\Auth\Middleware\Authorize::class,
    ];
}

我们可以通过向内核添加midlewarePriority属性来实现这一点,覆盖父级Kernel。在AuthenticatesRequests中间件和StartSession中间件之前这样做意味着,当URL中提供有效签名时,中间件可以对客户进行身份验证。

现在,每当访问者使用带有有效签名的url登陆该页面时,他们将通过我们的守卫进行身份验证,并能够在没有签名的情况下重新访问该url,直到他们的会话超时。不过,这仍然有一个问题,任何这样做的客户也不仅能够查看他们的订单,还可以通过简单地更改URL中的id来查看任何订单。请记住,Authentication不是Authorization,这意味着为了保护客户的其他订单,我们需要添加一些授权。

我们如何保护客户只看到一个订单?

这是一个相当简单的程序。我们现在只需要一个策略,但在本例中,我们需要使用guard参数作为policy make命令的一部分。这将允许我们生成我们需要的大部分代码。

php artisan make:policy --guard customer --model App/Models/Order CustomerOrderPolicy

现在,由于模型和可验证的匹配,我们需要重命名几个方法的参数,并为这些方法分配一个返回值,这将允许订单只查看和更新它自己。我们需要继续编辑app/policies/customerOrderPolicy.php。我们实现了用于updatingviewing单个订单的方法,其余的可以返回false。

CustomerOrderPolicy.php

<?php

namespace App\Policies;

use App\Models\Order;
use Illuminate\Auth\Access\HandlesAuthorization;

class CustomerOrderPolicy
{
    use HandlesAuthorization;

    public function viewAny(Order $order)
    {
        return false;
    }

    public function view(Order $customer, Order $order)
    {
        return $customer->is($order);
    }

    public function create(Order $order)
    {
        return false;
    }

    public function update(Order $customer, Order $order)
    {
        return $customer->is($order);
    }

    public function delete(Order $customer, Order $order)
    {
        return false;
    }

    public function restore(Order $customer, Order $order)
    {
        return false;
    }

    public function forceDelete(Order $customer, Order $order)
    {
        return false;
    }
}

一旦我们完成了这一点,我们只需要注册策略并将中间件添加到路由中。现在,当经过身份验证的用户试图访问除他们自己的订单之外的任何订单时,他们都将失败。这样,我们就通过对用户的身份验证和授权保护了应用程序。

AuthServiceProvider.php

<?php

namespace App\Providers;

use App\Models\Order;
use App\Policies\CustomerOrderPolicy;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;

class AuthServiceProvider extends ServiceProvider
{
    /**
     * 应用程序的策略映射.
     *
     * @var array
     */
    protected $policies = [
        Order::class => CustomerOrderPolicy::class,
    ];

    // AuthServiceProvider 的其余部分
}

现在,我们通过配置路由查看订单的策略。

web.php

<?php

use Illuminate\Support\Facades\Route;

Route::get(&#39;order/{order}&#39;, function (\App\Models\Order $order) {
    return view(&#39;order.view&#39;, [&#39;order&#39; => $order]);
})
    ->name('order.view')
    ->middleware([
        'auth.signed:order,customer',
        'auth:customer,seller',
        'can:view,order'
    ]);

将 Web Guard 重命名为更具描述性的东西有多难?

只有当我们也有一名称为卖家的守卫时,让一名称为客户的守卫才真正有意义,他们仍然会使用电子邮件和密码进行身份验证,为客户生成订单。我们已经有了 web 守卫,但这并不是真正适合所有的 web 用户,而是为卖家准备的,所以我们会相应地给它命名。

重命名默认守卫可能会变得很棘手,特别是在其他中间件和包(如Laravel Sanctum和Fortify)将按名称使用 Web 守卫的情况下。幸运的是,这两个包都有配置选项,可以轻松地更改这一点。

首先,我们必须编辑 configs/auth.php 以拥有一个名为卖家的守卫。然后,我们还需要更新默认值以反映名称更改。

auth.php

<?php
return [
    // auth.php 其余的配置部分

    &#39;guards&#39; => [
        'web' => [
            'driver' => 'session',
            'provider' => 'users',
        ],

        'customer' => [
            'driver' => 'session',
            'provider' => 'orders',
        ],
    ],

    // auth.php 其余的配置部分

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

        'orders' => [
            'driver' => 'eloquent',
            'model' => App\Models\Order::class,
        ],

        // 'users' => [
        //     'driver' => 'database',
        //     'table' => 'users',
        // ],
    ],
];

如果我们还使用 Fortify 和 Sanctum 那么每个配置都需要设置一个 guard ,该值将为这些包配置保护. 之后就可以使用了. 需要用 auth:seller 替代 auth:web 更新路由 。

结论

与 Guards 一起工作一开始可能会有点混乱,在做出长期决定之前肯定需要一些探索。我曾在多个项目中工作过,在这些项目中,分离警卫既是有益的,也是一种负担。通常,处理这种情况的最佳方法是构建一个快速原型,说明如何处理某些分离。通常,在决定访问者是否可以访问网站的特定部分时,使用 Gate  是一个更好的选择。

我已经简单介绍了本文中的所有步骤,如果您希望进行更多的实验或了解此工作流的实际工作方式,您可以从 github repository 克隆设置好的代码,在演示代码中包含了一个测试,如果您想进一步进行实验,可以使用它。

原文網址:https://dev.to/slyfirefox/laravel-authentication-understanding-guards-and-implementing-authenticatables-2364

翻譯網址:https://learnku.com /laravel/t/63367

以上是一文徹底弄清楚Laravel Guards的使用的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:learnku.com。如有侵權,請聯絡admin@php.cn刪除