Shiro 控制并发登录人数限制及登录踢出的实现代码
程序员文章站
2024-02-29 11:52:52
我们经常会有用到,当a 用户在北京登录 ,然后a用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。
这样保证了一个帐号只...
我们经常会有用到,当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> * 版权所有:© www.sojson.com<p> * 博客地址:http://www.sojson.com/blog/ <p> * <p> * * 相同帐号登录控制 * * <p> * * 区分 责任人 日期 说明<br/> * 创建 周柏成 2016年6月2日 <br/> * * @author zhou-baicheng * @email so@sojson.com * @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 控制并发登录人数限制及登录踢出的实现代码,希望对大家有所帮助