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

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

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

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

本文主题:

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

关于作者
一个低调而闷骚的男人。
相关文章
QQ互联登录现qzuser用户的原因及解决办法
N多系统单点登录实现、解决方案。四种解决方案
Shiro 通过配置Cookie 解决多个二级域名的单点登录问题。
Shiro 权限控制设计、权限控制Demo、基于RBAC3
Shiro教程(五)Shiro + Redis实现
IE、Firefox对同一域名访问并发限制,及解决优化方案
Shiro教程(七)Shiro Session共享配置以及实现
Shiro教程(四)Shiro + Redis配置
oracle数据库主从复怎么实现
oracle数据库主从复怎么实现
最新文章
Linux I/O重定向 201
Ruby 循环 - while、for、until、break、redo 和 retry 280
Node.js:全局对象 185
如何使用终端检查Linux上的内存使用情况 226
JavaScript对象详细剖析 121
Python print() 函数 212
PHP if/else/elseif 语句 193
HTML5 Canvas弧线教程 184
Java赋值运算符 206
XML内部实体和外部实体 293
最热文章
最新MyEclipse8.5注册码,有效期到2020年 (已经更新) 684357
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 674893
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 605521
免费天气API,天气JSON API,不限次数获取十五天的天气预报 593298
Jackson 时间格式化,时间注解 @JsonFormat 用法、时差问题说明 554169
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 509712
Elasticsearch教程(四) elasticsearch head 插件安装和使用 480416
Jackson 美化输出JSON,优雅的输出JSON数据,格式化输出JSON数据... ... 266322
Java 信任所有SSL证书,HTTPS请求抛错,忽略证书请求完美解决 244409
Elasticsearch教程(一),全程直播(小白级别) 226076
支付扫码

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

查看我的收藏

正在加载... ...