引言

开篇时说些题外话,最近刚刚被公司CY,不过很快找到了下家,也同时拿到了三家公司的Offer。一周面试下来,总体感觉面试题少了,不过多了上机程序题。新公司是做外包,不过相比于上一家公司,也算是因祸得福,有新的东西学习,也有更多的工作等待我去完成,比较于之前的闲的蛋疼和打杂的活,对我的个人技术磨炼应该是有百利而无一害。所以,非常庆幸公司把我CY。

刚入职的第三天,开通Git账号的第二天,临时分配的渗透测试漏洞修复,总共有四个漏洞修复,分别是:

1、管理员快速冻结普通用户,session 实时注销 - 《Web应用安全————账号冻结与 Session 实时失效》
2、会话固定漏洞 - 《Web应用安全————Shiro 解决会话固定漏洞》
3、多点登录互斥 - 《Web应用安全————多点登录互斥》
4、暴力破解(开启验证码)

其中,第一条算是比较常见的web安全功能,第二条不太常见,但是非常重要,也是解决起来最困难的一个,我前后花了整整两天的时间,才总算解决这个问题,由于这个问题掺杂了一些Shiro 框架的知识,我会在下一篇文章中仔细阐述一下这个问题的解决思路,和我都遇到了哪些疑惑,以及是如何思考和解决的。

第三条是一个session 互斥操作,其实也是比较简单的,而第四条暴力破解漏洞,通常只要开启一个验证码就可以解决了。

本篇文章讨论第一条,管理员如何在冻结普通用户的同时,使其Session 注销掉

一、注销用户 session 的解决思路

处理掉他人session 的最核心思路就是:

1、明确当前使用的安全框架(或没有)的 session 管理的 session 失效API 是什么。

2、找到要被注销的 session  的 session id。

3、冻结或删除用户后,通过 session id,找到其对应的session 对象,并立刻调用 session 失效 API 方法。

简单来说,就是 先找到 session id ,然后再找到 session ,最后在冻结用户或者删除用户的时候,立刻调用使 session 失效的方法。

二、确定 Session 失效的方法

我们以往在处理Web应用的Session时,会使用HttpSession对象,它是javax.servlet.api中的底层接口。而我的项目使用的是Shiro 安全管理框架,又有自己的 Session 接口:org.apache.shiro.session.Session。

不过我想说的是,对于处理 Session 失效的问题,其实无关框架,只要是处理会话操作,无非就是几个:获取 session id、存取session 中的属性、设置/获取超时时间、获取最后访问时间、以及和我们本篇相关的: 使Session 失效 等等

在HttpSession对象中,我们使用 void invalidate(); 方法 来使 Session 失效。那么同样的,在 Shiro 中也有类似的方法,只不过换了个葫芦:void stop();

所以,不论是使用哪种安全框架 ,什么 Shiro、Spring Security,甚至是原始接口,都可以找到对应的 Session 失效的方法。

明确了如何让Session 失效,那么接下来就是如何确定用户的 session id。

三、管理员清理用户 session 的实现

在 Shiro 中,session的创建是通过org.apache.shiro.session.mgt.DefaultSessionManager.doCreateSession(SessionContext),它先是 new SimpleSession(host); 创建了一个Session 对象,然后再通过 this.sessionDAO.create(session) 创建了 session id ,并将 session 一同 放入一个 MapCache<K, V> 中。

MapCache<K, V> 这是一个值是 session 对象,键是 session id 的 Map对象

3.1 自定义 session id 池

对于指定用户,要想获得他的session id ,恐怕没什么好的途径。那么我们可以利用Map 来自行维护一个 用户与其 Session id 的唯一对应关系,但值得注意的是,必须要考虑多个管理员同时操作同一个用户的情况,因此就必须做线程安全处理,并且要全局唯一

我们可以创建一个类,来专门维护用户的 Account和 session id的对应关系类:

/*** * session id 管理池,方便超级管理员获取普通用户的session id,并及时注销。 (临时解决方案)* * @author mouhaotian* @date 2019/09/12*/
public class CcShiroSessionIdPoolVo {/** this Object */private volatile static CcShiroSessionIdPoolVo sessionIdPoolVo;private volatile Map<String, ShiroSessionKey> sessionIdPool;private CcShiroSessionIdPoolVo() {this.sessionIdPool = new ConcurrentHashMap<>();}/*** 获取vo对象,双重检查* * @return*/public static CcShiroSessionIdPoolVo getInstance() {if (sessionIdPoolVo == null) {synchronized (CcShiroSessionIdPoolVo.class) {if (sessionIdPoolVo == null) {sessionIdPoolVo = new CcShiroSessionIdPoolVo();}}}return sessionIdPoolVo;}/*** 通过账号信息获取用户的sessionid,并直接从pool中销毁* * @param account* @return*/public synchronized ShiroSessionKey getAndDestroySessionId(String account) {return deleteSessionId(account);}public void putSessionId(String account, ShiroSessionKey sessionId) {sessionIdPool.put(account, sessionId);}private ShiroSessionKey deleteSessionId(String account) {return sessionIdPool.remove(account);}public String getKey(Session session) {String sessionId = session.getId().toString();for (String acc : sessionIdPool.keySet()) {if (sessionId.equals(sessionIdPool.get(acc).toString()))return acc;}return null;}}

CcShiroSessionIdPoolVo 类是一个单例类,这是为了可以通过其内置的ConcurrentHaspMap 管理所有登录用户的 session id。

使用ConcurrentHashMap是为了能够在大量用户登录,并存储 session id 的时候,能够有一个比较不错的并发性。提供的方法并不多,主要就是存入和取出,而取出方法:getAndDestroySessionId() 也只是为了配合冻结账号的功能,在取出的时候直接从map 中移除。

注意,自行维护账号和 session id 的关键是要与 系统内部的 session 管理的生命周期保持一致!换句话说,当我们系统的session 没有创建,就不能在我们自行维护的map 中插入数据,而当系统中用户的 session 过期或者主动注销的时候,就必须要同步将我们map 中对应的session id 也移除,一般情况下,如果是手动注销 session ,那么我们可以控制这个流程,并一同删除 session id,但如果是系统自动过期,如何处理呢?这个时候我们就可以利用 session 发布的事件,用监听的方式,来检测session 的过期事件,并移除对应的 session id。

另外,id 容器的单例实现也是需要考虑的重要课题,必须要考虑性能问题。

3.2 Session id 池处理器

创建好了 session id 的容器,我们就来创建一个对这个池容器的处理器类:

import org.apache.shiro.session.Session;
import org.apache.shiro.session.SessionListenerAdapter;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;@Service
public class ShiroSessionProcessor {/** ShiroConfig 中配置的 session 管理器 */@AutowiredDefaultWebSessionManager sessionManager;/*** Shiro session 监听器*/public static class ExpiredSessionListener extends SessionListenerAdapter {@Overridepublic void onExpiration(Session session) {CcShiroSessionIdPoolVo sessionIdPool = CcShiroSessionIdPoolVo.getInstance();// 当监听到清理过期的Session ,清理掉CcShiroSessionIdPoolVo中对应的Session idsessionIdPool.getAndDestroySessionId(sessionIdPool.getKey(session));}}/*** 将每个用户的shiro session id放入shiro session池中管理* * @author mouhaotian* @date 2019年9月12日 下午5:42:15*/public void shiroSessionIdIntoPool() {String currAccount = ShiroKit.getUser().getAccount();// CcShiroSessionIdPoolVo单例全局唯一,且线程安全CcShiroSessionIdPoolVo sessionIdPool = CcShiroSessionIdPoolVo.getInstance();// 用户浏览器关闭后,或有其他人在异端登录,旧的失效的 session 依然存在,需要手动注销ShiroSessionKey invaliSessionId = sessionIdPool.getAndDestroySessionId(currAccount);if (invaliSessionId != null) {Session invaliSession = sessionManager.getSession(invaliSessionId);if (invaliSession != null) {invaliSession.stop();}}// 刷新当前 shiro sessionShiroKit.getSession().stop();Session newSession = ShiroKit.getSession();// 将新的session id 放入管理池中sessionIdPool.putSessionId(currAccount, new ShiroSessionKey(newSession.getId()));}/*** 用户登出后,需要手动清理自定义维护的Session id* * @author mouhaotian* @date 2019年9月15日 下午8:39:10*/public void clearSessionId(ShiroUser subject) {CcShiroSessionIdPoolVo.getInstance().getAndDestroySessionId(subject.getAccount());}}

这个 处理器类只有两个方法最关键:

1、shiroSessionIdIntoPool()

2、clearSessionId()

一个负责将 session id 和对应的 用户名 一起存入 刚刚定义的 session id 容器中,另一个则是在一些必要的时候,手动清理对应的 session id。

说明:上面的实现稍显冗余,主要是在 sessionid 入池的时候,用到了一个 session 刷新的操作,这是一个解决会话固定的操作,后面会介绍。ExpiredSessionListener 是一个内部类,主要负责监听系统的 session 过期事件,因为如果用户并没有点击“退出”按钮,而是下意识的直接关闭了浏览器,那么一般情况下,应用程序是收不到任何 注销 session 的请求的,这种情况下就会依赖于 session 的超时时间,自动进行注销操作,我们的ExpiredSessionListener 就是负责监听“过期事件” 并即时处理掉 池中的 session id ,与系统的 session 状态保持一致。

3.2 在冻结或删除用户的同时使 session 失效

到了这一步,我们已经完成了 用户登录时,将用户名与 session id 保存起来的操作,有了这个 session id 池,我们就可以在删除用户或者是冻结用户的时候通过 用户名,直接在 池中 查找对应的 session id ,并注销。

    @AutowiredDefaultWebSessionManager defaultWebSessionManager;    /*** 冻结用户前,第一时间注销该用户的session* * @author mouhaotian* @date 2019/09/12*/@Before("clearSession()")public void freezeUserShiroSession() {Integer userId = Integer.valueOf(getRequest().getParameter("userId"));// 处理掉该用户的 sessionCcShiroSessionIdPoolVo pool = CcShiroSessionIdPoolVo.getInstance();ShiroSessionKey sessionId = pool.getAndDestroySessionId(managerDao.selectUser(userId).getAccount());if (sessionId != null) {// 冻结该用户的sessiondefaultWebSessionManager.getSession(sessionId).stop();}}

这里我使用了 Spring AOP 来切入相应的方法完成注销 session 。或者干脆在删除或冻结的 Service 方法中加入这段逻辑。

简单来说就是先获取 session id 池,然后通过 用户的用户名或者是id (具体根据你实际维护的 对应关系 是 用户名 - session id ,还是 用户ID - session id)来获取池中的 session id。注意,一定要判空,这是因为如果有两个管理员同时操作的话,可能有一个取到的是null 。最后调用 Shiro 的会话管理器的 getSession() 方法,并执行 stop()操作。

至于会话管理器,不需要追究太深,总之任何安全框架肯定都有 getSession(sessionId)  方,Shiro 是通过 SessionManager 的Session getSession(SessionKey key) 方法 来获取对应的 session 。如果在 Shiro 配置的JavaConfig 中你是用DefaultWebSessionManager 来作为shiro 的默认会话管理器,那么只需要通过@Autowired 注解注入到你需要的位置就可以使用 会话管理器对象了。

总结

一般的Web 安全框架都集成了会话管理机制,这是一个最基本的功能需求。

一般的 session 管理,都没有太好的获取非当前用户 Session 的API 接口,因此,我们可以考虑自行定义一个 Map 对象,来维护每个用户 和其 session id 的对应管理。

在用户登录成功后,我们将 用户名(或用户id)与其对应的 session id 存入 map 中,在用户登出或 系统 session 自动超时的时候将其清理出 map。这些自动的操作,一定要与系统的 session 状态保持一致,以免造成不必要的内存消耗。

在管理员进行手动冻结用户或删除用户的时候,可以通过 Spring AOP来统一管理切面,统一执行注销 session 的操作。具体做法就是通过 map 中的用户与 session id 的对应关系,按照 用户名——>session id ——> session ——> 注销 的流程完成指定用户的注销操作。

注意在实现 map 的时候,要考虑单例和 线程安全的问题。在插入、移除操作的时候要考虑性能问题。

Web应用安全————账号冻结与 Session 实时失效相关推荐

  1. java session 永不过期_Java Web Application使Session永不失效(利用cookie隐藏登录)

    在做 Web Application 时,因为 Web Project 有 session 自动失效的问题,所以如何让用户登录一次系统就能长时间运行三个月,就是个问题. 后来,看到 session 失 ...

  2. C#发布网站在web.config和IIS中设置Session过期时间

    C#发布网站在web.config和IIS中设置Session过期时间 web.config <system.web> <sessionState mode="InProc ...

  3. Java web servers 间是如何实现 session 同步的

     Java web servers 间是如何实现 session 同步的 有一个多月的时间没有更新博客了,今天终于忙里偷闲,可以把近期的收获总结一下. 本文是关于Java web servers 之间 ...

  4. arp截取web图片与账号密码

    进行 arp 欺骗 # 扫描同网段的机器 nmap -sP 192.168.10.*​```result Starting Nmap 7.80 ( https://nmap.org ) at 2020 ...

  5. 【记录3】小程序账号冻结之十分钟内解决(忘记原始ID或者公众号名称的解决方法)

    小程序账号冻结十分钟解决 第一步 第二步 核心部分:忘记原始ID或者公众号名称"的解决办法 结束 小程序申诉链接https://mp.weixin.qq.com/acct/findacct? ...

  6. Java Web基础面试问题——Cookie和Session

    Java Web基础面试问题 Cookie 和 Session 的区别 什么是HTTP 超文本传输协议,是一种用于分布式.协作式和超媒体信息系统的应用层协议. 设计HTTP最初的目的是为了提供一种发布 ...

  7. 2020-09-02 微信小程序账号冻结找回

    微信小程序账号冻结找回 不记得原始ID 微信关注'公众平台安全助手' 绑定查询(我是微信号绑定账号) 进入微信号绑定的账号就可以看到你的小程序,直接把冻结账号的原始ID复制过去再认证一下就OK啦

  8. Session对象失效的客户端解决方法

    ASP(Active Server Pages)技术的Session对象用于存储用户在对话期间的私有信息.当前用户的Session对象中定义的变量和对象能在页面之间共享,但是不能为应用中其他用户所访问 ...

  9. session很快失效_一口气说出 4 种分布式一致性 Session 实现方式,面试杠杠的~

    前言 公司有一个 Web 管理系统,使用 Tomcat 进行部署.由于是后台管理系统,所有的网页都需要登录授权之后才能进行相应的操作. 起初这个系统的用的人也不多,为了节省资源,这个系统仅仅只是单机部 ...

最新文章

  1. 2017年7月十三日正式开始记录
  2. Linux kdb命令
  3. 地理标志农产品数据发布 特色产业对话农民丰收节交易会
  4. gdi在固定范围内绘图_寿光计算机绘图CAD学费诚信经营
  5. eclipse 修改maven项目的jdk版本
  6. Markdown预览功能不可用解决方案
  7. springcloud云服务架构-HongHu commonservice-eureka项目构建过程
  8. pythonwin是什么_winpython是什么
  9. python 二进制,十进制,十六进制
  10. 方舟生存进化服务器Linux,方舟生存进化官方服务器与私人服务器有什么区别
  11. oralce 表字段扩容(修改表字段长度)
  12. 最小二乘法求解线性回归模型及求解
  13. EGE绘图之四 Gif动图播放
  14. mysql 两表关联查询 group by having
  15. linux系统挂载光盘镜像ISO的方法
  16. MATLAB中clc命令详解
  17. 概率论与数理统计期末考试复习总结
  18. 《商君列传第八》–读书总结
  19. OPNET入门2-Basic Process
  20. 人为什么要学数学 ——数学意义的哲学思考

热门文章

  1. 微信小程序开发自学笔记 —— 六、底层框架
  2. 如何在ChemDraw中缩短双键长度
  3. 什么是axios拦截器?有哪些作用和使用场景
  4. Windows 10上安装Oracle 11g报错: [INS-30131] 执行安装程序验证所需的初始设置失败。无法从节点 “zb-m2004-05243“ 检索 exectask 的版本
  5. 程序员踩点下班,领导:不想干的请办理离职,我这里不养闲人与废物
  6. HALCON已知平面两点图像坐标和对应机械坐标求仿射变换矩阵
  7. 中航无人机科创板上市:市值385亿 拳头产品是翼龙无人机
  8. [硬件相关]夏雨专用驱动光盘 11.0 Build 0218┊装机必备驱动盘包括常见驱动┊简体中文版
  9. Django笔记二十八之数据库查询优化汇总
  10. 利用博客赚钱的常见方法