web-dev-qa-db-fra.com

Comment définir manuellement un utilisateur authentifié dans Spring Security / SpringMVC

Après qu'un nouvel utilisateur ait envoyé un formulaire "Nouveau compte", je souhaite le connecter manuellement afin qu'il ne soit pas obligé de se connecter à la page suivante.

La page de connexion de formulaire normale passant par l'intercepteur de sécurité à ressort fonctionne parfaitement.

Dans le contrôleur de formulaire new-account, je crée un UsernamePasswordAuthenticationToken et le configure manuellement dans le SecurityContext:

SecurityContextHolder.getContext().setAuthentication(authentication);

Sur cette même page, je vérifie plus tard que l'utilisateur est connecté avec:

SecurityContextHolder.getContext().getAuthentication().getAuthorities();

Cela retourne les autorités que j'ai définies précédemment dans l'authentification. Tout est bien.

Mais lorsque ce même code est appelé sur la page suivante que je charge, le jeton d'authentification est simplement UserAnonymous.

Je ne comprends pas pourquoi il n'a pas conservé l'authentification que j'ai définie lors de la demande précédente. Des pensées?

  • Serait-ce dû au fait que l'ID de session n'est pas configuré correctement?
  • Y a-t-il quelque chose qui remplace peut-être mon authentification d'une manière ou d'une autre?
  • Peut-être ai-je juste besoin d'une autre étape pour sauvegarder l'authentification?
  • Ou dois-je faire quelque chose pour déclarer l'authentification sur l'ensemble de la session plutôt que sur une seule demande?

Je cherche juste des idées qui pourraient m'aider à voir ce qui se passe ici.

101
David Parks

J'ai eu le même problème que vous il y a quelque temps. Je ne me souviens pas des détails, mais le code suivant a fonctionné pour moi. Ce code est utilisé dans un flux Spring Webflow, d'où les classes RequestContext et ExternalContext. Mais la partie la plus pertinente pour vous est la méthode doAutoLogin.

public String registerUser(UserRegistrationFormBean userRegistrationFormBean,
                           RequestContext requestContext,
                           ExternalContext externalContext) {

    try {
        Locale userLocale = requestContext.getExternalContext().getLocale();
        this.userService.createNewUser(userRegistrationFormBean, userLocale, Constants.SYSTEM_USER_ID);
        String emailAddress = userRegistrationFormBean.getChooseEmailAddressFormBean().getEmailAddress();
        String password = userRegistrationFormBean.getChoosePasswordFormBean().getPassword();
        doAutoLogin(emailAddress, password, (HttpServletRequest) externalContext.getNativeRequest());
        return "success";

    } catch (EmailAddressNotUniqueException e) {
        MessageResolver messageResolvable 
                = new MessageBuilder().error()
                                      .source(UserRegistrationFormBean.PROPERTYNAME_EMAIL_ADDRESS)
                                      .code("userRegistration.emailAddress.not.unique")
                                      .build();
        requestContext.getMessageContext().addMessage(messageResolvable);
        return "error";
    }

}


private void doAutoLogin(String username, String password, HttpServletRequest request) {

    try {
        // Must be called from request filtered by Spring Security, otherwise SecurityContextHolder is not updated
        UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(username, password);
        token.setDetails(new WebAuthenticationDetails(request));
        Authentication authentication = this.authenticationProvider.authenticate(token);
        logger.debug("Logging in with [{}]", authentication.getPrincipal());
        SecurityContextHolder.getContext().setAuthentication(authentication);
    } catch (Exception e) {
        SecurityContextHolder.getContext().setAuthentication(null);
        logger.error("Failure in autoLogin", e);
    }

}
61
Kevin Stembridge

Je ne pouvais pas trouver d'autres solutions complètes alors j'ai pensé poster la mienne. Cela peut être un peu un bidouillage, mais cela a résolu le problème ci-dessus:

public void login(HttpServletRequest request, String userName, String password)
{

    UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, password);

    // Authenticate the user
    Authentication authentication = authenticationManager.authenticate(authRequest);
    SecurityContext securityContext = SecurityContextHolder.getContext();
    securityContext.setAuthentication(authentication);

    // Create a new session and add the security context.
    HttpSession session = request.getSession(true);
    session.setAttribute("SPRING_SECURITY_CONTEXT", securityContext);
}
60
Stuart McIntyre

En fin de compte compris la racine du problème.

Lorsque je crée manuellement le contexte de sécurité, aucun objet de session n'est créé. Le mécanisme Spring Security réalise que l'objet de session est nul uniquement lorsque la requête a terminé son traitement. Lorsqu'il tente de stocker le contexte de sécurité dans la session après le traitement de la demande.

À la fin de la demande, Spring Security crée un nouvel objet de session et un nouvel identifiant de session. Toutefois, ce nouvel ID de session ne parvient jamais au navigateur car il se produit à la fin de la demande, une fois que la réponse au navigateur a été effectuée. Cela entraîne la perte du nouvel ID de session (et donc du contexte de sécurité contenant mon utilisateur connecté manuellement) lorsque la demande suivante contient l'ID de session précédent.

15
David Parks

Activez la journalisation du débogage pour obtenir une meilleure image de ce qui se passe.

Vous pouvez savoir si les cookies de session sont configurés en utilisant un débogueur côté navigateur pour examiner les en-têtes renvoyés dans les réponses HTTP. (Il y a aussi d'autres moyens.)

Une possibilité est que SpringSecurity mette en place des cookies de session sécurisés et que votre page suivante demandée comporte une URL "http" au lieu de "https". (Le navigateur n'enverra pas de cookie sécurisé pour une URL "http".)

5
Stephen C

La nouvelle fonctionnalité de filtrage de Servlet 2.4 supprime fondamentalement la restriction selon laquelle les filtres ne peuvent fonctionner que dans le flux de demandes avant et après le traitement de la demande par le serveur d'applications. Au lieu de cela, les filtres Servlet 2.4 peuvent désormais interagir avec le répartiteur de demandes à chaque point de répartition. Cela signifie que lorsqu'une ressource Web transmet une demande à une autre ressource (par exemple, un servlet transférant la demande à une page JSP de la même application), un filtre peut être utilisé avant que la demande ne traite la demande. Cela signifie également que si une ressource Web inclut la sortie ou la fonction d’autres ressources Web (par exemple, une page JSP incluant la sortie de plusieurs autres pages JSP), les filtres Servlet 2.4 peuvent fonctionner avant et après chacune des ressources incluses. .

Pour activer cette fonctionnalité, il vous faut:

web.xml

<filter>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class> 
</filter>  
<filter-mapping>   
    <filter-name>springSecurityFilterChain</filter-name>   
    <url-pattern>/<strike>*</strike></url-pattern>
    <dispatcher>REQUEST</dispatcher>
    <dispatcher>FORWARD</dispatcher>
</filter-mapping>

RegistrationController

return "forward:/login?j_username=" + registrationModel.getUserEmail()
        + "&j_password=" + registrationModel.getPassword();
5
AlexK

J'essayais de tester une application extjs et, après avoir paramétré avec succès un testingAuthenticationToken, cela a soudainement cessé de fonctionner sans cause évidente.

Je ne pouvais pas obtenir les réponses ci-dessus au travail alors ma solution était de sauter ce brin de printemps dans l'environnement de test. J'ai introduit une couture au printemps comme celle-ci:

public class SpringUserAccessor implements UserAccessor
{
    @Override
    public User getUser()
    {
        SecurityContext context = SecurityContextHolder.getContext();
        Authentication authentication = context.getAuthentication();
        return (User) authentication.getPrincipal();
    }
}

Utilisateur est un type personnalisé ici.

Je l’emballe ensuite dans une classe qui ne dispose que d’une option permettant au code de test de commuter le ressort.

public class CurrentUserAccessor
{
    private static UserAccessor _accessor;

    public CurrentUserAccessor()
    {
        _accessor = new SpringUserAccessor();
    }

    public User getUser()
    {
        return _accessor.getUser();
    }

    public static void UseTestingAccessor(User user)
    {
        _accessor = new TestUserAccessor(user);
    }
}

La version test ressemble à ceci:

public class TestUserAccessor implements UserAccessor
{
    private static User _user;

    public TestUserAccessor(User user)
    {
        _user = user;
    }

    @Override
    public User getUser()
    {
        return _user;
    }
}

Dans le code d'appel, j'utilise toujours un utilisateur approprié chargé à partir de la base de données:

    User user = (User) _userService.loadUserByUsername(username);
    CurrentUserAccessor.UseTestingAccessor(user);

Évidemment, cela ne conviendra pas si vous devez réellement utiliser la sécurité, mais je suis exécuté avec une configuration sans sécurité pour le déploiement de test. Je pensais que quelqu'un d'autre pourrait se retrouver dans une situation similaire. C’est un motif que j’ai utilisé pour simuler des dépendances statiques auparavant. L'autre alternative est que vous pouvez conserver le caractère statique de la classe wrapper, mais je préfère celle-ci car les dépendances du code sont plus explicites, car vous devez passer CurrentUserAccessor dans les classes où il est requis.

1
JonnyRaa