想象一下,如果您想要使您的網(wǎng)站只能在下午兩點(diǎn)到四點(diǎn)之間才能被訪問(wèn)。在 Symfony 2.4 之前必須創(chuàng)建一個(gè)自定義的令牌、 工廠、 監(jiān)聽(tīng)器和提供者才能實(shí)現(xiàn)。在本節(jié)中,您將學(xué)習(xí)如何在一個(gè)登錄表單(即您的用戶提交他們的用戶名和密碼的頁(yè)面)中實(shí)現(xiàn)上述功能 。在 Symfony 2.6 之前,您必須使用密碼編碼器來(lái)驗(yàn)證用戶的密碼。
2.6 在 Symfony 2.6 介紹了 UserPasswordEncoderInterface 接口。
首先,創(chuàng)建新的類來(lái)實(shí)現(xiàn) SimpleFormAuthenticatorInterface 接口。最終,這將允許您創(chuàng)建自定義邏輯來(lái)對(duì)用戶進(jìn)行身份驗(yàn)證:
// src/Acme/HelloBundle/Security/TimeAuthenticator.php
namespace Acme\HelloBundle\Security;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Security\Core\Authentication\SimpleFormAuthenticatorInterface;
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\AuthenticationException;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\User\UserProviderInterface;
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) {
throw new AuthenticationException('Invalid username or password');
}
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
if ($passwordValid) {
$currentHour = date('G');
if ($currentHour < 14 || $currentHour > 16) {
throw new AuthenticationException(
'You can only log in between 2 and 4!',
100
);
}
return new UsernamePasswordToken(
$user,
$user->getPassword(),
$providerKey,
$user->getRoles()
);
}
throw new AuthenticationException('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);
}
}
很好!現(xiàn)在你只需要設(shè)置一些配置。但首先,你可以了解更多有關(guān)此類中的每個(gè)方法的作用。
當(dāng) Symfony 開(kāi)始處理請(qǐng)求,createToken() 方法會(huì)被調(diào)用,在這個(gè)方法中會(huì)創(chuàng)建一個(gè) TokenInterface 對(duì)象,并且該對(duì)象包含的所有您需要的信息都會(huì)在 authenticateToken() 中進(jìn)行用戶身份驗(yàn)證 (例如用戶名和密碼) 。
您在這里創(chuàng)建的所有令牌對(duì)象隨后都將通過(guò) authenticateToken() 方法傳遞給您。
當(dāng) Symfony 調(diào)用 createToken() 方法后,它將會(huì)調(diào)用您創(chuàng)建的類中的 supportsToken() 方法(和任何其它身份驗(yàn)證監(jiān)聽(tīng)器) 來(lái)弄清誰(shuí)應(yīng)該處理令牌。這僅僅是一種允許同一個(gè)防火墻使用幾種身份驗(yàn)證機(jī)制的方法。 (通過(guò)這種方式,例如您可以首先通過(guò)證書(shū)或者 API 密鑰來(lái)對(duì)用戶進(jìn)行身份驗(yàn)證然后再回退到登錄表單)。
大多數(shù)情況下,你只需要確保在這個(gè)方法中,給 createToken() 方法中建立的令牌返回一個(gè)真值。您的程序邏輯應(yīng)該如同這個(gè)例子一樣。
如果 supportsToken 方法返回 true,Symfony 將會(huì)調(diào)用 authenticateToken() 方法。您現(xiàn)在應(yīng)該做的是檢查令牌是否允許首先通過(guò)的用戶提供程序來(lái)獲得用戶對(duì)象,然后通過(guò)檢查密碼和當(dāng)前時(shí)間來(lái)登錄。
關(guān)于如何獲取用戶對(duì)象以及確定令牌是否有效 (例如檢查密碼)的"流",可能會(huì)隨著您的需求而改變。
最終,您的任務(wù)是返回一個(gè)新的并且已經(jīng)"身份驗(yàn)證"過(guò)的令牌對(duì)象(即:至少給它設(shè)定了一個(gè)角色),并且這個(gè)令牌對(duì)象中含有用戶對(duì)象。
在這個(gè)方法中,需要使用密碼編碼器被來(lái)檢查密碼的有效性:
$passwordValid = $this->encoder->isPasswordValid($user, $token->getCredentials());
這已經(jīng)是 Symfony 中可用的服務(wù),并且它使用的是編碼密鑰下的安全配置 (例如 security.yml) 中的密碼算法。下面,您將會(huì)看到如何把它注入 到 TimeAuthenticator 中。
現(xiàn)在,把您的 TimeAuthenticator 作為一種服務(wù)來(lái)配置:
YAML:
# app/config/config.yml
services:
# ...
time_authenticator:
class: Acme\HelloBundle\Security\TimeAuthenticator
arguments: ["@security.password_encoder"]
XML:
<!-- 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>
PHP:
// 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'))
));
接著,使用 simple_form 密鑰在安全配置中的防火墻中激活它:
YAML:
# app/config/security.yml
security:
# ...
firewalls:
secured_area:
pattern: ^/admin
# ...
simple_form:
authenticator: time_authenticator
check_path: login_check
login_path: login
XML:
<!-- 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>
PHP:
// 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 相同的選項(xiàng),但在 simple_form 密鑰中具有指向新服務(wù)的附加的身份驗(yàn)證器密鑰。有關(guān)詳細(xì)信息,請(qǐng)參閱表單登錄配置。
一般情況下,如果您對(duì)如何創(chuàng)建一個(gè)新的登錄表單不熟悉或者是不明白 check_path 和 login_path 選項(xiàng),請(qǐng)參閱如何自定義您的表單登錄。