如何创建一个自定义的表单密码验证器
参考 如何使用Guard创建一个自定义验证系统 以一个更简单、更灵活的方式来完成类似的自定义身份认证的任务。
假设你希望仅在下午2点至下午4点(UTC)才能访问自己的网站。在本文中,你将学习到对于登录表单((即,你的用户提交他们的用户名和密码的地方))来说应如何去做。
Password Authenticator ¶
首先创建一个新类去实现 SimpleFormAuthenticatorInterface
接口。最终,它会允许你创建自定义的逻辑来认证用户:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 |
// src/Acme/HelloBundle/Security/TimeAuthenticator.PHP
namespace Acme\HelloBundle\Security;
use Symfony\Component\Httpfoundation\Request;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken;
use Symfony\Component\Security\Core\Encoder\UserPasswordEncoderInterface;
use Symfony\Component\Security\Core\Exception\CustomUserMessageAuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Http\Authentication\SimpleFormAuthenticatorInterface;
class TimeAuthenticator implements SimpleFormAuthenticatorInterface
{
private $encoder;
public function __construct(UserPasswordEncoderInterface $encoder)
{
$this->encoder = $encoder;
}
public function authenticateToken(TokenInterface $token, UserProviderInterface $userProvider, $providerKey)
{
try {
$user = $userProvider->loadUserByUsername($token->getUsername());
} catch (UsernameNotFoundException $e) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
// 警告: 此信息将返回给客户端(不要在这里放置任何敏感信息/错误字符串)
throw new CustomUserMessageAuthenticationException('Invalid username or password');
}
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
if ($passwordValid) {
$currentHour = date('G');
if ($currentHour < 14 || $currentHour > 16) {
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
// 警告: 此信息将返回给客户端(不要在这里放置任何敏感信息/错误字符串)
throw new CustomUserMessageAuthenticationException(
'You can only log in between 2 and 4!',
100
);
}
return new UsernamePasswordToken(
$user,
$user->getPassword(),
$providerKey,
$user->getRoles()
);
}
// CAUTION: this message will be returned to the client
// (so don't put any un-trusted messages / error strings here)
throw new CustomUserMessageAuthenticationException('Invalid username or password');
}
public function supportsToken(TokenInterface $token, $providerKey)
{
return $token instanceof UsernamePasswordToken
&& $token->getProviderKey() === $providerKey;
}
public function createToken(Request $request, $username, $password, $providerKey)
{
return new UsernamePasswordToken($username, $password, $providerKey);
}
} |
它是如何工作的 ¶
真太好!现在你只需设置一些 配置即可。但首先,不妨看看这个类中的每个方法都做了些什么。
1)createToken ¶
当Symfony开始处理一个请求时,createToken()
被调用,这是你创建了TokenInterface 对象之处,token包含了你在 authenticateToken()
方法中所需之任何信息,用于认证用户(如,对用户名和密码认证)。
无论你创建了何种Token对象,都会被传入 authenticateToken()
。
2)supportsToken ¶
Symfony 调用 createToken()
之后,将调用你的类中的 supportsToken()
方法(以及任何其它的authentication listeners)来计算出应该由谁来处理(当前的)token。这只是一种 “允许多个身份认证机制用于同一防火墙” 的方式(通过这种方式,你可以先尝试使用certificate或者API key来认证用户,[未通过的话]然后再回滚到表单登录)。
多数情况下,对于由 createToken()
方法所创建的token,只需要去让方法返回true
。你的逻辑应与本例颇为相似。
3)authenticateToken ¶
如果 supportsToken()
返回 true
,Symfony即调用 authenticateToken()
。此时你要做的,是检查token是否被允许登录进来,首先通过user provider来获得 User
对象,然后再检查密码和当前时间。
关于如何获取 User
对象的“流程”,以及确定令牌是否有效(如,检查密码),可能会随着你的需求而变。
最后,你要返回一个新的token对象,它是“authenticated”的(已认证。即,它被设置了至少一个role),同时它还包含了 User
对象。
在这个方法中,需要使用password encoder来检查密码的有效性:
1 |
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials()); |
这是Symfony中一个可用的服务,它使用了你在security配置信息(security.yml
)中配置的算法(encoders
键)。下一小节中,你将看到如何把它注入到 TimeAuthenticator
中。
配置 ¶
现在,配置 TimeAuthenticator
令其成为一个服务:
1 2 3 4 5 6 7 |
# app/config/config.yml
services:
# ...
time_authenticator:
class: Acme\HelloBundle\Security\TimeAuthenticator
arguments: ["@security.password_encoder"] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<!-- app/config/config.xml -->
<?xml version="1.0" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<services>
<!-- ... -->
<service id="time_authenticator"
class="Acme\HelloBundle\Security\TimeAuthenticator"
>
<argument type="service" id="security.password_encoder" />
</service>
</services>
</container> |
1 2 3 4 5 6 7 8 9 10 |
// app/config/config.php
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Reference;
// ...
$container->setDefinition('time_authenticator', new Definition(
'Acme\HelloBundle\Security\TimeAuthenticator',
array(new Reference('security.password_encoder'))
)); |
然后,在安全配置的 firewalls
区块中通过 simple_form
键来激活它:
1 2 3 4 5 6 7 8 9 10 11 12 |
# app/config/security.yml
security:
# ...
firewalls:
secured_area:
pattern: ^/admin
# ...
simple_form:
authenticator: time_authenticator
check_path: login_check
login_path: login |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!-- app/config/security.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<srv:container xmlns="http://symfony.com/schema/dic/security"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:srv="http://symfony.com/schema/dic/services"
xsi:schemaLocation="http://symfony.com/schema/dic/services
http://symfony.com/schema/dic/services/services-1.0.xsd">
<config>
<!-- ... -->
<firewall name="secured_area"
pattern="^/admin"
>
<simple-form authenticator="time_authenticator"
check-path="login_check"
login-path="login"
/>
</firewall>
</config>
</srv:container> |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
// app/config/security.php
// ..
$container->loadFromExtension('security', array(
'firewalls' => array(
'secured_area' => array(
'pattern' => '^/admin',
'simple_form' => array(
'provider' => ...,
'authenticator' => 'time_authenticator',
'check_path' => 'login_check',
'login_path' => 'login',
),
),
),
)); |
simple_form
键和通常的 form_login
选项有相同的配置子项,但多了一个 authenticator
键用于指向这个新的服务。更多细节,参考 表单登录配置。
如果你对创建登录表单还是个新手,或者不理解 check_path
和 login_path
选项,参考 如何自定义你的表单登录。