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

 2019-11-02 21:26  阅读(1403)
文章分类:Spring boot

SessionManagementFilter过滤器对应的类路径为

org.springframework.security.web.session.SessionManagementFilter

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

Java代码

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

接着看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. if (authentication != null && !authenticationTrustResolver.isAnonymous(authentication)) {
  10. try {
  11. //再通过sessionStrategy执行session固化、并发处理
  12. //与UsernamePasswordAuthenticationFilter时处理一样,后面会仔细分析。
  13. sessionStrategy.onAuthentication(authentication, request, response);
  14. } catch (SessionAuthenticationException e) {
  15. SecurityContextHolder.clearContext();
  16. failureHandler.onAuthenticationFailure(request, response, e);
  17. return;
  18. }
  19. //把SecurityContext设置到当前session中
  20. securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
  21. } else {
  22. if (request.getRequestedSessionId() != null && !request.isRequestedSessionIdValid()) {
  23. if (invalidSessionUrl != null) {
  24. request.getSession();
  25. redirectStrategy.sendRedirect(request, response, invalidSessionUrl);
  26. return;
  27. }
  28. }
  29. }
  30. }
  31. chain.doFilter(request, response);
  32. }

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

那么分析完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. return;
  19. }

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

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

如果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就放到下部分再分析了


来源:http://ddrv.cn

点赞(0)
版权归原创作者所有,任何形式转载请联系作者; Java 技术驿站 >> Spring Security3源码分析(13)-SessionManagementFilter分析-上

相关推荐