有時候需要顯示當前在線人數、當前在線用戶,有時候可能需要強制某個用戶下線等;此時就需要獲取相應的在線用戶并進行一些操作。
本章基于《第十六章 綜合實例》代碼構建。
## 會話控制器
``` java
@RequiresPermissions("session:*")
@Controller
@RequestMapping("/sessions")
public class SessionController {
@Autowired
private SessionDAO sessionDAO;
@RequestMapping()
public String list(Model model) {
Collection<Session> sessions = sessionDAO.getActiveSessions();
model.addAttribute("sessions", sessions);
model.addAttribute("sesessionCount", sessions.size());
return "sessions/list";
}
@RequestMapping("/{sessionId}/forceLogout")
public String forceLogout(@PathVariable("sessionId") String sessionId,
RedirectAttributes redirectAttributes) {
try {
Session session = sessionDAO.readSession(sessionId);
if(session != null) {
session.setAttribute(
Constants.SESSION_FORCE_LOGOUT_KEY, Boolean.TRUE);
}
} catch (Exception e) {/*ignore*/}
redirectAttributes.addFlashAttribute("msg", "強制退出成功!");
return "redirect:/sessions";
}
}
```
1. list 方法:提供了展示所有在線會話列表,通過 sessionDAO.getActiveSessions() 獲取所有在線的會話。
2. forceLogout 方法:強制退出某一個會話,此處只在指定會話中設置 Constants.SESSION\_FORCE\_LOGOUT\_KEY 屬性,之后通過 ForceLogoutFilter 判斷并進行強制退出。
此處展示會話列表的缺點是:sessionDAO.getActiveSessions() 提供了獲取所有活躍會話集合,如果做一般企業級應用問題不大,因為在線用戶不多;但是如果應用的在線用戶非常多,此種方法就不適合了,解決方案就是分頁獲取:
`Page<Session> getActiveSessions(int pageNumber, int pageSize);`
Page 對象除了包含 pageNumber、pageSize 屬性之外,還包含 totalSessions(總會話數)、Collection (當前頁的會話)。
分頁獲取時,如果是 MySQL 這種關系數據庫存儲會話比較好辦,如果使用 Redis 這種數據庫可以考慮這樣存儲:
```
session.id=會話序列化數據
session.ids=會話id Set列表(接著可以使用LLEN獲取長度,LRANGE分頁獲取)
```
會話創建時(如 sessionId=123),那么 redis 命令如下所示:
```
SET session.123 "Session序列化數據"
LPUSH session.ids 123
```
會話刪除時(如 sessionId=123),那么 redis 命令如下所示:
```
DEL session.123
LREM session.ids 123
```
獲取總活躍會話:
`LLEN session.ids`
分頁獲取活躍會話:
```
LRANGE key 0 10 #獲取到會話ID
MGET session.1 session.2…… #根據第一條命令獲取的會話ID獲取會話數據
```
## ForceLogoutFilter
``` java
public class ForceLogoutFilter extends AccessControlFilter {
protected boolean isAccessAllowed(ServletRequest request, ServletResponse response, Object mappedValue) throws Exception {
Session session = getSubject(request, response).getSession(false);
if(session == null) {
return true;
}
return session.getAttribute(Constants.SESSION_FORCE_LOGOUT_KEY) == null;
}
protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
try {
getSubject(request, response).logout();//強制退出
} catch (Exception e) {/*ignore exception*/}
String loginUrl = getLoginUrl() + (getLoginUrl().contains("?") ? "&" : "?") + "forceLogout=1";
WebUtils.issueRedirect(request, response, loginUrl);
return false;
}
}
```
強制退出攔截器,如果用戶會話中存在 Constants.SESSION\_FORCE\_LOGOUT\_KEY 屬性,表示被管理員強制退出了;然后調用 Subject.logout() 退出,且重定向到登錄頁面(自動拼上 fourceLogout 請求參數)。
## 登錄控制器
在 LoginController 類的 showLoginForm 方法中最后添加如下代碼:
``` java
if(req.getParameter("forceLogout") != null) {
model.addAttribute("error", "您已經被管理員強制退出,請重新登錄");
}
```
即如果有請求參數 forceLogout 表示是管理員強制退出的,在界面上顯示相應的信息。
## Shiro 配置 spring-config-shiro.xml
和之前的唯一區別是在 shiroFilter 中的 filterChainDefinitions 攔截器鏈定義中添加了 forceLogout 攔截器:
`/** = forceLogout,user,sysUser`
## 測試
1、首先輸入 `http://localhost:8080/chapter24/` 跳轉到登錄頁面輸入 admin/123456 登錄;
2、登錄成功后,點擊菜單的 “會話管理”,可以看到當前在線會話列表:

3、點擊 “強制退出” 按鈕,會話相應的用戶再點擊界面的話會看到如下界面,表示已經被強制退出了:

另外可參考我的 ES 中的在線會話管理功能:UserOnlineController.java,其使用數據庫存儲會話,并分頁獲取在線會話。
- 第1章 Shiro簡介
- 1.1 簡介
- 第2章 身份驗證
- 第3章 授權
- 第4章 INI配置
- 第5章 編碼 / 加密
- 第6章 Realm及相關對象
- 第7章 與Web集成
- 第8章 攔截器機制
- 第9章 JSP標簽
- 第10章 會話管理
- 第11章 緩存機制
- 第12章 與Spring集成
- 第13章 RememberMe
- 第14章 SSL
- 第15章 單點登錄
- 第16章 綜合實例
- 第17章 OAuth2集成
- 第18章 并發登錄人數控制
- 第19章 動態URL權限控制
- 第20章 無狀態Web應用集成
- 第21章 授予身份及切換身份
- 第22章 集成驗證碼
- 第23章 多項目集中權限管理及分布式會話
- 第24章 在線會話管理