100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > 【Shiro】7 Shiro实现控制用户并发登录并踢人下线

【Shiro】7 Shiro实现控制用户并发登录并踢人下线

时间:2024-05-25 02:10:33

相关推荐

【Shiro】7 Shiro实现控制用户并发登录并踢人下线

在传统的项目中,同一账户是允许多人同时登录在线的,有的使用场景恰恰是不允许多人同时在线的,那么我们可以通过 Shiro 来控制并发登录,并实现后登录的用户,挤掉前面登录的用户

1、并发登录过滤器

package mon.shiro;import com.asurplus.system.entity.SysUserInfo;import org.apache.shiro.SecurityUtils;import org.apache.shiro.cache.Cache;import org.apache.shiro.cache.CacheManager;import org.apache.shiro.session.Session;import org.apache.shiro.session.mgt.DefaultSessionKey;import org.apache.shiro.session.mgt.SessionManager;import org.apache.shiro.subject.Subject;import org.apache.shiro.web.filter.AccessControlFilter;import org.apache.shiro.web.util.WebUtils;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;import java.io.Serializable;import java.util.Deque;import java.util.LinkedList;/*** 同一用户登录后踢出前面的用户*/public class KickoutSessionFilter extends AccessControlFilter {/*** 踢出后到的地址*/private String kickoutUrl;/*** 踢出之前登录的/之后登录的用户 默认踢出之前登录的用户*/private boolean kickoutAfter = false;/*** 同一个帐号最大会话数 默认1*/private int maxSession = 1;/*** session管理器*/private SessionManager sessionManager;/*** 缓存管理器*/private Cache<String, Deque<Serializable>> cache;public void setKickoutUrl(String kickoutUrl) {this.kickoutUrl = kickoutUrl;}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(CacheManager cacheManager) {this.cache = cacheManager.getCache("kickoutSession");}/*** 是否允许访问*/@Overrideprotected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {return false;}@Overrideprotected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {Subject subject = getSubject(request, response);if (!subject.isAuthenticated() && !subject.isRemembered()) {// 如果没有登录,直接进行之后的流程return true;}Session session = subject.getSession();SysUserInfo object = (SysUserInfo) SecurityUtils.getSubject().getPrincipal();Serializable sessionId = session.getId();// 同步控制Deque<Serializable> deque = cache.get(object.getAccount());if (deque == null) {deque = new LinkedList<Serializable>();cache.put(object.getAccount(), deque);}// 如果队列里没有此sessionId,且用户没有被踢出;放入队列if (!deque.contains(sessionId) && session.getAttribute("kickout") == null) {deque.push(sessionId);}// 如果队列里的sessionId数超出最大会话数,开始踢人while (deque.size() > maxSession) {Serializable kickoutSessionId = null;// 如果踢出后者if (kickoutAfter) {kickoutSessionId = deque.removeFirst();}// 否则踢出前者else {kickoutSessionId = deque.removeLast();}try {Session kickoutSession = sessionManager.getSession(new DefaultSessionKey(kickoutSessionId));if (kickoutSession != null) {// 设置会话的kickout属性表示踢出了kickoutSession.setAttribute("kickout", true);}} catch (Exception e) {e.printStackTrace();}}// 如果被踢出了,直接退出,重定向到踢出后的地址if (session.getAttribute("kickout") != null) {// 会话被踢出了try {subject.logout();} catch (Exception e) {e.printStackTrace();}saveRequest(request);HttpServletRequest httpRequest = WebUtils.toHttp(request);// 如果是ajax请求if (isAjax(httpRequest)) {HttpServletResponse httpServletResponse = WebUtils.toHttp(response);// 使得http会话过期httpServletResponse.sendError(0);return false;} else {WebUtils.issueRedirect(request, response, kickoutUrl);return false;}}return true;}/*** 判断是否为ajax请求** @param request* @return boolean对象*/public static boolean isAjax(ServletRequest request) {return "XMLHttpRequest".equalsIgnoreCase(((HttpServletRequest) request).getHeader("X-Requested-With"));}}

这里我们使用了 ehcache,我们需要在 shiro-ehcache.xml 配置文件中,加一个存储对象,如下:

<!-- 并发登录控制 --><cache name="kickoutSession" maxElementsInMemory="10000" eternal="false" timeToIdleSeconds="120"timeToLiveSeconds="120" maxElementsOnDisk="10000000" overflowToDisk="true" memoryStoreEvictionPolicy="LRU"/>

2、注册过滤器

/*** 并发登录控制** @return*/@Beanpublic KickoutSessionFilter kickoutSessionControlFilter() {KickoutSessionFilter kickoutSessionControlFilter = new KickoutSessionFilter();// 用于根据会话ID,获取会话进行踢出操作的;kickoutSessionControlFilter.setSessionManager(sessionManager());// 使用cacheManager获取相应的cache来缓存用户登录的会话;用于保存用户—会话之间的关系的;kickoutSessionControlFilter.setCacheManager(ehCacheManager());// 是否踢出后来登录的,默认是false;即后者登录的用户踢出前者登录的用户;kickoutSessionControlFilter.setKickoutAfter(false);// 同一个用户最大的会话数,默认1;比如2的意思是同一个用户允许最多同时两个人登录;kickoutSessionControlFilter.setMaxSession(1);// 被踢出后重定向到的地址;kickoutSessionControlFilter.setKickoutUrl("/login?kickout=1");return kickoutSessionControlFilter;}

其中用到的 session 管理器,和 ehcache 管理器在之前的博客中都有讲到,本次不再赘述

我们将踢出的用户重定向到登录界面,并携带参数 kickout

3、注入自定义过滤器

/*** 地址过滤器** @param securityManager* @return*/@Beanpublic ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();// 设置securityManagershiroFilterFactoryBean.setSecurityManager(securityManager);// 设置登录urlshiroFilterFactoryBean.setLoginUrl("/login");// 设置主页urlshiroFilterFactoryBean.setSuccessUrl("/");// 设置未授权的urlshiroFilterFactoryBean.setUnauthorizedUrl("/403");// 自定义拦截器限制并发人数LinkedHashMap<String, Filter> filtersMap = new LinkedHashMap<>();// 限制同一帐号同时在线的个数filtersMap.put("kickout", kickoutSessionControlFilter());shiroFilterFactoryBean.setFilters(filtersMap);Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();// 注销登录filterChainDefinitionMap.put("/loginOut", "logout");// 开放登录接口filterChainDefinitionMap.put("/doLogin", "anon");// 开放获取登录验证码接口filterChainDefinitionMap.put("/kaptcha/**", "anon");// 开放Api接口filterChainDefinitionMap.put("/api/**", "anon");// 开放微信接口filterChainDefinitionMap.put("/weixin/**", "anon");// 开放websocket接口filterChainDefinitionMap.put("/websocket/**", "anon");// 开放接口文档filterChainDefinitionMap.put("/doc.html", "anon");filterChainDefinitionMap.put("/service-worker.js", "anon");filterChainDefinitionMap.put("/swagger-resources/**", "anon");filterChainDefinitionMap.put("/webjars/**", "anon");filterChainDefinitionMap.put("/v2/**", "anon");// 开放静态资源filterChainDefinitionMap.put("/css/**", "anon");filterChainDefinitionMap.put("/img/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/layui/**", "anon");filterChainDefinitionMap.put("/layuimini/**", "anon");filterChainDefinitionMap.put("/module/**", "anon");filterChainDefinitionMap.put("/upload/**", "anon");// 其余url全部拦截,必须放在最后filterChainDefinitionMap.put("/**", "kickout,user");shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);return shiroFilterFactoryBean;}

我们在 ShiroFilterFactoryBean 对象中注入了自定义过滤器,并在最后的地址拦截规则中增加了 kickout,即使我们开启了记住登录功能,该用户也会被踢下线

4、提示信息

我们在登录页面中,需要获取地址中是否有 kickout 参数

// 是否被挤下线if(location.href.indexOf("kickout") > 0){setTimeout(function () {layNotify.notice({title: "登录提示",type: "error",message: '您的账户已在另一台设备上登录,如非本人操作,请立即修改密码!'});}, 1000)}

这样后面登录的用户就会挤掉前面登录用户,导致前面登录的用户被踢下线了

如您在阅读中发现不足,欢迎留言!!!

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。