Laravel has one of the most common features, which is to authenticate persistent users, in which case these are stored in any supported database (MySQL, SQLite, etc.), and when you consider that you can This stuff is amazing when it comes to setting up a web app in minutes and authenticating users with forgotten passwords and all the tweaks. What happens when you need something more complex?
For example, what if you need multiple user types with their own authentication portal? These different types of users can be customers, vendors, and administrators. Mechanics like this can get very confusing very quickly, I know because I've been there. We usually pre-plan the structure of the database but not what our authentication processes and mechanisms will look like.
Recommended: "laravel tutorial"
In this article, I hope to break down and explain how to handle these scenarios.
What are Guards?
Guards in Laravel are a mechanism by which your application can know if someone or even something is authenticated. When we look at the default installation of Laravel, we usually see a guard, which is web. When a visitor authenticates through this guard, any use of the auth middleware will allow the user to view the page because the default guard out of the box is always web. If a person is browsing and is not authenticated at any time, they are called a guest in that guard.
Typically, when adding additional safeguards to a web application, it provides a different authentication mechanism for the API, such as user tokens. However, this default guard does not have to be the only web guard in your application. In fact, we can set up a Guard for different user types, even those who don't authenticate using traditional usernames and passwords.
How do I create a new Authenticatable for an Eloquent provider?
To demonstrate how to create a new Authenticatable, we will use an example of a page through which customers of orders can authenticate. Customers can only authenticate to the application using a signed URL, and once authenticated, they can perform other actions, such as canceling an order.
First, we create a new model:
php artisan make:model Order
Now, we need to modify the Order model in app/Models/Order.php and add some interfaces and traits. This satisfies the Order model which can be used with guard and Eloquent provider types.
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); } }
Please note that compared to the out-of-the-box User model, we can simply extend the framework's User class, but since we're not going to use passwords, we'll ignore models that are able to reset their passwords.
After completing this, we need to add our protection to the auth configuration in configs/auth.php. Because we are also using a different model, we need to implement an additional provider, which we will call the order provider, to be used by the client guard.
auth.php
<?php return [ // auth.php 配置的其余部分 'guards' => [ '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', // ], ], ];
That’s it, our new guard is now authenticated, but we need a mechanism to authenticate visitors without requiring a password.
Does authentication require a password?
Technically, yes, authentication requires a password because it is part of the Lightmate\Contracts\Auth\Authenticatable interface , this interface requires the implementation of getAuthPassword(). In the previous example, we used the Authenticatable trait to provide the implementation. However, this code will only be used if we are trying to use the guard's try method, which we won't be using.
In this case, we have no plans to verify our order via email and password, so we don't have to worry about that. Instead, we will simply create a new Middleware component that will handle the authentication from the signed URL that only our application can generate for sellers to provide to customers.
First, we will set up a sample route for our order in routes/web.php.
web.php
<?php use Illuminate\Support\Facades\Route; Route::get('order/{order}', function (\App\Models\Order $order) { return view('order.view', ['order' => $order]); }) ->name('order.view') ->middleware([ 'auth.signed:order,customer', 'auth:customer,seller', ]);
Please note that we have added an authenticated middleware. It doesn't exist yet, so we have to create one and add it to the http core. We can create middleware using the following command:
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 = [ // 数组的其余部分 'auth.signed' => \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。我们实现了用于updating和viewing单个订单的方法,其余的可以返回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('order/{order}', function (\App\Models\Order $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' => 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 克隆设置好的代码,在演示代码中包含了一个测试,如果您想进一步进行实验,可以使用它。
Original address: https://dev.to/slyfirefox/laravel-authentication-understanding-guards-and-implementing-authenticatables-2364
Translation address: https://learnku.com /laravel/t/63367
The above is the detailed content of One article to thoroughly understand the use of Laravel Guards. For more information, please follow other related articles on the PHP Chinese website!