2015-11-30 101 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