Shiro 控制并发登录人数限制及登录踢出的实现代码

时间:2021-05-20

我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。

这样保证了一个帐号只能同时一个人使用。那么下面来讲解一下 Shiro 怎么实现这个功能,现在是用到了缓存 Redis 。我们也可以用其他缓存。如果是单个点,直接用一个静态的Map<String,Object> 或者 Ehcache 即可。

XML配置。

<!-- session 校验单个用户是否多次登录 --><bean id="kickoutSessionFilter" class="com.sojson.core.shiro.filter.KickoutSessionFilter"> <property name="kickoutUrl" value="/u/login.shtml?kickout"/> </bean> <!-- 静态注入 jedisShiroSessionRepository--><bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean"> <property name="staticMethod" value="com.sojson.core.shiro.filter.KickoutSessionFilter.setShiroSessionRepository"/> <property name="arguments" ref="jedisShiroSessionRepository"/></bean>

这里用到了静态注入。如果不了解请看这篇:Spring 静态注入讲解(MethodInvokingFactoryBean)

加入到 shiro 的Filter 拦截序列

<bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean"> <property name="securityManager" ref="securityManager" /> <property name="loginUrl" value="/u/login.shtml" /> <!-- TODO 待提取 --><property name="successUrl" value="/" /><property name="unauthorizedUrl" value="/?login" /> <property name="filterChainDefinitions" value="#{shiroManager.loadFilterChainDefinitions()}"/> <property name="filters"> <util:map> <entry key="login" value-ref="login"></entry> <entry key="role" value-ref="role"></entry> <entry key="simple" value-ref="simple"></entry> <entry key="permission" value-ref="permission"></entry> <entry key="kickout" value-ref="kickoutSessionFilter"></entry> </util:map> </property></bean>

Java代码,下面看实现的Filter代码。

package com.sojson.core.shiro.filter;import java.io.IOException;import java.io.PrintWriter;import java.io.Serializable;import java.util.HashMap;import java.util.LinkedHashMap;import java.util.Map;import javax.servlet.ServletRequest;import javax.servlet.ServletResponse;import javax.servlet.http.HttpServletRequest;import net.sf.json.JSONObject;import org.apache.shiro.session.Session;import org.apache.shiro.subject.Subject;import org.apache.shiro.web.filter.AccessControlFilter;import org.apache.shiro.web.util.WebUtils;import com.sojson.common.utils.LoggerUtils;import com.sojson.core.shiro.cache.VCache;import com.sojson.core.shiro.session.ShiroSessionRepository;import com.sojson.core.shiro.token.manager.TokenManager;/** * * 开发公司:SOJSON在线工具 <p> * 版权所有:© * @version 1.0,2016年6月2日 <br/> * */@SuppressWarnings({"unchecked","static-access"})public class KickoutSessionFilter extends AccessControlFilter { //静态注入 static String kickoutUrl; //在线用户 final static String ONLINE_USER = KickoutSessionFilter.class.getCanonicalName()+ "_online_user"; //踢出状态,true标示踢出 final static String KICKOUT_STATUS = KickoutSessionFilter.class.getCanonicalName()+ "_kickout_status"; static VCache cache; //session获取 static ShiroSessionRepository shiroSessionRepository; @Override protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception { HttpServletRequest httpRequest = ((HttpServletRequest)request); String url = httpRequest.getRequestURI(); Subject subject = getSubject(request, response); //如果是相关目录 or 如果没有登录 就直接return true if(url.startsWith("/open/") || (!subject.isAuthenticated() && !subject.isRemembered())){ return Boolean.TRUE; } Session session = subject.getSession(); Serializable sessionId = session.getId(); /** * 判断是否已经踢出 * 1.如果是Ajax 访问,那么给予json返回值提示。 * 2.如果是普通请求,直接跳转到登录页 */ Boolean marker = (Boolean)session.getAttribute(KICKOUT_STATUS); if (null != marker && marker ) { Map<String, String> resultMap = new HashMap<String, String>(); //判断是不是Ajax请求 if (ShiroFilterUtils.isAjax(request) ) { LoggerUtils.debug(getClass(), "当前用户已经在其他地方登录,并且是Ajax请求!"); resultMap.put("user_status", "300"); resultMap.put("message", "您已经在其他地方登录,请重新登录!"); out(response, resultMap); } return Boolean.FALSE; } //从缓存获取用户-Session信息 <UserId,SessionId> LinkedHashMap<Long, Serializable> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class); //如果不存在,创建一个新的 infoMap = null == infoMap ? new LinkedHashMap<Long, Serializable>() : infoMap; //获取tokenId Long userId = TokenManager.getUserId(); //如果已经包含当前Session,并且是同一个用户,跳过。 if(infoMap.containsKey(userId) && infoMap.containsValue(sessionId)){ //更新存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期) cache.setex(ONLINE_USER, infoMap, 3600); return Boolean.TRUE; } //如果用户相同,Session不相同,那么就要处理了 /** * 如果用户Id相同,Session不相同 * 1.获取到原来的session,并且标记为踢出。 * 2.继续走 */ if(infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){ Serializable oldSessionId = infoMap.get(userId); Session oldSession = shiroSessionRepository.getSession(oldSessionId); if(null != oldSession){ //标记session已经踢出 oldSession.setAttribute(KICKOUT_STATUS, Boolean.TRUE); shiroSessionRepository.saveSession(oldSession);//更新session LoggerUtils.fmtDebug(getClass(), "kickout old session success,oldId[%s]",oldSessionId); }else{ shiroSessionRepository.deleteSession(oldSessionId); infoMap.remove(userId); //存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期) cache.setex(ONLINE_USER, infoMap, 3600); } return Boolean.TRUE; } if(!infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){ infoMap.put(userId, sessionId); //存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期) cache.setex(ONLINE_USER, infoMap, 3600); } return Boolean.TRUE; } @Override protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception { //先退出 Subject subject = getSubject(request, response); subject.logout(); WebUtils.getSavedRequest(request); //再重定向 WebUtils.issueRedirect(request, response,kickoutUrl); return false; } private void out(ServletResponse hresponse, Map<String, String> resultMap) throws IOException { try { hresponse.setCharacterEncoding("UTF-8"); PrintWriter out = hresponse.getWriter(); out.println(JSONObject.fromObject(resultMap).toString()); out.flush(); out.close(); } catch (Exception e) { LoggerUtils.error(getClass(), "KickoutSessionFilter.class 输出JSON异常,可以忽略。"); } } public static void setShiroSessionRepository( ShiroSessionRepository shiroSessionRepository) { KickoutSessionFilter.shiroSessionRepository = shiroSessionRepository; } public static String getKickoutUrl() { return kickoutUrl; } public static void setKickoutUrl(String kickoutUrl) { KickoutSessionFilter.kickoutUrl = kickoutUrl; }}

前端页面(登录页面)代码。

try{ var _href = window.location.href+""; if(_href && _href.indexOf('?kickout')!=-1){ layer.msg('您已经被踢出,请重新登录!'); }}catch(e){}

Ok了,这样效果就出来了。(效果图)

总结

以上所述是小编给大家介绍的Shiro 控制并发登录人数限制及登录踢出的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对网站的支持!

声明:本页内容来源网络,仅供用户参考;我单位不保证亦不表示资料全面及准确无误,也不保证亦不表示这些资料为最新信息,如因任何原因,本网内容或者用户因倚赖本网内容造成任何损失或损害,我单位将不会负任何法律责任。如涉及版权问题,请提交至online#300.cn邮箱联系删除。

相关文章