Spring Security3源码分析(13)-SessionManagementFilter分析-上

SessionManagementFilter过滤器对应的类路径为 

org.springframework.security.web.session.SessionManagementFilter 

这个过滤器看名字就知道是管理session的了,http标签是自动配置时,默认是添加SessionManagementFilter过滤器到filterChainProxy中的,如果不想使用这个过滤器,需要做如下配置 

Java代码  

  1. <security:http auto-config=“true”>  
  2.   <security:session-management session-fixation-protection=“none”/>  
  3. </security:http>  

其实在之前的过滤器中有使用到session策略了,但是没有细说。 

SessionManagementFilter提供两大类功能: 

1.session固化保护-通过session-fixation-protection配置 

2.session并发控制-通过concurrency-control配置 

下面看SessionManagementFilter的bean是如何创建的 

Java代码  

  1. void createSessionManagementFilters() {  
  2.     Element sessionMgmtElt = DomUtils.getChildElementByTagName(httpElt, Elements.SESSION_MANAGEMENT);  
  3.     Element sessionCtrlElt = null;  
  4.   
  5.     String sessionFixationAttribute = null;  
  6.     String invalidSessionUrl = null;  
  7.     String sessionAuthStratRef = null;  
  8.     String errorUrl = null;  
  9.     //如果配置了标签,解析标签的属性、子标签  
  10.     if (sessionMgmtElt != null) {  
  11.         sessionFixationAttribute = sessionMgmtElt.getAttribute(ATT_SESSION_FIXATION_PROTECTION);  
  12.         invalidSessionUrl = sessionMgmtElt.getAttribute(ATT_INVALID_SESSION_URL);  
  13.         sessionAuthStratRef = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_STRATEGY_REF);  
  14.         errorUrl = sessionMgmtElt.getAttribute(ATT_SESSION_AUTH_ERROR_URL);  
  15.         sessionCtrlElt = DomUtils.getChildElementByTagName(sessionMgmtElt, Elements.CONCURRENT_SESSIONS);  
  16.         //判断是否配置了concurrency-control子标签  
  17.         if (sessionCtrlElt != null) {  
  18.             //配置了并发控制标签则创建并发控制过滤器和session注册的bean定义  
  19.             createConcurrencyControlFilterAndSessionRegistry(sessionCtrlElt);  
  20.         }  
  21.     }  
  22.   
  23.     if (!StringUtils.hasText(sessionFixationAttribute)) {  
  24.         sessionFixationAttribute = OPT_SESSION_FIXATION_MIGRATE_SESSION;  
  25.     } else if (StringUtils.hasText(sessionAuthStratRef)) {  
  26.         pc.getReaderContext().error(ATT_SESSION_FIXATION_PROTECTION + ” attribute cannot be used” +  
  27.                 ” in combination with “ + ATT_SESSION_AUTH_STRATEGY_REF, pc.extractSource(sessionCtrlElt));  
  28.     }  
  29.   
  30.     boolean sessionFixationProtectionRequired = !sessionFixationAttribute.equals(OPT_SESSION_FIXATION_NO_PROTECTION);  
  31.   
  32.     BeanDefinitionBuilder sessionStrategy;  
  33.     //如果配置了concurrency-control子标签  
  34.     if (sessionCtrlElt != null) {  
  35.         assert sessionRegistryRef != null;  
  36.         //session控制策略为ConcurrentSessionControlStrategy  
  37.         sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionControlStrategy.class);  
  38.         sessionStrategy.addConstructorArgValue(sessionRegistryRef);  
  39.   
  40.         String maxSessions = sessionCtrlElt.getAttribute(“max-sessions”);  
  41.         //添加最大session数  
  42.         if (StringUtils.hasText(maxSessions)) {  
  43.             sessionStrategy.addPropertyValue(“maximumSessions”, maxSessions);  
  44.         }  
  45.         String exceptionIfMaximumExceeded = sessionCtrlElt.getAttribute(“error-if-maximum-exceeded”);  
  46.   
  47.         if (StringUtils.hasText(exceptionIfMaximumExceeded)) {  
  48.             sessionStrategy.addPropertyValue(“exceptionIfMaximumExceeded”, exceptionIfMaximumExceeded);  
  49.         }  
  50.     } else if (sessionFixationProtectionRequired || StringUtils.hasText(invalidSessionUrl)  
  51.             || StringUtils.hasText(sessionAuthStratRef)) {  
  52.         //如果没有配置concurrency-control子标签  
  53.           //session控制策略是SessionFixationProtectionStrategy  
  54.         sessionStrategy = BeanDefinitionBuilder.rootBeanDefinition(SessionFixationProtectionStrategy.class);  
  55.     } else {  
  56.         //<session-management session-fixation-protection=”none”/>  
  57.         sfpf = null;  
  58.         return;  
  59.     }  
  60.     //创建SessionManagementFilter,并设置依赖的bean、property  
  61.     BeanDefinitionBuilder sessionMgmtFilter = BeanDefinitionBuilder.rootBeanDefinition(SessionManagementFilter.class);  
  62.     RootBeanDefinition failureHandler = new RootBeanDefinition(SimpleUrlAuthenticationFailureHandler.class);  
  63.     if (StringUtils.hasText(errorUrl)) {  
  64.         failureHandler.getPropertyValues().addPropertyValue(“defaultFailureUrl”, errorUrl);  
  65.     }  
  66.     sessionMgmtFilter.addPropertyValue(“authenticationFailureHandler”, failureHandler);  
  67.     sessionMgmtFilter.addConstructorArgValue(contextRepoRef);  
  68.   
  69.     if (!StringUtils.hasText(sessionAuthStratRef)) {  
  70.         BeanDefinition strategyBean = sessionStrategy.getBeanDefinition();  
  71.   
  72.         if (sessionFixationProtectionRequired) {  
  73.             sessionStrategy.addPropertyValue(“migrateSessionAttributes”,  
  74.                     Boolean.valueOf(sessionFixationAttribute.equals(OPT_SESSION_FIXATION_MIGRATE_SESSION)));  
  75.         }  
  76.         sessionAuthStratRef = pc.getReaderContext().generateBeanName(strategyBean);  
  77.         pc.registerBeanComponent(new BeanComponentDefinition(strategyBean, sessionAuthStratRef));  
  78.     }  
  79.   
  80.     if (StringUtils.hasText(invalidSessionUrl)) {  
  81.         sessionMgmtFilter.addPropertyValue(“invalidSessionUrl”, invalidSessionUrl);  
  82.     }  
  83.   
  84.     sessionMgmtFilter.addPropertyReference(“sessionAuthenticationStrategy”, sessionAuthStratRef);  
  85.   
  86.     sfpf = (RootBeanDefinition) sessionMgmtFilter.getBeanDefinition();  
  87.     sessionStrategyRef = new RuntimeBeanReference(sessionAuthStratRef);  
  88. }  
  89.   
  90. //创建并发控制Filter和session注册的bean  
  91. private void createConcurrencyControlFilterAndSessionRegistry(Element element) {  
  92.     final String ATT_EXPIRY_URL = “expired-url”;  
  93.     final String ATT_SESSION_REGISTRY_ALIAS = “session-registry-alias”;  
  94.     final String ATT_SESSION_REGISTRY_REF = “session-registry-ref”;  
  95.   
  96.     CompositeComponentDefinition compositeDef =  
  97.         new CompositeComponentDefinition(element.getTagName(), pc.extractSource(element));  
  98.     pc.pushContainingComponent(compositeDef);  
  99.   
  100.     BeanDefinitionRegistry beanRegistry = pc.getRegistry();  
  101.   
  102.     String sessionRegistryId = element.getAttribute(ATT_SESSION_REGISTRY_REF);  
  103.     //判断是否配置了session-registry-ref属性,用于扩展  
  104.      //默认情况下使用SessionRegistryImpl类管理session的注册  
  105.     if (!StringUtils.hasText(sessionRegistryId)) {  
  106.         // Register an internal SessionRegistryImpl if no external reference supplied.  
  107.         RootBeanDefinition sessionRegistry = new RootBeanDefinition(SessionRegistryImpl.class);  
  108.         sessionRegistryId = pc.getReaderContext().registerWithGeneratedName(sessionRegistry);  
  109.         pc.registerComponent(new BeanComponentDefinition(sessionRegistry, sessionRegistryId));  
  110.     }  
  111.   
  112.     String registryAlias = element.getAttribute(ATT_SESSION_REGISTRY_ALIAS);  
  113.     if (StringUtils.hasText(registryAlias)) {  
  114.         beanRegistry.registerAlias(sessionRegistryId, registryAlias);  
  115.     }  
  116.     //创建并发session控制的Filter  
  117.     BeanDefinitionBuilder filterBuilder =  
  118.             BeanDefinitionBuilder.rootBeanDefinition(ConcurrentSessionFilter.class);  
  119.     //注入session的注册实现类  
  120.     filterBuilder.addPropertyReference(“sessionRegistry”, sessionRegistryId);  
  121.   
  122.     Object source = pc.extractSource(element);  
  123.     filterBuilder.getRawBeanDefinition().setSource(source);  
  124.     filterBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);  
  125.   
  126.     String expiryUrl = element.getAttribute(ATT_EXPIRY_URL);  
  127.   
  128.     if (StringUtils.hasText(expiryUrl)) {  
  129.         WebConfigUtils.validateHttpRedirect(expiryUrl, pc, source);  
  130.         filterBuilder.addPropertyValue(“expiredUrl”, expiryUrl);  
  131.     }  
  132.   
  133.     pc.popAndRegisterContainingComponent();  
  134.   
  135.     concurrentSessionFilter = filterBuilder.getBeanDefinition();  
  136.     sessionRegistryRef = new RuntimeBeanReference(sessionRegistryId);  
  137. }  

接着看SessionManagementFilter过滤器执行过程 

Java代码  

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.         throws IOException, ServletException {  
  3.     HttpServletRequest request = (HttpServletRequest) req;  
  4.     HttpServletResponse response = (HttpServletResponse) res;  
  5.     //省略……  
  6.      //判断当前session中是否有SPRING_SECURITY_CONTEXT属性  
  7.     if (!securityContextRepository.containsContext(request)) {  
  8.         Authentication authentication = SecurityContextHolder.getContext().getAuthentication();  
  9.   
  10.         if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {  
  11.             try {  
  12.                 //再通过sessionStrategy执行session固化、并发处理  
  13.                    //与UsernamePasswordAuthenticationFilter时处理一样,后面会仔细分析。  
  14.                 sessionStrategy.onAuthentication(authentication, request, response);  
  15.             } catch (SessionAuthenticationException e) {  
  16.                 SecurityContextHolder.clearContext();  
  17.                 failureHandler.onAuthenticationFailure(request, response, e);  
  18.                 return;  
  19.             }  
  20.             //把SecurityContext设置到当前session中  
  21.             securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);  
  22.         } else {  
  23.             if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {  
  24.                 if (invalidSessionUrl != null) {  
  25.                     request.getSession();  
  26.                     redirectStrategy.sendRedirect(request, response, invalidSessionUrl);  
  27.   
  28.                     return;  
  29.                 }  
  30.             }  
  31.         }  
  32.     }  
  33.   
  34.     chain.doFilter(request, response);  
  35. }  

如果项目需要使用session的并发控制,需要做如下的配置 

Xml代码  

  1. <session-management invalid-session-url=“/login.jsp”>  
  2.     <concurrency-control max-sessions=“1” error-if-maximum-exceeded=“true” expired-url=“/login.jsp”/>  
  3. </session-management>  

session-fixation-protection属性支持三种不同的选项允许你使用 
none:使得session固化攻击失效(未配置其他属性) 
migrateSession:当用户经过认证后分配一个新的session,它保证原session的所有属性移到新session中 
newSession:当用户认证后,建立一个新的session,原(未认证时)session的属性不会进行移到新session中来 

如果使用了标签concurrency-control,那么filterchainProxy中会添加新的过滤器 

ConcurrentSessionFilter。这个过滤器的顺序在SecurityContextPersistenceFilter之前。说明未创建空的认证实体时就需要对session进行并发控制了 

看ConcurrentSessionFilter执行过程 

Java代码  

  1. public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)  
  2.         throws IOException, ServletException {  
  3.     HttpServletRequest request = (HttpServletRequest) req;  
  4.     HttpServletResponse response = (HttpServletResponse) res;  
  5.   
  6.     HttpSession session = request.getSession(false);  
  7.     if (session != null) {  
  8.         //这个SessionInformation是在执行SessionManagementFilter时通过sessionRegistry构造的并且放置在map集合中的  
  9.         SessionInformation info = sessionRegistry.getSessionInformation(session.getId());  
  10.         //如果当前session已经注册了  
  11.         if (info != null) {  
  12.             //如果当前session失效了  
  13.             if (info.isExpired()) {  
  14.                 // Expired – abort processing  
  15.                 //强制退出  
  16.                 doLogout(request, response);  
  17.                 //目标url为expired-url标签配置的属性值  
  18.                 String targetUrl = determineExpiredUrl(request, info);  
  19.                 //跳转到指定url  
  20.                 if (targetUrl != null) {  
  21.                     redirectStrategy.sendRedirect(request, response, targetUrl);  
  22.   
  23.                     return;  
  24.                 } else {  
  25.                     response.getWriter().print(“This session has been expired (possibly due to multiple concurrent “ +  
  26.                             “logins being attempted as the same user).”);  
  27.                     response.flushBuffer();  
  28.                 }  
  29.   
  30.                 return;  
  31.             } else {  
  32.                 // Non-expired – update last request date/time  
  33.                 //session未失效,刷新时间  
  34.                 info.refreshLastRequest();  
  35.             }  
  36.         }  
  37.     }  
  38.   
  39.     chain.doFilter(request, response);  
  40. }  

那么分析完ConcurrentSessionFilter过滤器的执行过程,具体有什么作用呢? 

简单点概括就是:从session缓存中获取当前session信息,如果发现过期了,就跳转到expired-url配置的url或者响应session失效提示信息。当前session有哪些情况会导致session失效呢?这里的失效并不是指在web容器中session的失效,而是spring security把登录成功的session封装为SessionInformation并放到注册类缓存中,如果SessionInformation的expired变量为true,则表示session已失效。 
所以,ConcurrentSessionFilter过滤器主要检查SessionInformation的expired变量的值 

为了能清楚解释session 并发控制的过程,现在引入UsernamePasswordAuthenticationFilter过滤器,因为该过滤器就是对登录账号进行认证的,并且在分析UsernamePasswordAuthenticationFilter过滤器时,也没有详细讲解session的处理。 

UsernamePasswordAuthenticationFilter的doFilter是由父类AbstractAuthenticationProcessingFilter完成的,截取部分重要代码 

Java代码  

  1. try {  
  2.     //由子类UsernamePasswordAuthenticationFilter认证  
  3.       //之前已经详细分析  
  4.     authResult = attemptAuthentication(request, response);  
  5.     if (authResult == null) {  
  6.         // return immediately as subclass has indicated that it hasn’t completed authentication  
  7.         return;  
  8.     }  
  9.     //由session策略类完成session固化处理、并发控制处理  
  10.       //如果当前认证实体的已注册session数超出最大并发的session数  
  11.       //这里会抛出AuthenticationException  
  12.     sessionStrategy.onAuthentication(authResult, request, response);  
  13. }  
  14. catch (AuthenticationException failed) {  
  15.     // Authentication failed  
  16.     //捕获到异常,直接跳转到失败页面或做其他处理  
  17.     unsuccessfulAuthentication(request, response, failed);  
  18.   
  19.     return;  
  20. }  

session处理的方法就是这一语句 

Java代码  

  1. sessionStrategy.onAuthentication(authResult, request, response);  

如果是采用了并发控制session,则sessionStrategy为ConcurrentSessionControlStrategy类,具体源码: 

Java代码  

  1. public void onAuthentication(Authentication authentication, HttpServletRequest request,  
  2.         HttpServletResponse response) {  
  3.     //检查是否允许认证  
  4.     checkAuthenticationAllowed(authentication, request);  
  5.   
  6.     // Allow the parent to create a new session if necessary  
  7.     //执行父类SessionFixationProtectionStrategy的onAuthentication,完成session固化工作。其实就是重新建立一个session,并且把之前的session失效掉。  
  8.     super.onAuthentication(authentication, request, response);  
  9.     //向session注册类SessionRegistryImpl注册当前session、认证实体  
  10.      //实际上SessionRegistryImpl维护两个缓存列表,分别是  
  11.      //1.sessionIds(Map):key=sessionid,value=SessionInformation  
  12.     //2.principals(Map):key=principal,value=HashSet  
  13.     sessionRegistry.registerNewSession(request.getSession().getId(), authentication.getPrincipal());  
  14. }  
  15. //检查是否允许认证通过,如果通过直接返回,不通过,抛出AuthenticationException  
  16. private void checkAuthenticationAllowed(Authentication authentication, HttpServletRequest request)  
  17.         throws AuthenticationException {  
  18.     //获取当前认证实体的session集合  
  19.     final List<SessionInformation> sessions = sessionRegistry.getAllSessions(authentication.getPrincipal(), false);  
  20.   
  21.     int sessionCount = sessions.size();  
  22.     //获取的并发session数(由max-sessions属性配置)  
  23.     int allowedSessions = getMaximumSessionsForThisUser(authentication);  
  24.     //如果当前认证实体的已注册session数小于max-sessions,允许通过  
  25.     if (sessionCount < allowedSessions) {  
  26.         // They haven’t got too many login sessions running at present  
  27.         return;  
  28.     }  
  29.     //如果allowedSessions配置为-1,说明未限制并发session数,允许通过  
  30.     if (allowedSessions == –1) {  
  31.         // We permit unlimited logins  
  32.         return;  
  33.     }  
  34.     //如果当前认证实体的已注册session数等于max-sessions  
  35.     //判断当前的session是否已经注册过了,如果注册过了,允许通过  
  36.     if (sessionCount == allowedSessions) {  
  37.         HttpSession session = request.getSession(false);  
  38.   
  39.         if (session != null) {  
  40.             // Only permit it though if this request is associated with one of the already registered sessions  
  41.             for (SessionInformation si : sessions) {  
  42.                 if (si.getSessionId().equals(session.getId())) {  
  43.                     return;  
  44.                 }  
  45.             }  
  46.         }  
  47.         // If the session is null, a new one will be created by the parent class, exceeding the allowed number  
  48.     }  
  49.     //以上条件都不满足时,进一步处理  
  50.     allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);  
  51. }  
  52.   
  53. protected void allowableSessionsExceeded(List<SessionInformation> sessions, int allowableSessions,  
  54.         SessionRegistry registry) throws SessionAuthenticationException {  
  55.     //判断配置的error-if-maximum-exceeded属性,如果为true,抛出异常  
  56.     if (exceptionIfMaximumExceeded || (sessions == null)) {  
  57.         throw new SessionAuthenticationException(messages.getMessage(“ConcurrentSessionControllerImpl.exceededAllowed”,  
  58.                 new Object[] {new Integer(allowableSessions)},  
  59.                 “Maximum sessions of {0} for this principal exceeded”));  
  60.     }  
  61.     //如果配置的error-if-maximum-exceeded为false,接下来就是取出最先注册的session信息(这里是封装到SessionInformation),然后让最先认证成功的session过期。当ConcurrentSessionFilter过滤器检查到这个过期的session,就执行session失效的处理。  
  62.     // Determine least recently used session, and mark it for invalidation  
  63.     SessionInformation leastRecentlyUsed = null;  
  64.   
  65.     for (int i = 0; i < sessions.size(); i++) {  
  66.         if ((leastRecentlyUsed == null)  
  67.                 || sessions.get(i).getLastRequest().before(leastRecentlyUsed.getLastRequest())) {  
  68.             leastRecentlyUsed = sessions.get(i);  
  69.         }  
  70.     }  
  71.     leastRecentlyUsed.expireNow();  
  72. }  

经过以上分析,可以这么理解 


如果concurrency-control标签配置了error-if-maximum-exceeded=”true”,max-sessions=”1″,那么第二次登录时,是登录不了的。如果error-if-maximum-exceeded=”false”,那么第二次是能够登录到系统的,但是第一个登录的账号再次发起请求时,会跳转到expired-url配置的url中(如果没有配置,则显示This session has been expired (possibly due to multiple concurrent logins being attempted as the same user).提示信息) 

由于篇幅过长,SessionManagementFilter、org.springframework.security.web.session.HttpSessionEventPublisher就放到下部分再分析了

    原文作者:Spring Boot
    原文地址: https://blog.csdn.net/benjamin_whx/article/details/39204699
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞