How to create “two” (chat and admin) or more secur

2019-05-11 10:18发布

I'm working in a Symfony 2.8.x app and I need to setup two secured areas: chat and admin. This means that chat and admin will use the same login template (if this is possible and I don't need to setup different one for this purpose). I have googled before ask here and there is a few things related showing up and I read a lot of post about this topic: 1, 2, 3, 4 just as an example of them but I am doing something wrong since I can't get this working properly. This is how /app/config/security.yml looks like (just the firewalls and access_control piece of code):

security:
    ....
    firewalls:
        admin:
            pattern: /admin/(.*)
            anonymous: ~
            form_login:
                provider: fos_userbundle
                csrf_provider: security.csrf.token_manager
                login_path: fos_user_security_login
                check_path: fos_user_security_check  
                use_forward: true                                           
                always_use_default_target_path: true
                default_target_path: /admin
                target_path_parameter: _target_path
                use_referer: true
                remember_me: true
            logout:
              target: /admin
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /
        chat:
            pattern: ^/chat/(.*)
            anonymous: ~
            form_login:
                provider: fos_userbundle
                csrf_provider: security.csrf.token_manager
                login_path: fos_user_security_login
                check_path: fos_user_security_check
                use_forward: true
                always_use_default_target_path: true
                default_target_path: /chat
                target_path_parameter: _target_path
                use_referer: true    
                remember_me: true
            logout: ~
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds
                path:     /

    access_control:
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/chat/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/chat/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/, role: ROLE_CHATTER }
        - { path: ^/admin/, role: ROLE_ADMIN }

Now this is the config for my bundles at app/config/routing.yml:

platform_chat:
    resource: "@PlatformChatBundle/Controller/"
    type:     annotation
    prefix:   /chat/
    options:
            expose: true

platform_admin:
    resource: "@PlatformAdminBundle/Controller/"
    type:     annotation
    prefix:   /admin/
    options:
        expose: true

And for FOSUserBundle I have tried this two (both without success and each at a time):

#FOSUser
fos_user:
    resource: "@FOSUserBundle/Resources/config/routing/all.xml"



#FOSUser    
# this second causes doubts to me since I think I will need 
# to repeat the same routes for chat prefix but I'm not sure at all
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /admin

fos_user_profile:
    resource: "@FOSUserBundle/Resources/config/routing/profile.xml"
    prefix: /admin/profile

fos_user_register:
    resource: "@FOSUserBundle/Resources/config/routing/registration.xml"
    prefix: /admin/register

fos_user_resetting:
    resource: "@FOSUserBundle/Resources/config/routing/resetting.xml"
    prefix: /admin/resetting

fos_user_change_password:
    resource: "@FOSUserBundle/Resources/config/routing/change_password.xml"
    prefix: /admin/profile

I have overwrite the login template at app/Resources/FOSUserBundle/views/Security/login.html.twig. (if source is needed I can provide just ommit for not make the post longer than it's already).

When I call the URL: http://domain.tld/app_dev.php/admin and try to login I got this error:

Translation not found. Context: { "id": "Symfony\Component\Security\Core\Exception\BadCredentialsException: Bad credentials. in /var/www/html/platform-cm/vendor/symfony/symfony/src/Symfony/Component/Security/Core/Authentication/Provider/UserAuthenticationProvider.php:90\nStack trace:\n#0

(I can provide the full stacktrace if needed)

this is weird to me but perhaps it's caused for a bad configuration since I have double checked credentials.

When I call the URL: http://domain.tld/app_dev.php/chat and try to login I got Access Denied but it's right because I am redirected to http://domain.tld/app_dev.php/admin/. Can any give me some help on this configuration? I am stuck and can't move forward because of this

2nd approach

This is a second approach bassed on @heah suggestion using a listener but is not working too I am still getting the same "Bad credentials" message and can't login at all. I have changed back the routing.yml to this:

#FOSUser
fos_user:
    resource: "@FOSUserBundle/Resources/config/routing/all.xml"

I have changed back the security.yml to this:

security: ... firewalls: admin: pattern: ^/ anonymous: ~ form_login: provider: fos_userbundle csrf_provider: security.csrf.token_manager login_path: fos_user_security_login check_path: fos_user_security_check

            # if true, forward the user to the login form instead of redirecting
            use_forward: true

            # login success redirecting options (read further below)
            always_use_default_target_path: true
            default_target_path:            /admin
            target_path_parameter:          _target_path
            use_referer: true
            remember_me:    true
        logout:
          target: /admin
        remember_me:
            secret:   '%secret%'
            lifetime: 604800 # 1 week in seconds
            path:     /

access_control:
    - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/admin/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/chat/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/chat/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/, role: ROLE_CHATTER }
    - { path: ^/admin/, role: ROLE_ADMIN }

Then I have defined a listener for the event security.interactive_login as suggested in app/config/config.yml:

parameters:
    account.security_listener.class: PlatformAdminBundle\Listener\SecurityListener

Then at app/config/services.yml:

services:
    account.security_listener:
        class: %account.security_listener.class%
        arguments: ['@security.context', '@session']
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }

And finally here is the class definition at src/PlatformAdminBundle/Listener/SecurityListener.php:

namespace PlatformAdminBundle\Listener;

use Symfony\Component\Security\Core\SecurityContextInterface;
use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class SecurityListener
{

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

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        $user = $this->security->getToken()->getUser();
        var_export($user);
    }

}

I am running the same issue, again, perhaps I am doing something wrong and I am not seeing but I accept any ideas. What could be wrong here?

3rd approach

I have take a review to my code and change it slighty mostly following @heah suggestions. So, now security.yml is as follow:

access_control:
    - { path: ^/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/resetting$, role: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/logout$, role: IS_AUTHENTICATED_ANONYMOUSLY }

    - { path: ^/chat/, role: ROLE_CHATTER }
    - { path: ^/admin/, role: ROLE_ADMIN }

the changes at services.yml are basically fix the arguments since security.context has been split in Symfony 2.6+ although I am not using it at all:

services:
    ...
    account.security_listener:
        class: %account.security_listener.class%
        arguments: ['@security.authorization_checker']
        tags:
            - { name: kernel.event_listener, event: security.interactive_login, method: onSecurityInteractiveLogin }

And lastly the changes at the class PlatformAdminBundle/Listener/SecurityListener.php:

namespace Clanmovil\PlatformAdminBundle\Listener;

use Symfony\Component\Finder\Exception\AccessDeniedException;
use Symfony\Component\Security\Core\Authorization\AuthorizationCheckerInterface;
use Symfony\Component\Security\Http\Event\InteractiveLoginEvent;

class SecurityListener
{

    public function __construct(AuthorizationCheckerInterface $authorizationChecker)
    {
        $this->security = $authorizationChecker;
    }

    public function onSecurityInteractiveLogin(InteractiveLoginEvent $event)
    {
        if ($this->security->isGranted('ROLE_ADMIN')) {
            // this is something for testing
            throw new AccessDeniedException(
                'Access Denied. You are ADMIN'
            );
        } elseif ($this->security->isGranted('ROLE_CHATTER')) {
            // this is something for testing
            throw new AccessDeniedException(
                'Access Denied. You are CHATTER'
            );
        }
    }
}

When I login as user with ROLE_CHATTER everything seems to work since I got the AccessDenied exception but when I try to login as a user with ROLE_ADMIN it stop working and I come back to the initial error: Bad credentials, why is this? I am getting crazy!!

2条回答
smile是对你的礼貌
2楼-- · 2019-05-11 11:18

The issue may be in the routing. Since in both cases your are using only one from FOSUserBundle to authenticate, you should try to create two different routes one for each firewall, example:

#FOSUser
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /admin

#FOSUser
fos_user_security:
    resource: "@FOSUserBundle/Resources/config/routing/security.xml"
    prefix: /chat
查看更多
▲ chillily
3楼-- · 2019-05-11 11:20

You need to enable translation :

# config.yml

framework:
    translator: ~
...

fos_user:
    db_driver: orm # or mongodb|couchdb|propel
    firewall_name: global
    user_class: AppBundle\Entity\User

see https://symfony.com/doc/master/bundles/FOSUserBundle/index.html

#security.yml

security:
    firewalls:
        # ...
        global:
            pattern:  ^/
            anonymous: true
            provider:  fos_userbundle
            form_login:
                csrf_token_generator: security.csrf.token_manager
                remember_me: true
                default_target_path: root
            logout:
                path: fos_user_security_logout
                target: root
            remember_me:
                secret:   '%secret%'
                lifetime: 604800 # 1 week in seconds

    access_control:
        - { path: ^/admin/login$, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/register, role: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/admin/resetting, role: IS_AUTHENTICATED_ANONYMOUSLY }

        - { path: ^/chat/, role: ROLE_CHATTER }
        - { path: ^/admin/, role: ROLE_ADMIN }

see http://symfony.com/doc/current/components/security/authentication.html

Also you should use the same firewall since they share the same configuration and you already define access controls based on user role.

You would only need to create a core controller for '/' as :

# routing.yml
root:
    path: /
    defaults: { _controller: Your\Namespace\Controller\RootController::rootAction }

and

namespace Your\Namespace\Controller;

use Symfony\Bundle\FrameworkBundle\Controller;

class RootControler extends Controller
{
    public function rootAction()
    {
        $security = $this->get('security.authorization_checker');

        if ($security->isGranted('ROLE_ADMIN')) {
            return $this->redirectToRoute('your_admin_root');
        }

        if ($security->isGranted('ROLE_CHATTER')) {
            return $this->redirectToRoute('your_chatter_route');
        }

        return $this->redirectToRoute('fos_user_security_login');
    }
}
查看更多
登录 后发表回答