有时候需要显示当前在线人数、当前在线用户,有时候可能需要强制某个用户下线等;此时就需要获取相应的在线用户并进行一些操作。
本章基于《第十六章 综合实例》代码构建。
## 会话控制器
``` 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、登录成功后,点击菜单的 “会话管理”,可以看到当前在线会话列表:
![](https://box.kancloud.cn/65995fd7c71dfee2904d2641c4529872_865x166.png)
3、点击 “强制退出” 按钮,会话相应的用户再点击界面的话会看到如下界面,表示已经被强制退出了:
![](https://box.kancloud.cn/b0b082f6948baf2ea9dcf59aaf3104a1_394x136.png)
另外可参考我的 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章 在线会话管理