Shiro教程(十一)Shiro 控制并发登录人数限制实现,登录踢出实现

soゝso 2016-08-26 01:30:13 30184

shiro demo下载

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




我们经常会有用到,当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>
 * 博客地址:https://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了,这样效果就出来了。(效果图)


版权所属:SO JSON在线解析

原文地址:https://www.sojson.com/blog/158.html

转载时必须以链接形式注明原始出处及本声明。

本文主题:

如果本文对你有帮助,那么请你赞助我,让我更有激情的写下去,帮助更多的人。

相关文章
JSON压缩算法 JSON.hpack ,JSON压缩实现
Shiro + EHCache 缓存的使用。
单个项目多个二级域名简单实现思路
Java 实现多个二级域名访问同一个Tomcat(系统)。
N多系统单点登录,实现、解决方案。四种解决方案
Elasticsearch教程(一),全程直播(小白级别)
Elasticsearch教程(二),IK分词器安装
Elasticsearch教程(三),IK分词器安装 (极速版)
Elasticsearch教程(四) elasticsearch head 插件安装和使用
Elasticsearch教程(五) elasticsearch Mapping的创建
最新文章
ICP怎么操作取消备案,企业、个人怎么自己申请取消备案? 19
Javascript 加密/压缩后运行不了,JavaScript报错排查讲解 44
JavaScript 不可逆加密问题排查之方案一(闭包优化) 45
Java爬虫,Jsoup 爬取大众点评、美团商家信息电话号码解决方案 244
分解质因数 JavaScript 计算方式,及分解质因数讲解 8
湖南腾众4s店服务怎么样?进来看看你就知道了 29
SEO 之 SpringMVC redirect 301,301和302区别详细讲解 82
网站如何设置404页面、500等错误页面,有什么漏洞?怎么预防? 103
Java 文字转图片输出,Java 输出透明背景图片,Java文字转图片防爬虫 155
HttpClient获取访问域名的真实ip,HttpClient请求获取目标IP地址 299
最热文章
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 175216
Elasticsearch教程(四) elasticsearch head 插件安装和使用 138313
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 118026
Elasticsearch教程(六) elasticsearch Client创建 81990
Elasticsearch教程(一),全程直播(小白级别) 77107
Elasticsearch教程(二),IK分词器安装 77063
Elasticsearch教程(八) elasticsearch delete 删除数据(Java) 76749
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 75285
Elasticsearch教程(五) elasticsearch Mapping的创建 69399
Elasticsearch教程(三),IK分词器安装 (极速版) 58152

骚码加入我们 / 千人QQ群:259217951

入群需要5元,如果没有QQ钱包,可以先Alipay、微信,赞助然后加群主拉进。

二维码生成 来自 >> 二维码生成器

支付扫码

所有赞助/开支都讲公开明细,用于网站维护:赞助名单查看

正在加载... ...