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

我们经常会有用到,当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 控制并发登录人数限制及登录踢出的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对我们网站的支持!

(0)

相关推荐

  • 使用Shiro实现登录成功后跳转到之前的页面

    这个问题是之前在做登录注册模块时遇到的需求,如何用户直接访问登录页面,可以控制直接跳到首页,但是如果是用户没有登录直接访问了购物车等需要经过身份认证的页面,或者是因为session超时,用户需要重新登录,那么这时跳回之前的页面就是提升用户体验的事情.实现此功能比较好的方法是用ajax的方式登陆,这样直接在当前页面弹窗让用户登录既可,二是把用户未登录前的url存在session中,login成功之后先检查session中是否存在这样的一个url.下面通过一段代码给大家分享下: 项目中集成了shir

  • springmvc集成shiro登录权限示例代码

    一般的登录流程会有:用户名不存在,密码错误,验证码错误等.. 在集成shiro后,应用程序的外部访问权限以及访问控制交给了shiro来管理. shiro提供了两个主要功能:认证(Authentication)和授权(Authorization);认证的作用是证明自身可以访问,一般是用户名加密码,授权的作用是谁可以访问哪些资源,通过开发者自己的用户角色权限系统来控制. shiro的会话管理和缓存管理不在本文范围内. 下面通过登录失败的处理流程来介绍springmvc与shiro的集成. 项目依赖:

  • shiro并发人数登录控制的实现代码

    在某些项目中可能会遇到如每个账户同时只能有一个人登录或几个人同时登录,如果同时有多人登录:要么不让后者登录:要么踢出前者登录(强制退出).比如spring security就直接提供了相应的功能:Shiro的话没有提供默认实现,不过可以很容易的在Shiro中加入这个功能. 通过Shiro Filter机制扩展KickoutSessionControlFilter完成. 首先来看看如何配置使用(spring-config-shiro.xml)   kickoutSessionControlFilt

  • shiro实现单点登录(一个用户同一时刻只能在一个地方登录)

    我这里 shiro 并没有集成 springMVC,直接使用 ini 配置文件. shiro.ini [main] # Objects and their properties are defined here, # Such as the securityManager, Realms and anything # else needed to build the SecurityManager authc.loginUrl = /login.jsp authc.successUrl = /w

  • Java中SSM+Shiro系统登录验证码的实现方法

     先给大家展示下效果图: 1.验证码生成类: import java.util.Random; import java.awt.image.BufferedImage; import java.awt.Graphics; import java.awt.Font; import java.awt.Color; /** * 验证码生成器类,可生成数字.大写.小写字母及三者混合类型的验证码. 支持自定义验证码字符数量: 支持自定义验证码图片的大小: 支持自定义需排除的特殊字符: * 支持自定义干扰线

  • SpringBoot+Shiro学习之密码加密和登录失败次数限制示例

    这个项目写到现在,基本的雏形出来了,在此感谢一直关注的童鞋,送你们一句最近刚学习的一句鸡汤:念念不忘,必有回响.再贴一张ui图片: 前篇思考问题解决 前篇我们只是完成了同一账户的登录人数限制shiro拦截器的编写,对于手动踢出用户的功能只是说了采用在session域中添加一个key为kickout的布尔值,由之前编写的KickoutSessionControlFilter拦截器来判断是否将用户踢出,还没有说怎么获取当前在线用户的列表的核心代码,下面贴出来: /** * <p> * 服务实现类

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

    我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态.如果用户在北京重新登录,那么又要踢出天津的用户,这样反复. 这样保证了一个帐号只能同时一个人使用.那么下面来讲解一下 Shiro  怎么实现这个功能,现在是用到了缓存 Redis  .我们也可以用其他缓存.如果是单个点,直接用一个静态的Map<String,Object> 或者 Ehcache  即可. XML配置. <!-- session 校验单个用户是否多次登录 --> <bean

  • SpringBoot 并发登录人数控制的实现方法

    通常系统都会限制同一个账号的登录人数,多人登录要么限制后者登录,要么踢出前者,Spring Security 提供了这样的功能,本文讲解一下在没有使用Security的时候如何手动实现这个功能 demo 技术选型 SpringBoot JWT Filter Redis + Redisson JWT(token)存储在Redis中,类似 JSessionId-Session的关系,用户登录后每次请求在Header中携带jwt 如果你是使用session的话,也完全可以借鉴本文的思路,只是代码上需要

  • SpringBoot 整合 Shiro 密码登录与邮件验证码登录功能(多 Realm 认证)

    导入依赖(pom.xml) <!--整合Shiro安全框架--> <dependency> <groupId>org.apache.shiro</groupId> <artifactId>shiro-spring</artifactId> <version>1.4.0</version> </dependency> <!--集成jwt实现token认证--> <dependency

  • java中使用session监听实现同帐号登录限制、登录人数限制

    本文主要介绍了java中使用session监听实现同帐号登录限制.登录人数限制,具体代码如下: 问题域: 1.同帐号登录:若此帐号已登录,不可再次登录(与QQ模式相反). 2.登录人数限制,超过.已达人数限制则提示:系统繁忙,稍后再试. 解决思路:使用HttpSessionAttributeListener监听器(虽然我同时使用了HttpSessionListener不过感觉不好操作) 知识储备:HttpSessionAttributeListener中有attributeAdd.attribu

  • python实现控制电脑鼠标和键盘,登录QQ的方法示例

    本文实例讲述了python实现控制电脑鼠标和键盘,登录QQ的方法.分享给大家供大家参考,具体如下: import os from pynput.mouse import Button,Controller from pynput.keyboard import Key from pynput.keyboard import Controller as W from time import sleep mouse = Controller() keyboard = W() #点击右下角刷新桌面图标

  • Asp.net mvc 权限过滤和单点登录(禁止重复登录)

    1.权限控制使用controller和 action来实现,权限方式有很多种,最近开发项目使用控制控制器方式实现代码如下 /// <summary> /// 用户权限控制 /// </summary> public class UserAuthorize : AuthorizeAttribute { /// <summary> /// 授权失败时呈现的视图 /// </summary> public string AuthorizationFailView

  • js当前页面登录注册框,固定div,底层阴影的实例代码

    这是一个实例,保存代码为html文件运行试试吧.兼容火狐.ie6.ie7.ie8.Chrome等. <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1- transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"&

  • Android实现背景可滑动登录界面 (不压缩背景弹出键盘)

    废话不多说,先看下实现后的效果: 实现思路 看到上边 gif 图的效果,主要列举一下实现过程过程中遇到的难点. 如何使键盘弹出时候不遮挡底部登录布局: 当键盘弹出的时候如何不压缩背景图片或者背景延伸至「屏幕以外」: 从 「 windowSoftInputMode 」 说起 相信大家都清楚,Google 官方提供给开发者控制软键盘显示隐藏的方法不多,「windowSoftInputMode」算是我们可控制的软键盘弹出模式的方法之一.关于其属性的说明Google 官方和网上的教程说了很多,他的属性值

  • 微信小程序静默登录和维护自定义登录态详解

    目录 1.背景 2.什么是静默登录? 3.如何维护自定义登录态 4.静默登录整体流程 4.1app.onLaunch中发起登录 4.2处理小程序不支持异步阻塞 4.2.1粗糙的方案 4.2.2优雅的方式 4.3 整体流程图 5.写在最后 1.背景 在小程序中,openid是一个用户对于一个小程序/公众号的标识,开发者可以通过这个标识识别出用户,就如同你的身份证一样. 2.什么是静默登录? 在普通的应用中,用户通过表单验证登录建立用户体系,这种常见的登录方式一般是通过登录页面表单进行登录,对用户来

  • Vue-router路由判断页面未登录跳转到登录页面的实例

    如下所示: router.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requireAuth)){ // 判断该路由是否需要登录权限 if (token) { // 判断当前的token是否存在 next(); } else { next({ path: '/login', query: {redirect: to.fullPath} // 将跳转的路由path作为参数,登录成功后跳转到该

随机推荐