強制使用者下線
- 前言
-
- 設計思路
- 查找資料
- 自己手寫緩存過濾器
-
- HttpSession監聽器
- 強制使用者下線
-
- 結尾
前言
最近相對較忙,沒有更新部落格,正值假期,趕緊抽空寫兩篇。也是遇到的項目上的需求:管理者變更了使用者的權限,但是使用者如果系統線上的話,有些權限功能仍舊可以使用。對此,強制使用者下線的需求來了。括弧:由于系統預期為一對一的單服務系統,沒有使用redis做緩存。是以對強制使用者下線的功能帶來了一些麻煩。
設計思路
- 對于系統的使用者資訊,既然沒用redis做緩存,我想到了對session下手,用session存儲使用者資訊。
- 了解springsecurity對session的操作
查找資料
- 網上的解決辦法
springsecurity 實作強制使用者下線-前後端分離前言 - 我發現這個bean類并不能取到session,列印的session永遠是一個空的數組。
-
于是,通過查閱資料,springsecurity通過對使用者的認證之後會銷毀
session的資訊。也就是說,想要取到SecurityContextHolder,必須是在過濾器鍊上的操作。想要單獨使用,沒有辦法取到使用者的資訊。
自己手寫緩存過濾器
HttpSession監聽器
需要在安全配置類中注入該bean類
- 注入該bean類
- 這個時候,我們就可以随時監聽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]
結尾
如果有不當之處歡迎指正,疑惑的地方可以留言~!代碼暫時不能全貼哦,我得整理以下!