3

我正在使用 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); */
    }
  }
}

所以,有了这段代码,就会发生这样的事情:

  • 如果我输入数据库中不存在的用户名,我会收到错误消息“凭据错误”。
  • 如果我输入数据库中确实存在的用户名:
    • 密码错误,我得到“错误凭据”。
    • 使用正确的密码,我通过了身份验证,分析器说我使用 Token 类“UsernamePasswordToken”登录。

现在我想告诉你 IntraProvider::supports 方法。现在,它是这样的:

public function       supports(TokenInterface $token)
{
  return $token instanceof IntraUserToken;
}

如果我这样改变它:

public function       supports(TokenInterface $token)
{
  return (true);
}

我输入一个好的用户名(无论密码是什么)的情况有所不同:我已经过身份验证,分析器现在说我使用的是 Token 类“IntraUserToken”(这似乎正是我想要的) .

所以我的问题是:

  • 为什么我的身份验证解决方案不适用于 IntraProvider::supports 方法的第一个版本?
  • 为什么每次在 IntraProvider::supports 方法中都返回 true 时它会起作用?

在食谱中,他们像我在这个方法的第一个版本中那样做,所以我认为每次都返回 true 不是一个好主意,是吗?

我希望我清楚我的问题,感谢您的时间和您的回答!

4

0 回答 0