会话管理

简介: 会话管理

会话管理

当浏览器调用登录接口登录成功后,服务端会和浏览器之间建立一个会话Session,浏览器在每次发送请求时都会携带一个SessionId,服务器会根据这个SessionId来判断用户身份。当浏览器关闭后,服务端的Session并不会自动销毁,需要开发者手动在服务端调用Session销毁方法,或者等Session过期时间到了自动销毁。

会话并发管理就是指在当前系统中,同一个用户可以同时创建多少个会话,如果一台设备对应一个会话,那么可以简单理解为同一个用户可以同时在多少台设备上登录,默认同一个用户在设备上登录并没有限制,可以在Security中配置。

protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
            .anyRequest().authenticated()
            .and()
            .formLogin()
            .and()
            .csrf()
            .disable()
            .sessionManagement()
            .sessionFixation()
            .none()
            .maximumSessions(1)
            .expiredSessionStrategy(event -> {
                HttpServletResponse response = event.getResponse();
                response.setContentType("application/json;charset=utf-8");
                Map<String, Object> result = new HashMap<>();
                result.put("status", 500);
                result.put("msg", "当前会话已经失效,请重新登录");
                String s = new ObjectMapper().writeValueAsString(result);
                response.getWriter().print(s);
                response.flushBuffer();
            });
}

在登录过滤器AbstractAuthenticationProcessingFilter的doFilter方法中,调用attemptAuthentication方法进行登录认证后,调用sessionStrategy.onAuthentication方法进行Session并发的管理,默认是CompositeSessionAuthenticationStrategy

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
      throws IOException, ServletException {

   HttpServletRequest request = (HttpServletRequest) req;
   HttpServletResponse response = (HttpServletResponse) res;

   if (!requiresAuthentication(request, response)) {
      chain.doFilter(request, response);

      return;
   }

   if (logger.isDebugEnabled()) {
      logger.debug("Request is to process authentication");
   }

   Authentication authResult;

   try {
      authResult = attemptAuthentication(request, response);
      if (authResult == null) {
         // return immediately as subclass has indicated that it hasn't completed
         // authentication
         return;
      }
      sessionStrategy.onAuthentication(authResult, request, response);
   }
   catch (InternalAuthenticationServiceException failed) {
      logger.error(
            "An internal error occurred while trying to authenticate the user.",
            failed);
      unsuccessfulAuthentication(request, response, failed);

      return;
   }
   catch (AuthenticationException failed) {
      // Authentication failed
      unsuccessfulAuthentication(request, response, failed);

      return;
   }

   // Authentication success
   if (continueChainBeforeSuccessfulAuthentication) {
      chain.doFilter(request, response);
   }

   successfulAuthentication(request, response, chain, authResult);
}

CompositeSessionAuthenticationStrategy的onAuthentication方法中遍历集合,依次调用集合元素的onAuthentication方法

public void onAuthentication(Authentication authentication,
      HttpServletRequest request, HttpServletResponse response)
            throws SessionAuthenticationException {
   for (SessionAuthenticationStrategy delegate : this.delegateStrategies) {
      if (this.logger.isDebugEnabled()) {
         this.logger.debug("Delegating to " + delegate);
      }
      delegate.onAuthentication(authentication, request, response);
   }
}

sessionStrategy是AbstractAuthenticationFilterConfigurer类的configure方法中进行配置的,可以看到,这里从HttpSecurity的共享对象中获取到SessionAuthenticationStrategy的实例,并设置到authFilter过滤器中

public void configure(B http) throws Exception {
   PortMapper portMapper = http.getSharedObject(PortMapper.class);
   if (portMapper != null) {
      authenticationEntryPoint.setPortMapper(portMapper);
   }

   RequestCache requestCache = http.getSharedObject(RequestCache.class);
   if (requestCache != null) {
      this.defaultSuccessHandler.setRequestCache(requestCache);
   }

   authFilter.setAuthenticationManager(http
         .getSharedObject(AuthenticationManager.class));
   authFilter.setAuthenticationSuccessHandler(successHandler);
   authFilter.setAuthenticationFailureHandler(failureHandler);
   if (authenticationDetailsSource != null) {
      authFilter.setAuthenticationDetailsSource(authenticationDetailsSource);
   }
   SessionAuthenticationStrategy sessionAuthenticationStrategy = http
         .getSharedObject(SessionAuthenticationStrategy.class);
   if (sessionAuthenticationStrategy != null) {
      authFilter.setSessionAuthenticationStrategy(sessionAuthenticationStrategy);
   }
   RememberMeServices rememberMeServices = http
         .getSharedObject(RememberMeServices.class);
   if (rememberMeServices != null) {
      authFilter.setRememberMeServices(rememberMeServices);
   }
   F filter = postProcess(authFilter);
   http.addFilter(filter);
}

SessionAuthenticationStrategy的实例是在SessionManagementConfigurer的init方法中存入的

public void init(H http) {
   SecurityContextRepository securityContextRepository = http
         .getSharedObject(SecurityContextRepository.class);
   boolean stateless = isStateless();

   if (securityContextRepository == null) {
      if (stateless) {
         http.setSharedObject(SecurityContextRepository.class,
               new NullSecurityContextRepository());
      }
      else {
         HttpSessionSecurityContextRepository httpSecurityRepository = new HttpSessionSecurityContextRepository();
         httpSecurityRepository
               .setDisableUrlRewriting(!this.enableSessionUrlRewriting);
         httpSecurityRepository.setAllowSessionCreation(isAllowSessionCreation());
         AuthenticationTrustResolver trustResolver = http
               .getSharedObject(AuthenticationTrustResolver.class);
         if (trustResolver != null) {
            httpSecurityRepository.setTrustResolver(trustResolver);
         }
         http.setSharedObject(SecurityContextRepository.class,
               httpSecurityRepository);
      }
   }

   RequestCache requestCache = http.getSharedObject(RequestCache.class);
   if (requestCache == null) {
      if (stateless) {
         http.setSharedObject(RequestCache.class, new NullRequestCache());
      }
   }
   http.setSharedObject(SessionAuthenticationStrategy.class,
         getSessionAuthenticationStrategy(http));
   http.setSharedObject(InvalidSessionStrategy.class, getInvalidSessionStrategy());
}

方法中 首先从HttpSecurity中获取SecurityContextRepository实例,没有则进行创建,创建的时候如果是Session的创建策略是STATELESS,则使用NullSecurityContextRepository来保存SecurityContext,如果不是则构建HttpSessionSecurityContextRepository,并存入HTTPSecurity共享对象中。

如果Session的创建策略是STATELESS,还要把请求缓存对象替换为NullRequestCache

最后构建SessionAuthenticationStrategy的实例和InvalidSessionStrategy的实例,SessionAuthenticationStrategy的实例从getSessionAuthenticationStrategy中获得

private SessionAuthenticationStrategy getSessionAuthenticationStrategy(H http) {
   if (this.sessionAuthenticationStrategy != null) {
      return this.sessionAuthenticationStrategy;
   }
   List<SessionAuthenticationStrategy> delegateStrategies = this.sessionAuthenticationStrategies;
   SessionAuthenticationStrategy defaultSessionAuthenticationStrategy;
   if (this.providedSessionAuthenticationStrategy == null) {
      // If the user did not provide a SessionAuthenticationStrategy
      // then default to sessionFixationAuthenticationStrategy
      defaultSessionAuthenticationStrategy = postProcess(
            this.sessionFixationAuthenticationStrategy);
   }
   else {
      defaultSessionAuthenticationStrategy = this.providedSessionAuthenticationStrategy;
   }
   if (isConcurrentSessionControlEnabled()) {
      SessionRegistry sessionRegistry = getSessionRegistry(http);
      ConcurrentSessionControlAuthenticationStrategy concurrentSessionControlStrategy = new ConcurrentSessionControlAuthenticationStrategy(
            sessionRegistry);
      concurrentSessionControlStrategy.setMaximumSessions(this.maximumSessions);
      concurrentSessionControlStrategy
            .setExceptionIfMaximumExceeded(this.maxSessionsPreventsLogin);
      concurrentSessionControlStrategy = postProcess(
            concurrentSessionControlStrategy);

      RegisterSessionAuthenticationStrategy registerSessionStrategy = new RegisterSessionAuthenticationStrategy(
            sessionRegistry);
      registerSessionStrategy = postProcess(registerSessionStrategy);

      delegateStrategies.addAll(Arrays.asList(concurrentSessionControlStrategy,
            defaultSessionAuthenticationStrategy, registerSessionStrategy));
   }
   else {
      delegateStrategies.add(defaultSessionAuthenticationStrategy);
   }
   this.sessionAuthenticationStrategy = postProcess(
         new CompositeSessionAuthenticationStrategy(delegateStrategies));
   return this.sessionAuthenticationStrategy;
}

getSessionAuthenticationStrategy方法中把ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy添加到集合中,并返回代理类CompositeSessionAuthenticationStrategy

而sessionStrategy

ConcurrentSessionControlAuthenticationStrategy

主要用来处理Session并发问题,并发控制实际是由这个类来完成的

public void onAuthentication(Authentication authentication,
      HttpServletRequest request, HttpServletResponse response) {

   final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
         authentication.getPrincipal(), false);

   int sessionCount = sessions.size();
   int allowedSessions = getMaximumSessionsForThisUser(authentication);

   if (sessionCount < allowedSessions) {
      // They haven't got too many login sessions running at present
      return;
   }

   if (allowedSessions == -1) {
      // We permit unlimited logins
      return;
   }

   if (sessionCount == allowedSessions) {
      HttpSession session = request.getSession(false);

      if (session != null) {
         // Only permit it though if this request is associated with one of the
         // already registered sessions
         for (SessionInformation si : sessions) {
            if (si.getSessionId().equals(session.getId())) {
               return;
            }
         }
      }
      // If the session is null, a new one will be created by the parent class,
      // exceeding the allowed number
   }

   allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
}
  1. 从sessionRegistry中获取当前用户所有未失效的SessionInformation,然后获取当前项目允许的最大session数。如果获取到的SessionInformation实例小于当前项目允许的最大session数,说明当前登录没有问题,直接return
  2. 如果允许的最大session数为-1,表示应用并不限制登录并发数,当前登录没有问题,直接return
  3. 如果两者相等,判断当前sessionId是否在SessionInformation中,如果存在,直接return
  4. 超出最大并发数,进入allowableSessionsExceeded方法
protected void allowableSessionsExceeded(List<SessionInformation> sessions,
      int allowableSessions, SessionRegistry registry)
      throws SessionAuthenticationException {
   if (exceptionIfMaximumExceeded || (sessions == null)) {
      throw new SessionAuthenticationException(messages.getMessage(
            "ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
            new Object[] {allowableSessions},
            "Maximum sessions of {0} for this principal exceeded"));
   }

   // Determine least recently used sessions, and mark them for invalidation
   sessions.sort(Comparator.comparing(SessionInformation::getLastRequest));
   int maximumSessionsExceededBy = sessions.size() - allowableSessions + 1;
   List<SessionInformation> sessionsToBeExpired = sessions.subList(0, maximumSessionsExceededBy);
   for (SessionInformation session: sessionsToBeExpired) {
      session.expireNow();
   }
}

allowableSessionsExceeded方法中判断exceptionIfMaximumExceeded属性为true,则直接抛出异常,exceptionIfMaximumExceeded的属性是在SecurityConfig中

通过maxSessionPreventsLogin方法的值来改变,即禁止后来者的登录,抛出异常后,本次登录失败。否则对查询当前用户所有登录的session按照最后一次请求时间进行排序,计算出需要过期的session数量,从session集合中取出来进行遍历,依次调用expireNow方法让session过期。

ChangeSessionIdAuthenticationStrategy

通过修改sessionId来防止会话固定攻击。

所谓会话固定攻击是一种潜在的风险,恶意攻击者可能通过访问当前应用程序来创建会话,然后诱导用户以相同的会话Id登录,进而获取用户登录身份。

RegisterSessionAuthenticationStrategy

在认证成功后把HttpSession信息记录到SessionRegistry中。

public void onAuthentication(Authentication authentication,
      HttpServletRequest request, HttpServletResponse response) {
   sessionRegistry.registerNewSession(request.getSession().getId(),
         authentication.getPrincipal());
}

用户使用RememberMe的方式进行身份认证,则会通过SessionManagementFilter的doFilter方法触发Session并发管理。

SessionManagementConfigurer的configure方法中构建了这两个过滤器SessionManagementFilter和ConcurrentSessionFilter

public void configure(H http) {
   SecurityContextRepository securityContextRepository = http
         .getSharedObject(SecurityContextRepository.class);
   SessionManagementFilter sessionManagementFilter = new SessionManagementFilter(
         securityContextRepository, getSessionAuthenticationStrategy(http));
   if (this.sessionAuthenticationErrorUrl != null) {
      sessionManagementFilter.setAuthenticationFailureHandler(
            new SimpleUrlAuthenticationFailureHandler(
                  this.sessionAuthenticationErrorUrl));
   }
   InvalidSessionStrategy strategy = getInvalidSessionStrategy();
   if (strategy != null) {
      sessionManagementFilter.setInvalidSessionStrategy(strategy);
   }
   AuthenticationFailureHandler failureHandler = getSessionAuthenticationFailureHandler();
   if (failureHandler != null) {
      sessionManagementFilter.setAuthenticationFailureHandler(failureHandler);
   }
   AuthenticationTrustResolver trustResolver = http
         .getSharedObject(AuthenticationTrustResolver.class);
   if (trustResolver != null) {
      sessionManagementFilter.setTrustResolver(trustResolver);
   }
   sessionManagementFilter = postProcess(sessionManagementFilter);

   http.addFilter(sessionManagementFilter);
   if (isConcurrentSessionControlEnabled()) {
      ConcurrentSessionFilter concurrentSessionFilter = createConcurrencyFilter(http);

      concurrentSessionFilter = postProcess(concurrentSessionFilter);
      http.addFilter(concurrentSessionFilter);
   }
}
  1. SessionManagementFilter创建过程中调用getSessionAuthenticationStrategy方法获取SessionAuthenticationStrategy的实例放入过滤器中,然后配置各种回调函数,最终创建的SessionManagementFilter过滤器放入HttpSecurity中。
  2. 如果开启会话并发控制(只要maximumSessions不会空就算开启会话并发控制),则创建ConcurrentSessionFilter过滤器 加入到HttpSecurity中。

总结

用户通过用户名密码发起认证请求,当认证成功后,在AbstractAuthenticationProcessingFilter的doFilter方法中触发Session并发管理。默认的sessionStrategy是CompositeSessionAuthenticationStrategy,它代理了三个类ConcurrentSessionControlAuthenticationStrategy ChangeSessionIdAuthenticationStrategy RegisterSessionAuthenticationStrategy。当前请求在这三个SessionAuthenticationStrategy中分别走一圈,第一个用来判断当前用户的Session数是否超过限制,第二个用来修改sessionId(防止会话固定攻击),第三个用来将当前Session注册到SessionRegistry中。

如果用户使用RememberMe的方式进行身份认证,则会通过SessionManagementFilter的doFilter方法触发Session并发管理。当用户认证成功后,以后的每一次请求都会经过ConcurrentSessionFilter,在该过滤器中,判断当前会话是否过期,如果过期执行注销流程,如果没有过期,更新最近一次请求时间。

相关文章
|
5月前
|
存储 缓存 搜索推荐
session 详解:掌握客户端会话管理
session 详解:掌握客户端会话管理
|
1月前
|
存储 安全 数据库
Flask框架中,如何实现用户身份验证和会话管理
Flask框架中,如何实现用户身份验证和会话管理
|
5月前
|
存储 缓存 JSON
【Web开发】会话管理与无 Cookie 环境下的实现策略
【Web开发】会话管理与无 Cookie 环境下的实现策略
|
5月前
|
存储 安全 数据库
InfluxDB安全机制:用户认证与访问控制
【4月更文挑战第30天】InfluxDB的安全机制聚焦于用户认证和访问控制,包括启用默认禁用的用户认证,创建和管理加密密码的用户,以及实施细粒度的权限和角色管理。建议启用认证、设置强密码,合理分配权限,定期更新和审计,以及使用HTTPS确保数据传输安全,以增强数据库安全性。
|
5月前
|
安全 搜索推荐 Java
【SpringSecurity6.x】会话管理
【SpringSecurity6.x】会话管理
72 0
|
5月前
|
XML 缓存 Java
Shiro - 会话管理与SessionDao持久化Session
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如web容器tomcat),不管JavaSE还是JavaEE环境都可以使用,提供了会话管理、会话事件监听、会话存储/持久化、容器无关的集群、失效/过期支持、对Web 的透明支持、SSO 单点登录的支持等特性。
217 0
|
存储 缓存 数据安全/隐私保护
Jasny SSO是如何处理用户会话的?底层原理是什么?
Jasny SSO是如何处理用户会话的?底层原理是什么?
|
JSON 监控 前端开发
OIDC协议会话管理相关技术介绍
## 介绍 > OpenID Connect 1.0 is a simple identity layer on top of the OAuth 2.0 [[RFC6749]](https://openid.net/specs/openid-connect-session-1_0.html#RFC6749) protocol. It enables Clients to verify the i
1099 0
OIDC协议会话管理相关技术介绍
|
存储 缓存 开发框架
shiro会话管理
Shiro提供了完整的企业级会话管理功能,不依赖于底层容器(如Tomcat、WebLogic),不管是J2SE还是J2EE环境都可以使用,提供了会话管理,会话事件监听,会话存储/持久化,容器无关的集群,失效/过期支持,对Web的透明支持,SSO单点登录的支持等特性。
|
数据库 Python 内存技术
用户认证系统
建立用户认证系统需要经过以下几步: app/__init__.py中声明并初始化数据库扩展 app/models.py中定义模型 app/auth/__init__.
1006 0