只有一个身份验证点的多站点

时间:2011-12-15 15:29:34

标签: symfony

对于未来的项目,我正在寻找一种使用Symfony2管理多站点开发的方法。实际上,每个站点都将位于不同的子域中,但工作方式相同;只有风格会有所改变。

问题是:身份验证对所有子网站都是通用的,并由主站点(www.mydomain.com)管理。然后,每个多站点都有自己的数据库。

是否可以使用Symfony2进行此操作?我知道可以使用多域,但我不知道身份验证系统如何。你有关于如何进行的想法吗?

谢谢!

1 个答案:

答案 0 :(得分:9)

实际上我已经设法在我正在开展的一个项目中做到这一点。

这有点棘手但是一旦你理解了symfony安全层背后的基本概念,它就很容易集成到你现有的项目中。

首先,请务必阅读:http://symfony.com/doc/current/book/security.html。我还建议您查看食谱的安全部分。

您不会在手册中找到直接的答案,但它有助于理解我要在此处粘贴的代码。

基本思路是在子域中共享会话ID。

注意:为了空间,我将省略PHP中的usenamespace标记。不要忘记导入并指定适当的名称空间。

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,它只检查会话中会话ID的可用性。

祝你好运,并随意在评论部分提问。我知道一开始可能会让人感到困惑。