Shiro 控制并发登录人数限制实现,登录踢出实现

shiro demo下载

Shiro + SSM(框架) + Freemarker(jsp)讲解的权限控制Demo,还不赶快去下载?

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

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

XML配置。


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

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

加入到 shiro  Filter 拦截序列


  
  
  1. <bean id="shiroFilter" class="org.apache.shiro.spring.web.ShiroFilterFactoryBean">
  2. <property name="securityManager" ref="securityManager" />
  3. <property name="loginUrl" value="/u/login.shtml" />
  4. <!-- TODO 待提取 -->
  5. <property name="successUrl" value="/" />
  6. <property name="unauthorizedUrl" value="/?login" />
  7. <property name="filterChainDefinitions" value="#{shiroManager.loadFilterChainDefinitions()}"/>
  8. <property name="filters">
  9. <util:map>
  10. <entry key="login" value-ref="login"></entry>
  11. <entry key="role" value-ref="role"></entry>
  12. <entry key="simple" value-ref="simple"></entry>
  13. <entry key="permission" value-ref="permission"></entry>
  14. <entry key="kickout" value-ref="kickoutSessionFilter"></entry>
  15. </util:map>
  16. </property>
  17. </bean>

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


  
  
  1. package com.sojson.core.shiro.filter;
  2. import java.io.IOException;
  3. import java.io.PrintWriter;
  4. import java.io.Serializable;
  5. import java.util.HashMap;
  6. import java.util.LinkedHashMap;
  7. import java.util.Map;
  8. import javax.servlet.ServletRequest;
  9. import javax.servlet.ServletResponse;
  10. import javax.servlet.http.HttpServletRequest;
  11. import net.sf.json.JSONObject;
  12. import org.apache.shiro.session.Session;
  13. import org.apache.shiro.subject.Subject;
  14. import org.apache.shiro.web.filter.AccessControlFilter;
  15. import org.apache.shiro.web.util.WebUtils;
  16. import com.sojson.common.utils.LoggerUtils;
  17. import com.sojson.core.shiro.cache.VCache;
  18. import com.sojson.core.shiro.session.ShiroSessionRepository;
  19. import com.sojson.core.shiro.token.manager.TokenManager;
  20. /**
  21. *
  22. * 开发公司:SOJSON在线工具 <p>
  23. * 版权所有:© www.sojson.com<p>
  24. * 博客地址:http://www.sojson.com/blog/ <p>
  25. * <p>
  26. *
  27. * 相同帐号登录控制
  28. *
  29. * <p>
  30. *
  31. * 区分 责任人 日期    说明<br/>
  32. * 创建 周柏成 2016年6月2日  <br/>
  33. *
  34. * @author zhou-baicheng
  35. * @email so@sojson.com
  36. * @version 1.0,2016年6月2日 <br/>
  37. *
  38. */
  39. @SuppressWarnings({"unchecked","static-access"})
  40. public class KickoutSessionFilter extends AccessControlFilter {
  41. //静态注入
  42. static String kickoutUrl;
  43. //在线用户
  44. final static String ONLINE_USER = KickoutSessionFilter.class.getCanonicalName()+ "_online_user";
  45. //踢出状态,true标示踢出
  46. final static String KICKOUT_STATUS = KickoutSessionFilter.class.getCanonicalName()+ "_kickout_status";
  47. static VCache cache;
  48. //session获取
  49. static ShiroSessionRepository shiroSessionRepository;
  50. @Override
  51. protected boolean isAccessAllowed(ServletRequest request,
  52. ServletResponse response, Object mappedValue) throws Exception {
  53. HttpServletRequest httpRequest = ((HttpServletRequest)request);
  54. String url = httpRequest.getRequestURI();
  55. Subject subject = getSubject(request, response);
  56. //如果是相关目录 or 如果没有登录 就直接return true
  57. if(url.startsWith("/open/") || (!subject.isAuthenticated() && !subject.isRemembered())){
  58. return Boolean.TRUE;
  59. }
  60. Session session = subject.getSession();
  61. Serializable sessionId = session.getId();
  62. /**
  63. * 判断是否已经踢出
  64. * 1.如果是Ajax 访问,那么给予json返回值提示。
  65. * 2.如果是普通请求,直接跳转到登录页
  66. */
  67. Boolean marker = (Boolean)session.getAttribute(KICKOUT_STATUS);
  68. if (null != marker && marker ) {
  69. Map<String, String> resultMap = new HashMap<String, String>();
  70. //判断是不是Ajax请求
  71. if (ShiroFilterUtils.isAjax(request) ) {
  72. LoggerUtils.debug(getClass(), "当前用户已经在其他地方登录,并且是Ajax请求!");
  73. resultMap.put("user_status", "300");
  74. resultMap.put("message", "您已经在其他地方登录,请重新登录!");
  75. out(response, resultMap);
  76. }
  77. return Boolean.FALSE;
  78. }
  79. //从缓存获取用户-Session信息 <UserId,SessionId>
  80. LinkedHashMap<Long, Serializable> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class);
  81. //如果不存在,创建一个新的
  82. infoMap = null == infoMap ? new LinkedHashMap<Long, Serializable>() : infoMap;
  83. //获取tokenId
  84. Long userId = TokenManager.getUserId();
  85. //如果已经包含当前Session,并且是同一个用户,跳过。
  86. if(infoMap.containsKey(userId) && infoMap.containsValue(sessionId)){
  87. //更新存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
  88. cache.setex(ONLINE_USER, infoMap, 3600);
  89. return Boolean.TRUE;
  90. }
  91. //如果用户相同,Session不相同,那么就要处理了
  92. /**
  93. * 如果用户Id相同,Session不相同
  94. * 1.获取到原来的session,并且标记为踢出。
  95. * 2.继续走
  96. */
  97. if(infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){
  98. Serializable oldSessionId = infoMap.get(userId);
  99. Session oldSession = shiroSessionRepository.getSession(oldSessionId);
  100. if(null != oldSession){
  101. //标记session已经踢出
  102. oldSession.setAttribute(KICKOUT_STATUS, Boolean.TRUE);
  103. shiroSessionRepository.saveSession(oldSession);//更新session
  104. LoggerUtils.fmtDebug(getClass(), "kickout old session success,oldId[%s]",oldSessionId);
  105. }else{
  106. shiroSessionRepository.deleteSession(oldSessionId);
  107. infoMap.remove(userId);
  108. //存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
  109. cache.setex(ONLINE_USER, infoMap, 3600);
  110. }
  111. return Boolean.TRUE;
  112. }
  113. if(!infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){
  114. infoMap.put(userId, sessionId);
  115. //存储到缓存1个小时(这个时间最好和session的有效期一致或者大于session的有效期)
  116. cache.setex(ONLINE_USER, infoMap, 3600);
  117. }
  118. return Boolean.TRUE;
  119. }
  120. @Override
  121. protected boolean onAccessDenied(ServletRequest request,
  122. ServletResponse response) throws Exception {
  123. //先退出
  124. Subject subject = getSubject(request, response);
  125. subject.logout();
  126. WebUtils.getSavedRequest(request);
  127. //再重定向
  128. WebUtils.issueRedirect(request, response,kickoutUrl);
  129. return false;
  130. }
  131. private void out(ServletResponse hresponse, Map<String, String> resultMap)
  132. throws IOException {
  133. try {
  134. hresponse.setCharacterEncoding("UTF-8");
  135. PrintWriter out = hresponse.getWriter();
  136. out.println(JSONObject.fromObject(resultMap).toString());
  137. out.flush();
  138. out.close();
  139. } catch (Exception e) {
  140. LoggerUtils.error(getClass(), "KickoutSessionFilter.class 输出JSON异常,可以忽略。");
  141. }
  142. }
  143. public static void setShiroSessionRepository(
  144. ShiroSessionRepository shiroSessionRepository) {
  145. KickoutSessionFilter.shiroSessionRepository = shiroSessionRepository;
  146. }
  147. public static String getKickoutUrl() {
  148. return kickoutUrl;
  149. }
  150. public static void setKickoutUrl(String kickoutUrl) {
  151. KickoutSessionFilter.kickoutUrl = kickoutUrl;
  152. }
  153. }

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


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

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


  • 1
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值