web-dev-qa-db-fra.com

Comment gérer une session expirée avec Spring-security et jQuery?

J'utilise Spring-security et jQuery dans mon application. La page principale utilise le chargement dynamique du contenu dans des onglets via Ajax. Et tout va bien, mais parfois la page de connexion se trouve dans mon onglet et si je tape les informations d'identification, je serai redirigé vers la page de contenu sans onglets.

Je voudrais donc gérer cette situation. Je sais que certaines personnes utilisent l'authentification ajax, mais je ne suis pas sûre que cela me convienne, car cela semble assez compliqué pour moi et mon application ne permet aucun accès sans connexion préalable. Je voudrais juste écrire un gestionnaire global pour toutes les réponses ajax qui fera window.location.reload() si nous devons nous authentifier. Je pense que dans ce cas, il vaut mieux obtenir l'erreur 401 plutôt que le formulaire de connexion standard, car il est plus facile à gérer.

Alors,

1) Est-il possible d'écrire un gestionnaire d'erreurs global pour toutes les requêtes ajax jQuery?

2) Comment puis-je personnaliser le comportement de spring-security pour envoyer une erreur 401 pour les demandes ajax mais pour les demandes régulières d'afficher la page de connexion standard comme d'habitude?

3) Peut-être avez-vous une solution plus élégante? S'il vous plaît partagez-le.

Merci.

22
viator

Voici une approche que je pense est assez simple. C'est une combinaison d'approches que j'ai observées sur ce site. J'ai écrit un billet de blog à ce sujet: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-security-ajax-session-timeout-problem/

L'idée de base est d'utiliser un préfixe d'URL d'api (c'est-à-dire/api/secured) comme suggéré ci-dessus avec un point d'entrée d'authentification. C'est simple et fonctionne.

Voici le point d'entrée de l'authentification:

package com.yoyar.yaya.config;

import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint;

import javax.servlet.ServletException;
import javax.servlet.http.*;
import Java.io.IOException;

public class AjaxAwareAuthenticationEntryPoint 
             extends LoginUrlAuthenticationEntryPoint {

    public AjaxAwareAuthenticationEntryPoint(String loginUrl) {
        super(loginUrl);
    }

    @Override
    public void commence(
        HttpServletRequest request, 
        HttpServletResponse response, 
        AuthenticationException authException) 
            throws IOException, ServletException {

        boolean isAjax 
            = request.getRequestURI().startsWith("/api/secured");

        if (isAjax) {
            response.sendError(403, "Forbidden");
        } else {
            super.commence(request, response, authException);
        }
    }
}

Et voici ce qui se passe dans votre contexte printanier xml:

<bean id="authenticationEntryPoint"
  class="com.yoyar.yaya.config.AjaxAwareAuthenticationEntryPoint">
    <constructor-arg name="loginUrl" value="/login"/>
</bean>

<security:http auto-config="true"
  use-expressions="true"
  entry-point-ref="authenticationEntryPoint">
    <security:intercept-url pattern="/api/secured/**" access="hasRole('ROLE_USER')"/>
    <security:intercept-url pattern="/login" access="permitAll"/>
    <security:intercept-url pattern="/logout" access="permitAll"/>
    <security:intercept-url pattern="/denied" access="hasRole('ROLE_USER')"/>
    <security:intercept-url pattern="/" access="permitAll"/>
    <security:form-login login-page="/login"
                         authentication-failure-url="/loginfailed"
                         default-target-url="/login/success"/>
    <security:access-denied-handler error-page="/denied"/>
    <security:logout invalidate-session="true"
                     logout-success-url="/logout/success"
                     logout-url="/logout"/>
</security:http>
10
Matt Friedman

J'ai utilisé la solution suivante.

Au printemps, la sécurité a défini une URL de session non valide. 

<security:session-management invalid-session-url="/invalidate.do"/>

Pour cette page ajouté contrôleur suivant

@Controller
public class InvalidateSession
{
    /**
     * This url gets invoked when spring security invalidates session (ie timeout).
     * Specific content indicates ui layer that session has been invalidated and page should be redirected to logout. 
     */
    @RequestMapping(value = "invalidate.do", method = RequestMethod.GET)
    @ResponseBody
    public String invalidateSession() {
        return "invalidSession";
    }
}

Et pour ajax utilisé ajaxSetup pour gérer toutes les requêtes ajax:

// Checks, if data indicates that session has been invalidated.
// If session is invalidated, page is redirected to logout
   $.ajaxSetup({
    complete: function(xhr, status) {
                if (xhr.responseText == 'invalidSession') {
                    if ($("#colorbox").count > 0) {
                        $("#colorbox").destroy();
                    }
                    window.location = "logout";
                }
            }
        });
9
andro83

Jetez un coup d’œil à http://forum.springsource.org/showthread.php?t=95881 , je pense que la solution proposée est bien plus claire que d’autres réponses ici:

  1. Ajoutez un en-tête personnalisé dans vos appels ajax jquery (en utilisant le crochet 'beforeSend'). Vous pouvez également utiliser l'en-tête "X-Requested-With" envoyé par jQuery.
  2. Configurez Spring Security pour rechercher cet en-tête côté serveur afin de renvoyer un code d'erreur HTTP 401 au lieu d'amener l'utilisateur à la page de connexion.
4
Guido

Je viens juste de trouver une solution à ce problème, mais je ne l'ai pas testée à fond. J'utilise également printemps, sécurité de printemps et jQuery. Tout d'abord, depuis le contrôleur de mon identifiant, j'ai défini le code d'état sur 401:

LoginController {

public ModelAndView loginHandler(HttpServletRequest request, HttpServletResponse response) {

...
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
... 
return new ModelAndView("login", model);
}

Dans leurs méthodes onload (), toutes mes pages appellent une fonction dans mon fichier JavaScript global:

function initAjaxErrors() {

jQuery(window).ajaxError(function(event, xmlHttpRequest, ajaxOptions, thrownError) {
    if (403 == xmlHttpRequest.status)
        showMessage("Permission Denied");
    else
        showMessage("An error occurred: "+xmlHttpRequest.status+" "+xmlHttpRequest.statusText);
});

}

À ce stade, vous pouvez gérer l'erreur 401 comme vous le souhaitez. Dans un projet, j'ai géré l'authentification jQuery en plaçant une boîte de dialogue jQuery autour d'un iframe contenant un formulaire de connexion.

3
Hank

Voici comment je le fais généralement. À chaque appel AJAX, vérifiez le résultat avant de l'utiliser.

$.ajax({ type: 'GET',
    url: GetRootUrl() + '/services/dosomething.ashx',
    success: function (data) {
      if (HasErrors(data)) return;

      // process data returned...

    },
    error: function (xmlHttpRequest, textStatus) {
      ShowStatusFailed(xmlHttpRequest);
    }
  });

Et ensuite, la fonction HasErrors() ressemble à ceci et peut être partagée sur toutes les pages.

function HasErrors(data) {
  // check for redirect to login page
  if (data.search(/login\.aspx/i) != -1) {
    top.location.href = GetRootUrl() + '/login.aspx?lo=TimedOut';
    return true;
  }
  // check for IIS error page
  if (data.search(/Internal Server Error/) != -1) {
    ShowStatusFailed('Server Error.');
    return true;
  }
  // check for our custom error handling page
  if (data.search(/Error.aspx/) != -1) {
    ShowStatusFailed('An error occurred on the server. The Technical Support Team has been provided with the error details.');
    return true;
  }
  return false;
}
2
Glen Little

Donc, il y a 2 problèmes ici. 1) La sécurité de Spring fonctionne, mais la réponse revient au navigateur dans un appel ajax. 2) Spring Security garde trace de la page demandée à l'origine afin qu'il puisse vous rediriger après la connexion (sauf si vous indiquez que vous souhaitez toujours utiliser une page donnée après la connexion). Dans ce cas, la demande était une chaîne Ajax, vous serez donc redirigé vers cette chaîne et c'est ce que vous verrez dans le navigateur.

Une solution simple consiste à détecter l'erreur Ajax. Si la demande renvoyée est spécifique à votre page de connexion (Spring renverra le code HTML de la page de connexion, ce sera la propriété 'responseText' de la demande) le détectera. Ensuite, il vous suffit de recharger votre page actuelle, ce qui retirera l'utilisateur du contexte de l'appel Ajax. Spring les enverra automatiquement à la page de connexion. (J'utilise le nom d'utilisateur par défaut, j_username, qui est une chaîne unique à ma page de connexion).

$(document).ajaxError( function(event, request, settings, exception) {
    if(String.prototype.indexOf.call(request.responseText, "j_username") != -1) {
        window.location.reload(document.URL);
    }
});
0
MattC

Lorsqu'un délai d'attente survient, l'utilisateur est redirigé vers la page de connexion après le déclenchement d'une action ajax alors que la session est déjà effacée.

contexte de sécurité:

<http use-expressions="true" entry-point-ref="authenticationEntryPoint">
    <logout invalidate-session="true" success-handler-ref="logoutSuccessBean" delete-cookies="JSESSIONID" />
    <custom-filter position="CONCURRENT_SESSION_FILTER" ref="concurrencyFilter" />
    <custom-filter position="FORM_LOGIN_FILTER" ref="authFilter" />
    <session-management invalid-session-url="/logout.xhtml" session-authentication-strategy-ref="sas"/>
</http>

<beans:bean id="concurrencyFilter"
  class="org.springframework.security.web.session.ConcurrentSessionFilter">
    <beans:property name="sessionRegistry" ref="sessionRegistry" />
    <beans:property name="expiredUrl" value="/logout.xhtml" />
</beans:bean>

<beans:bean id="authenticationEntryPoint"  class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
    <beans:property name="loginFormUrl" value="/login.xhtml" />
</beans:bean>

<beans:bean id="authFilter"
  class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
    <beans:property name="sessionAuthenticationStrategy" ref="sas" />
    <beans:property name="authenticationManager" ref="authenticationManager" />
    <beans:property name="authenticationSuccessHandler" ref="authenticationSuccessBean" />
    <beans:property name="authenticationFailureHandler" ref="authenticationFailureBean" />
</beans:bean>

<beans:bean id="sas" class="org.springframework.security.web.authentication.session.ConcurrentSessionControlStrategy">
    <beans:constructor-arg name="sessionRegistry" ref="sessionRegistry" />
    <beans:property name="maximumSessions" value="1" />
    <beans:property name="exceptionIfMaximumExceeded" value="1" />
</beans:bean>

Auditeur de connexion:

public class LoginListener implements PhaseListener {

@Override
public PhaseId getPhaseId() {
    return PhaseId.RESTORE_VIEW;
}

@Override
public void beforePhase(PhaseEvent event) {
    // do nothing
}

@Override
public void afterPhase(PhaseEvent event) {
    FacesContext context = event.getFacesContext();
    HttpServletRequest request = (HttpServletRequest) context.getExternalContext().getRequest();
    String logoutURL = request.getContextPath() + "/logout.xhtml";
    String loginURL = request.getContextPath() + "/login.xhtml";

    if (logoutURL.equals(request.getRequestURI())) {
        try {
            context.getExternalContext().redirect(loginURL);
        } catch (IOException e) {
            throw new FacesException(e);
        }
    }
}

}

0
Thomas Crook