背景: 原登陆接口,校验密码通过后,使用springsession记录会话信息,将信息存入在redis中
基于原逻辑进行多设备登陆开发,默认的时候多设备登陆开关开启,即按原来逻辑处理,只要密码登陆校验成功之后,都会将当前的会话信息存入redis中.
当多设备开关关闭时候,同一个账号同一时间只能在一个设备上运行.实现思路如下:
1.用户每次登录时,前端在header请求头中加入DeviceId字段传给后端,登陆校验成功后,springsession将 `spring:session:sessions:+sessionid`作为key存入到redis中,将deviceId作为属性存入该key中.前端使用的库提示获取的diviceId唯一概率100%,所以加上后端获取的客户端ip+设备的mac地址,计算md5值作为设备唯一id
而后将userId与sessionId进行映射,即一个userId对应多次登陆会话sessionId.
2.用户访问的时候,在过滤器解析sessionid,获取userid不为空,则说明有权限访问。如果获取userid为空,则告诉无权限访问,跳转到登录页面。
3.用户在另一台设备登录时,登陆成功后,根据userId查看对应的sessionId,根据sessionId查找在线中的会话,然后与当前登陆设备的deviceId进行对比
如果一致则不处理(同设备下该账号可以在其他浏览器登陆),如果不一致,则删除redis缓存中的session会话信息,使之前该账号的登陆会话失效.
探测接口需要注意:
修改原因:
因为springsession有过期时间,当客户端给服务器发送会话请求的时候,
他会将该会话的最后活跃时间更新,那么过期时间会等于最后活跃时间加上过期时限,只要有请求一直发送,就一直不会过期.
当前方案:
现在采用无状态接口,请求的时候不需要判断会话的sessionid(在过滤器已经排除在外),
请求后判断该会话的sessonid是否失效,将结果返回给前端
springsession提供的API 可以通过属性字段查询当前的用户对应的sessionid,支持分布式
int currentLoginNumber = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, user.getUserId()).size();
而自己维护的userid->sessionid关系,存储在进程的内存中,可能出现分布式不同客户端的时候数据不一致.逻辑上都是使用userID与SessionId进行关系映射.
代码示例:
// 多设备登陆关闭 只允许同一时间,同一个userId只能在一个设备上登陆
if (!systemServer.getMultipleDeviceLoginSetting()) {
//当前用户下存活的会话
Set<String> userIdSessions = sessionRepository.findByIndexNameAndIndexValue(FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME, user.getUserId()).keySet();
this.handleMultipleDeviceLoginSetting(userIdSessions);
}
// 删除存活的SESSIONID
private void handleMultipleDeviceLoginSetting(Set<String> userIdSessions) {
String SESSION_PREFIX = "spring:session:sessions:";
if (userIdSessions != null && !userIdSessions.isEmpty()) {
userIdSessions.forEach(o -> {
String sessionId = SESSION_PREFIX + o;
final Object user = sessionRepository.getSessionRedisOperations().opsForHash().get(sessionId, "sessionAttr:UserId");
if (null != user) { //用户在线中
// 认为该账号在其他设备登陆 需要将该会话强制清除
sessionRepository.getSessionRedisOperations().delete(sessionId);
}
});
}
}