SpringBoot-Shiro在線會話管理(6)

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 實現, 註冊的beanSessionDAO:

@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框架

項目代碼(點擊打開)

發佈了29 篇原創文章 · 獲贊 3 · 訪問量 1613
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章