【问题标题】:Detect Session Timeout in Ajax Request in Spring MVC在 Spring MVC 中检测 Ajax 请求中的会话超时
【发布时间】:2011-06-25 05:59:07
【问题描述】:

当会话超时时,我似乎找不到一个很好的示例/答案来说明如何从 ajax 请求中发回一些数据。它发回登录页面 HTML,我想发送 json 或我可以拦截的状态代码。

【问题讨论】:

    标签: ajax session spring-mvc spring-security session-timeout


    【解决方案1】:

    执行此操作的最简单方法是对 AJAX 请求的 URL 使用过滤器。

    在下面的示例中,我只是发送带有指示会话超时的响应正文的 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);
        }
    }
    

    【讨论】:

    • 在 bean 配置文件的哪里添加这个?我之前尝试过,但遇到了问题。我不确定我第一次配置是否正确。这似乎是在正确的轨道上。
    • 假设您已正确配置 Spring Security,您只需将此过滤器添加到 applicationContext-security.xml 中的安全过滤器链中:将您的过滤器定义为 并使用 将其添加到链中
    • 从 Spring Security 3.1 开始,您可以仅为您感兴趣的 URL 定义单独的配置(使用 pattern 属性,如static.springsource.org/spring-security/site/docs/3.1.x/… 中所述)。但是,在 3.0 版中,没有名称空间支持。作为替代方案,您可以检查 doFilter 中的 URL 匹配并决定是否应用它。如果您有多个 URL 模式要处理,我建议为此创建一个 FilterWrapper 类。
    • Boris,我真的很喜欢您的方法并尝试实施它,但即使在我注销后 isRequestedSessionIdValid 仍然正确。知道什么会导致注销后会话仍然有效吗?如果我尝试访问任何安全页面,我会重定向到登录,因此我的安全设置可以正常工作
    • 我很好奇你为什么需要 FILTER_APPLIED 设置/检查。
    【解决方案2】:

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

    基本思想是使用上面建议的 api url 前缀(即 /api/secured)以及身份验证入口点。这很简单,而且很有效。

    这是身份验证入口点:

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

    这就是你的 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>
    

    【讨论】:

    • 看起来链接的网站是 DOA。
    【解决方案3】:

    我在后端使用@Matt 的相同解决方案。如果您在前端使用 angularJs,请在 angular $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);
        }
      };
    

    });

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

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

    【讨论】:

      【解决方案4】:

      鉴于现在所有的答案都已经有几年了,我将分享我目前在 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());
          }
      }
      

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

      【讨论】:

        猜你喜欢
        • 2015-08-08
        • 2013-11-05
        • 1970-01-01
        • 2011-04-25
        • 2013-05-27
        • 2013-06-10
        • 2010-11-03
        • 1970-01-01
        • 2012-08-18
        相关资源
        最近更新 更多