天天看點

springsecurity 實作強制使用者下線-前後端分離前言

強制使用者下線

  • 前言
    • 設計思路
    • 查找資料
    • 自己手寫緩存過濾器
      • HttpSession監聽器
    • 強制使用者下線
      • 結尾

前言

最近相對較忙,沒有更新部落格,正值假期,趕緊抽空寫兩篇。也是遇到的項目上的需求:管理者變更了使用者的權限,但是使用者如果系統線上的話,有些權限功能仍舊可以使用。對此,強制使用者下線的需求來了。括弧:由于系統預期為一對一的單服務系統,沒有使用redis做緩存。是以對強制使用者下線的功能帶來了一些麻煩。

設計思路

  • 對于系統的使用者資訊,既然沒用redis做緩存,我想到了對session下手,用session存儲使用者資訊。
  • 了解springsecurity對session的操作

查找資料

  • 網上的解決辦法
    springsecurity 實作強制使用者下線-前後端分離前言
  • 我發現這個bean類并不能取到session,列印的session永遠是一個空的數組。
  • 于是,通過查閱資料,springsecurity通過對使用者的認證之後會銷毀

    session的資訊。也就是說,想要取到SecurityContextHolder,必須是在過濾器鍊上的操作。想要單獨使用,沒有辦法取到使用者的資訊。

自己手寫緩存過濾器

HttpSession監聽器

需要在安全配置類中注入該bean類

springsecurity 實作強制使用者下線-前後端分離前言
  • 注入該bean類
springsecurity 實作強制使用者下線-前後端分離前言
  • 這個時候,我們就可以随時監聽session的建立,移除,和銷毀的事件了,如下:
@WebListener
@Component
@Slf4j
public class SessionListener implements HttpSessionAttributeListener {
    @Override
    public void attributeAdded(HttpSessionBindingEvent httpSessionBindingEvent) {
        log.info("--attributeAdded--");
        log.info("key----:"+httpSessionBindingEvent.getName());
        log.info("value---:"+httpSessionBindingEvent.getValue());
    }

    @Override
    public void attributeRemoved(HttpSessionBindingEvent httpSessionBindingEvent) {
        log.info("--attributeRemoved--");
        log.info("key----:"+httpSessionBindingEvent.getName());
        log.info("value---:"+httpSessionBindingEvent.getValue());
    }

    @Override
    public void attributeReplaced(HttpSessionBindingEvent httpSessionBindingEvent) {
        log.info("--attributeReplaced--");
        String oldName = httpSessionBindingEvent.getName();
        log.info("--old key--"+oldName);
        log.info("--old value--"+httpSessionBindingEvent.getValue());
        HttpSession session = httpSessionBindingEvent.getSession();
        log.info("new value---:"+session.getAttribute(oldName));
    }
           
  • 自己手寫實作,當然,上面僅是列印日志資訊。
  • 過濾器:寫一個靜态set集合,然後加入到使用者認證之後
http.addFilterAfter(sessionFilter, UsernamePasswordAuthenticationFilter.class);
           
  • 在session監聽器的attributeAdded方法上加入以下代碼:
HttpSession session=httpSessionBindingEvent.getSession();
 		//至于為什麼是擷取SPRING_SECURITY_CONTEXT這個名字,springsecurity規定的
        if(httpSessionBindingEvent.getName().equals("SPRING_SECURITY_CONTEXT")){
            //1.從HttpServletRequest中擷取SecurityContextImpl對象
            SecurityContextImpl securityContextImpl = (SecurityContextImpl)session.getAttribute("SPRING_SECURITY_CONTEXT");
            //2.從SecurityContextImpl中擷取Authentication對象
            Authentication authentication = securityContextImpl.getAuthentication();
            //3.強轉User
            User user = (User) authentication.getPrincipal();
            //4.得到使用者名,存入集合
            String userName = user.getUsername();
            SessionFilter.set.add(userName);
        }
           
  • 這裡我存入的是使用者名,因為本系統的使用者名是唯一的。
  • 有了使用者資訊的集合,下面我們再來實作該過濾器了:
@Slf4j
@Component
public class SessionFilter extends GenericFilterBean {
    public static Set<String> set = new HashSet<>();

    @Override
    public void doFilter(ServletRequest request, ServletResponse response,FilterChain chain) throws IOException, ServletException {
        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
        String requestURI = httpServletRequest.getRequestURI();
        //log.info(requestURI);
        if(requestURI.startsWith("/jiLongS/") || requestURI.equals("/getHeader") || requestURI.equals("/getContent")||requestURI.equals("/getArticlesForHome")){
            // 繼續調用 Filter 鍊
            chain.doFilter(request, response);
            return;
        }
        log.info("進入到自己的SessionFilter");
        //1、擷取HttpSession對象,并強轉成SecurityContextImpl
        //2、注意:HttpServletRequest是ServletRequest的子接口
        SecurityContextImpl securityContextImpl = (SecurityContextImpl)
                ((HttpServletRequest) request).getSession().getAttribute("SPRING_SECURITY_CONTEXT");
        if (securityContextImpl !=  null){
            //3.從SecurityContextImpl中擷取Authentication對象
            Authentication authentication = securityContextImpl.getAuthentication();
            if(authentication !=null ){
                //4.強轉User
                User user = (User) authentication.getPrincipal();
                //5.得到使用者名
                String userName = user.getUsername();
                if (!set.contains(userName)){
                    return;
                }
            }
            log.info("轉化成功!");
        } else {
            return;
        }
        // 繼續調用 Filter 鍊
        chain.doFilter(request, response);
    }

}
           
  • 該過濾器的作用:檢視集合中是否有該使用者,沒有該使用者的化直接return,不在調用下面的過濾器,到此,我們就可以實作使用者的強制下線啦。

強制使用者下線

  • 我們隻需要知道使用者名,到set集合中将其删除即可。下面,看一下我的過濾器鍊。
[org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter@58472096, 
org.springframework.security.web.context.SecurityContextPersistenceFilter@7aac8884, 
org.springframework.security.web.header.HeaderWriterFilter@26c47874, 
org.springframework.security.web.authentication.logout.LogoutFilter@117525fe, 
org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter@25ad4f71,
com.example.jilong.security.SessionFilter@1ddd3478, 
org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter@6981f8f3, 
org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter@a92be4f, 
org.springframework.security.web.session.ConcurrentSessionFilter@3c5dbdf8, 
org.springframework.security.web.savedrequest.RequestCacheAwareFilter@5b852b49, 
org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter@4c6007fb, 
org.springframework.security.web.authentication.AnonymousAuthenticationFilter@53e800f9, 
org.springframework.security.web.session.SessionManagementFilter@2849434b, 
org.springframework.security.web.access.ExceptionTranslationFilter@63c12e52, 
org.springframework.security.web.access.intercept.FilterSecurityInterceptor@77bbadc]
 
           

結尾

如果有不當之處歡迎指正,疑惑的地方可以留言~!代碼暫時不能全貼哦,我得整理以下!