2010-07-26 189 views
22

Estoy usando spring-security y jQuery en mi aplicación. La página principal usa cargar contenido dinámicamente en pestañas a través de Ajax. Y todo está bien, sin embargo, algunas veces tengo la página de inicio de sesión dentro de mi pestaña y si escribo las credenciales, seré redirigido a la página de contenido sin pestañas.¿Cómo manejar la sesión expirada utilizando spring-security y jQuery?

Así que me gustaría manejar esta situación. Sé que algunas personas usan la autenticación ajax, pero no estoy seguro de que sea adecuada para mí porque me parece bastante complicado y mi aplicación no permite ningún acceso sin iniciar sesión antes. Me gustaría simplemente escribir un controlador global para todas las respuestas ajax que harán window.location.reload() si necesitamos autenticarnos. Creo que en este caso es mejor obtener el error 401 en lugar del formulario de inicio de sesión estándar porque es más fácil de manejar.

Así,

1) ¿Es posible escribir controlador de errores global para todas las peticiones Ajax jQuery?

2) ¿Cómo puedo personalizar el comportamiento de spring-security para enviar un error 401 para las solicitudes ajax pero para las solicitudes regulares para mostrar la página de inicio de sesión estándar como de costumbre?

3) ¿Puede ser que tenga una solución más elegante? Por favor, comparta.

Gracias.

+0

Ha pasado un tiempo desde que lo solicitó. ¿Has encontrado una buena solución tú mismo? –

+1

Recientemente escribí una publicación de blog sobre este tema: http://www.to-string.com/2012/08/03/springsecurity-authenticating-authorizing-ajax-requests/ – craftsman

+0

Me gusta la solución de @craftsman. Incluso lo simplifiqué (al menos eso creo). Ver http://gedrox.blogspot.com/2013/03/blog-post.html. – Gedrox

Respuesta

2

Así es como normalmente lo hago. En cada llamada AJAX, verifique el resultado antes de usarlo.

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

     // process data returned... 

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

Y entonces la función HasErrors() se parece a esto, y puede ser compartido en todas las páginas.

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; 
} 
3

Acabo de encontrar una solución a este problema, pero no lo he probado a fondo. También estoy usando primavera, seguridad de primavera y jQuery. En primer lugar, desde el controlador de mi entrada, me puse el código de estado 401:

LoginController { 

public ModelAndView loginHandler(HttpServletRequest request, HttpServletResponse response) { 

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

En su onload() métodos, todos mis páginas llamar a una función en mi archivo mundial javascript:

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); 
}); 

}

En este punto, puede manejar el error 401 como quiera. En un proyecto, he manejado la autenticación jQuery colocando un diálogo jQuery alrededor de un iframe que contiene un formulario de inicio de sesión.

4

Tome un vistazo a http://forum.springsource.org/showthread.php?t=95881, creo que la solución propuesta es mucho más claro que otras respuestas aquí:

  1. agregar un encabezado personalizado en sus llamadas ajax jquery (usando el gancho 'beforeSend'). También puede usar el encabezado "X-Requested-With" que jQuery envía.
  2. Configure Spring Security para buscar ese encabezado en el servidor para devolver un código de error HTTP 401 en lugar de llevar al usuario a la página de inicio de sesión.
+1

¿Y qué pasa si alguien usa '$ .getJSON() {...}'? "beforeSend" no es posible. – zygimantus

10

Aquí hay un enfoque que creo que es bastante simple. Es una combinación de enfoques que he observado en este sitio.Escribí una publicación en un blog al respecto: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/

La idea básica es usar un prefijo url de la API (es decir,/api/asegurado) como se sugiere arriba junto con un punto de entrada de autenticación. Es simple y funciona.

Aquí está el punto de entrada de autenticación:

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); 
     } 
    } 
} 

Y esto es lo que pasa en su contexto XML de Spring:

<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> 
+0

Implementé este enfoque y aprovecho el tiempo de espera y luego redirijo a la página de inicio de sesión para iniciar sesión de nuevo. Después de iniciar sesión, veo la url ajax y la cadena de consulta. ¿Hay alguna manera de volver a la página desde la que se inició la solicitud de Ajax? Gracias – blong824

+0

Ese es un gran punto.Esa es una característica de la seguridad de primavera y ortogonal a esta solución. Sin embargo, es un problema, no obstante. Actualmente estoy experimentando el mismo problema y actualizaré esta publicación una vez que la resuelvo. Por favor, avíseme si lo resuelve mientras tanto. –

+0

Re: comentario de blong824, esta página es instructiva: http://static.springsource.org/spring-security/site/docs/3.1.x/reference/appendix-namespace.html#nsa-form-login. Si establece el parámetro 'always-use-default-target' en verdadero, puede hacer que el sistema siempre redirija después de iniciar sesión en la página deseada. También busque soluciones relacionadas con el tipo de bean: 'SimpleUrlAuthenticationSuccessHandler'. Creo que las soluciones más complejas se describen mejor en una publicación separada. –

8

que utiliza la siguiente solución.

En la seguridad del muelle definidas sesión no válido url

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

Por esa página añade lo siguiente controlador

@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"; 
    } 
} 

Y para ajax utilizado ajaxSetup para manejar todas las peticiones 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"; 
       } 
      } 
     }); 
+0

En mi caso, para que esto funcione, tuve que agregar 'invalidate-session =" false "' a '', de lo contrario, la primavera me redirigió a '/ invalidate.do' después de hacer clic en el botón de cerrar sesión. – matthaeus

+0

¿Funciona esto si uso 'expired-url ="/login "'? – zygimantus

0

Así hay 2 problemas aquí. 1) La seguridad de primavera está funcionando, pero la respuesta está volviendo al navegador en una llamada ajax. 2) La seguridad de primavera realiza un seguimiento de la página solicitada originalmente para que pueda redirigirlo a ella DESPUÉS de iniciar sesión (a menos que especifique que siempre desea utilizar una determinada página después de iniciar sesión). En este caso, la solicitud fue una cadena Ajax, por lo que será redirigido a esa cadena y eso es lo que verá en el navegador.

Una solución simple es detectar el error Ajax, y si la solicitud enviada es específica a su página de inicio de sesión (Spring enviará la página de inicio de sesión html, será la propiedad 'responseText' de la solicitud) detectarlo . Luego solo recarga tu página actual, lo que eliminará al usuario del contexto de la llamada Ajax. Spring los enviará automáticamente a la página de inicio de sesión. (Estoy usando el j_username predeterminado, que es un valor de cadena exclusivo de mi página de inicio de sesión).

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

Cuando se produce un tiempo de espera, el usuario es redirigido a la página de acceso después de cualquier acción Ajax se activa mientras sesión ya se aclaró

contexto de seguridad:

<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> 

sesión de escucha:

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); 
     } 
    } 
} 

}

Cuestiones relacionadas