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

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

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>
 * 博客地址: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了,这样效果就出来了。(效果图)


版权所属:SO JSON在线解析

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

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

本文主题:

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

关于作者
目前就职于国内某电商平台公司打杂。。
相关文章
Shiro教程十一Shiro 控制并发登录人数限制实现登录踢出实现
N多系统单点登录实现、解决方案。四种解决方案
Shiro 通过配置Cookie 解决多个二级域名的单点登录问题。
Shiro 权限控制设计、权限控制Demo、基于RBAC3
Shiro教程(五)Shiro + Redis实现
IE、Firefox对同一域名访问并发限制,及解决优化方案
Shiro教程(七)Shiro Session共享配置以及实现
Elasticsearch教程,Elasticsearch安全篇,通过Nginx http basic 限制访问
Shiro教程(八)Shiro Freemarker标签的使用。
Shiro教程(六)Shiro整体的配置文件
最新文章
天气API,全国天气 JSON API接口,可以获取十五天的天气预报 10
Springboot + Freemarker 集成配置 45
DNS查询, DNS测速检查接口,DNS测速接口 17
Mysql 并发插入、存在不插入,存在更新操作 36
SOJSON 阿里云活动达到67人,最终获得5年的1核2GB的ECS服务器使用权益 25
Java 完美解析.plist & 生成plist ,Android 解析.plist 28
Java SHA1的几种实现方式,Java SHA1 签名 50
Mac下Charles-proxy 抓包工具,iPhone 抓包演示 16
Mac Fidder 抓包替代方案 Charles-proxy 抓包工具下载 + 安装 + 破解 charles.jar 下载 178
阿里ECS云服务,1核2G1M宽带 40G SSD硬盘, 9 9一年,189二年,279三年 23
最热文章
Elasticsearch教程(四) elasticsearch head 插件安装和使用 117135
Elasticsearch教程(六) elasticsearch Client创建 75853
Elasticsearch教程(八) elasticsearch delete 删除数据(Java) 72356
Elasticsearch教程(二),IK分词器安装 70214
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 64083
Elasticsearch教程(一),全程直播(小白级别) 57793
Elasticsearch教程(五) elasticsearch Mapping的创建 55748
Elasticsearch教程(三),IK分词器安装 (极速版) 55125
Elasticsearch教程(七) elasticsearch Insert 插入数据(Java) 50396
Elasticsearch权威指南-中文.pdf,Elasticsearch 中文文档下载 43317

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

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

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

支付扫码

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

正在加载... ...