【问题标题】:Multisites with only one authentication point只有一个身份验证点的多站点
【发布时间】:2011-12-15 15:29:33
【问题描述】:

对于未来的项目,我正在寻找一种使用 Symfony2 管理多站点开发的方法。事实上,每个站点都在不同的子域上,但工作方式相同;只是样式会有一点变化。

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

Symfony2 可以这样做吗?我知道可以使用多域,但我不知道身份验证系统如何。您对如何进行有什么想法吗?

谢谢!

【问题讨论】:

    标签: symfony


    【解决方案1】:

    实际上,我已经在我正在进行的一个项目中做到了这一点。

    这有点棘手,但是一旦您了解了 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 的可用性。

    祝你好运,随时在 cmets 部分提问。我知道一开始这可能会让人很困惑。

    【讨论】:

    • 谢谢,我会尽快尝试 :)
    • 您可以为不同的域提供什么建议?我可以在他们之间分享记住我的 cookie,但用户可以在没有记住我参数的情况下登录。我也可以在域之间共享会话 id cookie,但恕我直言,这不是好方法。
    猜你喜欢
    • 2016-12-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多