简体   繁体   中英

Symfony 3 security After login there is an infinite redirect

I have a problem with custom symfony security login inspired by tutorial "How to Build a Traditional Login Form" from Symfony cookbook. Everything works fine until I log in from /login route. Then every route I want redirect to is causing an Infinite loop. Maybe I'm missing something?

//security.yml security:

encoders:
        AppBundle\Entity\User:
            algorithm: bcrypt

providers:
    mysql:
        entity:
            class: AppBundle:User
            property: username


firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

    main:
        anonymous: ~
        form_login:
            login_path: login
            check_path: login_check
        logout:
            path: logout


access_control:
    - { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

//routing.yml

index:
path: /
defaults: { _controller: AppBundle:Main:main }
app:
    resource: "@AppBundle/Controller/"
    type:     annotation

//SecurityController.php

<?php

namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class SecurityController extends Controller
{
/**
 * @Route("/login", name="login")
 */
public function loginAction() {
    $authenticationUtils = $this->get("security.authentication_utils");

    $error = $authenticationUtils->getLastAuthenticationError();
    $name = $authenticationUtils->getLastUsername();

    return $this->render(
        ':security:login.html.twig',
        ["name" => $name, "error" => $error]
    );
}
/**
 * @Route("/login_check", name="login_check")
 */
public function loginCheckAction() {
}
/**
 * @Route("/logout",name="logout")
 */
public function logoutAction() {
}

}

//login.twig.html

{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}

<form action="{{ path('login_check') }}" method="post">
     <label for="username">Username:</label>
     <input type="text" id="username" name="_username" value="{{ name }}" />

     <label for="password">Password:</label>
     <input type="password" id="password" name="_password" />
     <input type="hidden" name="_target_path" value="/" />

     <button type="submit">login</button>
</form>

Okay so I finally located problem and the problem wasnt in the routes or security at all. I just did serializing in User Entity wrong. Some misspell symfony doesnt mentioned in debugger. It is weird it caused this kind of behaviour. Anyway thanks to anyone who tried to help me.

You should change

access_control:
- { path: ^/login, roles: IS_AUTHENTICATED_ANONYMOUSLY }

to

access_control:
- { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }

to have login_check behind the firewall.

Your routing.yml seems odd to me, could you post your controller and your twig to see your routes ?

The reason is because the login script is forwarding you to the login page, when it sees that you are logged in it's forwarding you to the referrer.. which is the login page.. and so on..

To get around this you have a few options.

1) You could set a default target path in your app/config/security.yml so that every time a user logs in they are forwarded to the specific route. The issue with this approach is that the use of always_use_default_target_path will mean that it will never go to the referring page but if you don't use it and you fail an attempt at logging in it will direct you to the login page (the referrer at this point).

firewalls:
    //..
    main:
        anonymous: ~
        form_login:
            default_target_path: **default_route**
            always_use_default_target_path: true
            login_path: login
            check_path: login_check
        logout:
            path: logout

2) You could set a field in your form login that sets a default target path, which could be the referrer or a specified one depending on the case. This issue with this approach is that the user will need to go through the login process to be forwarded so if the users goes back to the login page for some reason they will be required to login again before being redirected.

//...

{% set referer = app.request.headers.get('referer') %}
{% set targetPath = referer is not null and referer != url('login')
    ? referer
    : path('**default_route**')
%}

<form action="{{ path('login_check') }}" method="post">
    //...

    <input type="hidden" name="_target_path" value="{{ targetPath }}" />
</form>

3) My recommendation You could set a check in your login controller that can check if the user is already logged in and then redirecting the user to a specific route. The benefit of this is that it will stop users that are already logged in going to the login form but will still enable you to forward user to the referring page/route when possible.

/**
 * @Route("/login", name="login")
 */
public function loginAction() {
    if (null !== $this->getUser()) {
        return new RedirectResponse($this->generateUrl('**default_route**'));
    }

    //...
}

@qooplmao I don't understand why you said "the referrer.. which is the login page" the referrer is "/" witch is not the login page ("/login") but a route somewhere in the mainController. If the /login route match before / it will be ok no ?

Earlier Routes always Win then the routing.yml could be

login:
    resource: "@AppBundle/Controller/SecurityController.php"
    type:     annotation
app:
    resource: "@AppBundle/Controller/"
    type:     annotation
index:
    path: /
    defaults: { _controller: AppBundle:Main:main }

You could remove the index too if you use annotations in the main controller.

try like this : security.yml security:

encoders:
        AppBundle\Entity\User:
            algorithm: bcrypt

providers:
    mysql:
        entity:
            class: AppBundle:User
            property: username


firewalls:
    # disables authentication for assets and the profiler, adapt it according to your needs
    dev:
        pattern: ^/(_(profiler|wdt)|css|images|js)/
        security: false

    main:
        anonymous: ~
        form_login:
            login_path: login
            check_path: login_check
        logout:
            path: logout


access_control:
    - { path: ^/login$, roles: IS_AUTHENTICATED_ANONYMOUSLY }
    - { path: ^/, roles: IS_AUTHENTICATED_FULLY }

routing.yml

login:
    resource: "@AppBundle/Controller/SecurityController.php"
    type:     annotation
app:
    resource: "@AppBundle/Controller/"
    type:     annotation

SecurityController.php

<?php

namespace AppBundle\Controller;

use Symfony\Bundle\FrameworkBundle\Controller\Controller;
use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;

class SecurityController extends Controller
{
/**
 * @Route("/login", name="login")
 */
public function loginAction() {
    $authenticationUtils = $this->get("security.authentication_utils");

    $error = $authenticationUtils->getLastAuthenticationError();
    $name = $authenticationUtils->getLastUsername();

    return $this->render(
        ':security:login.html.twig',
        ["name" => $name, "error" => $error]
    );
}
/**
 * @Route("/login_check", name="login_check")
 */
public function loginCheckAction() {
}
/**
 * @Route("/logout",name="logout")
 */
public function logoutAction() {
}

}

login.twig.html

{% if error %}
<div>{{ error.messageKey|trans(error.messageData, 'security') }}</div>
{% endif %}

<form action="{{ path('login_check') }}" method="post">
     <label for="username">Username:</label>
     <input type="text" id="username" name="_username" value="{{ name }}" />

     <label for="password">Password:</label>
     <input type="password" id="password" name="_password" />
     <input type="hidden" name="_target_path" value="{{ path('THE_NAME_OF_THE_ROUTE_YOU_WANT_TO_GO') }}" />

     <button type="submit">login</button>
</form>

In your mainController need to use annotations routes and replace for example 'THE_NAME_OF_THE_ROUTE_YOU_WANT_TO_GO' by '/page' if it's the route you want to go...

some help here : http://symfony.com/doc/current/book/routing.html

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM