栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > Java

springboot+shiro自定义拦截器互踢问题

Java 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

springboot+shiro自定义拦截器互踢问题

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;
    
    private int maxSession = 1;
    
    private SessionManager sessionManager;
    
    private Cache> 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");
    }

    
    @Override
    protected boolean isAccessAllowed(ServletRequest servletRequest, ServletResponse servletResponse, Object obj) {
        return false;
    }

    
    @Override
    protected 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 deque = cache.get(username);
        if (deque == null) {
            // 初始化队列
            deque = new ArrayDeque();
        }
        
        // 如果队列里没有当前会话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 filters = new HashMap<>();
        // 同一用户登陆互踢
        filters.put("kickOut", kickOutSessionControlFilter());
		filterChainDefinitionMap.put("/**", "kickOut,authc");

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

	//@Bean
    public 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,进行登出返回
转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/591704.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号