shiro自定义拦截器继承AccessControllerFilter,实现session互踢机制。
应用场景:
我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。又或是需要限制同一用户的同时在线数量,超出限制后,踢出最先登录的或是踢出最后登录的。
分析:
spring security就直接提供了相应的功能;Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能。那就是使用shiro强大的自定义访问控制拦截器:AccessControlFilter,集成这个接口后要实现下面这2个方法:isAccessAllowed、onAccessDenied
isAccessAllowed:表示是否允许访问;mappedValue就是[urls]配置中拦截器参数部分,如果允许访问返回true,否则false;
onAccessDenied:表示当访问拒绝时是否已经处理了;如果返回true表示需要继续处理;如果返回false表示该拦截器实例已经处理了,将直接返回即可。
部分代码:

public class KickOutSessionControlFilter extends AccessControlFilter {private static final Logger logger = LoggerFactory.getLogger(KickOutSessionControlFilter.class);/*** 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户*/private boolean kickOutAfter = false;/*** 同一个帐号最大会话数 默认1*/private int maxSession = 1;/*** 会话管理器*/private SessionManager sessionManager;/*** 会话缓存*/private Cache<String, Deque<Serializable>> cache;public void setKickOutAfter(boolean kickOutAfter) {this.kickOutAfter = kickOutAfter;}public void setMaxSession(int maxSession) {this.maxSession = maxSession;}public void setSessionManager(SessionManager sessionManager) {this.sessionManager = sessionManager;}public void setCacheManager(RedisCacheManager cacheManager) {this.cache = cacheManager.getCache("shiro_redis_cache");}/*** 是否允许访问,返回true表示允许** @param servletRequest* @param servletResponse* @param obj* @return*/@Overrideprotected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object obj) {return false;}/*** 表示访问拒绝时是否自己处理,如果返回true表示自己不处理且继续拦截器链执行,返回false表示自己已经处理了(比如重定向到另一个页面)。** @param servletRequest* @param servletResponse* @return* @throws Exception*/@Overrideprotected boolean onAccessDenied(ServletRequest servletRequest, ServletResponse servletResponse) throws Exception {// 1同一个用户在不同ip上,不可以同时访问,后者会把前者踢出,即同一个用户不可以同时访问Subject subject = getSubject(servletRequest, servletResponse);System.out.println("===当前subject:==" + SecurityUtils.getSubject());if (!subject.isAuthenticated() && !subject.isRemembered()) {// 如果没有登录,直接进行之后的拦截器链return true;}// 当前用户User user = (User) subject.getPrincipal();String username = user.getUserName();// 当前会话Session session = subject.getSession();Serializable sessionId = session.getId();// 读取缓存用户 没有就存入Deque<Serializable> deque = cache.get(username);if (deque == null) {// 初始化队列deque = new ArrayDeque<Serializable>();}// 如果队列里没有当前会话sessionId,且当前会话未设置踢出标记(用户没有被踢出),放入队列if (!deque.contains(sessionId) && session.getAttribute("kickOut") == null) {// 将用户的sessionId存入队列deque.push(sessionId);// 将用户的sessionId存入队列缓存cache.put(username, deque);}// 如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {Serializable kickOutSessionId = null;// 是否踢出后来登录的,默认是false,即后者登录的用户踢出前者登录的用户;if (kickOutAfter) {// 如果踢出后者kickOutSessionId = deque.removeFirst();} else {// 否则踢出前者kickOutSessionId = deque.removeLast();}// 踢出后再更新下缓存队列cache.put(username, deque);try {// 获取被踢出的sessionId的session对象Session kickOutSession = sessionManager.getSession(new DefaultSessionKey(kickOutSessionId));if (kickOutSession != null) {// 设置会话的kickOut属性表示踢出了kickOutSession.setAttribute("kickOut", true);System.out.println("===将sessionId:==" + kickOutSession.getId() + "设置踢出标记");}} catch (Exception e) {// ignore exception}}// ajax请求,如果被踢出了,(前者或后者)直接退出,返回相应的状态if (session.getAttribute("kickOut") != null && (Boolean) session.getAttribute("kickOut") == true) {// 当前会话踢出标记不为空且等于true,会话被踢出了try {// 退出登录String ip = IPUtil.getIpAddress((HttpServletRequest) servletRequest);String url = ((HttpServletRequest) servletRequest).getRequestURL() + "";SecurityLogoutFilter.logout(subject);logger.info("IP地址为:" + ip + "的用户【" + username + "】被踢出,已在其他ip地址登录");ResponseUtil.returnResultAjax();return false;} catch (Exception e) {// ignore}return false;}return true;}
}

shiroFilterFactoryBean方法

     // 自定义过滤器Map<String, Filter> filters = new HashMap<>();// 同一用户登陆互踢filters.put("kickOut", kickOutSessionControlFilter());filterChainDefinitionMap.put("/**", "kickOut,authc");

定义拦截器的时候不需要加@Bean;

 //@Beanpublic KickoutSessionControlFilter kickoutSessionControlFilter(){KickoutSessionControlFilter kickoutSessionControlFilter = new KickoutSessionControlFilter();//用于根据会话ID,获取会话进行踢出操作的;//是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;kickoutSessionControlFilter.setKickoutAfter(false);//同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;kickoutSessionControlFilter.setMaxSession(1);//被踢出后重定向到的地址kickoutSessionControlFilter.setKickoutUrl("/a/login");return kickoutSessionControlFilter;}

互踢分析:

1A用户第一次访问登录,进入互踢过滤器,获取当前会话,会话未认证不处理进入后面的拦截器,拦截器均未拦截住,进行正常登录获得会话token
2.1A用户第一次访问请求,进入互踢过滤器,获取当前会话,从cahe中获取deque,该deque不包含当前会话token且当前会话未设置标记,将会话token放入deque,放入cache,deque中token数量未超过1,当前会话的标记为空进入后面的拦截器
2.11A用户第二次访问请求,deque未变化,当前会话的标记为空进入后面的拦截器
2.2B用户第一次访问登录,进入互踢过滤器,获取当前会话,会话未认证不处理进入后面的拦截器,拦截器未拦截住,进行正常登录获得会话token
2.21B用户第一次访问请求,进入互踢过滤器,获取当前会话,从cache中获取deque,该deque不包含当前会话token且当前会话未设置标记,将会话token放入deque,放入cache,deque中token数量超过1,将deque中A的token删除,cache更新,将A的会话设置标记,当前会话的标记为空进入后面的拦截器
2.22B用户第二次访问请求,deque未变化,当前会话的标记为空进入后面的拦截器
3.1A用户第三次访问请求,进入互踢过滤器,获取当前会话,从cahe中获取deque,该deque不包含当前会话token但当前会话已设置标记不更新deque和cache,deque中的token数量未超过1,当前会话的标记不为空且为true,进行登出返回
3.2A用户第四次访问请求,会话过期请重新登录
4.1A用户第二次访问登录,进入进入互踢过滤器,获取当前会话,会话未认证不处理进入后面的拦截器,拦截器均未拦截住,进行正常登录获得会话token
4.2A用户第四次访问请求,进入互踢过滤器,获取当前会话,从cahe中获取deque,该deque不包含当前会话token且当前会话未设置标记,将会话token放入deque,放入cache,deque中token数量超过1,将deque中B的token删除,cache更新,将B的会话设备标记,当前会话的标记为空进入后面的拦截器
5B用户第三次访问请求,进入互踢过滤器,获取当前会话,从cahe中获取deque,该deque不包含当前会话token但当前会话已设置标记不更新deque和cache,deque中的token数量未超过1,当前会话的标记不为空且为true,进行登出返回

springboot+shiro自定义拦截器互踢问题相关推荐

  1. SpringBoot中自定义拦截器

    场景 自定义拦截器,通过继承WebMvcConfigureAdapter然后重写父类中的方法进行扩展. 项目搭建专栏: https://blog.csdn.net/BADAO_LIUMANG_QIZH ...

  2. 【学习】SpringBoot之自定义拦截器

    /*** 自定义拦截器**/ @Configuration//声明这是一个拦截器 public class MyInterceptor extends WebMvcConfigurerAdapter ...

  3. SpringBoot之Interceptor拦截器注入使用

    相关文章: SpringBoot 之AOP切面的使用 SpringBoot之Listener注册到Spring容器中的多种方法 SpringBoot之Filter过滤器的实现及排序问题 SpringB ...

  4. springboot-2.2.5中自定义拦截器、静态资源映射、视图控制器和其他功能

    在spring-boot-2.2.5中对MVC自动配置类进行的更改,之前的WebMvcConfigurerAdapter类声明为过时的,现在进行自定义扩展需要实现WebMvcConfigurer类重写 ...

  5. Hadoop生态圈-Flume的组件之自定义拦截器(interceptor)

    Hadoop生态圈-Flume的组件之自定义拦截器(interceptor) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 本篇博客只是举例了一个自定义拦截器的方法,测试字节传输速 ...

  6. web开发(二十一)之自定义拦截器的使用

    转自博客:http://blog.csdn.net/pkgk2013/article/details/51985817 拦截器的作用 拦截器,在AOP(Aspect-Oriented Programm ...

  7. SpringMVC——自定义拦截器、异常处理以及父子容器配置

    SpringMVC--自定义拦截器.异常处理以及父子容器配置 参考文章: (1)SpringMVC--自定义拦截器.异常处理以及父子容器配置 (2)https://www.cnblogs.com/so ...

  8. Struts2自定义类型转换器、自定义拦截器和用户输入数据的验证

    一.自定义类型转换器 1.编写一个类,继承com.opensymphony.xwork2.conversion.impl.DefaultTypeConverter 2.覆盖掉其中的public Obj ...

  9. WebServices中使用cxf开发日志拦截器以及自定义拦截器

    首先下载一个cxf实例,里面包含cxf的jar包.我下的是apache-cxf-2.5.9 1.为什么要设置拦截器? 为了在webservice请求过程中,能动态操作请求和响应数据, CXF设计了拦截 ...

最新文章

  1. LARS 算法简介-机器学习
  2. 物体识别_小鼠新物体识别Protocol
  3. MVC基础知识-View
  4. 高计能计算要实现软着陆
  5. 终极教程,带具体实验现象,1个GPIO控制2个LED显示4种状态,欢迎讨论!
  6. 鸿蒙分布式通讯子系统,【鸿蒙】分布式通信子系统--让华为手机发现Hi3861开发板...
  7. 【HDOJ】4541 Ten Googol
  8. python安装mysqldb模块_Python的MySQLdb模块安装
  9. 广东地下水资源摘录(早期版的)
  10. leetcode笔记--7 Find the Difference
  11. 027_《Delphi Direct X 图形与游戏程序设计》
  12. Vue打开外部链接问题
  13. 微软晓晓朗读录音工具windows-文字转语音
  14. 能给客户带来什么价值_您给公司带来什么价值?
  15. pg_squeeze安装及简单使用
  16. elasticsearch7.x clusterAPI之settings
  17. 苹果iOS越狱元老:想尽快实现iOS9.3.3越狱就自己开发
  18. mysql数字大小排序函数_mysql按照数字大小排序的方法
  19. 计算机专业英语词汇表RSTUVW
  20. 关于VSCode以及DEV-C++在进行网络编程时出现的WS2_32链接问题

热门文章

  1. matlab正负序分离模块,一种自适应宽频带正负序分离方法与流程
  2. 了解计算机软件系统教学设计,认识计算机系统教案.doc
  3. Qt编写可视化大屏电子看板系统30-模块8物料管理
  4. quartusii verilog语言create bsf文件的问题
  5. InfoPath 系列:了解INFOPATH XSN文件的格式(1)
  6. [Elasticsearch](五)Docker环境下搭建Elasticsearch,Elasticsearch集群,Elasticsearch-Head以及IK分词插件和拼音分词插件
  7. 美国L1签证面谈的时候一般VO会问到什么问题?
  8. 6.3 Faddeev-Leverrier算法求特征多项式
  9. IDEA创建maven项目没有srcmainjava目录问题解决
  10. SQL-Server常用系统存储过程