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

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

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整体的配置文件
最新文章
HttpClient获取访问域名的真实ip,HttpClient请求获取目标IP地址 106
又拍云24小时日志下载方法,日志合并,.gz 压缩包多个合并方法 24
今天DNS挂了,Mac添加修改DNS,114DNS挂了修改成阿里DNS 52
查询任意一个域名是否是阿里云备案接入 API 169
Javascript不可逆加密,JS加密工具sojson.v5 版本更新说明 868
网站页面添加 Google AdSense 后页面加载慢? 283
2018总结及2019年计划与2019定下一个小目标 788
Javascript 判断域名合法性,JS域名格式检测 574
谈谈斐讯路由器劫持,你用斐讯路由器,你需要知道的事情 8754
根据域名获取IP地址,Java 获取域名的IP地址 1024
最热文章
免费天气API,全国天气 JSON API接口,可以获取五天的天气预报 172012
Elasticsearch教程(四) elasticsearch head 插件安装和使用 136876
我为什么要选择RabbitMQ ,RabbitMQ简介,各种MQ选型对比 107619
Elasticsearch教程(六) elasticsearch Client创建 81679
Elasticsearch教程(二),IK分词器安装 76629
Elasticsearch教程(八) elasticsearch delete 删除数据(Java) 76419
Elasticsearch教程(一),全程直播(小白级别) 75952
Elasticsearch教程(五) elasticsearch Mapping的创建 68540
苹果电脑Mac怎么恢复出厂系统?苹果系统怎么重装系统? 68181
Elasticsearch教程(三),IK分词器安装 (极速版) 57954

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

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

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

支付扫码

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

正在加载... ...