SpringBoot-Shiro在線會話管理(2019.12.18)
在Shiro中可以通過org.apache.shiro.session.mgt.eis.SessionDAO
對象的getActiveSessions()
方法方便的獲取到當前所有有效的Session
對象。通過這些Session
對象,可以實現一些功能,比如查看當前系統的在線人數,查看這些在線用戶的一些基本信息,強制讓某個用戶下線與分佈式Session
共享等 。
在原有的項目進行改造, 使用的是Redis緩存
1. 改造ShiroConfig.java
註冊sessionDAO
/**
* 註冊RedisSessionDAO-bean,作用在與能使用sessionDAO
*
* @author: zhihao
* @date: 2019/12/17
*/
@Bean
public sessionDAO redisSessionDAO() {
RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
//添加之前權限管理的redisManager 注意如果之前設置過redisManager過期時間,就需要考慮緩存過期造成的重新登錄問題! 適當的延長
redisSessionDAO.setRedisManager(redisManager());
return redisSessionDAO;
}
2. 配置SessionManager
進行管理SessionDAO
,繼續在ShiroConfig.java
添加管理器
/**
* 註冊SessionManager會話管理器
*
* @return org.apache.shiro.session.mgt.SessionManager
* @author: zhihao
* @date: 2019/12/17
*/
@Bean
public SessionManager sessionManager() {
DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
List<SessionListener> listeners = new ArrayList<>();
//需要添加自己實現的會話監聽器
listeners.add(new ShiroSessionListener());
//添加會話監聽器給sessionManager管理
sessionManager.setSessionListeners(listeners);
//添加SessionDAO給sessionManager管理
sessionManager.setSessionDAO(sessionDAO());
//設置全局(項目)session超時單位 毫秒 -1爲永不超時
//sessionManager.setGlobalSessionTimeout(-1);
return sessionManager;
}
//------------------------定義完SessionManager後,還需將其注入到SecurityManager中:-----------
@Bean
public DefaultWebSecurityManager securityManager() {
// 配置SecurityManager,並注入shiroRealm...
//設置管理器記住我...
//設置緩存管理器...
//設置會話管理器
securityManager.setSessionManager(sessionManager());
return securityManager;
}
3. SessionManager會話管理器需要添加個會話監聽,需要自己實現。
/**
* @Author: zhihao
* @Date: 2019/12/17 18:22
* @Description: shiro會話監聽器
* @Versions 1.0
**/
public class ShiroSessionListener implements SessionListener {
/**
* 維護着個原子類型的Integer對象,用於統計在線Session的數量
*/
private final AtomicInteger sessionCount = new AtomicInteger(0);
@Override
public void onStart(Session session) {
sessionCount.getAndIncrement();
System.out.println("登錄+1=="+sessionCount.get());
}
@Override
public void onStop(Session session) {
sessionCount.decrementAndGet();
System.out.println("登錄-1=="+sessionCount.get());
}
@Override
public void onExpiration(Session session) {
sessionCount.decrementAndGet();
System.out.println("過期-1=="+sessionCount.get());
}
}
4. 創建個UserOnline
用戶在線狀態類
來實現當前系統的在線人和踢人下線
@Data
public class UserOnline implements Serializable {
private static final long serialVersionUID = 3828664348416633852L;
// session id
private String sessionId;
// 用戶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;
}
5. 創建Sessionservice
進行獲取系統會話信息和踢人與鎖號功能
/**
* 獲取會話信息與踢人功能並鎖號
*/
public interface SessionService {
/**
* 獲取所有在線用戶
*
* @return java.util.List<com.zhihao.entity.UserOnline>
* @author: zhihao
* @date: 2019/12/17
*/
List<UserOnline> findUserOnlineAll();
/**
* 強轉註銷用戶並鎖號
*
* @param sessionId 用戶會話id
* @return boolean
* @author: zhihao
* @date: 2019/12/17
*/
boolean forceLogout(String sessionId);
/**
* 根據用戶id進行鎖號與解鎖
*
* @param id 用戶名
* @param status 狀態
* @return boolean
* @author: zhihao
* @date: 2019/12/18
*/
boolean lockNumber(String id,String status);
/**
* 獲取所有鎖號用戶
*
* @return java.util.List<com.zhihao.entity.User>
* @author: zhihao
* @date: 2019/12/18
*/
List<User> findLockNumberAll();
}
實現SessionService:
import org.apache.shiro.session.Session;
import org.apache.shiro.session.mgt.eis.SessionDAO;
import org.apache.shiro.subject.SimplePrincipalCollection;
import org.apache.shiro.subject.support.DefaultSubjectContext;
/**
* @Author: zhihao
* @Date: 2019/12/17 21:50
* @Description:
* @Versions 1.0
**/
@Service("sessionService")
public class SessionServiceImpl implements SessionService {
/**
* 注入會話dao
*/
@Autowired
private SessionDAO sessionDAO;
/**
* 注入用戶dao
*/
@Autowired
private UserMapper userMapper;
@Override
public List<UserOnline> findUserOnlineAll() {
List<UserOnline> list = new ArrayList<>();
Collection<Session> sessions = sessionDAO.getActiveSessions();
for (Session session : sessions) {
UserOnline userOnline = new UserOnline();
User user = new User();
SimplePrincipalCollection principalCollection = new SimplePrincipalCollection();
if (session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY) == null) {
continue;
} else {
principalCollection = (SimplePrincipalCollection) session
.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
user = (User) principalCollection.getPrimaryPrincipal();
userOnline.setUsername(user.getUsername());
userOnline.setUserId(user.getId());
}
userOnline.setSessionId((String) session.getId());
userOnline.setHost(session.getHost());
userOnline.setStartTimestamp(session.getStartTimestamp());
userOnline.setLastAccessTime(session.getLastAccessTime());
Long timeout = session.getTimeout();
if (timeout !=null && timeout.equals(0L)) {
userOnline.setStatus("離線");
} else {
userOnline.setStatus("在線");
}
userOnline.setTimeout(timeout);
list.add(userOnline);
}
return list;
}
private final String LOCK = "0";
@Override
public boolean forceLogout(String sessionId) {
Session session = sessionDAO.readSession(sessionId);
SimplePrincipalCollection principalCollection2 = (SimplePrincipalCollection) session.getAttribute(DefaultSubjectContext.PRINCIPALS_SESSION_KEY);
User user = (User) principalCollection2.getPrimaryPrincipal();
//鎖號 修改用戶表的用戶狀態爲鎖號
if (this.lockNumber(user.getId(), LOCK)){
//強制註銷
sessionDAO.delete(session);
//或者可以設置session 馬上過期過期
//session.setTimeout(0L);
//sessionDAO.update(session); 更新session
return true;
}
return false;
}
@Transactional
@Override
public boolean lockNumber(String id, String status) {
return userMapper.lockNumber(id, status) == 1;
}
@Override
public List<User> findLockNumberAll() {
return userMapper.findLockNumberAll();
}
}
6. 創建SessionController
用於處理session
操作
/**
* 操作session控制層 所有api都需要管理員角色
*/
@RestController
@RequestMapping("/online")
public class SessionController {
@Autowired
private SessionService sessionService;
private Map<String,String> resultMap = new HashMap<>();
/**
* 獲取所有在線用戶並跳轉頁面
*
* @return org.springframework.web.servlet.ModelAndView
* @author: zhihao
* @date: 2019/12/18
*/
@RequiresRoles(value = {"admin"})
@RequestMapping("/list")
public ModelAndView list() {
List<UserOnline> userOnlineAll = sessionService.findUserOnlineAll();
ModelAndView view = new ModelAndView();
view.setViewName("online");
view.addObject("list",userOnlineAll );
return view;
}
/**
* 強轉註銷用戶與鎖號
*
* @param sessionId 會話id
* @return java.util.Map<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2019/12/18
*/
@RequiresRoles(value = {"admin"})
@RequestMapping("/forceLogout")
public Map<String, String> forceLogout(String sessionId) {
try {
boolean forceLogout = sessionService.forceLogout(sessionId);
if (forceLogout){
resultMap.put("code","success");
resultMap.put("msg","踢人與鎖號成功");
}
} catch (Exception e) {
resultMap.put("code","error");
resultMap.put("msg","踢人與鎖號失敗");
e.printStackTrace();
}
return resultMap;
}
/**
* 獲取所有鎖號用戶
*
* @param
* @return org.springframework.web.servlet.ModelAndView
* @author: zhihao
* @date: 2019/12/18
*/
@RequiresRoles(value = {"admin"})
@RequestMapping("/locknumber")
public ModelAndView getLockNumber(){
ModelAndView view = new ModelAndView();
List<User> lockNumberAll = sessionService.findLockNumberAll();
view.addObject("list", lockNumberAll);
view.setViewName("locknumber");
return view;
}
private final String UNLOCK = "2";
/**
* 解鎖賬號
*
* @param id 用戶id
* @return java.util.Map<java.lang.String,java.lang.String>
* @author: zhihao
* @date: 2019/12/18
*/
@RequiresRoles(value = {"admin"})
@RequestMapping("/unlockNumber")
public Map<String,String> unlockNumber(String id){
boolean number = sessionService.lockNumber(id, UNLOCK);
if (!number){
resultMap.put("code", "error");
resultMap.put("msg", "解鎖失敗");
}
resultMap.put("code", "success");
resultMap.put("msg", "解鎖成功");
return resultMap;
}
}
7. 編寫online
頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>在線用戶管理</title>
<script th:src="@{/js/jquery-3.3.1.min.js}"></script>
</head>
<body>
<h3>在線用戶數:<span th:text="${list.size()}"></span></h3>
<table border="1px">
<tr>
<th>用戶id</th>
<th>用戶名稱</th>
<th>登錄時間</th>
<th>最後訪問時間</th>
<th>主機</th>
<th>狀態</th>
<th>操作</th>
</tr>
<tr th:each="user : ${list}">
<th th:text="${user.userId}"></th>
<th th:text="${user.username}"></th>
<th th:text="${#dates.format(user.startTimestamp, 'yyyy-MM-dd HH:mm:ss')}"></th>
<th th:text="${#dates.format(user.lastAccessTime, 'yyyy-MM-dd HH:mm:ss')}"></th>
<th th:text="${user.host}"></th>
<th th:text="${user.status}"></th>
<th ><a th:onclick="forceLogout([[${user.sessionId}]],[[${user.status}]]);" href="javascript:;">點擊鎖號</a></th>
</tr>
</table>
<p><a th:href="@{/online/locknumber}">獲取所有鎖號用戶</a></p>
<p><a th:href="@{/index}">返回</a></p>
</body>
<script type="text/javascript">
function forceLogout(sessionID,status) {
if(status == "離線"){
alert("該用戶已是離線狀態!!");
return;
}
$.get("/online/forceLogout",'sessionId='+sessionID,function (result) {
if (result.code === 'success') {
alert(result.msg);
location.href='/online/list';
}else {
alert(result.msg);
}
});
}
</script>
</html>
8. locknumber
解鎖賬號頁面
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>鎖號用戶管理</title>
<script th:src="@{/js/jquery-3.3.1.min.js}"></script>
</head>
<body>
<table border="1px">
<tr>
<th>用戶id</th>
<th>用戶名稱</th>
<th>創建時間</th>
<th>狀態</th>
<th>操作</th>
</tr>
<tr th:each="user : ${list}">
<th th:text="${user.id}"></th>
<th th:text="${user.username}"></th>
<th th:text="${#dates.format(user.createTime, 'yyyy-MM-dd HH:mm:ss')}"></th>
<th th:if="${user.status == '0' }">鎖號</th>
<th ><a th:onclick="unlockNumber([[${user.id}]]);" href="javascript:;">點擊解鎖號</a></th>
</tr>
</table>
<p><a th:href="@{/online/list}">返回</a></p>
</body>
<script type="text/javascript">
function unlockNumber(id) {
$.get("/online/unlockNumber",'id='+id,function (result) {
if (result.code === 'success') {
alert(result.msg);
location.href='/online/locknumber';
}else {
alert(result.msg);
}
});
}
</script>
</html>
9. 在index首頁加上權限鏈接進行測試
<body>
<p>[[${user.username}]]帥哥你好!</p>
<div>
<a shiro:hasPermission="user:delete" th:href="@{/delete}">刪除用戶</a>
<p><a shiro:hasRole="admin" th:href="@{/online/list}">獲取所有在線用戶</a></p>
</div>
<a th:href="@{/logout}">註銷</a>
</body>
</html>
擴展資料:
註冊sessionDAO
如果使用的緩存是Ehcache
實現, 註冊的bean
是SessionDAO
:
@Bean
public SessionDAO sessionDAO() {
MemorySessionDAO sessionDAO = new MemorySessionDAO();
return sessionDAO;
}
強轉註銷用戶並鎖號如果使用的緩存是Ehcache
實現,需要修改爲:
@Override
public boolean forceLogout(String sessionId) {
.......
//鎖號 修改用戶表的用戶狀態爲鎖號
if (userService.lockNumber(user.getId())){
//強制註銷
session.setTimeout(0L);
sessionDAO.update(session); //更新session
return true;
}
return false;
}
}
通過該Session,我們還可以獲取到當前用戶的Principal信息。
值得說明的是,當某個用戶被踢出後(Session Time置爲0),該Session並不會立刻從ActiveSessions中剔除,所以我們可以通過其timeout信息來判斷該用戶在線與否。
備註:需提前開啓Redis服務,分佈式系統中的每個子系統都需要配置Shiro框架
緩存是Ehcache
實現,需要修改爲:
@Override
public boolean forceLogout(String sessionId) {
.......
//鎖號 修改用戶表的用戶狀態爲鎖號
if (userService.lockNumber(user.getId())){
//強制註銷
session.setTimeout(0);
return true;
}
return false;
}
}
通過該Session,我們還可以獲取到當前用戶的Principal信息。
值得說明的是,當某個用戶被踢出後(Session Time置爲0),該Session並不會立刻從ActiveSessions中剔除,所以我們可以通過其timeout信息來判斷該用戶在線與否。
備註:需提前開啓Redis服務,分佈式系統中的每個子系統都需要配置Shiro框架