【问题标题】:how to handle session timeout manually using HandlerInterceptorAdapter in spring mvc如何在spring mvc中使用HandlerInterceptorAdapter手动处理会话超时
【发布时间】:2017-09-22 07:33:54
【问题描述】:

我想在会话超时时做一些活动,我使用了看起来像这样的 HandlerInterceptorAdapter

package com.practice.security;


import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;

public class SessionHandelerInterceptor extends HandlerInterceptorAdapter{
    private static final long MAX_INACTIVE_SESSION_TIME = 5 * 10000;
    @Autowired
    private HttpSession session;
    @Override
    public boolean preHandle(HttpServletRequest request,HttpServletResponse response, Object handler) throws Exception {

        //long startTime = System.currentTimeMillis();

        //request.setAttribute("executionTime", startTime);
        if (this.isUserLogged()) {
            session = request.getSession();
            if (System.currentTimeMillis() - session.getLastAccessedTime()
              > MAX_INACTIVE_SESSION_TIME) {

                //Do some more activity

                SecurityContextHolder.clearContext();
                request.logout();
                response.sendRedirect("/userLogin/logout");
            }
        }
        return true;
    }

    private boolean isUserLogged(){
        try {
            return !SecurityContextHolder.getContext().getAuthentication()
              .getName().equals("anonymousUser");
        } catch (Exception e) {
            return false;
        }
    }
}

问题是当请求到达拦截器的 preHandle 方法时,会话的 lastAccessTime 被更新为当前时间。 我认为原因是在去预处理方法之前,每个请求都去我的自定义 UsernamePasswordAuthenticationFilter 的 doget 方法。我还在我的应用程序中使用了 Spring 安全性。

import java.io.IOException;

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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.authority.AuthorityUtils;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.web.context.support.WebApplicationContextUtils;

import com.practice.AppConstant;

public class AuthenticationTokenFilter extends UsernamePasswordAuthenticationFilter {

    private Tokenutils tokenUtils;

    //@Autowired
    //private UserDetailsService userDetailService;

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

            tokenUtils=(Tokenutils) WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext()).getBean(Tokenutils.class);

            //userDetailService=WebApplicationContextUtils.getRequiredWebApplicationContext(this.getServletContext()).getBean(UserDetailsService.class);

            HttpServletResponse response = (HttpServletResponse) res;
            response.setHeader("Access-Control-Allow-Origin", "*");
            response.setHeader("Access-Control-Allow-Methods", "POST, GET, OPTIONS, DELETE");
            response.setHeader("Access-Control-Max-Age", "3600");
            //response.setHeader("Access-Control-Allow-Headers", "x-requested-with");
            response.setHeader("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type,"+AppConstant.tokenHeader);

            HttpServletRequest httpRequest = (HttpServletRequest)request;
            //String authToken=httpRequest.getHeader(AppConstant.tokenHeader);
            String authToken=httpRequest.getParameter("XAuthToken");
            String userName=this.tokenUtils.getUsernameFromToken(authToken);
            if(userName!=null && SecurityContextHolder.getContext().getAuthentication()==null){
                //UserDetails userDetails= this.userDetailService.loadUserByUsername(userName);
                UserDetails userDetails=new SpringSecurityUser(1L, userName, null, null, null, AuthorityUtils.commaSeparatedStringToAuthorityList(""));
                if(this.tokenUtils.validateToken(authToken,userDetails)){
                    UsernamePasswordAuthenticationToken authentication= new UsernamePasswordAuthenticationToken(userDetails, null,userDetails.getAuthorities());
                    authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(httpRequest));
                    SecurityContextHolder.getContext().setAuthentication(authentication);
                }
            }
            chain.doFilter(request, res);
    }
}

任何帮助将不胜感激谢谢。

【问题讨论】:

    标签: java spring spring-mvc session


    【解决方案1】:

    如果您使用的是 Spring 安全性,那么最好依靠会话超时而不是自己检查非活动时间。这种方法的几个问题是 - 您在堆栈中实现更深层次的安全性,因为它应该在过滤器级别处理,这是 Spring 安全处理它的地方。其次,根据 servlet 规范

    当请求是一部分时,会话被认为被访问 会话的一部分首先由 servlet 容器处理。

    此外,session.getLastAccessedTime() 会在调用 request.getSession 时随时更新(至少在 Tomcat 7 中是这样,可能有多种行为,如 here 所述)可以在任何地方调用 - 任何其他地方过滤器配置,Servlet,HandlerInterceptor,Controller。最后但非常重要 - SecurityContextHolder.clearContext() 将从当前线程中删除 SecurityContext(它由 ThreadLocal 保存)但它仍将存在于会话中(默认情况下将保存它),这不是你真正想要的。

    另一种选择可能是将登录时间存储在登录时存储在 Spring 安全上下文中的用户对象中,然后检查时间并停用会话并重新重定向到登录页面。

    更新:- 还有另一种简单的方法,您可以编写如下所示的自定义过滤器并在 web.xml 中的 Spring 安全过滤器之前注册它。 我们在这里所做的是为首次访问保存会话中的最后一次访问时间,然后在每次访问时更新会话中的最后一次访问时间,但如果它超过了非活动时间,则重定向到登录页面。所以基本上你不是依赖会话对象上次访问时间,而是存储在会话中的用户定义变量。如果超过非活动时间,我们也会使当前会话无效。我已经测试了这个过滤器和 Spring 安全过滤器,它工作正常。你可以在 Spring HandlerInterceptor 中做类似的事情,但我觉得这种检查应该在 Spring 安全参与之前就在某个地方实现。但这是一种选择。

    public class InactivityFilter implements Filter {
    
        private static final long MAX_INACTIVE_SESSION_TIME = 5 * 1000;
    
        public void init(FilterConfig config) throws ServletException {
            System.out.println("InactivityFilter.init()");
        }
    
        public void doFilter(ServletRequest req, ServletResponse resp, FilterChain chain)
                throws IOException, ServletException {
    
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) resp;
    
            String requestURI = request.getRequestURI();
            System.out.printf("InactivityFilter.doFilter(): requestURI = %s; ", requestURI);
    
            Long firstAccessTime = (Long) request.getSession().getAttribute("lastAccessTime");
    
            if (firstAccessTime == null) {
                request.getSession().setAttribute("lastAccessTime", new Date().getTime());
                chain.doFilter(req, resp);
            }
    
            if (firstAccessTime != null) {
                if (System.currentTimeMillis() - firstAccessTime > MAX_INACTIVE_SESSION_TIME) {
                    request.getSession().invalidate();
                    response.sendRedirect("the url for login page");
                    return;
                } else {
                    request.getSession().setAttribute("lastAccessTime", new Date().getTime());
                    chain.doFilter(req, resp);
                }
    
            }
    
        }
    
        public void destroy() {
            System.out.println("InactivityFilter.destroy()");
        }
    
    }
    

    【讨论】:

    • 您能否提供一些示例代码以满足我的上述要求
    • 最简单的方法是在每个用户的会话中存储第一次访问时间(检查第一次是否为空,然后设置访问时间,否则不设置)。这可以在为每次访问运行的全局拦截器中完成(或位于应用程序前面的过滤器)。然后在您的 spring 拦截器处理程序中从会话中获取值,如果在非活动时间范围内找到,让用户继续。否则调用 session.invalidate 并重定向到登录页面。所以基本上你不是依赖会话对象的上次访问时间,而是存储在会话中的用户定义变量
    • 感谢您的回复 Shailendra。在处理每个请求之前也会调用 HandlerInterceptorAdapter 的 preHandle 方法,所以我只能在此方法中应用您给定的逻辑吗?
    • 是的,我认为这没有问题。
    猜你喜欢
    • 1970-01-01
    • 2017-10-06
    • 2011-09-25
    • 1970-01-01
    • 2012-09-16
    • 2019-08-16
    • 2012-11-10
    • 2016-03-27
    • 2017-12-17
    相关资源
    最近更新 更多