欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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 控制并发登录人数限制及登录踢出的实现代码

总结

以上所述是小编给大家介绍的shiro 控制并发登录人数限制及登录踢出的实现代码,希望对大家有所帮助