建立用户提供商 Creation of User Providersphp
与用户提供商合做 Working with User Providershtml
会话 Sessionsweb
用户操做 User Operations算法
基于控制器的认证 Controller-based authentication数据库
Luthier CI Authentication Framework 是哪一个用户认证系统是创建在笨的结构。它提供了两个主要困境的答案:从中获取用户以及在应用程序中如何使用这些困境。数组
在此过程当中使用用户提供程序。用户提供程序是一个类,负责从某个地方获取通过身份验证的用户,做为CodeIgniter之间的中介,例如,数据库,API或甚至加载到内存中的用户数组。安全
本文针对高级用户并具备很是特定的身份验证需求。若是您正在寻找预先配置且易于使用的解决方案,请参阅SimpleAuth的文档,事实上,这是您将在下面阅读的全部内容的实现。session
全部用户提供商都存储在该application/security/providers文件夹中。此外,您的类必须实现LuthierAuthUserProviderInterface接口,该接口定义如下方法:app
public function getUserClass(); public function loadUserByUsername($username, $password = null); public function hashPassword($password); public function verifyPassword($password, $hash); public function checkUserIsActive(UserInterface $user); public function checkUserIsVerified(UserInterface $user);
让咱们首先建立一个名为的文件MyUserProvider.php,它将成为咱们的第一个用户提供者:框架
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserProviderInterface; class MyUserProvider implements UserProviderInterface { }
该用户实例是经过认证的用户的逻辑表示:它们包含(和返回)全部的细节,角色和权限。咱们必须在User Provider中实现的第一个方法是getUserClass()返回从如今开始使用的用户实例的名称。
咱们将调用咱们的用户实例MyUser,而后:
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserProviderInterface; class MyUserProvider implements UserProviderInterface { public function getUserClass() { return 'MyUser'; } }
下一步是建立类MyUser。用户实例文件保存在application/security/providers文件夹中。为了让Luthier CI使用它们,类的名称和文件名必须与getUserClass()方法中返回的名称相匹配。
用户实例必须实现LuthierAuthUserInterface接口,该接口定义如下方法:
public function __construct($instance, $roles, $permissions); public function getEntity(); public function getUsername(); public function getRoles(); public function getPermissions();
实现全部这些方法,咱们的MyUser类看起来像这样:
<?php # application/security/providers/MyUser.php use Luthier\Auth\UserInterface; class MyUser implements UserInterface { private $user; private $roles; private $permissions; public function __construct($entity, $roles, $permissions) { $this->user = $entity; $this->roles = $roles; $this->permissions = $permissions; } public function getEntity() { return $this->user; } public function getUsername() { return $this->user->email; } public function getRoles() { return $this->roles; } public function getPermissions() { return $this->permissions; } }
咱们的文件结构以下:
application |- security | |- providers | | - MyUserProvider.php | | - MyUser.php
必须从某个地方获取用户,而且要实现如下方法,才能实现loadUserByUsername()该功能。
用户加载只有两种可能的结果:
最简单的示例是在同一个User Provider中声明一个数组,其中包含可用的用户:
$users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => 'foo123', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => 'bar456', 'active' => 1, 'verified' => 1, ] ];
话虽如此,咱们将使用如下代码更新代码:
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserInterface; use Luthier\Auth\UserProviderInterface; use Luthier\Auth\Exception\UserNotFoundException; class MyUserProvider implements UserProviderInterface { public function getUserClass() { return 'MyUser'; } public function loadUserByUsername($username, $password = null) { $users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => 'foo123', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => 'bar456', 'active' => 1, 'verified' => 1, ] ]; $userEmails = array_column($users, 'email'); $foundedIndex = array_search($username, $userEmails); if($foundedIndex === false) { throw new UserNotFoundException('Invalid user credentials!'); } $user = (object) $users[$foundedIndex]; if($user->password != $password) { throw new UserNotFoundException('Invalid user credentials!'); } $userClass = $this->getUserClass(); return new $userClass( /* User data */ $user, /* Roles */ ['user'], /* Permissions */ [] ); } }
如今,咱们的用户提供程序可以搜索数组并在匹配时返回用户对象,或者若是找不到用户则抛出UserNotFoundException异常。可是,做为通常规则,密码一般不会(也不该该)直接存储。
相反,存储使用单向加密算法生成的哈希。考虑这个新用户数组:
$users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => '$2y$10$c1iqXvXuFKZ4hI4l.LhCvuacba1fR3OX.uPfPD29j4DkyayC6p4uu', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => '$2y$10$xNHf.J7fbNdph2dy26JAdeQEA70aL/SG9ojrkpR3ocf1qph0Bafay', 'active' => 1, 'verified' => 1, ] ];
每个的密码仍然彻底相同,不一样的是如今存储的是你的哈希而不是纯文本的密码,
因此比较$user-> password == $password
是不够的。
要实现的如下方法负责在用户提供程序中生成和验证密码哈希:
hashPassword()
: [string] 以纯文本形式接收密码并返回其哈希值verifyPassword()
: [bool] ]以纯文本和密码哈希方式接收密码,验证它们是否匹配逻辑和实现由开发人员自行决定。在咱们的例子中,咱们将使用blowfish算法,留下以下代码:
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserInterface; use Luthier\Auth\UserProviderInterface; use Luthier\Auth\Exception\UserNotFoundException; class MyUserProvider implements UserProviderInterface { public function getUserClass() { return 'MyUser'; } public function loadUserByUsername($username, $password = null) { $users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => '$2y$10$c1iqXvXuFKZ4hI4l.LhCvuacba1fR3OX.uPfPD29j4DkyayC6p4uu', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => '$2y$10$xNHf.J7fbNdph2dy26JAdeQEA70aL/SG9ojrkpR3ocf1qph0Bafay', 'active' => 1, 'verified' => 1, ] ]; $userEmails = array_column($users, 'email'); $foundedIndex = array_search($username, $userEmails); if($foundedIndex === false) { throw new UserNotFoundException('Invalid user credentials!'); } $user = (object) $users[$foundedIndex]; if(!$this->verifyPassword($password, $user->password)) { throw new UserNotFoundException('Invalid user credentials!'); } $userClass = $this->getUserClass(); return new $userClass( /* User data */ $user, /* Roles */ ['user'], /* Permissions */ [] ); } public function hashPassword($password) { return password_hash($password, PASSWORD_DEFAULT); } public function verifyPassword($password, $hash) { return password_verify($password, $hash); } }
您可能已经注意到$password该loadUserByUsername()方法的参数必须定义为可选参数。这是由于在每一个请求开始时,Luthier CI会尝试使用其用户Provider从新加载最后一个通过身份验证的用户,这只有在能够从会话中相对安全的数据存储中获取用户时才有可能,例如ID或用户名。
所以,咱们必须稍微修改咱们的代码,以确保即便没有提供密码,用户提供商仍然可以得到用户:
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserInterface; use Luthier\Auth\UserProviderInterface; use Luthier\Auth\Exception\UserNotFoundException; class MyUserProvider implements UserProviderInterface { public function getUserClass() { return 'MyUser'; } public function loadUserByUsername($username, $password = null) { $users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => '$2y$10$c1iqXvXuFKZ4hI4l.LhCvuacba1fR3OX.uPfPD29j4DkyayC6p4uu', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => '$2y$10$xNHf.J7fbNdph2dy26JAdeQEA70aL/SG9ojrkpR3ocf1qph0Bafay', 'active' => 1, 'verified' => 1, ] ]; $userEmails = array_column($users, 'email'); $foundedIndex = array_search($username, $userEmails); if($foundedIndex === false) { throw new UserNotFoundException('Invalid user credentials!'); } $user = (object) $users[$foundedIndex]; if($password !== NULL) { if(!$this->verifyPassword($password, $user->password)) { throw new UserNotFoundException('Invalid user credentials!'); } } $userClass = $this->getUserClass(); return new $userClass( /* User data */ $user, /* Roles */ ['user'], /* Permissions */ [] ); } public function hashPassword($password) { return password_hash($password, PASSWORD_DEFAULT); } public function verifyPassword($password, $hash) { return password_verify($password, $hash); } }
不要使用md5()或sha1()函数进行密码哈希
这些算法很是有效,任何人(使用现代计算机和足够的空闲时间)均可以尝试经过暴力破解加密。有一节介绍了PHP文档中的password hashes ,对于那些担忧安全性这一重要方面的人来讲,毫无疑问是必读的。
这一切仍然是执行方法checkUserIsActive()和checkUserIsVerified(),正如它们的名字,验证用户是主动和他们的信息被验证。
您能够选择激活和验证用户的条件。在咱们的例子中,对于要激活的用户,其值active必须等于1,而且要验证它的值verified必须等于1。
经过实现这两种方法,咱们的用户提供程序如今以下所示:
<?php # application/security/providers/MyUserProvider.php use Luthier\Auth\UserInterface; use Luthier\Auth\UserProviderInterface; use Luthier\Auth\Exception\UserNotFoundException; use Luthier\Auth\Exception\InactiveUserException; use Luthier\Auth\Exception\UnverifiedUserException; class MyUserProvider implements UserProviderInterface { public function getUserClass() { return 'MyUser'; } public function loadUserByUsername($username, $password = null) { $users = [ [ 'name' => 'John Doe', 'email' => 'john@doe.com', 'password' => '$2y$10$c1iqXvXuFKZ4hI4l.LhCvuacba1fR3OX.uPfPD29j4DkyayC6p4uu', 'active' => 1, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => '$2y$10$xNHf.J7fbNdph2dy26JAdeQEA70aL/SG9ojrkpR3ocf1qph0Bafay', 'active' => 1, 'verified' => 1, ] ]; $userEmails = array_column($users, 'email'); $foundedIndex = array_search($username, $userEmails); if($foundedIndex === false) { throw new UserNotFoundException('Invalid user credentials!'); } $user = (object) $users[$foundedIndex]; if($password !== NULL) { if(!$this->verifyPassword($password, $user->password)) { throw new UserNotFoundException('Invalid user credentials!'); } } $userClass = $this->getUserClass(); return new $userClass( /* User data */ $user, /* Roles */ ['user'], /* Permissions */ [] ); } public function hashPassword($password) { return password_hash($password, PASSWORD_DEFAULT); } public function verifyPassword($password, $hash) { return password_verify($password, $hash); } final public function checkUserIsActive(UserInterface $user) { /* * The getEntity() method is used to return an array / object / entity with the * user data. In our case, it is an object, so we can use * the following chained syntax: */ if($user->getEntity()->active == 0) { throw new InactiveUserException(); } } final public function checkUserIsVerified(UserInterface $user) { /* * The same here: */ if($user->getEntity()->verified == 0) { throw new UnverifiedUserException(); } } }
完成!您已经建立了第一个用户提供程序和附加的用户实例。您已准备好对用户进行身份验证。
在使用用户提供程序以前,您应该作的第一件事是将其上传到您的应用程序。为此,请使用类的静态loadUserProvider()方法Auth。
例如,要加载之前的User Provider,语法以下:
$myUserProvider = Auth::loadUserProvider('MyUserProvider');
要执行登陆,请使用loadUserByUsername()User Provider 的方法,其中第一个参数是用户名/电子邮件,第二个参数是您的密码:
// We load a User Provider: $myUserProvider = Auth::loadUserProvider('MyUserProvider'); // Returns the user object corresponding to 'john@doe.com': $john = $myUserProvider->loadUserByUsername('john@doe.com', 'foo123'); // Returns the corresponding user object to 'alice@brown.com': $alice = $myUserProvider->loadUserByUsername('alice@brown.com', 'bar456');
用户提供程序的设计使得只能使用用户名/电子邮件登陆:
$alice = $myUserProvider->loadUserByUsername('alice@brown.com');
登陆期间的任何错误都会产生异常,应根据具体状况捕获并处理:
// ERROR: The password of john@doe.com is incorrect! // (An exception 'UserNotFoundException' will be thrown) $jhon = $myUserProvider->loadUserByUsername('john@doe.com', 'wrong123'); // ERROR: The user anderson@example.com doesn't exist! // (An exception 'UserNotFoundException' will be thrown) $anderson = $myUserProvider->loadUserByUsername('anderson@example.com', 'test123');
该用户提供返回用户,但这并不意味着他们真的有权登陆。checkUserIsActive()和checkUserIsVerified()方法添加方便额外的检查。
考虑如下用户数组:
$users = [ [ 'name' => 'Alex Rodriguez', 'email' => 'alex@rodriguez.com', 'password' => '$2y$10$2nXHy1LyNL217hfyINGKy.Ef5uhxa1FdmlMDw.nbGOkSEJtT6IJWy', 'active' => 0, 'verified' => 1, ], [ 'name' => 'Alice Brown', 'email' => 'alice@brown.com', 'password' => '$2y$10$xNHf.J7fbNdph2dy26JAdeQEA70aL/SG9ojrkpR3ocf1qph0Bafay', 'active' => 1, 'verified' => 0, ], [ 'name' => 'Jessica Hudson', 'email' => 'jessica@example.com', 'password' => '$2y$10$IpNrG1VG53DrborE4Tl6LevtVgVfoO9.Ef9TBVgH9I10DLRnML9gi', 'active' => 1, 'verified' => 1, ], ];
如下登陆代码:
use Luthier\Auth\Exception\UserNotFoundException; use Luthier\Auth\Exception\InactiveUserException; use Luthier\Auth\Exception\UnverifiedUserException; function advanced_login($username) { $myUserProvider = Auth::loadUserProvider('MyUserProvider'); try { $user = $myUserProvider->loadUserByUsername($username); $myUserProvider->checkUserIsActive($user); $myUserProvider->checkUserIsVerified($user); } catch(UserNotFoundException $e) { return 'ERROR: User not found!'; } catch(InactiveUserException $e) { return 'ERROR: Inactive user!'; } catch(UnverifiedUserException $e) { return 'ERROR: Unverified user!'; } return 'OK: Login success!'; } var_dump( advanced_login('alex@rodriguez.com') ); // ERROR: Inactive user! var_dump( advanced_login('alice@brown.com') ); // ERROR: Unverified user! var_dump( advanced_login('jack@grimes.com') ); // ERROR: User not found! var_dump( advanced_login('jessica@example.com') ); // OK: Login success!
虽然alex@rodriguez.com而且alice@brown.com存在于用户阵列内,可是根据用户提供者,第一个是非活动的而第二个未被验证,而且因为用户jack@grimes .com不存在,惟一能够登陆的用户是jessica@example.com。
因此,你没必要定义advanced_login在应用程序中一次又一次的功能,已经有两个方法,作一样的事情:Auth::attempt()和Auth::bypass(),第一个用于经过用户名和密码,第二经过用户名登陆登陆只。
除了处理异常外,如下表达式与前面的代码等效:
Auth::bypass('alex@rodriguez.com', 'MyUserProvider'); Auth::bypass('alice@brown.com', 'MyUserProvider'); Auth::attempt('alex@rodriguez.com', 'foo123', 'MyUserProvider'); Auth::attempt('alice@brown.com', 'bar456', 'MyUserProvider');
若是通过身份验证的用户不能继续浏览,那么可以登陆的用途是什么?所述Auth类包括用于存储和会话中得到的用户的功能。
要在会话中存储用户,请使用静态方法store():
$alice = $myUserProvider->loadUserByUsername('alice@brown.com'); Auth::store($alice);
只要您不删除会话或它过时,这将在导航的其他部分保存通过身份验证的用户。
要获取存储在会话中的用户,请使用静态方法user():
$alice = Auth::user();
此方法返回用户实例的对象,或者NULL若是没有用户存储。
例:
$alice = Auth::user(); // The user entity // (The returned value depends on the User Provider, although the most common is that it is an object) $alice->getEntity(); // An array with the roles that the User Provider has assigned to the user $alice->getRoles(); // An array with the permissions that the User Provider has assigned to the user $alice->getPermissions();
您可使用静态方法检查用户是匿名的(或邀请的)isGuest():
if( Auth::isGuest() ) { echo "Hi Guest!"; } else { echo "Welcome " . Auth::user()->getEntity()->name . "!"; }
要在一个位置获取并存储您本身的会话数据(与身份验证相关),请使用静态方法session(),其第一个参数是要存储的值的名称,第二个参数是指定的值。
例:
// Store a value Auth::session('my_value', 'foo'); // Get a value $myValue = Auth::session('my_value'); var_dump( $myValue ); // foo // Get ALL stored values var_dump( Auth::session() ); // [ 'my_value' => 'foo' ]
要从当前身份验证会话中删除全部数据(包括当前存储的通过身份验证的用户),请使用静态方法destroy:
Auth::destroy();
有两种操做可用于对通过身份验证的用户执行:角色验证和权限验证。
要验证用户是否具备某个角色,请使用static方法isRole(),其第一个参数是要验证的角色的名称:
Auth::isRole('user');
You can supply a different user object to the one stored in session as a second argument:
$alice = Auth::loadUserProvider('MyUserProvider')->bypass('alice@brown.com'); Auth::isRole('admin', $user);
要验证用户是否具备特定权限,请使用static方法isGranted(),其第一个参数是要验证的权限的名称:
Auth::isGranted('general.read');
您能够将另外一个用户对象提供给存储在会话中的用户对象做为第二个参数:
$alice = Auth::loadUserProvider('MyUserProvider')->bypass('alice@brown.com'); Auth::isGranted('general.read', $user);
到目前为止,您已经看到Luthier CI身份验证框架的元素单独工做。好消息是你可让它们一块儿工做!这一切都归功于咱们称之为基于控制器的身份验证的方法。
基于控制器的身份验证包括两个接口的实现,一个在控制器中,另外一个在中间件中,您能够选择自动化用户身份验证过程。
您能够建立(尽管不是必需的)在应用程序文件夹auth.php内调用的config文件,以配置基于驱动程序的身份验证的选项。SimpleAuth文档中解释了每一个选项的含义
这是配置文件的示例:
<?php # application/config/auth.php $config['auth_login_route'] = 'login'; $config['auth_logout_route'] = 'logout'; $config['auth_login_route_redirect'] = 'dashboard'; $config['auth_logout_route_redirect'] = 'homepage'; $config['auth_route_auto_redirect'] = []; $config['auth_form_username_field'] = 'email'; $config['auth_form_password_field'] = 'password'; $config['auth_session_var'] = 'auth';
若是该文件不存在,将使用默认配置,如上所述。
身份验证驱动程序是实现该Luthier\Auth\ControllerInterface
接口的任何CodeIgniter控制器,它定义了如下方法:
public function getUserProvider(); public function getMiddleware(); public function login(); public function logout(); public function signup(); public function emailVerification($token); public function passwordReset(); public function passwordResetForm($token);
让咱们首先建立一个名为的控制器 AuthController.php
, 它实现了全部必需的方法:
<?php # application/controllers/AuthController.php defined('BASEPATH') OR exit('No direct script access allowed'); use Luthier\Auth\ControllerInterface; class AuthController extends CI_Controller implements ControllerInterface { public function getUserProvider() { return 'MyUserProvider'; } public function getMiddleware() { return 'MyAuthMiddleware'; } public function login() { $this->load->view('auth/login.php'); } public function logout() { return; } public function signup() { $this->load->view('auth/signup.php'); } public function emailVerification($token) { $this->load->view('auth/email_verification.php'); } public function passwordReset() { $this->load->view('auth/password_reset.php'); } public function passwordResetForm($token) { $this->load->view('auth/password_reset_form.php'); } }
getUserProvider()和getMiddleware()方法返回的值对应于用户提供程序和中间件,其中包含将在后续过程当中使用的身份验证事件。对于User Provider,它将与前面的示例相同,MyUserProvider:
public function getUserProvider() { return 'MyUserProvider'; }
对于具备身份验证事件的中间件,将使用一个调用MyAuthMiddleware(尚不存在),咱们将在稍后讨论:
public function `getMiddleware() { return 'MyAuthMiddleware'; }
该login()和logout()方法定义会话的开始和结束。当用户登陆时,Luthier CI会自动截获并处理请求,所以在咱们的控制器中,咱们只须要使用登陆表单显示一个视图:
public function login() { $this->load->view('auth/login.php'); }
注销也将由Luthier CI处理,所以咱们的logout()方法在控制器级别绝对没有任何做用:
public function logout() { return; }
其他方法的实现取决于您,但咱们会让您了解它们的功能应该是什么:
$token
): 负责验证新注册用户的电子邮件。一般,您已收到一封电子邮件,其中包含带有验证令牌($token)的连接。$token
): 检查密码重置请求。几乎老是它是一封发送给用户的电子邮件,其中包含一个带有密码重置令牌的连接($token)咱们的login()方法是指一个叫作的视图auth/login.php。让咱们建立它:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Log in</title> </head> <body> <h1>Log in</h1> <form method="post"> <input type="text" name="username" required /> <input type="password" name="oasswird" required /> <button type="submit">Log in</button> </form> </body> </html>
而后,咱们必须在咱们的web.php文件中添加如下路径:
Route::match(['get', 'post'], 'login', 'AuthController@login')->name('login');
访问url时/login,必须显示咱们建立的登陆表单:
<p align="center">
<img src="img/luthier-ci-login-screen.png" alt="Login screen" class="img-responsive" />
</p>
您能够得到具备在身份验证过程当中发生的错误的安排,并在您的视图中使用它来通知用户。使用该Auth::messages()方法,以下所示:
<!doctype html> <html lang="en"> <head> <meta charset="UTF-8"> <title>Log in</title> </head> <body> <h1>Log in</h1> <?php // We will help ourselves with an array with the translations of the returned // error codes (they are always the same) $errorMessages = [ 'ERR_LOGIN_INVALID_CREDENTIALS' => 'Incorrect email or password', 'ERR_LOGIN_INACTIVE_USER' => 'Inactive user', 'ERR_LOGIN_UNVERIFIED_USER' => 'Unverified user', ]; ?> <?php foreach(Auth::messages() as $type => $message){ ?> <div class="alert alert-<?= $type ;?>"> <?= $errorMessages[$message] ;?> </div> <?php } ?> <form method="post"> <input type="email" name="email" required /> <input type="password" name="password" required /> <button type="submit">Log in</button> </form> </body> </html>
您的登陆表已准备就绪!
您能够随意尝试用户提供商中提供的任意用户名/密码组合。登陆时,您将被重定向到您在选项中定义的路径$config ['auth_login_route_redirect'],或者若是没有这样的路径,则重定向到应用程序的根URL。
如今咱们将配置注销。惟一须要的是定义将要使用的路由,默认状况下它将是您调用的路由logout:
Route::get('logout', 'AuthController@logout')->name('logout');
咱们的路线文件最终将相似于:
Route::match(['get', 'post'], 'login', 'AuthController@login')->name('login'); Route::get('logout', 'AuthController@logout')->name('logout');
你还记得getMiddleware()控制器的方法吗?返回特殊中间件的名称:具备身份验证事件的中间件。
咱们将建立一个名为MyAuthMiddleware扩展抽象类的中间件LuthierAuthMiddleware,经过实现全部必需的方法,它将以下所示:
<?php # application/middleware/MyAuthMiddleware.php defined('BASEPATH') OR exit('No direct script access allowed'); use Luthier\Route; use Luthier\Auth\UserInterface; class MyAuthMiddleware extends Luthier\Auth\Middleware { public function preLogin(Route $route) { return; } public function onLoginSuccess(UserInterface $user) { return; } public function onLoginFailed($username) { return; } public function onLoginInactiveUser(UserInterface $user) { return; } public function onLoginUnverifiedUser(UserInterface $user) { return; } public function onLogout() { return; } }
每种方法都对应一个身份验证事件,以下所述: