爱分享666

当前位置:首页 >java技术博客>springBoot

在Shiro中我们可以通过org.apache.shiro.session.mgt.eis.SessionDAO对象的getActiveSessions()方法方便的获取到当前所有有效的Session对象。通过这些Session对象,我们可以实现一些比较有趣的功能,比如查看当前系统的在线人数,查看这些在线用户的一些基本信息,强制让某个用户下线等。

为了达到这几个目标,我们在现有的Spring Boot Shiro项目基础上进行一些改造(缓存使用Ehcache)。

更改ShiroConfig

为了能够在Spring Boot中使用SessionDao,我们在ShiroConfig中配置该Bean:

1
2
3
4
5
@Bean
public SessionDAO sessionDAO() {
   MemorySessionDAO sessionDAO = new MemorySessionDAO();
   return sessionDAO;
}


如果使用的是Redis作为缓存实现,那么SessionDAO则为RedisSessionDAO

1
2
3
4
5
6
@Bean
public RedisSessionDAO sessionDAO() {
   RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
   redisSessionDAO.setRedisManager(redisManager());
   return redisSessionDAO;
}


在Shiro中,SessionDao通过org.apache.shiro.session.mgt.SessionManager进行管理,所以继续在ShiroConfig中配置SessionManager

1
2
3
4
5
6
7
8
9
@Bean
public SessionManager sessionManager() {
   DefaultWebSessionManager sessionManager = new DefaultWebSessionManager();
   Collection<SessionListener> listeners = new ArrayList<SessionListener>();
   listeners.add(new ShiroSessionListener());
   sessionManager.setSessionListeners(listeners);
   sessionManager.setSessionDAO(sessionDAO());
   return sessionManager;
}


其中ShiroSessionListenerorg.apache.shiro.session.SessionListener接口的手动实现,所以接下来定义一个该接口的实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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();
   }
}


其维护着一个原子类型的Integer对象,用于统计在线Session的数量。

定义完SessionManager后,还需将其注入到SecurityManager中:

1
2
3
4
5
6
7
8
@Bean  
public SecurityManager securityManager(){  
   DefaultWebSecurityManager securityManager =  new DefaultWebSecurityManager();
   securityManager.setRealm(shiroRealm());
   ...
   securityManager.setSessionManager(sessionManager());
   return securityManager;  
}


UserOnline

配置完ShiroConfig后,我们可以创建一个UserOnline实体类,用于描述每个在线用户的基本信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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踢出用户抽象方法:

1
2
3
4
public interface SessionService {
   List<UserOnline> list();
   boolean forceLogout(String sessionId);
}


其具体实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@Service("sessionService")
public class SessionServiceImpl implements SessionService {
   @Autowired
   private SessionDAO sessionDAO;

   @Override
   public List<UserOnline> list() {
       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().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()方法需要稍作修改:

1
2
3
4
5
6
@Override
public boolean forceLogout(String sessionId) {
   Session session = sessionDAO.readSession(sessionId);
   sessionDAO.delete(session);
   return true;
}


Controller

定义一个SessionContoller,用于处理Session的相关操作:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
@Controller
@RequestMapping("/online")
public class SessionController {
   @Autowired
   SessionService sessionService;
   
   @RequestMapping("index")
   public String online() {
       return "online";
   }

   @ResponseBody
   @RequestMapping("list")
   public List<UserOnline> list() {
       return sessionService.list();
   }

   @ResponseBody
   @RequestMapping("forceLogout")
   public ResponseBo forceLogout(String id) {
       try {
           sessionService.forceLogout(id);
           return ResponseBo.ok();
       } catch (Exception e) {
           e.printStackTrace();
           return ResponseBo.error("踢出用户失败");
       }
   }
}


页面

我们编写一个online.html页面,用于展示所有在线用户的信息:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
   <meta charset="UTF-8">
   <title>在线用户管理</title>
   <script th:src="@{/js/jquery-1.11.1.min.js}"></script>
   <script th:src="@{/js/dateFormat.js}"></script>
</head>
<body>
   <h3>在线用户数:<span id="onlineCount"></span></h3>
   <table>
       <tr>
           <th>序号</th>
           <th>用户名称</th>
           <th>登录时间</th>
           <th>最后访问时间</th>
           <th>主机</th>
           <th>状态</th>
           <th>操作</th>
       </tr>
   </table>
   <a th:href="@{/index}">返回</a>
</body>
<script th:inline="javascript">
   var ctx = [[@{/}]];
   $.get(ctx + "online/list", {}, function(r){
       var length = r.length;
       $("#onlineCount").text(length);
       var html = "";
       for(var i = 0; i < length; i++){
           html += "<tr>"
               + "<td>" + (i+1) + "</td>"
               + "<td>" + r[i].username + "</td>"
               + "<td>" + new Date(r[i].startTimestamp).Format("yyyy-MM-dd hh:mm:ss") + "</td>"
               + "<td>" + new Date(r[i].lastAccessTime).Format("yyyy-MM-dd hh:mm:ss") + "</td>"
               + "<td>" + r[i].host + "</td>"
               + "<td>" + r[i].status + "</td>"
               + "<td><a href='#' onclick='offline(\"" + r[i].id + "\",\"" + r[i].status +"\")'>下线</a></td>"
               + "</tr>";
       }
       $("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");
   }
</script>
</html>


在index.html中加入该页面的入口:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
...
<body>
   <p>你好![[${user.userName}]]</p>
   <p shiro:hasRole="admin">你的角色为超级管理员</p>
   <p shiro:hasRole="test">你的角色为测试账户</p>
   <div>
       <a shiro:hasPermission="user:user" th:href="@{/user/list}">获取用户信息</a>
       <a shiro:hasPermission="user:add" th:href="@{/user/add}">新增用户</a>
       <a shiro:hasPermission="user:delete" th:href="@{/user/delete}">删除用户</a>
   </div>
   <a shiro:hasRole="admin" th:href="@{/online/index}">在线用户管理</a>
   <a th:href="@{/logout}">注销</a>
</body>
...


测试

启动项目,在Opera浏览器中使用mrbird账户访问:

QQ截图20171214191456.png

在FireFox浏览器中使用tester账户访问:

QQ截图20171214191543.png

然后在mrbird主界面点击“在线用户管理”:

QQ截图20171214191641.png

显示的信息符合我们的预期,点击tester的下线按钮,强制将其踢出:

QQ截图20171214191917.png

回到tester用户的主界面,点击“查看用户信息”,会发现页面已经被重定向到login页面,因为其Session已经失效!

再次刷新mrbird的online页面,显示如下:

QQ截图20171214192219.png

源码链接(Ehcache版):https://github.com/wuyouzhuguli/Spring-Boot-Demos/tree/master/17.Spring-Boot-Shiro-Session

  • 本文作者: MrBird

  • 本文链接: http://mrbird.cc/Spring-Boot-Shiro session.html

  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!


上一篇:

Spring Boot Thymeleaf中使用Shiro标签_Spring Boot 整合Shiro (全套学习五)

下一篇:

Spring Boot Shiro中使用缓存_Spring Boot 整合Shiro (全套学习七)

0 +1
打赏 ×

如果网站能给予您帮助,欢迎给网站捐助,给我打赏个吧!
您的支持是我的动力,让网站能一直陪伴着大家,共同学习进步。
捐助费用将用于网站日常运营(服务器租费、域名租费等)
捐助者请发送邮箱提供姓名至 zhaoqn@163.com 留言以表感谢。

网友评论


  • 验证码:

热门评论

本月热门

推荐资料

精彩评论

回到顶部