web-dev-qa-db-fra.com

Détecter le délai de session dans la requête Ajax dans Spring MVC

Je ne vois pas comment semble trouver un bon exemple/réponse sur la façon de renvoyer des données à partir d'une demande ajax lorsqu'une session a expiré. Il renvoie la page de connexion HTML et je souhaite envoyer un json ou un code de statut que je peux intercepter.

20
Mike Flynn

Pour ce faire, le moyen le plus simple consiste à utiliser un filtre sur les URL de vos demandes AJAX.

Dans l'exemple ci-dessous, je n'envoie que le code de réponse HTTP 500 avec un corps de réponse indiquant le délai d'expiration de la session, mais vous pouvez facilement définir le code et le corps de la réponse sur ce qui convient le mieux à votre cas.

package com.myapp.security.authentication;

import org.springframework.web.filter.GenericFilterBean;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import Java.io.IOException;

public class ExpiredSessionFilter extends GenericFilterBean {

    static final String FILTER_APPLIED = "__spring_security_expired_session_filter_applied";

    @Override
    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {

        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (request.getAttribute(FILTER_APPLIED) != null) {
            chain.doFilter(request, response);
            return;
        }

        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {               
            response.sendError(HttpServletResponse.SC_INTERNAL_SERVER_ERROR, "SESSION_TIMED_OUT");
            return;
        }

        chain.doFilter(request, response);
    }
}
11
Boris Kirzner

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 à 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>
3
Matt Friedman

Comme toutes les réponses actuelles datent de quelques années maintenant, je vais partager ma solution que je travaille actuellement dans une application Spring Boot REST:

@Configuration
@EnableWebSecurity
public class UISecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        ...
        http.exceptionHandling.authenticationEntryPoint(authenticationEntryPoint());
        ...
    }

    private AuthenticationEntryPoint authenticationEntryPoint() {
        // As a REST service there is no 'authentication entry point' like MVC which can redirect to a login page
        // Instead just reply with 401 - Unauthorized
        return (request, response, authException) -> response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());
    }
}

Le principe de base est que je remplace le point d'entrée d'authentification qui, par défaut, émettait une redirection vers ma page de connexion inexistante. Il répond maintenant en envoyant un message 401. Spring crée également implicitement un objet JSON à réponse d'erreur standard qu'il renvoie également.

1
pedorro

J'utilise la même solution par @Matt en backend. Si vous utilisez angularJs en front-end, ajoutez interceptor dans angular $ http pour permettre au navigateur de rediriger la page de connexion.

var HttpInterceptorModule = angular.module('httpInterceptor', [])
.config(function ($httpProvider) {
  $httpProvider.interceptors.Push('myInterceptor');
  $httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest'; 
})
 .factory('myInterceptor', function ($q) {
return {
    'responseError': function(rejection) {
      // do something on error
        if(rejection.status == 403 || rejection.status == 401) window.location = "login";   
        return $q.reject(rejection);
    }
  };

});

Notez que la ligne ci-dessous est nécessaire uniquement si vous utilisez AngularJs après la version 1.1.1 (angularJS a supprimé l'en-tête "X-Requested-With" à partir de cette version).

$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
1
bnguyen82