在本文中,我们将介绍Laravel框架中的身份验证系统。 本文的主要目的是通过扩展核心身份验证系统来创建自定义身份验证防护。
Laravel在内核中提供了一个非常可靠的身份验证系统,使基本身份验证的实现变得轻而易举。 实际上,您只需要运行几个artisan命令即可设置身份验证系统的支架。
此外,系统本身的设计方式是可以扩展它并插入自定义身份验证适配器。 这就是我们将在本文中详细讨论的内容。 在继续研究自定义身份验证保护的实现之前,我们将首先讨论Laravel身份验证系统中的基本元素-保护和提供程序。
核心要素:警卫和提供者
Laravel身份验证系统由两个核心要素组成:防护和提供者。
守卫
您可以将防护视为提供用于标识经过身份验证的用户的逻辑的一种方式。 在核心中,Laravel提供了不同的保护措施,例如会话和令牌。 会话保护程序通过cookie维护每个请求中用户的状态,另一方面,令牌保护程序通过检查每个请求中的有效令牌来验证用户身份。
因此,您可以看到,防护定义了身份验证的逻辑,并且不必总是通过从后端检索有效凭据来处理身份验证。 您可以实现一种防护措施,该防护措施仅检查请求标头中是否存在特定内容,并根据该内容对用户进行身份验证。
在本文的后面,我们将实现一种保护措施,该保护措施检查请求标头中的某些JSON参数并从MongoDB后端检索有效用户。
提供者
如果防护定义了身份验证的逻辑,则身份验证提供者负责从后端存储中检索用户。 如果防护措施要求必须针对后端存储对用户进行验证,则检索用户的实现将进入身份验证提供程序。
Laravel附带了两个默认的身份验证提供程序-数据库和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
:实现可认证合同的是用户模型类。 -
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',
],
'custom' => [
'driver' => 'json',
'provider' => 'mongo',
],
],
...
...
如您所见,我们在自定义键下添加了自定义防护。
接下来,我们需要在providers部分添加一个关联的provider条目。
...
...
'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 $connection;
private $database;
public function __construct($host, $port, $database)
{
$this->connection = new MongoClient( "mongodb://{$host}:{$port}" );
$this->database = $this->connection->{$database};
}
/**
* @see \App\Services\Contracts\NosqlServiceInterface::find()
*/
public function find($collection, Array $criteria)
{
return $this->database->{$collection}->findOne($criteria);
}
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驱动程序的实现,我尝试使其保持尽可能简单。
设置用户模型
遵循身份验证系统的标准,我们需要实现必须实现Illuminate\Contracts\Auth\Authenticatable
合同的User模型。
继续并使用以下内容创建文件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
类,将调用该类来检索必要的信息。
这就是用户模型的实现。
设置身份验证提供程序
正如我们之前讨论的那样,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
身份验证提供程序进行交互的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
方法从请求参数中检索用户凭证。
接下来,我们调用MongoUserProvider
提供程序的retrieveByCredentials
方法,该方法从MongoDB数据库后端检索用户。 最后,检查用户有效性的是MongoUserProvider
提供程序的validateCredentials
方法。
这就是我们的海关守卫的实现。 下一节将介绍如何将这些部分缝合在一起以形成成功的身份验证系统。
放在一起
到目前为止,我们已经开发了定制身份验证防护的所有元素,这些元素应该为我们提供新的身份验证系统。 但是,它不能立即使用,因为我们首先需要使用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身份验证系统了。
我们已经使用Auth
Facade的provider方法在键mongo下添加了自定义身份验证提供程序。 回想一下,该键反映了先前在auth.php
文件中添加的设置。
Auth::provider('mongo', function ($app, array $config) {
return new MongoUserProvider($app->make('App\Models\Auth\User'));
});
以类似的方式,我们将使用Auth
门面的extend方法注入自定义防护实现。
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
适配器的实现来响应。
使用这种方法的好处是可以轻松地将给定的实现与自定义实现互换。 例如,假设有人将来希望用CouchDB适配器替换App\Database\MongoDatabase
实现。 在这种情况下,他们只需要在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!';
}
}
}
仔细查看登录方法的依赖关系,该依赖关系要求实现Illuminate\Contracts\Auth\Guard
保护。 由于我们已在auth.php
文件中将自定义防护设置为默认防护,因此实际上将注入App\Services\Auth\JsonGuard
!
接下来,我们调用了App\Services\Auth\JsonGuard
类的validate
方法,该方法又启动了一系列方法调用:
- 它调用
App\Extensions\MongoUserProvider
类的retrieveByCredentials
方法。 - 该
retrieveByCredentials
方法调用fetchUserByCredentials
用户的方法App\Models\Auth\User
类。 -
fetchUserByCredentials
方法调用App\Database\MongoDatabase
的find
方法来检索用户凭据。 - 最后,
App\Database\MongoDatabase
的find
方法返回响应!
如果一切都按预期工作,则应通过调用防护的user
方法来获取经过身份验证的用户。
要访问控制器,您应该在routes/web.php
文件中添加关联的routes/web.php
。
Route::get('/custom/mongo/login', 'MongoController@login');
尝试在不传递任何参数的情况下访问URL http:// your-laravel-site / custom / mongo / login ,您会看到“未授权”消息。
另一方面,尝试使用类似http:// your-laravel-site / custom / mongo / login?jsondata = {“ username”:“ admin”,“ password”:“ admin”}之类的方法 ,它应该返回成功消息如果用户存在于您的数据库中。
请注意,这只是出于示例目的,以演示自定义防护的工作原理。 您应该为登录等功能实现万无一失的解决方案。 实际上,我只是提供了对身份验证流程的深入了解; 您负责为您的应用程序构建健壮且安全的解决方案。
今天结束了我们的旅程,希望我会再提供更多有用的东西。 如果您想让我写任何特定的主题,请不要忘了给我留言!
结论
Laravel框架在核心中提供了一个可靠的身份验证系统,如果您想实现自定义身份验证系统,则可以对其进行扩展。 这就是今天文章的主题,以实现自定义防护并将其插入Laravel身份验证工作流。
在此过程中,我们继续开发了一个系统,该系统基于请求中的JSON负载对用户进行身份验证,并将其与MongoDB数据库进行匹配。 为此,我们最终创建了一个自定义防护和一个自定义提供程序实现。
我希望该练习为您提供了Laravel身份验证流程的真知灼见,现在您应该对它的内部运作更有信心。
对于那些刚刚开始使用Laravel或希望通过扩展来扩展您的知识,网站或应用程序的人,我们可以在Envato Market上进行各种研究。
我希望收到您的反馈和建议,请使用下面的Feed大声喊叫!