Shiro 控制并发登录人数限制及登录踢出的实现代码

网友投稿 310 2023-04-02


Shiro 控制并发登录人数限制及登录踢出的实现代码

我们经常会有用到,当A 用户在北京登录 ,然后A用户在天津再登录 ,要踢出北京登录的状态。如果用户在北京重新登录,那么又要踢出天津的用户,这样反复。

这样保证了一个帐号只能同时一个人使用。那么下面来讲解一下 Shiro  怎么实现这个功能,现在是用到了缓存 Redis  。我们也可以用其他缓存。如果是单个点,直接用一个静态的Map 或者 Ehcache  即可。

XML配置。

这里用到了静态注入。如果不了解请看这篇:Spring 静态注入讲解(MethodInvokingFactoryBean)

加入到 shiro  的Filter 拦截序列

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在线工具

* 版权所有:© sojson.com

* 博客地址:http://sojson.com/blog/

*

*

* 相同帐号登录控制

*

*

*

* 区分 责任人 日期    说明

* 创建 周柏成 2016年6月2日  

*

* @author zhou-baicheng

* @email so@sojson.com

* @version 1.0,2016年6月2日

*

*/

@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 resultMap = new HashMap();

//判断是不是Ajax请求

if (ShiroFilterUtils.isAjax(request) ) {

LoggerUtils.debug(getClass(), "当前用户已经在其他地方登录,并且是Ajax请求!");

resultMap.put("user_status", "300");

resultMap.put("message", "您已经在其他地方登录,请重新登录!");

out(response, resultMap);

}

return Boolean.FALSE;

}

//从缓存获取用户-Session信息

LinkedHashMap infoMap = cache.get(ONLINE_USER, LinkedHashMap.class);

//如果不存在,创建一个新的

infoMap = null == infoMap ? new LinkedHashMap() : 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 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了,这样效果就出来了。(效果图)

总结

以上所述是给大家介绍的Shiro 控制并发登录人数限制及登录踢出的实现代码,希望对大家有所帮助,如果大家有任何疑问请给我留言,会及时回复大家的。在此也非常感谢大家对我们网站的支持!


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序 input表单与redio及下拉列表的使用实例
下一篇:Freemarker如何生成树形导航菜单(递归)
相关文章

 发表评论

暂时没有评论,来抢沙发吧~