在Spring MVC中的Ajax请求中检测会话超时

时间:2022-10-14 21:05:44

I can't see seem to find a good example/answer on how to send back some data from an ajax request when a session has timed out. It sends back the login page HTML and I want to either send json or a status code I can intercept.

我看不出在会话超时时如何从ajax请求返回一些数据的好例子/答案。它返回登录页面HTML,我想发送json或可以截获的状态码。

4 个解决方案

#1


10  

The simplest way for doing this is using a filter on URLs of your AJAX requests.

最简单的方法是对AJAX请求的url使用过滤器。

In the example below I'm just sending HTTP 500 response code with a response body indicating the session timeout, but you can easily set the response code and body to what is more suitable for your case..

在下面的示例中,我只是发送HTTP 500响应代码,响应主体指示会话超时,但是您可以轻松地将响应代码和响应主体设置为更适合您的情况。

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

#2


3  

Here's an approach that I think is quite simple. It's a combination of approaches that I've observed on this site. I wrote a blog post about it: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/

我认为这是一种非常简单的方法。这是我在这个网站上观察到的方法的组合。我写了一篇关于它的博客:http://yoyar.com/blog/2012/06/deal with-the-spring-security- ajax-sessiontimeoutproblem /

The basic idea is to use an api url prefix (i.e. /api/secured) as suggested above along with an authentication entry point. It's simple and works.

其基本思想是使用api url前缀(即/api/安全),以及身份验证入口点。它是简单和有效。

Here's the authentication entry point:

以下是身份验证入口点:

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

And here's what goes in your spring context xml:

这是spring上下文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


1  

I use the same solution by @Matt in backend. If you're using angularJs on front end, add below interceptor in angular $http to let browser actually do a redirect to login page.

我在后台使用了@Matt的相同解决方案。如果您使用的是前端的angularJs,请在角$http中添加以下拦截器,让浏览器实际执行重定向到登录页面。

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

});

});

Note that below line is needed only if you're using AngularJs after version 1.1.1 (angularJS removed header "X-Requested-With" from that version onward)

请注意,只有在1.1.1版本之后使用AngularJs时才需要以下行(AngularJs从该版本开始删除了标题“X-Requested-With”)

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

#4


1  

Seeing as all of the present answers are a few years old now, I'll share my solution which I currently have working in a Spring Boot REST 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());
    }
}

The basic premise here is that I override the authentication entry point which by default was issuing a redirect to my non-existent login page. It now responds by sending a 401. Spring also implicitly creates an standard error response JSON object that it returns as well.

这里的基本前提是,我重写了身份验证入口点,默认情况下,它会向我不存在的登录页面发出重定向。它现在的回应是发送401退休金计划。Spring还隐式地创建一个标准错误响应JSON对象,它也返回这个对象。

#1


10  

The simplest way for doing this is using a filter on URLs of your AJAX requests.

最简单的方法是对AJAX请求的url使用过滤器。

In the example below I'm just sending HTTP 500 response code with a response body indicating the session timeout, but you can easily set the response code and body to what is more suitable for your case..

在下面的示例中,我只是发送HTTP 500响应代码,响应主体指示会话超时,但是您可以轻松地将响应代码和响应主体设置为更适合您的情况。

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

#2


3  

Here's an approach that I think is quite simple. It's a combination of approaches that I've observed on this site. I wrote a blog post about it: http://yoyar.com/blog/2012/06/dealing-with-the-spring-security-ajax-session-timeout-problem/

我认为这是一种非常简单的方法。这是我在这个网站上观察到的方法的组合。我写了一篇关于它的博客:http://yoyar.com/blog/2012/06/deal with-the-spring-security- ajax-sessiontimeoutproblem /

The basic idea is to use an api url prefix (i.e. /api/secured) as suggested above along with an authentication entry point. It's simple and works.

其基本思想是使用api url前缀(即/api/安全),以及身份验证入口点。它是简单和有效。

Here's the authentication entry point:

以下是身份验证入口点:

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

And here's what goes in your spring context xml:

这是spring上下文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


1  

I use the same solution by @Matt in backend. If you're using angularJs on front end, add below interceptor in angular $http to let browser actually do a redirect to login page.

我在后台使用了@Matt的相同解决方案。如果您使用的是前端的angularJs,请在角$http中添加以下拦截器,让浏览器实际执行重定向到登录页面。

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

});

});

Note that below line is needed only if you're using AngularJs after version 1.1.1 (angularJS removed header "X-Requested-With" from that version onward)

请注意,只有在1.1.1版本之后使用AngularJs时才需要以下行(AngularJs从该版本开始删除了标题“X-Requested-With”)

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

#4


1  

Seeing as all of the present answers are a few years old now, I'll share my solution which I currently have working in a Spring Boot REST 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());
    }
}

The basic premise here is that I override the authentication entry point which by default was issuing a redirect to my non-existent login page. It now responds by sending a 401. Spring also implicitly creates an standard error response JSON object that it returns as well.

这里的基本前提是,我重写了身份验证入口点,默认情况下,它会向我不存在的登录页面发出重定向。它现在的回应是发送401退休金计划。Spring还隐式地创建一个标准错误响应JSON对象,它也返回这个对象。