FOSUserbundle + Additional HTTP auth without user

2019-06-09 10:33发布

I have a "little" problem with the Symfony2 security system. This is what I need to achieve:

  • / needs a HTTP auth BEFORE any page can be seen. I want to protect the whole page with a constant user / pass pair. After the user has entered the right pair, he should be a guest (and not ROLE_USER) und be able to login via the FOSUserBundle form
  • /api needs a separate login via HTTP auth, independent from FOSUserBundle and the other HTTP auth

I already managed to provide a separate login for the API. This is my complete security.yml:

security:
    encoders:
        FOS\UserBundle\Model\UserInterface: sha512
        Symfony\Component\Security\Core\User\User: plaintext

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

    providers:
        in_memory_http:
            memory:
                users:
                    User1: { password: PW1, roles: ROLE_HTTP }
        in_memory_api:
            memory:
                users:
                    User2: { password: PW2, roles: ROLE_API }
        fos_userbundle:
            id: fos_user.user_provider.username_email

    firewalls:
        api:
            pattern: ^/api
            http_basic:
                provider: in_memory_api
                realm: "API login"

        http:
            pattern: ^/
            provider: in_memory_http
            http_basic:
                realm: "Hello"
            context: primary_auth            

        main:
            pattern: ^/                      
            form_login:                 
                provider: fos_userbundle    
                login_path: fos_user_security_login                            
                csrf_provider: form.csrf_provider
                check_path: fos_user_security_check
            logout: 
                path: fos_user_security_logout
                target: home         
            anonymous: true                   
            context: primary_auth        

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

        login:
            security: false

        secured_area: 
            anonymous: ~

    access_control:
        - { path: ^/api, roles: ROLE_API }
        - { path: ^/user/login.html, roles: IS_AUTHENTICATED_ANONYMOUSLY }
        - { path: ^/user/logout.html, roles: IS_AUTHENTICATED_REMEMBERED }

This works nearly as expected, but unfortunately not completely... The /api part works like I wanted it to work. Nothing to do here, I hope.

But when I navigate to /, enter User1/PW1 and send the credentials, I get access to the page, just like expected. The only problem is that the User1 gets logged in! But I want User1 not to be handled like a normal user. He should just be required to access the normal login form and the rest of / except of /api. I can't even log out this user. If I navigate to /user/login.html while User1 is logged in (due to the required http auth) and enter valid user data of a real fosuserbundle user, I get: "You must configure the check path to be handled by the firewall using form_login in your security firewall configuration."

If I want to log out, I get: "You must activate the logout in your security firewall configuration."

What I want is kind of a two step authentication. First HTTP Auth, then the FOSUserBundle form.

Can somebody help me? :) The documentation is not very good at this point...

1条回答
放我归山
2楼-- · 2019-06-09 10:49

So.....

After several hours of pure pain I gave up on trying it with the Symfony2 Security Component. I also did not manage to realize what I want with Apache (Tried SetEnvIf and FilesMatch).

So I wrote a request listener that does what I want. If anybody has the same problem, here is my solution!

use Symfony\Component\HttpKernel\Event\GetResponseEvent;
use Symfony\Component\HttpFoundation\Response;

class RequestListener
{
    /**
    * Returns true iff the specified $password belongs to the $user and the $user has access to the specified $area
    */
    private function hasAccess($user, $password, $area) {
        $users = array("User1" => array("password" => "PW1", 
                                             "areas" => array("portal")),
                       "User2" => array("password" => "PW2", 
                                                     "areas" => array("API", "portal"))
                 );

        return $user
            && array_key_exists($user, $users)
            && $users[$user]["password"] == $password
            && in_array($area, $users[$user]["areas"]);
    }

    /**
    * Extracts the area out of the $path
    */
    public function getArea($path) {
        if (substr($path, 0, 4) == "/api") {
            return "API";    
        } else {
            return "portal";
        }
    }

    /**
    * Service handler
    */
    public function onKernelRequest(GetResponseEvent $event) {      
        $request = $event->getRequest();    

        # $path cointains the path to the virtual resource the user requested!
        # If the user calls app_dev.php/api/users, then $path is "/api/users"
        # If the user calls app.php/api/users,     then $path is "/api/users"
        # If the user calls /api/users,            then $path is "/api/users"
        # If the user calls /app_dev.php,          then $path is "/"
        # If the user calls /,                     then $path is "/"
        # and so on
        #
        # ==> $path abstracts the front controller away
        $path = $request->getPathInfo();
        $area = $this->getArea($path);

        # $user, $password are null if no AUTH data is sent
        $user = $request->server->get("PHP_AUTH_USER");
        $password = $request->server->get("PHP_AUTH_PW");   

        # If the user has no access, he must log in as another user
        if (!$this->hasAccess($user, $password, $area)) {   
            # In case the response already exists (in most cases not) we use it and modify it
            $response = $event->hasResponse() ? $event->getResponse() : new Response();             
            $response->setStatusCode(Response::HTTP_UNAUTHORIZED); # Code 401
            $response->headers->set("WWW-Authenticate", "Basic realm=\"".$area."\"");   
            $response->setContent("Please provide valid data");         
            $response->send();
            die(); # To make sure the page is not shown!
        }   
    }
}

Now everything seems to work...

查看更多
登录 后发表回答