当先锋百科网

首页 1 2 3 4 5 6 7

前言

最近相对较忙,没有更新博客,正值假期,赶紧抽空写两篇。也是遇到的项目上的需求:管理员变更了用户的权限,但是用户如果系统在线的话,有些权限功能仍旧可以使用。对此,强制用户下线的需求来了。括弧:由于系统预期为一对一的单服务系统,没有使用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]
 

结尾

如果有不当之处欢迎指正,疑惑的地方可以留言~!代码暂时不能全贴哦,我得整理以下!