在Shiro中我们可以通过org.apache.shiro.session.mgt.eis.SessionDAO对象的getActiveSessions()方法方便的获取到当前所有有效的Session对象。通过这些Session对象,我们可以实现一些比较有趣的功能,比如查看当前系统的在线人数,查看这些在线用户的一些基本信息,强制让某个用户下线等。
我们在现有的Spring Boot Shiro项目基础上进行一些改造。
Redis Session管理
Redis作为缓存实现,那么SessionDAO为RedisSessionDAO:
/**
* session会话
*
* @return
*/
@Bean
public RedisSessionDAO sessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
Ehcache Session管理
Ehcache作为缓存实现,那么SessionDAO为RedisSessionDAO:
/**
* session会话
*
* @return
*/
@Bean
public SessionDAO sessionDAO() {
MemorySessionDAO sessionDAO = new MemorySessionDAO();
return sessionDAO;
}
SessionManager 管理器
SessionDao通过org.apache.shiro.session.mgt.SessionManager进行管理,在ShiroConfig中配置SessionManager:
/**
* session会话管理器
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
Collection listeners = new ArrayList<>();
listeners.add(new ShiroSessionListener());
sessionManager.setSessionListeners(listeners);
sessionManager.setSessionDAO(sessionDAO());
return sessionManager;
}
ShiroSessionListener 监听器
public class ShiroSessionListener implements SessionListener{
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.incrementAndGet();
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
}
}
ShiroSessionListener维护着一个原子类型的Integer对象,用于统计在线Session的数量。
定义完SessionManager后,还需将其注入到SecurityManager中:
@Bean
public SecurityManager securityManager(){
DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
securityManager.setRealm(shiroRealm());
...
securityManager.setSessionManager(sessionManager());
return securityManager;
}
UserOnline
配置完ShiroConfig后,我们可以创建一个UserOnline实体类,用于描述每个在线用户的基本信息:
public class UserOnline implements Serializable{
private static final long serialVersionUID = 3828664348416633856L;
// session id
private String id;
// 用户id
private String userId;
// 用户名称
private String username;
// 用户主机地址
private String host;
// 用户登录时系统IP
private String systemHost;
// 状态
private String status;
// session创建时间
private Date startTimestamp;
// session最后访问时间
private Date lastAccessTime;
// 超时时间
private Long timeout;
// get set略
}
Service
创建一个Service接口,包含查看所有在线用户和根据SessionId踢出用户抽象方法:
public interface SessionService {
List list();
boolean forceLogout(String sessionId);
}
其具体实现:
@Service
public class SessionServiceImpl implements SessionService {
@Autowired
private SessionDAO sessionDAO;
@Override
public List list() {
List list = new ArrayList<>();
Collection sessions = sessionDAO.getActiveSessions();
for (Session session : sessions) {
UserOnline userOnline = new UserOnline();
TbUser user;
SimplePrincipalCollection principalCollection;
if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
continue;
} else {
principalCollection = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
user = (TbUser) principalCollection.getPrimaryPrincipal();
userOnline.setUsername(user.getUserName());
userOnline.setUserId(user.getId().toString());
}
userOnline.setId((String) session.getId());
userOnline.setHost(session.getHost());
userOnline.setStartTimestamp(session.getStartTimestamp());
userOnline.setLastAccessTime(session.getLastAccessTime());
Long timeout = session.getTimeout();
if (timeout == 0L) {
userOnline.setStatus("离线");
} else {
userOnline.setStatus("在线");
}
userOnline.setTimeout(timeout);
list.add(userOnline);
}
return list;
}
@Override
public boolean forceLogout(String sessionId) {
Session session = sessionDAO.readSession(sessionId);
session.setTimeout(0);
return true;
}
}
通过SessionDao的getActiveSessions()方法,我们可以获取所有有效的Session,通过该Session,我们还可以获取到当前用户的Principal信息。
值得说明的是,当某个用户被踢出后(Session Time置为0),该Session并不会立刻从ActiveSessions中剔除,所以我们可以通过其timeout信息来判断该用户在线与否。
如果使用的Redis作为缓存实现,那么,forceLogout()方法需要稍作修改:
@Override
public boolean forceLogout(String sessionId) {
Session session = sessionDAO.readSession(sessionId);
sessionDAO.delete(session);
return true;
}
Controller
定义一个SessionContoller,用于处理Session的相关操作:
@Controller
@RequestMapping("/online")
public class SessionController {
@Autowired
SessionService sessionService;
@RequestMapping("/index")
public String online() {
return "online";
}
@ResponseBody
@RequestMapping("/list")
public List list() {
return sessionService.list();
}
@ResponseBody
@RequestMapping("/forceLogout")
public AjaxResult forceLogout(String id) {
try {
sessionService.forceLogout(id);
return AjaxResult.success();
} catch (Exception e) {
e.printStackTrace();
return AjaxResult.error("踢出用户失败");
}
}
}
页面
我们编写一个online.html页面,用于展示所有在线用户的信息:
在线用户管理table {
margin: 20px 40px 20px 0px;
width: 100%;
border-collapse: collapse;
border-spacing: 0;
table-layout: automatic;
word-wrap: break-all
}
table > tbody > tr:nth-of-type(odd) {
background-color: #F7F7F7
}
th, td {
padding: 8px;
text-align: left;
vertical-align: middle;
font-weight: normal;
font-size: 12px;
border-bottom: 1px solid #fff;
}
th {
padding-bottom: 10px;
color: #fff;
font-weight: 700;
background: rgba(66, 185, 131, .9)
}
td {
border-bottom-width: 1px
}
在线用户数:
序号 | 用户名称 | 登录时间 | 最后访问时间 | 主机 | 状态 | 操作 |
---|
返回
var ctx = [[@{/}]];
$.get(ctx + "online/list", {}, function (r) {
console.log(r);
var length = r.length;
$("#onlineCount").text(length);
var html = "";
for (var i = 0; i < length; i++) {
html += "
"+ "
" + (i + 1) + ""+ "
" + r[i].username + ""+ "
" + r[i].startTimestamp + ""+ "
" + r[i].lastAccessTime + ""+ "
" + r[i].host + ""+ "
" + r[i].status + ""+ "
下线"+ "
";}
$("table").append(html);
}, "json");
function offline(id, status) {
if (status == "离线") {
alert("该用户已是离线状态!!");
return;
}
$.get(ctx + "online/forceLogout", {"id": id}, function (r) {
if (r.code == 0) {
alert('该用户已强制下线!');
location.href = ctx + 'online/index';
} else {
alert(r.msg);
}
}, "json");
}
在index.html中加入该页面的入口:
xmlns:th="http://www.thymeleaf.org"
xmlns:shiro="http://www.pollix.at/thymeleaf/shiro">
首页div {
border: 1px dashed #ddd;
padding: 10px;
margin: 10px 10px 10px 0px;
}
你好![[${user.userName}]]
你的角色为超级管理员
你的角色为测试账户
获取用户信息
新增用户
删除用户
在线用户管理
注销
测试
在主界面点击“在线用户管理”:
下线按钮,成功将其强制踢出: