在本文中,我们将介绍 Laravel 框架中的身份验证系统。本文的主要目的是通过扩展核心身份验证系统来创建自定义身份验证防护。
Laravel 在核心中提供了一个非常可靠的身份验证系统,使基本身份验证的实现变得轻而易举。事实上,您只需要运行几个工匠命令来设置身份验证系统的脚手架。
此外,系统本身的设计方式是您可以扩展它并插入您的自定义身份验证适配器。这就是我们将在本文中详细讨论的内容。在我们继续深入研究自定义身份验证守卫的实现之前,我们将首先讨论 Laravel 身份验证系统中的基本元素——守卫和提供者。
核心元素:Guards 和 Providers
Laravel 身份验证系统由两个核心元素组成——守卫和提供者。
警卫
您可以将守卫视为一种提供用于识别经过身份验证的用户的逻辑的方式。在核心,Laravel 提供了不同的守卫,比如 session 和 token。会话守卫通过 cookie 在每个请求中维护用户的状态,另一方面,令牌守卫通过检查每个请求中的有效令牌来验证用户。
因此,如您所见,守卫定义了身份验证的逻辑,它不必总是通过从后端检索有效凭据来处理它。您可以实现一个守卫,它只是检查请求标头中是否存在特定事物并基于此对用户进行身份验证。
在本文后面,我们将实现一个守卫,它检查请求标头中的某些 JSON 参数并从 mongodb 后端检索有效用户。
提供者
如果守卫定义了身份验证的逻辑,则身份验证提供者负责从后端存储中检索用户。如果守卫要求必须针对后端存储验证用户,则检索用户的实现进入身份验证提供程序。
Laravel 附带了两个默认的身份验证提供程序——database 和 Eloquent。数据库身份验证提供程序处理从后端存储中直接检索用户凭据,而 Eloquent 提供了一个抽象层来满足需要。
在我们的示例中,我们将实现一个 MongoDB 身份验证提供程序,该提供程序从 MongoDB 后端获取用户凭据。
以上就是对 Laravel 身份验证系统中的守卫和提供者的基本介绍。从下一节开始,我们将专注于自定义身份验证守卫和提供者的开发!
快速浏览文件设置
让我们快速浏览一下我们将在本文的整个过程中实现的文件列表。
- config/auth.php:这是身份验证配置文件,我们将在其中添加自定义保护的条目。
- config/mongo.php:它是保存 MongoDB 配置的文件。
- app/services/Contracts/NosqlServiceInterface.php:这是我们自定义的 Mongo 数据库类实现的接口。
- app/Database/MongoDatabase.php:它是与 MongoDB 交互的主要数据库类。
- app/Models/Auth/User.php:它是实现 Authenticable 合约的 User 模型类。
- app/Extensions/MongoUserProvider.php:它是身份验证提供程序的实现。
- app/Services/Auth/JsonGuard.php:它是身份验证保护驱动程序的实现。
- app/Providers/AuthServiceProvider.php:这是一个现有文件,我们将使用它来添加我们的服务容器绑定。
- app/Http/Controllers/MongoController.php:这是一个演示控制器文件,我们将实现它来测试我们的自定义守卫。
如果文件列表还没有多大意义,请不要担心,因为我们将在阅读时详细讨论所有内容。
深入实施
在本节中,我们将介绍所需文件的实现。
我们需要做的第一件事是通知 Laravel 我们的自定义守卫。继续并在config/auth.php文件中输入自定义保护详细信息,如图所示。
...
...
'guards' => [
'web' => [
'driver' => 'session',
'provider' => 'users',
],
'api' => [
'driver' => 'token',
'provider' => 'users',
'hash' => false,
],
'custom' => [
'driver' => 'json',
'provider' => 'mongo',
],
],
...
...
如您所见,我们在 custom键下添加了自定义保护。
接下来,我们需要在该providers部分中添加一个关联的提供者条目。
...
...
'providers' => [
'users' => [
'driver' => 'eloquent',
'model' => App\User::class,
],
'mongo' => [
'driver' => 'mongo'
],
// 'users' => [
// 'driver' => 'database',
// 'table' => 'users',
// ],
],
...
...
我们在 mongo键下添加了我们的提供者条目。
最后,让我们将默认身份验证保护从 更改web为custom。
...
...
'defaults' => [
'guard' => 'custom',
'passwords' => 'users',
],
...
...
当然,它还不行,因为我们还没有实现必要的文件。这就是我们将在接下来的几节中讨论的内容。
设置 MongoDB 驱动程序
在本节中,我们将实现与底层 MongoDB 实例对话的必要文件。
让我们首先创建config/mongo.php配置文件,其中包含默认的 MongoDB 连接设置。
<?php
return [
'defaults' => [
'host' => '{HOST_IP}',
'port' => '{HOST_PORT}',
'database' => '{DB_NAME}'
]
];
当然,您需要根据您的设置更改占位符值。
我们将首先创建一个接口,而不是直接创建一个与 MongoDB 交互的类。
创建接口的好处是它提供了开发人员在实现它时必须遵守的契约。此外,如果需要,我们的 MongoDB 实现可以很容易地与另一个 NoSQL 实现交换。
继续创建app/Services/Contracts/NosqlServiceInterface.php接口文件,内容如下。
<?php
// app/Services/Contracts/NosqlServiceInterface.php
namespace App\Services\Contracts;
Interface NosqlServiceInterface
{
/**
* Create a Document
*
* @param string $collection Collection/Table Name
* @param array $document Document
* @return boolean
*/
public function create($collection, Array $document);
/**
* Update a Document
*
* @param string $collection Collection/Table Name
* @param mix $id Primary Id
* @param array $document Document
* @return boolean
*/
public function update($collection, $id, Array $document);
/**
* Delete a Document
*
* @param string $collection Collection/Table Name
* @param mix $id Primary Id
* @return boolean
*/
public function delete($collection, $id);
/**
* Search Document(s)
*
* @param string $collection Collection/Table Name
* @param array $criteria Key-value criteria
* @return array
*/
public function find($collection, Array $criteria);
}
这是一个非常简单的接口,它声明了实现该接口的类必须定义的基本 CRUD 方法。
现在,让我们使用以下内容定义app/Database/MongoDatabase.php类。
<?php
// app/Database/MongoDatabase.php
namespace App\Database;
use App\Services\Contracts\NosqlServiceInterface;
class MongoDatabase implements NosqlServiceInterface
{
private $manager;
private $database;
public function __construct($host, $port, $database)
{
$this->database = $database;
$this->manager = new \MongoDB\Driver\Manager( "mongodb://".$host.":".$port."/".$database );
}
/**
* @see \App\Services\Contracts\NosqlServiceInterface::find()
*/
public function find($collection, Array $criteria)
{
$query = new \MongoDB\Driver\Query($criteria);
$result = $this->manager->executeQuery($this->database.".".$collection, $query);
$user = array();
foreach ($result as $row) {
$user['username'] = $row->username;
$user['password'] = $row->password;
}
return $user;
}
public function create($collection, Array $document) {}
public function update($collection, $id, Array $document) {}
public function delete($collection, $id) {}
}
当然,我假设您已经安装了 MongoDB 和相应的 MongoDB PHP 扩展。
该方法使用必要的参数__construct实例化类。MongoClient我们感兴趣的另一个重要方法是find方法,它根据作为方法参数提供的标准检索记录。
这就是 MongoDB 驱动程序的实现,我试图让它尽可能简单。
设置User模型
遵守认证系统的标准,我们需要实现User模型,模型必须实现Illuminate\Contracts\Auth\Authenticatable契约。
继续创建一个包含以下内容的文件app/Models/Auth/User.php 。
<?php
// app/Models/Auth/User.php
namespace App\Models\Auth;
use Illuminate\Contracts\Auth\Authenticatable as AuthenticatableContract;
use App\Services\Contracts\NosqlServiceInterface;
class User implements AuthenticatableContract
{
private $conn;
private $username;
private $password;
protected $rememberTokenName = 'remember_token';
public function __construct(NosqlServiceInterface $conn)
{
$this->conn = $conn;
}
/**
* fetch user by Credentials
*
* @param array $credentials
* @return Illuminate\Contracts\Auth\Authenticatable
*/
public function fetchUserByCredentials(Array $credentials)
{
$arr_user = $this->conn->find('users', ['username' => $credentials['username']]);
if (! is_null($arr_user)) {
$this->username = $arr_user['username'];
$this->password = $arr_user['password'];
}
return $this;
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthIdentifierName()
*/
public function getAuthIdentifierName()
{
return "username";
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthIdentifier()
*/
public function getAuthIdentifier()
{
return $this->{$this->getAuthIdentifierName()};
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getAuthPassword()
*/
public function getAuthPassword()
{
return $this->password;
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getRememberToken()
*/
public function getRememberToken()
{
if (! empty($this->getRememberTokenName())) {
return $this->{$this->getRememberTokenName()};
}
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::setRememberToken()
*/
public function setRememberToken($value)
{
if (! empty($this->getRememberTokenName())) {
$this->{$this->getRememberTokenName()} = $value;
}
}
/**
* {@inheritDoc}
* @see \Illuminate\Contracts\Auth\Authenticatable::getRememberTokenName()
*/
public function getRememberTokenName()
{
return $this->rememberTokenName;
}
}
你应该已经注意到App\Models\Auth\User实现了Illuminate\Contracts\Auth\Authenticatable合约。
我们类中实现的大多数方法都是不言自明的。话虽如此,我们已经定义了fetchUserByCredentials从可用后端检索用户的方法。在我们的例子中,它将是一个MongoDatabase将被调用以检索必要信息的类。
这就是User模型的实现。
设置身份验证提供程序
正如我们之前所讨论的,Laravel 身份验证系统由两个元素组成——守卫和提供者。
在本节中,我们将创建一个身份验证提供程序,它处理来自后端的用户检索。
继续创建一个文件app/Extensions/MongoUserProvider.php,如下所示。
<?php
// app/Extensions/MongoUserProvider.php
namespace App\Extensions;
use Illuminate\Support\Str;
use Illuminate\Contracts\Auth\UserProvider;
use Illuminate\Contracts\Auth\Authenticatable;
class MongoUserProvider implements UserProvider
{
/**
* The Mongo User Model
*/
private $model;
/**
* Create a new mongo user provider.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
* @return void
*/
public function __construct(\App\Models\Auth\User $userModel)
{
$this->model = $userModel;
}
/**
* Retrieve a user by the given credentials.
*
* @param array $credentials
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function retrieveByCredentials(array $credentials)
{
if (empty($credentials)) {
return;
}
$user = $this->model->fetchUserByCredentials(['username' => $credentials['username']]);
return $user;
}
/**
* Validate a user against the given credentials.
*
* @param \Illuminate\Contracts\Auth\Authenticatable $user
* @param array $credentials Request credentials
* @return bool
*/
public function validateCredentials(Authenticatable $user, Array $credentials)
{
return ($credentials['username'] == $user->getAuthIdentifier() &&
md5($credentials['password']) == $user->getAuthPassword());
}
public function retrieveById($identifier) {}
public function retrieveByToken($identifier, $token) {}
public function updateRememberToken(Authenticatable $user, $token) {}
}
同样,您需要确保自定义提供程序必须实施Illuminate\Contracts\Auth\UserProvider合同。
继续前进,它定义了两个重要的方法 –retrieveByCredentials和validateCredentials.
该retrieveByCredentials方法用于使用User前面部分中讨论的模型类检索用户凭据。另一方面,该validateCredentials方法用于根据给定的凭据集验证用户。
这就是我们自定义身份验证提供程序的实现。在下一节中,我们将继续创建一个与MongoUserProvider身份验证提供程序交互的守卫。
设置身份验证防护
正如我们之前所讨论的,Laravel 身份验证系统中的守卫规定了用户的身份验证方式。在我们的例子中,我们将检查是否存在jsondata应该包含 JSON 编码的凭证字符串的请求参数。
在本节中,我们将创建一个与上一节中刚刚创建的身份验证提供程序交互的守卫。
继续创建一个包含以下内容的文件app/Services/Auth/JsonGuard.php 。
<?php
// app/Services/Auth/JsonGuard.php
namespace App\Services\Auth;
use Illuminate\Http\Request;
use Illuminate\Contracts\Auth\Guard;
use Illuminate\Contracts\Auth\UserProvider;
use GuzzleHttp\json_decode;
use phpDocumentor\Reflection\Types\Array_;
use Illuminate\Contracts\Auth\Authenticatable;
class JsonGuard implements Guard
{
protected $request;
protected $provider;
protected $user;
/**
* Create a new authentication guard.
*
* @param \Illuminate\Contracts\Auth\UserProvider $provider
* @param \Illuminate\Http\Request $request
* @return void
*/
public function __construct(UserProvider $provider, Request $request)
{
$this->request = $request;
$this->provider = $provider;
$this->user = NULL;
}
/**
* Determine if the current user is authenticated.
*
* @return bool
*/
public function check()
{
return ! is_null($this->user());
}
/**
* Determine if the current user is a guest.
*
* @return bool
*/
public function guest()
{
return ! $this->check();
}
/**
* Get the currently authenticated user.
*
* @return \Illuminate\Contracts\Auth\Authenticatable|null
*/
public function user()
{
if (! is_null($this->user)) {
return $this->user;
}
}
/**
* Get the JSON params from the current request
*
* @return string
*/
public function getJsonParams()
{
$jsondata = $this->request->query('jsondata');
return (!empty($jsondata) ? json_decode($jsondata, TRUE) : NULL);
}
/**
* Get the ID for the currently authenticated user.
*
* @return string|null
*/
public function id()
{
if ($user = $this->user()) {
return $this->user()->getAuthIdentifier();
}
}
/**
* Validate a user's credentials.
*
* @return bool
*/
public function validate(Array $credentials=[])
{
if (empty($credentials['username']) || empty($credentials['password'])) {
if (!$credentials=$this->getJsonParams()) {
return false;
}
}
$user = $this->provider->retrieveByCredentials($credentials);
if (! is_null($user) && $this->provider->validateCredentials($user, $credentials)) {
$this->setUser($user);
return true;
} else {
return false;
}
}
/**
* Set the current user.
*
* @param Array $user User info
* @return void
*/
public function setUser(Authenticatable $user)
{
$this->user = $user;
return $this;
}
}
首先,我们的类需要实现Illuminate\Contracts\Auth\Guard接口。因此,我们需要定义该接口中声明的所有方法。
这里要注意的重要一点是,该__construct函数需要实现Illuminate\Contracts\Auth\UserProvider. 在我们的例子中,我们将传递一个 的实例App\Extensions\MongoUserProvider,我们将在后面的部分中看到。
接下来,有一个getJsonParams从名为 的请求参数中检索用户凭据的函数jsondata。正如预期我们将收到用户凭据的 JSON 编码字符串一样,我们使用该 json_decode函数对 JSON 数据进行解码。
在 validate 函数中,我们首先检查的是$credentials参数是否存在。如果它不存在,我们将调用该getJsonParams方法从请求参数中检索用户凭据。
接下来,我们调用提供者的retrieveByCredentials方法,MongoUserProvider从 MongoDB 数据库后端检索用户。最后,是提供者的validateCredentials方法MongoUserProvider检查用户的有效性。
这就是我们自定义守卫的实现。下一节将介绍如何将这些部分拼接在一起以形成一个成功的身份验证系统。
把它们放在一起
到目前为止,我们已经开发了应该为我们提供新的身份验证系统的自定义身份验证防护的所有元素。但是,它不能开箱即用,因为我们需要首先使用 Laravel 服务容器绑定来注册它。
您应该已经知道,Laravel 服务提供者是实现必要绑定的正确位置。
继续打开文件app/Providers/AuthServiceProvider.php,它允许我们添加身份验证服务容器绑定。如果它不包含任何自定义更改,您可以将其替换为以下内容。
<?php
// app/Providers/AuthServiceProvider.php
namespace App\Providers;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Gate;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use App\Services\Auth\JsonGuard;
use App\Extensions\MongoUserProvider;
use App\Database\MongoDatabase;
use App\Models\Auth\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Config;
class AuthServiceProvider extends ServiceProvider
{
/**
* The policy mappings for the application.
*
* @var array
*/
protected $policies = [
'App\Model' => 'App\Policies\ModelPolicy',
];
/**
* Register any authentication / authorization services.
*
* @return void
*/
public function boot()
{
$this->registerPolicies();
$this->app->bind('App\Database\MongoDatabase', function ($app) {
return new MongoDatabase(config('mongo.defaults.host'), config('mongo.defaults.port'), config('mongo.defaults.database'));
});
$this->app->bind('App\Models\Auth\User', function ($app) {
return new User($app->make('App\Database\MongoDatabase'));
});
// add custom guard provider
Auth::provider('mongo', function ($app, array $config) {
return new MongoUserProvider($app->make('App\Models\Auth\User'));
});
// add custom guard
Auth::extend('json', function ($app, $name, array $config) {
return new JsonGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
});
}
public function register()
{
$this->app->bind(
'App\Services\Contracts\NosqlServiceInterface',
'App\Database\MongoDatabase'
);
}
}
让我们看一下boot包含大多数提供程序绑定的方法。
首先,我们将为 App\Database\MongoDatabase和App\Models\Auth\User元素创建绑定。
$this->app->bind('App\Database\MongoDatabase', function ($app) {
return new MongoDatabase(config('mongo.defaults.host'), config('mongo.defaults.port'), config('mongo.defaults.database'));
});
$this->app->bind('App\Models\Auth\User', function ($app) {
return new User($app->make('App\Database\MongoDatabase'));
});
我们已经讨论了一段时间的提供者和守卫,现在是时候将我们的自定义守卫插入 Laravel 身份验证系统了。
我们已经使用AuthFacade 的 provider 方法在 key 下添加了我们的自定义身份验证提供程序mongo。回想一下,该键反映了之前在auth.php文件中添加的设置。
Auth::provider('mongo', function ($app, array $config) {
return new MongoUserProvider($app->make('App\Models\Auth\User'));
});
Auth以类似的方式,我们将使用外观的扩展方法注入我们的自定义保护实现。
Auth::extend('json', function ($app, $name, array $config) {
return new JsonGuard(Auth::createUserProvider($config['provider']), $app->make('request'));
});
接下来是register我们用来将App\Services\Contracts\NosqlServiceInterface接口绑定到 App\Database\MongoDatabase实现的方法。
$this->app->bind( 'App\Services\Contracts\NosqlServiceInterface', 'App\Database\MongoDatabase' );
因此,每当需要解决 App\Services\Contracts\NosqlServiceInterface依赖关系时,Laravel 都会响应App\Database\MongoDatabase适配器的实现。
使用这种方法的好处是可以轻松地将给定的实现与自定义实现交换。例如,假设将来有人想App\Database\MongoDatabase用CouchDB实现适配器替换实现。在这种情况下,他们只需要在register方法中添加相应的绑定即可。
这就是您可以使用的服务提供商。此刻,我们已经拥有了测试自定义守卫实现所需的一切,所以下一个也是最后的部分就是关于这些的。
它有效吗?
您已经完成了设置您的第一个自定义身份验证防护的所有艰苦工作,现在是时候获得好处了,因为我们将继续尝试。
让我们快速实现app/Http/Controllers/MongoController.php控制器,如下所示。
<?php
// app/Http/Controllers/MongoController.php
namespace App\Http\Controllers;
use App\Http\Controllers\Controller;
use Illuminate\Contracts\Auth\Guard;
class MongoController extends Controller
{
public function login(Guard $auth_guard)
{
if ($auth_guard->validate()) {
// get the current authenticated user
$user = $auth_guard->user();
echo 'Success!';
} else {
echo 'Not authorized to access this page!';
}
}
}
仔细看一下login方法的依赖关系,这需要实现Illuminate\Contracts\Auth\Guardguard。由于我们已将自定义保护设置为auth.php文件中的默认保护,因此App\Services\Auth\JsonGuard实际上将被注入!
接下来,我们调用了该类的validate方法,该方法App\Services\Auth\JsonGuard又启动了一系列方法调用:
- 它调用类的
retrieveByCredentials方法App\Extensions\MongoUserProvider。 retrieveByCredentials方法调用类的fetchUserByCredentials方法App\Models\Auth\User。- 该
fetchUserByCredentials方法调用 的find方法App\Database\MongoDatabase来检索用户凭据。 - 最后,返回响应的
find方法!App\Database\MongoDatabase
如果一切正常,我们应该通过调用user我们的守卫方法来获取经过身份验证的用户。
要访问控制器,您应该在routes/web.php文件中添加关联的路由。
Route::get('/custom/mongo/login', 'MongoController@login');
尝试在不传递任何参数的情况下访问 URL https://your-laravel-site/custom/mongo/loginnot authorized ,您应该会看到该消息。
另一方面,尝试类似http://your-laravel-site/custom/mongo/login?jsondata={“username”:”admin”,”password”:”admin”}并且应该返回success消息,如果用户存在于您的数据库中。
请注意,这只是为了演示自定义防护的工作原理。您应该为登录等功能实施一个万无一失的解决方案。事实上,我刚刚提供了对身份验证流程的深入了解;您负责为您的应用程序构建强大且安全的解决方案。
今天的旅程到此结束,希望我能带着更多有用的东西回来。
结论
Laravel 框架在核心中提供了一个可靠的身份验证系统,如果您想实现自定义系统,可以对其进行扩展。这就是今天文章的主题:实现自定义守卫并将其插入 Laravel 身份验证工作流程。
在此过程中,我们继续开发了一个系统,该系统根据请求中的 JSON 有效负载对用户进行身份验证,并将其与 MongoDB 数据库进行匹配。为了实现这一点,我们最终创建了一个自定义守卫和一个自定义提供程序实现。
