2015-11-30 75 views
4

我正在使用spring引导,spring安全性和spring会话(redis)构建一个Spring REST Web应用程序。我正在使用Spring云和zuul代理在网关模式之后构建云应用程序。在这种模式下,我使用spring会话来管理redis中的HttpSesssion并使用它来授权我资源服务器上的请求。当执行更改会话权限的操作时,我想更新该对象,以便用户不必注销以反映更新。有没有人有这个解决方案?春季会议Redis和Spring Security如何更新用户会话?

回答

8

要更新权限,您需要在两个地方修改认证对象。一个在安全上下文中,另一个在请求上下文中。您的主体对象将是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中的身份验证。 用户不需要注销。当指定过滤器的顺序时,它在SpringSessionRepositoryFilter之后很重要。该对象的@Order为-2147483598,所以我只是更改了我的过滤器,以确保它是下一个被执行的过滤器。

的工作流程是这样的:

  1. 修改用户A管理局
  2. 信息发送到过滤
  3. 添加用户一个会话密钥设置(在过滤器)
  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); 
    } 
    
+0

这个答案帮了我一大堆,谢谢!但请注意:在修改身份验证之后,您需要将会话属性设置为SecurityContextImpl的实例,而不是身份验证。 – Lev

+0

谢谢@Ceekay,这节省了我的培根!我会同意@Lev,SecurityContext实现似乎是正确的选择。另外,如果操作的目标是当前登录的用户,我会说'RequestAttributes.SCOPE_SESSION'会更合适。 – demaniak

+0

只需添加,在对'SecurityContextHolder.getContext()。setAuthentication(newAuth)'的调用之后,您就可以使用从'SecurityContenxtHolder.getContext()'获得的上下文来提供给RequestContext更新。 – demaniak