【问题标题】:Spring Session Redis and Spring Security how to update user session?Spring Session Redis 和 Spring Security 如何更新用户会话?
【发布时间】:2016-03-03 14:18:54
【问题描述】:

我正在使用 spring boot、spring secuirity 和 spring session (redis) 构建一个 spring REST Web 应用程序。我正在使用 spring cloud 和 zuul 代理按照网关模式构建云应用程序。在这个模式中,我使用 spring session 来管理 redis 中的 HttpSesssion 并使用它来授权我的资源服务器上的请求。当执行更改会话权限的操作时,我想更新该对象,以便用户不必注销即可反映更新。有人对此有解决方案吗?

【问题讨论】:

    标签: spring spring-security redis spring-session spring-web


    【解决方案1】:

    要更新权限,您需要在两个地方修改身份验证对象。一个在安全上下文中,另一个在请求上下文中。您的主体对象将是 org.springframework.security.core.userdetails.User 或扩展该类(如果您已覆盖 UserDetailsS​​ervice)。这适用于修改当前用户。

        Authentication newAuth = new UsernamePasswordAuthenticationToken({YourPrincipalObject},null,List<? extends GrantedAuthority>)
    
        SecurityContextHolder.getContext().setAuthentication(newAuth);
        RequestContextHolder.currentRequestAttributes().setAttribute("SPRING_SECURITY_CONTEXT", newAuth, RequestAttributes.SCOPE_GLOBAL_SESSION);
    

    要使用春季会话为任何登录用户更新会话,需要自定义过滤器。过滤器存储一组已被某个进程修改的会话。当需要修改新会话时,消息传递系统会更新该值。当请求具有匹配的会话密钥时,过滤器会在数据库中查找用户以获取更新。然后它更新会话上的“SPRING_SECURITY_CONTEXT”属性并更新 SecurityContextHolder 中的 Authentication。 用户无需退出。在指定过滤器的顺序时,重要的是它位于 SpringSessionRepositoryFilter 之后。该对象的@Order 为-2147483598,因此我只是将过滤器更改了一个以确保它是下一个执行的过滤器。

    工作流程如下:

    1. 修改用户 A 权限
    2. 向过滤器发送消息
    3. 添加要设置的用户 A 会话密钥(在过滤器中)
    4. 下次用户 A 通过过滤器时,更新他们的会话

      @Component
      @Order(UpdateAuthFilter.ORDER_AFTER_SPRING_SESSION)
      public class UpdateAuthFilter extends OncePerRequestFilter
      {
      public static final int ORDER_AFTER_SPRING_SESSION = -2147483597;
      
      private Logger log = LoggerFactory.getLogger(this.getClass());
      
      private Set<String> permissionsToUpdate = new HashSet<>();
      
      @Autowired
      private UserJPARepository userJPARepository;
      
      private void modifySessionSet(String sessionKey, boolean add)
      {
          if (add) {
              permissionsToUpdate.add(sessionKey);
          } else {
              permissionsToUpdate.remove(sessionKey);
          }
      }
      
      public void addUserSessionsToSet(UpdateUserSessionMessage updateUserSessionMessage)
      {
          log.info("UPDATE_USER_SESSION - {} - received", updateUserSessionMessage.getUuid().toString());
          updateUserSessionMessage.getSessionKeys().forEach(sessionKey -> modifySessionSet(sessionKey, true));
          //clear keys for sessions not in redis
          log.info("UPDATE_USER_SESSION - {} - success", updateUserSessionMessage.getUuid().toString());
      }
      
      @Override
      public void destroy()
      {
      
      }
      
      @Override
      protected void doFilterInternal(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, FilterChain filterChain) throws ServletException, IOException
      {
          HttpSession session = httpServletRequest.getSession();
      
      if (session != null)
      {
          String sessionId = session.getId();
          if (permissionsToUpdate.contains(sessionId))
          {
              try
              {
                  SecurityContextImpl securityContextImpl = (SecurityContextImpl) session.getAttribute("SPRING_SECURITY_CONTEXT");
                  if (securityContextImpl != null)
                  {
                      Authentication auth = securityContextImpl.getAuthentication();
                      Optional<User> user = auth != null
                                            ? userJPARepository.findByUsername(auth.getName())
                                            : Optional.empty();
      
                      if (user.isPresent())
                      {
                          user.get().getAccessControls().forEach(ac -> ac.setUsers(null));
      
                          MyCustomUser myCustomUser = new MyCustomUser (user.get().getUsername(),
                                                                       user.get().getPassword(),
                                                                       user.get().getAccessControls(),
                                                                       user.get().getOrganization().getId());
      
                          final Authentication newAuth = new UsernamePasswordAuthenticationToken(myCustomUser ,
                                                                                                 null,
                                                                                                 user.get().getAccessControls());
                          SecurityContextHolder.getContext().setAuthentication(newAuth);
                          session.setAttribute("SPRING_SECURITY_CONTEXT", newAuth);
                      }
                      else
                      {
                          //invalidate the session if the user could not be found
                          session.invalidate();
                      }
                  }
                  else
                  {
                      //invalidate the session if the user could not be found
                      session.invalidate();
                  }
              }
              finally
              {
                  modifySessionSet(sessionId, false);
              }
          }
      }
      
      filterChain.doFilter(httpServletRequest, httpServletResponse);
      }
      

    【讨论】:

    • 这个答案帮了我很多忙,谢谢!!不过需要注意的是:修改 Authentication 后,您需要将 session 属性设置为 SecurityContextImpl 的实例,而不是 Authentication。
    • 谢谢@Ceekay,这救了我的培根!我同意@Lev,SecurityContext 实现似乎是正确的选择。另外,如果操作针对当前登录的用户,我想说RequestAttributes.SCOPE_SESSION 会更合适。
    • 并且只是添加,在调用SecurityContextHolder.getContext().setAuthentication(newAuth) 之后,您可以使用从SecurityContenxtHolder.getContext() 获得的上下文来提供给RequestContext 更新。
    猜你喜欢
    • 2016-07-08
    • 2018-03-14
    • 2015-09-07
    • 2015-11-05
    • 2015-06-13
    • 2015-06-11
    • 2021-02-13
    • 2015-09-01
    • 2018-02-09
    相关资源
    最近更新 更多