在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}]]
你的角色為超級管理員
你的角色為測試賬戶
獲取用戶信息
新增用戶
刪除用戶
在線用戶管理
注銷
測試
在主界面點擊“在線用戶管理”:
下線按鈕,成功將其強制踢出: