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
     * @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
     * @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 
     * @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
     * @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页面


<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>用户idth>
        <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解锁账号页面


<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>用户idth>
        <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>

SpringBoot-Shiro在线会话管理(6)_第1张图片

扩展资料:

注册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框架

项目代码(点击打开)

你可能感兴趣的:(Java,SpringBoot,shrio)