在Shiro中可以通过org.apache.shiro.session.mgt.eis.SessionDAO
对象的getActiveSessions()
方法方便的获取到当前所有有效的Session
对象。通过这些Session
对象,可以实现一些功能,比如查看当前系统的在线人数,查看这些在线用户的一些基本信息,强制让某个用户下线与分布式Session
共享等 。
在原有的项目进行改造, 使用的是Redis缓存
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;
}
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;
}
/**
* @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());
}
}
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;
}
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();
}
}
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;
}
}
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>
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>
<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框架