私は Symfony 2 を使用して Web アプリケーションに取り組んでいます。より具体的には、Symfony Cookbook の指示に従って、カスタム認証プロバイダーを作成しようとしています。
ログインフォームに入力されたユーザー名とパスワードをデータベースに保存されているものと照合してはならないため、カスタム認証プロバイダーを作成する必要があります。しかし、とにかく、それは私の問題ではありません。
問題は、私のコードを読んで(以下に貼り付けました)、パスワードが正しいかどうかに関係なく(データベースに保存されているものと比較して)、誰でもデータベースに既にあるユーザー名を入力してログインできるはずです。入力したユーザー名がデータベースに存在する場合。しかし、そうではありません。魔法のように、パスワードがチェックされ、間違ったパスワードでログインしようとすると「資格情報が正しくありません」というエラー メッセージが表示されます。
コードを貼り付けます。私の app/config/security.yml :
jms_security_extra:
secure_all_services: false
expressions: true
security:
encoders:
Symfony\Component\Security\Core\User\User: plaintext
Epilille\UserBundle\Entity\User: plaintext
role_hierarchy:
ROLE_STUDENT: ROLE_USER
ROLE_AER: ROLE_STUDENT
ROLE_PROF: ROLE_AER
ROLE_ADMIN: ROLE_PROF
ROLE_SUPER_ADMIN: [ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
providers:
in_memory:
memory:
users:
user: { password: userpass, roles: [ 'ROLE_USER' ] }
admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
student: { password: studentpass, roles: ['ROLE_STUDENT'] }
mestag_a: { password: mestag_apass, roles: ['ROLE_AER'] }
intra_provider:
entity: { class: EpililleUserBundle:User, property: username }
firewalls:
dev:
pattern: ^/(_(profiler|wdt)|css|images|js)/
security: false
login:
pattern: ^/login$
anonymous: true
intra_secured:
pattern: ^/
intra: true
provider: intra_provider
form_login:
login_path: login
check_path: login_check
default_target_path: epilille_home_homepage
logout:
path: logout
target: login
access_control:
# - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY}
私の UserBundle のアーキテクチャ:
.
├── Controller/
│ └── SecurityController.php
├── DataFixtures/
│ └── ORM/
│ └── Users.php
├── DependencyInjection/
│ ├── Configuration.php
│ ├── EpililleUserExtension.php
│ └── Security/
│ └── Factory/
│ └── IntraFactory.php
├── Entity/
│ ├── User.php
│ └── UserRepository.php
├── EpililleUserBundle.php
├── EpiLogin.class.php
├── Resources/
│ ├── config/
│ │ └── services.yml
│ ├── doc/
│ │ └── index.rst
│ ├── public/
│ │ ├── css/
│ │ ├── images/
│ │ └── js/
│ ├── translations/
│ │ └── messages.fr.xlf
│ └── views/
│ ├── layout.html.twig
│ └── User/
│ └── login.html.twig
└── Security/
├── Authentication/
│ ├── Provider/
│ │ └── IntraProvider.php
│ └── Token/
│ └── IntraUserToken.php
├── Firewall/
│ └── IntraListener.php
└── User/
└── IntraUserProvider.php
私の工場:
<?php
namespace Epilille\UserBundle\DependencyInjection\Security\Factory;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Reference;
use Symfony\Component\DependencyInjection\DefinitionDecorator;
use Symfony\Component\Config\Definition\Builder\NodeDefinition;
use Symfony\Bundle\SecurityBundle\DependencyInjection\Security\Factory\SecurityFactoryInterface;
class IntraFactory implements SecurityFactoryInterface
{
public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
{
$providerId = 'security.authentication.provider.intra.'.$id;
$container
->setDefinition($providerId, new DefinitionDecorator('intra.security.authentication.provider'))
->replaceArgument(0, new Reference($userProvider))
;
$listenerId = 'security.authentication.listener.intra.'.$id;
$listener = $container->setDefinition($listenerId, new DefinitionDecorator('intra.security.authentication.listener'));
return array($providerId, $listenerId, $defaultEntryPoint);
}
public function getPosition()
{
return 'pre_auth';
}
public function getKey()
{
return 'intra';
}
public function addConfiguration(NodeDefinition $node)
{
}
}
私のトークン:
<?php
namespace Epilille\UserBundle\Security\Authentication\Token;
use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;
class IntraUserToken extends AbstractToken
{
public function __construct(array $roles = array())
{
parent::__construct($roles);
$this->setAuthenticated(count($roles) > 0);
}
public function getCredentials()
{
return '';
}
}
私の認証プロバイダー:
<?php
namespace Epilille\UserBundle\Security\Authentication\Provider;
use Symfony\Component\Security\Core\Authentication\Provider\AuthenticationProviderInterface;
use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\Exception\NonceExpiredException;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
use Epilille\UserBundle\Security\Authentication\Token\IntraUserToken;
class IntraProvider implements AuthenticationProviderInterface
{
private $userProvider;
public function __construct(UserProviderInterface $userProvider)
{
$this->userProvider = $userProvider;
}
public function authenticate(TokenInterface $token)
{
$user = $this->userProvider->loadUserByUsername($token->getUsername());
if ($user) {
$authenticatedToken = new IntraUserToken($user->getRoles());
$authenticatedToken->setUser($user);
return $authenticatedToken;
}
throw new AuthenticationException('The Intranet authentication failed.');
}
public function supports(TokenInterface $token)
{
// I noticed something with this method I tell you below
return $token instanceof IntraUserToken;
}
}
私のリスナー:
<?php
namespace Epilille\UserBundle\Security\Firewall;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;
use Epilille\UserBundle\Security\Authentication\Token\IntraUserToken;
use Epilille\UserBundle\Security\Authentication\Provider\IntraProvider;
class IntraListener implements ListenerInterface
{
protected $securityContext;
protected $authenticationManager;
public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
{
$this->securityContext = $securityContext;
$this->authenticationManager = $authenticationManager;
}
public function handle(GetResponseEvent $event)
{
// I have to do this check because it seems like this function is called several times and if
// it's not the first time, it will try to reset a new IntraUserToken with _username and _password not set.
if ($this->securityContext->getToken()) {
return;
}
$request = $event->getRequest();
$token = new IntraUserToken();
$token->setUser($request->get('_username'));
$token->password = $request->get('_password');
try {
$authToken = $this->authenticationManager->authenticate($token);
$this->securityContext->setToken($authToken);
} catch (AuthenticationException $failed) {
// ... you might log something here
// To deny the authentication clear the token. This will redirect to the login page.
$this->securityContext->setToken(null);
return;
// Deny authentication with a '403 Forbidden' HTTP response
/* $response = new Response(); */
/* $response->setStatusCode(403); */
/* $event->setResponse($response); */
}
}
}
したがって、このコードでは、次のようになります。
- データベースに存在しないユーザー名を入力すると、「Bad credentials」というエラー メッセージが表示されます。
- データベースに存在するユーザー名を入力した場合:
- 間違ったパスワードを使用すると、「資格情報が正しくありません」というメッセージが表示されます。
- 正しいパスワードを使用すると、認証され、プロファイラーは、トークン クラス 'UsernamePasswordToken' でログに記録されていると言います。
ここで、メソッド IntraProvider::supports について説明したいと思います。今、それはそのようなものです:
public function supports(TokenInterface $token)
{
return $token instanceof IntraUserToken;
}
そして、そのように変更すると:
public function supports(TokenInterface $token)
{
return (true);
}
適切なユーザー名を入力した場合とは違いがあります (パスワードは関係ありません) : 私は認証されており、プロファイラーは私がトークン クラス 'IntraUserToken' を使用していると言っています (それはまさに私が望んでいるもののようです)。 .
だから私の問題は次のとおりです。
- 認証ソリューションが IntraProvider::supports メソッドの最初のバージョンで機能しないのはなぜですか?
- IntraProvider::supports メソッドで毎回 true を返すと機能するのはなぜですか?
クックブックでは、このメソッドの最初のバージョンで行ったのと同じように行っているので、毎回 true を返すのは得策ではないと思いますね。
お時間とご回答ありがとうございます。