Shiro 控制并發(fā)登錄人數(shù)限制及登錄踢出的實(shí)現(xiàn)代碼
我們經(jīng)常會(huì)有用到,當(dāng)A 用戶在北京登錄 ,然后A用戶在天津再登錄 ,要踢出北京登錄的狀態(tài)。如果用戶在北京重新登錄,那么又要踢出天津的用戶,這樣反復(fù)。
這樣保證了一個(gè)帳號(hào)只能同時(shí)一個(gè)人使用。那么下面來講解一下 Shiro 怎么實(shí)現(xiàn)這個(gè)功能,現(xiàn)在是用到了緩存 Redis 。我們也可以用其他緩存。如果是單個(gè)點(diǎn),直接用一個(gè)靜態(tài)的Map<String,Object> 或者 Ehcache 即可。
XML配置。
<!-- session 校驗(yàn)單個(gè)用戶是否多次登錄 --> <bean id="kickoutSessionFilter" class="com.sojson.core.shiro.filter.KickoutSessionFilter"> <property name="kickoutUrl" value="/u/login.shtml?kickout"/> </bean> <!-- 靜態(tài)注入 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>
這里用到了靜態(tài)注入。如果不了解請(qǐng)看這篇:Spring 靜態(tài)注入講解(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代碼,下面看實(shí)現(xiàn)的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; /** * * 開發(fā)公司:SOJSON在線工具 <p> * 版權(quán)所有:© www.sojson.com<p> * 博客地址:http://www.sojson.com/blog/ <p> * <p> * * 相同帳號(hào)登錄控制 * * <p> * * 區(qū)分 責(zé)任人 日期 說明<br/> * 創(chuàng)建 周柏成 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 { //靜態(tài)注入 static String kickoutUrl; //在線用戶 final static String ONLINE_USER = KickoutSessionFilter.class.getCanonicalName()+ "_online_user"; //踢出狀態(tài),true標(biāo)示踢出 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); //如果是相關(guān)目錄 or 如果沒有登錄 就直接return true if(url.startsWith("/open/") || (!subject.isAuthenticated() && !subject.isRemembered())){ return Boolean.TRUE; } Session session = subject.getSession(); Serializable sessionId = session.getId(); /** * 判斷是否已經(jīng)踢出 * 1.如果是Ajax 訪問,那么給予json返回值提示。 * 2.如果是普通請(qǐng)求,直接跳轉(zhuǎn)到登錄頁 */ Boolean marker = (Boolean)session.getAttribute(KICKOUT_STATUS); if (null != marker && marker ) { Map<String, String> resultMap = new HashMap<String, String>(); //判斷是不是Ajax請(qǐng)求 if (ShiroFilterUtils.isAjax(request) ) { LoggerUtils.debug(getClass(), "當(dāng)前用戶已經(jīng)在其他地方登錄,并且是Ajax請(qǐng)求!"); resultMap.put("user_status", "300"); resultMap.put("message", "您已經(jīng)在其他地方登錄,請(qǐng)重新登錄!"); out(response, resultMap); } return Boolean.FALSE; } //從緩存獲取用戶-Session信息 <UserId,SessionId> LinkedHashMap<Long, Serializable> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class); //如果不存在,創(chuàng)建一個(gè)新的 infoMap = null == infoMap ? new LinkedHashMap<Long, Serializable>() : infoMap; //獲取tokenId Long userId = TokenManager.getUserId(); //如果已經(jīng)包含當(dāng)前Session,并且是同一個(gè)用戶,跳過。 if(infoMap.containsKey(userId) && infoMap.containsValue(sessionId)){ //更新存儲(chǔ)到緩存1個(gè)小時(shí)(這個(gè)時(shí)間最好和session的有效期一致或者大于session的有效期) cache.setex(ONLINE_USER, infoMap, 3600); return Boolean.TRUE; } //如果用戶相同,Session不相同,那么就要處理了 /** * 如果用戶Id相同,Session不相同 * 1.獲取到原來的session,并且標(biāo)記為踢出。 * 2.繼續(xù)走 */ if(infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){ Serializable oldSessionId = infoMap.get(userId); Session oldSession = shiroSessionRepository.getSession(oldSessionId); if(null != oldSession){ //標(biāo)記session已經(jīng)踢出 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); //存儲(chǔ)到緩存1個(gè)小時(shí)(這個(gè)時(shí)間最好和session的有效期一致或者大于session的有效期) cache.setex(ONLINE_USER, infoMap, 3600); } return Boolean.TRUE; } if(!infoMap.containsKey(userId) && !infoMap.containsValue(sessionId)){ infoMap.put(userId, sessionId); //存儲(chǔ)到緩存1個(gè)小時(shí)(這個(gè)時(shí)間最好和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('您已經(jīng)被踢出,請(qǐng)重新登錄!'); } }catch(e){ }
Ok了,這樣效果就出來了。(效果圖)
總結(jié)
以上所述是小編給大家介紹的Shiro 控制并發(fā)登錄人數(shù)限制及登錄踢出的實(shí)現(xiàn)代碼,希望對(duì)大家有所幫助,如果大家有任何疑問請(qǐng)給我留言,小編會(huì)及時(shí)回復(fù)大家的。在此也非常感謝大家對(duì)腳本之家網(wǎng)站的支持!
- spring boot 集成 shiro 自定義密碼驗(yàn)證 自定義freemarker標(biāo)簽根據(jù)權(quán)限渲染不同頁面(推薦
- Java中SSM+Shiro系統(tǒng)登錄驗(yàn)證碼的實(shí)現(xiàn)方法
- Java中基于Shiro,JWT實(shí)現(xiàn)微信小程序登錄完整例子及實(shí)現(xiàn)過程
- spring boot整合Shiro實(shí)現(xiàn)單點(diǎn)登錄的示例代碼
- springmvc+shiro+maven 實(shí)現(xiàn)登錄認(rèn)證與權(quán)限授權(quán)管理
- shiro之記住登錄信息
- 關(guān)于Apache shiro實(shí)現(xiàn)一個(gè)賬戶同一時(shí)刻只有一個(gè)人登錄(shiro 單點(diǎn)登錄)
- shiro多驗(yàn)證登錄代碼實(shí)例及問題解決
相關(guān)文章
學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫
這篇文章主要介紹了學(xué)習(xí)Java中的日期和時(shí)間處理及Java日歷小程序的編寫,這個(gè)日歷小程序僅用簡(jiǎn)單的算法實(shí)現(xiàn)沒有用到date類等,但是帶有圖形界面,需要的朋友可以參考下2016-02-02IDEA?2021.3?使用及idea2021.3.1激活使用方法
IDEA?全稱?IntelliJ?IDEA,是java語言開發(fā)的集成環(huán)境,IntelliJ在業(yè)界被公認(rèn)為最好的java開發(fā)工具之一,今天通過本文給大家介紹idea2021.3.1激活及使用教程,感興趣的朋友一起看看吧2022-01-01Java中使用qsort對(duì)類進(jìn)行排序的操作代碼
這篇文章主要介紹了JAVA中如何使用qsort對(duì)類進(jìn)行排序,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-09-09在SpringBoot項(xiàng)目中實(shí)現(xiàn)讀寫分離的流程步驟
SpringBoot作為一種快速開發(fā)框架,廣泛應(yīng)用于Java項(xiàng)目中,在一些大型應(yīng)用中,數(shù)據(jù)庫的讀寫分離是提升性能和擴(kuò)展性的一種重要手段,本文將介紹如何在SpringBoot項(xiàng)目中優(yōu)雅地實(shí)現(xiàn)讀寫分離,并通過適當(dāng)?shù)拇a插入,詳細(xì)展開實(shí)現(xiàn)步驟,同時(shí)進(jìn)行拓展和分析2023-11-11一次因Java應(yīng)用造成CPU過高的排查實(shí)踐過程
一個(gè)應(yīng)用占用CPU很高,除了確實(shí)是計(jì)算密集型應(yīng)用之外,通常原因都是出現(xiàn)了死循環(huán)。下面這篇文章主要給大家介紹了一次因Java應(yīng)用造成CPU過高的排查實(shí)踐過程,文中通過示例代碼介紹的非常詳細(xì),需要的朋友可以參考下2018-11-11如何為Spring Cloud Gateway加上全局過濾器
這篇文章主要介紹了如何為Spring Cloud Gateway加上全局過濾器,幫助大家更好得理解和學(xué)習(xí)使用Gateway,感興趣的朋友可以了解下2021-03-03