Мультисайты только с одной точкой аутентификации - PullRequest
9 голосов
/ 15 декабря 2011

Для будущего проекта я ищу способ управления разработкой мультисайтов с помощью Symfony2.Фактически каждый сайт будет находиться на отдельном поддомене, но будет работать одинаково;только стиль немного изменится.

Дело в том, что аутентификация является общей для всех дочерних сайтов и управляется главным сайтом (www.mydomain.com).У каждого мультисайта будет своя собственная база данных.

Возможно ли это сделать с Symfony2?Я знаю, что можно использовать несколько доменов, но я не знаю, как насчет системы аутентификации.У вас есть идеи, как поступить?

Спасибо!

1 Ответ

9 голосов
/ 02 января 2012

На самом деле мне удалось сделать это в одном из проектов, над которыми я работаю.

Это немного сложно, но как только вы поймете основную концепцию, лежащую в основе уровня безопасности Symfony, вы сможете легко интегрироваться в существующий проект.

Прежде всего, обязательно прочитайте это: http://symfony.com/doc/current/book/security.html. Я также рекомендовал бы взглянуть на раздел безопасности поваренной книги.

Вы не найдете прямой ответ в руководстве, но это поможет понять код, который я собираюсь вставить здесь.

Основная идея состоит в том, чтобы разделить идентификатор сеанса между поддоменами.

Примечание: ради пространства я опущу теги use и namespace в PHP. Не забудьте импортировать и указать соответствующие пространства имен.

class LoginListener
{

    public function onLogin(InteractiveLoginEvent $event)
    {
        $token = $event->getAuthenticationToken();

        //multisite log-in
        if ($token->getUser() instanceof User)
        {
            $_SESSION['_user_id'] = $token->getUser()->getId();
        }
    }

}

class LogoutListener implements LogoutHandlerInterface
{
    public function logout(Request $request, Response $response, TokenInterface $token)
    {
        if (isset($_SESSION['_user_id']))
        {
            unset($_SESSION['_user_id']);
        }
    }
}

class SessionMatcher implements RequestMatcherInterface
{
    public function matches(Request $request)
    {
        $request->getSession()->start();
        return isset($_SESSION['_user_id']);
    }
}

class CrossLoginUserToken extends AbstractToken
{

    private $id;

    public function getId()
    {
        return $this->id;
    }

    public function __construct($id, array $roles = array())
    {
        parent::__construct($roles);

        $this->id = $id;

        parent::setAuthenticated(count($roles) > 0);
    }

    public function getCredentials()
    {
        return '';
    }

}

class CrossLoginProvider implements AuthenticationProviderInterface
{

    private $userProvider;

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

    public function authenticate(TokenInterface $token)
    {
        $user = $this->userProvider->loadUserByUsername($token->getId());

        if ($user)
        {
            $authenticatedToken = new CrossLoginUserToken($token->getId(),$user->getRoles());
            $authenticatedToken->setUser($user);

            return $authenticatedToken;
        }

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

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

}

class CrossLoginListener implements ListenerInterface
{

    protected $securityContext;
    protected $authenticationManager;
    protected $session;

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

    public function handle(GetResponseEvent $event)
    {
        $this->session->start();
        if (!is_null($this->securityContext->getToken()) && $this->securityContext->getToken()->isAuthenticated())
        {
            return;
        }
        if (isset($_SESSION['_user_id']))
        {
            try
            {
                $token = $this->authenticationManager->authenticate(new CrossLoginUserToken($_SESSION['_user_id']));
                $this->securityContext->setToken($token);
            }
            catch (AuthenticationException $e)
            {
                throw $e;
            }
        }
    }

}

class CrossLoginFactory implements SecurityFactoryInterface
{
    public function create(ContainerBuilder $container, $id, $config, $userProvider, $defaultEntryPoint)
    {
        $providerId = 'security.authentication.provider.crosslogin.' . $id;
        $container
                ->setDefinition($providerId, new DefinitionDecorator('crosslogin.security.authentication.provider'))
                ->replaceArgument(0, new Reference($userProvider))
        ;

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

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

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

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

    public function addConfiguration(NodeDefinition $node)
    {

    }

}

security_factories.yml:

   <?xml version="1.0" encoding="UTF-8"?>
    <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="security.authentication.factory.crosslogin" class="MyBundle\Security\Factory\CrossLoginFactory">
                <tag name="security.listener.factory" />
            </service>
        </services>
    </container>

config.xml:

<service id="crosslogin.security.authentication.provider" class="MyBundle\Security\Authentication\Provider\CrossLoginProvider">
    <argument />
</service>

<service id="crosslogin.security.authentication.listener" class="MyBundle\Security\Firewall\CrossLoginListener">
    <argument type="service" id="security.context" />
    <argument type="service" id="security.authentication.manager" />
    <argument type="service" id="session" />
</service>

<service id="crosslogin.session.matcher" class="MyBundle\Security\Matcher\SessionMatcher">

</service>

<service id="crosslogin.handler.logout" class="MyBundle\Listener\LogoutListener">
    <service id="listener.login" class="Backend\CmsBundle\Listener\LoginListener">
        <tag name="kernel.event_listener" event="security.interactive_login" method="onLogin" />
 </service>

И, наконец, - security.yml:

firewalls:

    ...

    crosslogin:
        crosslogin: true
        provider: dao_provider_by_id
        request_matcher: crosslogin.session.matcher
        logout:
            path: /secured/logout
            target: /
            invalidate_session: true
            handlers: [crosslogin.handler.logout]

providers:

    ...

    dao_provider_by_id:
        entity: { class: YOUR_SECURITY_CLASS_NAME, property: id }

factories:
  CrossLoginFactory: "%kernel.root_dir%/../src/MyBundle/Resources/config/security_factories.xml"

Это самая простая и аккуратная вещь, о которой я только мог подумать. Единственный «неправильно используемый» класс - это SessionMatcher, который проверяет только наличие идентификатора сеанса в сеансе.

Удачи, и не стесняйтесь задавать вопросы в разделе комментариев. Я знаю, что это может быть довольно запутанным в начале.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...