Symfony2 - 身份验证后的错误令牌

时间:2013-05-30 10:16:49

标签: symfony forms-authentication token

我是Symfony2的新手,因为我需要实施自定义身份验证,因此需要一周的时间来解决问题。我设法创建了所有内容,即令牌,监听器,提供者,工厂,用户提供者等。我的问题是,在身份验证后,我无法看到正确的令牌。我认为UsernamePasswordProvider(由symfony2提供)仍然在身份验证后运行,而用户提供程序中的refreshUser显示错误的令牌。我试着几乎到处搜索但无法解决这个问题。如果有人知道,需要一些帮助/提示。感谢。

这是错误令牌的图像(http://tinypic.com/r/33bl5l0/5)。这应该是 ADWSUserToken

以下是代码。我正在使用Symfony 2.2.0

ADWSUserToken.php

namespace StudentIntranet\GenericBundle\Security\Authentication\Token;

use Symfony\Component\Security\Core\Authentication\Token\AbstractToken;

class ADWSUserToken extends AbstractToken
{
    public $userObjectType = "";
    public $password = "";

    public function getCredentials()
    {
        return '';
    }
}

ADWSListener.php

namespace StudentIntranet\GenericBundle\Security\Firewall;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;

use StudentIntranet\GenericBundle\Security\Authentication\Token\ADWSUserToken;

class ADWSListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;
    protected $providerKey;

    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
    }

    public function handle(GetResponseEvent $event)
    {
        $httpUtils = new HttpUtils();
        $request = $event->getRequest();
        $form = $request->request->get('form');
        foreach (array('_token', 'username', 'password') as $val) {
            $$val = isset($form[$val]) ? $form[$val] : "";
        }
        $remember_me = isset($form['remember_me']) ? $form['remember_me'] : "Off";

        if (!$username || !$password) {
            return;
        }

        $token = new ADWSUserToken();
        $token->setUser($username);
        $token->password = $password;

        try {
            $authToken = $this->authenticationManager->authenticate($token);
            $this->securityContext->setToken($authToken);
        } catch (AuthenticationException $failed) {
            $this->securityContext->setToken(null);
            $event->setResponse($httpUtils->createRedirectResponse($request, '/login'));
            return;
        }

        // By default deny authorization
//        $response = new Response('NO');
//        $response->setStatusCode(403);
//        $event->setResponse($response);
    }
}

ADWSProvider.php

namespace StudentIntranet\GenericBundle\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 StudentIntranet\GenericBundle\Security\Authentication\Token\ADWSUserToken;

class ADWSProvider implements AuthenticationProviderInterface
{
    private $userProvider;

    public function __construct(UserProviderInterface $userProvider)
    {
        $this->userProvider = $userProvider;
    }

    public function authenticate(TokenInterface $token)
    {
        $username = $token->getUser();
        $password = $token->password;

        // here I need to load info using web service ...

        if ($username == 'admin' && $password == 'testadmin') {
            $token->userObjectType = "User-Staff-MSC";
        } elseif ($username == 'user' && $password == 'testuser') {
            $token->userObjectType = "User-Participant-MSC";
        }

        $userObjectTypes = explode("-", $token->userObjectType);
        $allowedEntites = array('Staff', 'Participant');

        $roles = array(
            'Participant' => 'ROLE_USER',
            'Staff' => 'ROLE_ADMIN'
        );

        if (count($userObjectTypes) >= 2 && in_array($userObjectTypes[1], $allowedEntites)) {
            $tmp = $this->userProvider->loadUserByUsername($token->getUsername());
            $role = $roles[$userObjectTypes[1]];
            $authToken = new ADWSUserToken(array($role));
            $authToken->setUser($username);
            $authToken->userObjectType = $token->userObjectType;
            $authToken->setAuthenticated(true);

            return $authToken;
        }

        //$user = $this->userProvider->loadUserByUsername($token->getUsername());

        throw new AuthenticationException('The ADWS authentication failed.');
    }

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

ADWSFactory.php

namespace StudentIntranet\GenericBundle\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 ADWSFactory implements SecurityFactoryInterface
{
    public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
    {
        $providerId = 'security.authentication.provider.adws.'.$id;
        $container
            ->setDefinition($providerId, new DefinitionDecorator('adws.security.authentication.provider'))
            ->replaceArgument(0, new Reference($userProvider))
        ;

        $listenerId = 'security.authentication.listener.adws.'.$id;
        $listener = $container
            ->setDefinition($listenerId, new DefinitionDecorator('adws.security.authentication.listener'))
        ;

        return array($providerId, $listenerId, $defaultEntryPoint);
    }

    public function getPosition()
    {
        return 'pre_auth';
    }

    public function getKey()
    {
        return 'adws';
    }

    public function addConfiguration(NodeDefinition $node)
    {
    }
}

ADWSUser.php

namespace StudentIntranet\GenericBundle\Security\User;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\User\EquatableInterface;
class ADWSUser implements UserInterface, EquatableInterface
{
    private $username;
    private $password;
    private $salt;
    private $roles;
    public function __construct($username, $password, $salt, array $roles)
    {
        $this->username = $username;
        $this->password = $password;
        $this->salt = $salt;
        $this->roles = $roles;
    }
    public function getRoles()
    {
        return $this->roles;
    }
    public function getPassword()
    {
        return $this->password;
    }
    public function getSalt()
    {
        return $this->salt;
    }
    public function getUsername()
    {
        return $this->username;
    }
    public function eraseCredentials()
    {
    }
    public function isEqualTo(UserInterface $user)
    {
        if (!$user instanceof ADWSUser) {
            return false;
        }
        if ($this->password !== $user->getPassword()) {
            return false;
        }
        if ($this->getSalt() !== $user->getSalt()) {
            return false;
        }
        if ($this->username !== $user->getUsername()) {
            return false;
        }
        return true;
    }
}

ADWSUserProvider.php

namespace StudentIntranet\GenericBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ADWSUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        return new ADWSUser($username, '', '', array('ROLE_USER'));
    }

    public function refreshUser(UserInterface $user)
    {
        return $user;
    }

    public function supportsClass($class)
    {
        return $class === 'StudentIntranet\GenericBundle\Security\User\ADWSUser';
    }
}

StudentIntranetGenericBundle.php

namespace StudentIntranet\GenericBundle;

use StudentIntranet\GenericBundle\DependencyInjection\Security\Factory\ADWSFactory;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Symfony\Component\DependencyInjection\ContainerBuilder;

class StudentIntranetGenericBundle extends Bundle
{
    public function build(ContainerBuilder $container)
    {
        parent::build($container);

        $extension = $container->getExtension('security');
        $extension->addSecurityListenerFactory(new ADWSFactory());
    }
}

config.yml

imports:
    - { resource: parameters.yml }
    - { resource: security.yml }
    - { resource: "@StudentIntranetGenericBundle/Resources/config/services.yml" }

framework:
    #esi:             ~
    #translator:      { fallback: %locale% }
    secret:          %secret%
    router:
        resource: "%kernel.root_dir%/config/routing.yml"
        strict_requirements: %kernel.debug%
    form:            ~
    csrf_protection: ~
    validation:      { enable_annotations: true }
    templating:
        engines: ['twig']
        #assets_version: SomeVersionScheme
    default_locale:  "%locale%"
    trusted_proxies: ~
    session:         ~
    fragments:       ~

# Twig Configuration
twig:
    debug:            %kernel.debug%
    strict_variables: %kernel.debug%

# Assetic Configuration
assetic:
    debug:          %kernel.debug%
    use_controller: false
    bundles:        [ ]
    #java: /usr/bin/java
    filters:
        cssrewrite: ~
        #closure:
        #    jar: %kernel.root_dir%/Resources/java/compiler.jar
        #yui_css:
        #    jar: %kernel.root_dir%/Resources/java/yuicompressor-2.4.7.jar

# Doctrine Configuration
doctrine:
    dbal:
        driver:   %database_driver%
        host:     %database_host%
        port:     %database_port%
        dbname:   %database_name%
        user:     %database_user%
        password: %database_password%
        charset:  UTF8
        # if using pdo_sqlite as your database driver, add the path in parameters.yml
        # e.g. database_path: %kernel.root_dir%/data/data.db3
        # path:     %database_path%

    orm:
        auto_generate_proxy_classes: %kernel.debug%
        auto_mapping: true

# Swiftmailer Configuration
swiftmailer:
    transport: %mailer_transport%
    host:      %mailer_host%
    username:  %mailer_user%
    password:  %mailer_password%
    spool:     { type: memory }

routing.yml

student_intranet_student:
    resource: "@StudentIntranetStudentBundle/Resources/config/routing.yml"
    prefix:   /student/

student_intranet_admin:
    resource: "@StudentIntranetAdminBundle/Resources/config/routing.yml"
    prefix:   /admin/

student_intranet_generic:
    resource: "@StudentIntranetGenericBundle/Resources/config/routing.yml"
    prefix:   /

student_intranet_empty:
    resource: "@StudentIntranetEmptyBundle/Resources/config/routing.yml"
    prefix:   /empty/

security.yml

jms_security_extra:
    secure_all_services: false
    expressions: true

security:
    encoders:
        #Symfony\Component\Security\Core\User\User: plaintext
        StudentIntranet\GenericBundle\Security\User\ADWSUser: plaintext

    role_hierarchy:
        ROLE_ADMIN:       [ROLE_USER, ROLE_ALLOWED_TO_SWITCH]
        #ROLE_ADMIN:       ROLE_USER
        #ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]

    providers:
        #chain_provider:
        #    chain:
        #        providers: in_memory #[user_provider, in_memory]
        user_provider:
            id: adws.user.provider
        #in_memory:
        #    memory:
        #        users:
        #            user:  { password: userpass, roles: [ 'ROLE_USER' ] }
        #            admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
        #            superadmin: { password: superadminpass, roles: [ 'ROLE_SUPER_ADMIN' ] }

    firewalls:
        dev:
            pattern:  ^/(_(profiler|wdt)|css|images|js)/
            security: false

        free_area:
            pattern: ^/default/
            anonymous: ~

        login:
            pattern:  ^/login$
            security: false

        secured_area:
            pattern:    ^/
            adws: true
            form_login:
                provider:               user_provider
                check_path:             login_check
                login_path:             login
            logout:
                path:   logout
                target: /
            switch_user: true

    access_control:
        - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY } #, requires_channel: https
        - { path: ^/student/, roles: ROLE_USER }
        - { path: ^/admin, roles: ROLE_ADMIN }
        #- { path: ^/empty, roles: ROLE_SUPER_ADMIN }

GenericBundle routing.yml

root:
    path:     /
    defaults:
        _controller: FrameworkBundle:Redirect:urlRedirect
        path: /student/

login:
    pattern:  /login
    defaults: { _controller: StudentIntranetGenericBundle:Security:login }

login_check:
    pattern:   /login_check
    #defaults: { _controller: StudentIntranetGenericBundle:Security:loginCheck }

logout:
    pattern:   /logout
    defaults: { _controller: StudentIntranetGenericBundle:Security:logout }

generic_homepage:
    pattern:  /generic
    defaults: { _controller: StudentIntranetGenericBundle:Default:index }

GenericBundle services.yml

parameters:
#    student_intranet_generic.example.class: StudentIntranet\GenericBundle\Example

services:
#    student_intranet_generic.example:
#        class: %student_intranet_generic.example.class%
#        arguments: [@service_id, "plain_value", %parameter%]

    adws.user.provider:
        class: StudentIntranet\GenericBundle\Security\User\ADWSUserProvider

    adws.security.authentication.provider:
        class:  StudentIntranet\GenericBundle\Security\Authentication\Provider\ADWSProvider
        arguments: [""]

    adws.security.authentication.listener:
        class:  StudentIntranet\GenericBundle\Security\Firewall\ADWSListener
        arguments: ["@security.context", "@security.authentication.manager"]

SecurityController.php

namespace StudentIntranet\GenericBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Symfony\Component\Security\Core\SecurityContext;
use Symfony\Component\Validator\Constraints\NotBlank;
//use Symfony\Component\HttpFoundation\Request;

class SecurityController extends Controller
{
    public function loginAction()
    {
        $request = $this->getRequest();
        $session = $request->getSession();
        $authError = SecurityContext::AUTHENTICATION_ERROR;

        // get the login error if there is one
        if ($request->attributes->has($authError)) {
            $error = $request->attributes->get($authError);
        } else {
            $error = $session->get($authError);
            $session->remove($authError);
        }

        $form = $this->createFormBuilder(array())
            ->add('username', 'text', array(
                'constraints' => new NotBlank(),
                ))
            ->add('password', 'password', array(
                'constraints' => new NotBlank(),
                ))
            //->add('remember_me', 'checkbox', array(
            //  'value' => 'On',
            //  'required' => false,
            //  ))
            ->getForm();

        return $this->render(
            'StudentIntranetGenericBundle:Login:index.html.twig',
            array(
                // last username entered by the user
                'last_username' => $session->get(SecurityContext::LAST_USERNAME),
                'error' => $error,
                'form' => $form->createView()
            )
        );
    }

    public function loginCheckAction()
    {
        // The security layer will intercept this request
        /* These are not working...
        if ($this->get('security.context')->isGranted('ROLE_SUPER_ADMIN')) {
            // ... load admin content here
            return $this->redirect($this->generateUrl('student_intranet_empty', array('name' => 'Super Admin')));
        } elseif ($this->get('security.context')->isGranted('ROLE_ADMIN')) {
            // ... load admin content here
            return $this->redirect($this->generateUrl('student_intranet_admin', array('name' => 'Admin')));
        } else {
            // ... load admin content here
            return $this->redirect($this->generateUrl('student_intranet_student', array('name' => 'Student')));
        }
        */
    }

    public function logoutAction()
    {
        // The security layer will intercept this request
    }
}

index.html.twig

{% extends 'StudentIntranetGenericBundle::layout.html.twig' %}
{% block content %}
    {% if error %}
        <div>{{ error.message }}</div>
    {% endif %}
    <form action="{{ path('login_check') }}" method="post">
        {# this has CSRF protection #}
        {{ form_widget(form) }}

        <button type="submit">login</button>
    </form>
{% endblock %}

1 个答案:

答案 0 :(得分:0)

我想我在听众中发现了这个问题。我们需要在成功验证后将事件响应设置为重定向,否则symfony留下的监听器即UsernamePasswordFormAuthenticationListener.php开始运行。另外,我需要正确配置我的用户提供程序。这是为我修改和工作的文件。

P.S。如果需要,我们还需要修改switch用户的监听器。我修改了我的监听器(此处未显示)以处理切换用户,但需要更多配置。

<强> ADWSListener.php

namespace StudentIntranet\GenericBundle\Security\Firewall;

use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\Security\Http\Firewall\ListenerInterface;
use Symfony\Component\Security\Http\HttpUtils;
use Symfony\Component\Security\Core\Exception\AuthenticationException;
use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\Security\Core\Authentication\AuthenticationManagerInterface;

use StudentIntranet\GenericBundle\Security\Authentication\Token\ADWSUserToken;

class ADWSListener implements ListenerInterface
{
    protected $securityContext;
    protected $authenticationManager;
    protected $providerKey;

    public function __construct(SecurityContextInterface $securityContext, AuthenticationManagerInterface $authenticationManager)
    {
        $this->securityContext = $securityContext;
        $this->authenticationManager = $authenticationManager;
    }

    public function handle(GetResponseEvent $event)
    {
        $request = $event->getRequest();
        $httpUtils = new HttpUtils();

        $form = $request->request->get('form');
        foreach (array('_token', 'username', 'password') as $val) {
            $$val = isset($form[$val]) ? $form[$val] : "";
        }
        $remember_me = isset($form['remember_me']) ? $form['remember_me'] : "Off";

        if (!$username || !$password) {
            return;
        }

        $token = new ADWSUserToken();
        $token->setUser($username);
        $token->password = $password;

        try {
            $authToken = $this->authenticationManager->authenticate($token);
            $this->securityContext->setToken($authToken);
            $event->setResponse($httpUtils->createRedirectResponse($request, '/'));
            return;
        } catch (AuthenticationException $failed) {
            $this->securityContext->setToken(null);
            //$event->setResponse($httpUtils->createRedirectResponse($request, '/login'));
            return;
        }

        // By default deny authorization
//        $response = new Response('NO');
//        $response->setStatusCode(403);
//        $event->setResponse($response);
    }

}

<强> ADWSUserProvider.php

namespace StudentIntranet\GenericBundle\Security\User;

use Symfony\Component\Security\Core\User\UserProviderInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Security\Core\Exception\UsernameNotFoundException;
use Symfony\Component\Security\Core\Exception\UnsupportedUserException;

class ADWSUserProvider implements UserProviderInterface
{
    public function loadUserByUsername($username)
    {
        // here I need to load info using web service ...

        if ($username == 'user') {
            return new ADWSUser($username, '', null, array('ROLE_USER'));
        } elseif ($username == 'admin') {
            return new ADWSUser($username, '', null, array('ROLE_ADMIN'));
        }

        throw new UsernameNotFoundException(sprintf('Username "%s" does not exist.', $username));
    }

    public function refreshUser(UserInterface $user)
    {
        if (!$user instanceof ADWSUser) {
            throw new UnsupportedUserException(sprintf('Instances of "%s" are not supported.', get_class($user)));
        }

        return $this->loadUserByUsername($user->getUsername());

    }

    public function supportsClass($class)
    {
        return $class === 'StudentIntranet\GenericBundle\Security\User\ADWSUser';
    }
}