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

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

更改ShiroConfig

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

@Bean
public SessionDAO sessionDAO() {
  MemorySessionDAO sessionDAO = new MemorySessionDAO();
  return sessionDAO;
}

 

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

@Bean
public RedisSessionDAO sessionDAO() {
  RedisSessionDAO redisSessionDAO = new RedisSessionDAO();
  redisSessionDAO.setRedisManager(redisManager());
  return redisSessionDAO;
}

 

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

@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接口的手动实现,所以接下来定义一个该接口的实现:

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中:

@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<UserOnline> list();
  boolean forceLogout(String sessionId);
}

 

其具体实现:

@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()方法需要稍作修改:

@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<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页面,用于展示所有在线用户的信息:

<!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 );
  }
</script>
</html>

 

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

...
<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账户访问:

Spring Boot Shiro在线会话管理

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

Spring Boot Shiro在线会话管理

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

Spring Boot Shiro在线会话管理

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

Spring Boot Shiro在线会话管理

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

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

Spring Boot Shiro在线会话管理

相关文章:

  • 2021-07-23
  • 2022-12-23
  • 2022-12-23
  • 2022-12-23
  • 2022-02-19
  • 2022-12-23
  • 2022-12-23
  • 2021-07-01
猜你喜欢
  • 2021-07-07
  • 2021-08-24
  • 2022-12-23
  • 2022-12-23
  • 2021-08-10
  • 2022-12-23
相关资源
相似解决方案