前言
最近相对较忙,没有更新博客,正值假期,赶紧抽空写两篇。也是遇到的项目上的需求:管理员变更了用户的权限,但是用户如果系统在线的话,有些权限功能仍旧可以使用。对此,强制用户下线的需求来了。括弧:由于系统预期为一对一的单服务系统,没有使用redis做缓存。所以对强制用户下线的功能带来了一些麻烦。
设计思路
- 对于系统的用户信息,既然没用redis做缓存,我想到了对session下手,用session存储用户信息。
- 了解springsecurity对session的操作
查找资料
- 网上的解决办法
- 我发现这个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]
结尾
如果有不当之处欢迎指正,疑惑的地方可以留言~!代码暂时不能全贴哦,我得整理以下!