Shiro 控制并發(fā)登錄人數(shù)限制及登錄踢出的實(shí)現(xiàn)代碼
我們經(jīng)常會有用到,當(dāng)A 用戶在北京登錄 ,然后A用戶在天津再登錄 ,要踢出北京登錄的狀態(tài)。如果用戶在北京重新登錄,那么又要踢出天津的用戶,這樣反復(fù)。
這樣保證了一個帳號只能同時一個人使用。那么下面來講解一下 Shiro 怎么實(shí)現(xiàn)這個功能,現(xiàn)在是用到了緩存 Redis 。我們也可以用其他緩存。如果是單個點(diǎn),直接用一個靜態(tài)的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> <!-- 靜態(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)注入。如果不了解請看這篇: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>
*
* 相同帳號登錄控制
*
* <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.如果是普通請求,直接跳轉(zhuǎn)到登錄頁
*/
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(), "當(dāng)前用戶已經(jīng)在其他地方登錄,并且是Ajax請求!");
resultMap.put("user_status", "300");
resultMap.put("message", "您已經(jīng)在其他地方登錄,請重新登錄!");
out(response, resultMap);
}
return Boolean.FALSE;
}
//從緩存獲取用戶-Session信息 <UserId,SessionId>
LinkedHashMap<Long, Serializable> infoMap = cache.get(ONLINE_USER, LinkedHashMap.class);
//如果不存在,創(chuàng)建一個新的
infoMap = null == infoMap ? new LinkedHashMap<Long, Serializable>() : infoMap;
//獲取tokenId
Long userId = TokenManager.getUserId();
//如果已經(jīng)包含當(dāng)前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,并且標(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);
//存儲到緩存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('您已經(jīng)被踢出,請重新登錄!');
}
}catch(e){
}
Ok了,這樣效果就出來了。(效果圖)

總結(jié)
以上所述是小編給大家介紹的Shiro 控制并發(fā)登錄人數(shù)限制及登錄踢出的實(shí)現(xiàn)代碼,希望對大家有所幫助,如果大家有任何疑問請給我留言,小編會及時回復(fù)大家的。在此也非常感謝大家對腳本之家網(wǎng)站的支持!
- spring boot 集成 shiro 自定義密碼驗證 自定義freemarker標(biāo)簽根據(jù)權(quán)限渲染不同頁面(推薦
- Java中SSM+Shiro系統(tǒng)登錄驗證碼的實(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)一個賬戶同一時刻只有一個人登錄(shiro 單點(diǎn)登錄)
- shiro多驗證登錄代碼實(shí)例及問題解決
相關(guān)文章
Spring Security基于HttpRequest配置權(quán)限示例詳解
這篇文章主要介紹了Spring Security基于HttpRequest配置權(quán)限示例詳解,我們在配置中配置的url被封裝成RequestMatcher,而hasRole被封裝成AuthorityAuthorizationManager,本文結(jié)合示例代碼講解的非常詳細(xì),需要的朋友可以參考下2024-03-03
java中unicode和中文相互轉(zhuǎn)換的簡單實(shí)現(xiàn)
下面小編就為大家?guī)硪黄猨ava中unicode和中文相互轉(zhuǎn)換的簡單實(shí)現(xiàn)。小編覺得挺不錯的,現(xiàn)在就分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2016-08-08
解決MyEclipse中Maven設(shè)置jdk版本jdk1.8報錯問題
今天安裝了jdk1.8、tomcat8、和maven3.5.2,弄好后在myeclipse新建了一個maven項目,項目默認(rèn)是jdk1.5,改成jdk1.8后項目報錯2018-10-10

