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

JSON 2016-08-26 01:30:13 65262

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

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

本文主题:

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

关于作者
一个低调而闷骚的男人。
相关文章
N多系统单点登录实现、解决方案。四种解决方案
QQ互联登录现qzuser用户的原因及解决办法
Shiro 通过配置Cookie 解决多个二级域名的单点登录问题。
Shiro 权限控制设计、权限控制Demo、基于RBAC3
Shiro教程(五)Shiro + Redis实现
IE、Firefox对同一域名访问并发限制,及解决优化方案
Shiro教程(七)Shiro Session共享配置以及实现
Shiro教程(四)Shiro + Redis配置
怎么实现一个视频录应用,教您实现视频录
Shiro教程(八)Shiro Freemarker标签的使用。
最新文章
PHP变量剖析 11
SQL全外连接剖析 119
SQL自然连接剖析 147
springboot启动原理 245
SQL右连接【RIGHT JOIN】详解及图解 450
SQL左链接【LEFT JOIN】详解及图解 357
SQL非等值连接剖析 262
SQL等链接剖析 291
SQL内连接详解及图解 385
python之numpy常用的100种数值相关方法及代码示例 231
最热文章
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 679241
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 674561
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 599031
免费天气API,天气JSON API,不限次数获取十五天的天气预报 565278
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 551715
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 509186
Elasticsearch教程(四) elasticsearch head 插件安装和使用 479645
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 262820
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 244092
Elasticsearch教程(一),全程直播(小白级别) 225127
支付扫码

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

查看我的收藏

正在加载... ...