从网上看了很多解决方案,用的最多的 应当是SessionId 了。方案虽多,适合自己的才是最好的。
之前做了一个 在线用户的统计 和 管理员 踢出激活在线用户的功能,因此我得到了一个启发。程序是死的,人是活得,我可不可以定一些规则,让程序 根据我的规定 来 运行。
思路:
1.定规则。
将 踢出的用户 画一个标识,也就是 访问的Sess ionId。踢出了 我将它标记为false
如果 SeeioId 标识为false 就强制退出用户
2.监听
定了规则之后我要监视它,如果它满足规则,要执行相应的操作
流程:
1.在登录时 保存 登录的用户Id 和访问的SessionId 到redis 使用Hash表的方式
2.在登录时,取出 保存的用户信息Map
3.判断 Map 的Size
4.大于1,说明是第二次登录,取出 Map中的信息
5.判断 如果 该次请求的SessionId 不等于Map中的SessionId 并且 该次请求的用户Id 等于Map 中的用户Id
6.满足上述条件,则是 同一账号多处登录。将Map 中的SessionId 标记为false (这里是标记前一位登录的用户) 存入redis
7.将标记的SessionId保存到redis中 用键值对的方式,Key 随便取,比如("ks",sessionId)
8.自定义Filter 这里使用 HandlerInterceptorAdapter 因为 该 颗粒度更高,可以使用 注入。
9.获取 被踢出的SessionId 的值 ,利用 get("ks") 在redis中取出
10.判断 值为 false 并且 该次请求的SessionId 等于 踢出的SessionId
11.退出,跳转
//源码
定规则:
//此方法在 登录时调用
public void kickOutLogin(String sessionId, String userId) {
Jedis jedis = null;
try {
jedis = jedisPool.getResource();
//保存用户Id,访问的SessionID
jedis.hset("kickoutlogin", sessionId, userId);
//保存请求的SessionId,标识为true
jedis.hset("kickoutSessionId", sessionId, "true");
//设置过期时间为30分钟
jedis.expire("kickoutSessionId", 30 * 60);
jedis.expire("kickoutlogin", 30 * 60);
//取出保存在redis中的域和值
Map map = jedis.hgetAll("kickoutlogin");
if (map.size() > MaxSize) {
for (Map.Entry entry : map.entrySet()) {
//域 = sessionId
String jessionId = (String) entry.getKey();
//值 = 用户Id
String uid = (String) entry.getValue();
//获取请求的标识
String kickoutSessionId = jedis.hget("kickoutSessionId", jessionId);
if (!kickoutSessionId.equals("false")) {
// sessionId 不同,userId相同, 表示同一账号 多处登录
if (!sessionId.equals(jessionId) && userId.equals(uid)) {
//false 表示踢出
jedis.hset("kickoutSessionId", jessionId, "false");
//被踢出的用户SesionId保存到redis
jedis.set("Ks", jessionId);
}
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
}
监听:
package com.example.springboot.shiro.core.shiro.filter;
import org.apache.shiro.SecurityUtils;
import org.apache.shiro.subject.Subject;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
//使用 HandlerInterceptorAdapter 可以注入资源
public class SessionControlInterceptor extends HandlerInterceptorAdapter {
@Autowired
private JedisPool jedisPool;
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
Subject subject = SecurityUtils.getSubject();
//如果没有登录
if (!subject.isAuthenticated()) {
return true;
}
Jedis jedis = null;
String kickoutlogin = null;
try {
//获取请求的SessionId
String sessionId = request.getSession().getId();
jedis = jedisPool.getResource();
//获取被踢出的SessionId
String jessionId=jedis.get("Ks");
if (sessionId != null) {
//做判断 容易空指针
if (null != jessionId) {
//获取这次请求SessionId 的标识
kickoutlogin = jedis.hget("kickoutSessionId", jessionId);
}
}
if (kickoutlogin != null) {
//标记为 false 并且 该 次请求的sessionId 和踢出的SessionId 相同
if (kickoutlogin.equals("false") && sessionId.equals(jessionId)) {
//退出
subject.logout();
//重定向
WebUtils.issueRedirect(request, response, "kickoutLogin");
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (jedis != null) {
jedis.close();
}
}
return Boolean.TRUE;
}
}
关于 HandlerInterceptorAdapter 的配置 可以去看一下 Springboot+Shiro+redis 踢出在线用户