站长资讯网
最全最丰富的资讯网站

一文彻底弄清Laravel Guards的使用

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 模型,添加一些 interfacestraits。 这满足 Order 模型可用于守卫和 Eloquent 提供者类型。

Order.php

<?php  namespace AppModels;  use IlluminateAuthAuthenticatable; use IlluminateAuthMustVerifyEmail; use IlluminateContractsAuthAccessAuthorizable as AuthorizableContract; use IlluminateContractsAuthAuthenticatable as AuthenticatableContract; use IlluminateDatabaseEloquentModel; use IlluminateDatabaseEloquentRelationsBelongsTo; use IlluminateFoundationAuthAccessAuthorizable;  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 配置的其余部分      'guards' => [         'web' => [             'driver' => 'session',             'provider' => 'users',         ],          'customer' => [             'driver' => 'session',             'provider' => 'orders',         ],     ],      // auth.php 配置的其余部分     'providers' => [         'users' => [             'driver' => 'eloquent',             'model' => AppModelsUser::class,         ],          'orders' => [             'driver' => 'eloquent',             'model' => AppModelsOrder::class,         ],          // 'users' => [         //     'driver' => 'database',         //     'table' => 'users',         // ],     ], ];

就是这样,我们的新守卫现在已经通过了身份验证,但是我们需要一个机制来验证访问者,而不需要输入密码。

身份验证是否需要密码?

从技术上讲,是的, 身份验证需要密码,因为它是LighmateContractsAuthAuthenticatable接口的一部分,该接口需要getAuthPassword()的实现。在前面的示例中,我们使用了Authenticatable特征来提供实现。不过,只有当我们尝试使用守卫的尝试方法时,才会使用此代码,而我们不会使用此方法。

在这种情况下,我们没有计划通过电子邮件和密码来验证我们的订单,所以我们不必担心这一点。取而代之的是,我们将简单地创建一个新的中间件 组件,它将处理来自签名的URL的身份验证,只有我们的应用程序才能生成该URL供卖家提供给客户。

首先,我们将在routes/web.php中为我们的订单设置一个示例路由。

web.php

<?php  use IlluminateSupportFacadesRoute;  Route::get('order/{order}', function (AppModelsOrder $order) {     return view('order.view', ['order' => $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 AppHttpMiddleware;  use Closure; use IlluminateHttpRequest; use IlluminateSupportFacadesAuth;  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 AppHttp;  use IlluminateFoundationHttpKernel as HttpKernel;  class Kernel extends HttpKernel {     // Http内核的其余部分      /**      * 应用程序的路由中间件。      *      * 这些中间件可以分配到组中,也可以单独使用。      *      * @var array      */     protected $routeMiddleware = [         // 数组的其余部分         'auth.signed' => AppHttpMiddlewareAuthenticateWhenRequestIsSigned::class,         // 数组的其余部分     ];      // Http内核的其余部分 }

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

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

Kernel.php

<?php  namespace AppHttp;  use IlluminateFoundationHttpKernel as HttpKernel;  class Kernel extends HttpKernel {     // HTTP内核的其余部分      /**      * 中间件的优先级排序列表。      *      * 强制非全局中间件始终处于给定顺序。      *      * @var string[]      */     protected $middlewarePriority = [         IlluminateCookieMiddlewareEncryptCookies::class,         IlluminateSessionMiddlewareStartSession::class,         IlluminateViewMiddlewareShareErrorsFromSession::class,         AppHttpMiddlewareAuthenticateWhenRequestIsSigned::class,         IlluminateContractsAuthMiddlewareAuthenticatesRequests::class,         IlluminateRoutingMiddlewareThrottleRequests::class,         IlluminateRoutingMiddlewareThrottleRequestsWithRedis::class,         IlluminateSessionMiddlewareAuthenticateSession::class,         IlluminateRoutingMiddlewareSubstituteBindings::class,         IlluminateAuthMiddlewareAuthorize::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 AppPolicies;  use AppModelsOrder; use IlluminateAuthAccessHandlesAuthorization;  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 AppProviders;  use AppModelsOrder; use AppPoliciesCustomerOrderPolicy; use IlluminateFoundationSupportProvidersAuthServiceProvider as ServiceProvider; use IlluminateSupportFacadesGate;  class AuthServiceProvider extends ServiceProvider {     /**      * 应用程序的策略映射.      *      * @var array      */     protected $policies = [         Order::class => CustomerOrderPolicy::class,     ];      // AuthServiceProvider 的其余部分 }

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

web.php

<?php  use IlluminateSupportFacadesRoute;  Route::get('order/{order}', function (AppModelsOrder $order) {     return view('order.view', ['order' => $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 其余的配置部分      'guards' => [         'web' => [             'driver' => 'session',             'provider' => 'users',         ],          'customer' => [             'driver' => 'session',             'provider' => 'orders',         ],     ],      // auth.php 其余的配置部分      'providers' => [         'users' => [             'driver' => 'eloquent',             'model' => AppModelsUser::class,         ],          'orders' => [             'driver' => 'eloquent',             'model' => AppModelsOrder::class,         ],          // 'users' => [         //     'driver' => 'database',         //     'table' => 'users',         // ],     ], ];

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

结论

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

我已经简单介绍了本文中的所有步骤,如果您希望进行

赞(0)
分享到: 更多 (0)