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

引言

开篇时说些题外话,最近刚刚被公司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 的时候,要考虑单例和 线程安全的问题。在插入、移除操作的时候要考虑性能问题。

 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/558746.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Web应用安全————Shiro 解决会话固定漏洞

引言 承接上一篇《Web应用安全————账号冻结与 Session 实时失效》关于 session 的学习&#xff0c;本篇博客聚焦如何通过 shiro 解决会话固定导致的漏洞问题。 首先&#xff0c;没怎么接触过应用安全方面的小伙伴可能会发起疑问 - 什么是会话固定&#xff1f; 简单来说&…

Web应用安全————多点登录互斥

引言 在实际生活中&#xff0c;很多网站都做了多点登录互斥的操作&#xff0c;简单来说就是同一个账号&#xff0c;只能在一台电脑上登录&#xff0c;如果有人在其他地方登录&#xff0c;那么原来登录的地方就会自动下线&#xff0c;再进行操作就会弹出登录界面。 实现思路 …

Linux进阶之路————磁盘查询

引言 承接《Linux进阶之路————Linux磁盘分区与挂载》&#xff0c;本文介绍实际生产中对于磁盘的监控和查询。 一、查询磁盘整体使用情况 基本语法&#xff1a; df -h 该命令会显示包括我们手动挂载的磁盘&#xff0c;如果使用 umount 卸载磁盘&#xff0c;那么将不会显示…

Linux进阶之路————CentOS网络配置

引言 Linux在装机后&#xff0c;如果没有特殊配置&#xff0c;会使用动态获取 IP 地址的策略。本文描述了&#xff0c;虚拟机使用网络的拓扑图&#xff0c;以及如何通过配置&#xff0c;将 IP 地址固定下来&#xff0c;不会因为重启而失效。同时可以访问外网地址。 一、NAT模…

Linux进阶之路————进程与服务管理

引言 在Linux 中&#xff0c;每个执行的程序&#xff08;代码&#xff09;都成为一个进程&#xff0c;Linux 为每一个进程分配了一个唯一的 id 号 - PID。 每个进程都会对应一个父进程&#xff0c;而这个父进程可以复制多个子进程&#xff0c;例如 www 服务器。 每个进程都可…

Linux进阶之路———— RPM 与 YUM 包管理

引言 rpm 是一种用于互联网下载的打包及安装工具&#xff0c;它包含在某些 Linux 发行版中&#xff0c;生成具有 .rpm 扩展名的文件。rpm 是 redhat package manager&#xff08;RedHat 软件包管理器&#xff09;的缩写&#xff0c;类似 Windows 下的 setup.exe 文件。这一文件…

Linux进阶之路———Shell 编程入门

引言 通过 Shell 编程的学习&#xff0c;铺平架构师道路上的一块大砖。 Shell 在Linux 系统中的定位如下所示&#xff1a; 一、第一个 Shell 脚本 我们通过一个简单的 Shell 脚本来感受一下。 在 Shell 中不需要加 “;” 结尾&#xff0c;通过 vim 可以进行 shell 的编程工…

Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8

引言 本篇博客也属于Linux进阶系列&#xff0c;主要讲解如何在CentOS 6 下安装并配置 JDK 8。由于通过 yum 搜索的结果都是 openjdk&#xff0c;而目前企业中还是以 Oracle jdk 为主&#xff0c;因此&#xff0c;操作步骤这样的。 在Oracle 官网把 jdk 1.8 下载下来&#xff…

Linux 实操———CentOS 6 安装配置 Tomcat

引言 Linux下安装Tomcat。 一、下载、传输与解压 同《Linux 实操———CentOS 6 安装配置 Oracle JDK 1.8》一样&#xff0c;前期都是先在远程机上下载压缩包&#xff0c;然后通过远程终端&#xff0c;将压缩包放在 Linux 的 opt 目录下&#xff0c;然后解压。 下载地址是T…

Spring Boot 实用开发技巧————Eclipse 远程调试

引言 在之前的开发当中&#xff0c;都会进行本地项目启动&#xff0c;然后向本地服务发起请求来进行 Debug 调试代码&#xff0c;这也是开发人员最常见的调试操作。但是当项目逐渐成型&#xff0c;慢慢的将各个模块部署到服务器后&#xff0c;调试的手段可能就仅仅剩下查看执行…

Linux 实操———— Shell 远程执行命令

引言 目前&#xff0c;开发人员的部署方式是&#xff0c;将项目打包(Maven 打包) 然后将 生成的 jar 包等文件&#xff0c;通过Xshell 等终端工具手动传输到远程服务器上&#xff0c;然后再通过在终端执行远程服务器上的 shell 脚本来启动服务。 本篇博客聚焦这样一种解决方案…

Spring Boot 设置 ASCII banner 艺术字

引言 无意中看到Spring boot 项目的 resources 目录下有一个 banner.txt &#xff0c;打开一看&#xff0c;居然是ASCII 字符画。于是兴起&#xff0c;简单研究了一下。 Spring boot 可以加载 resources 目录下的 banner.txt 文件&#xff0c;将字符画在启动之初输出到日志或…

MySQL 基础 ———— 分组查询

引言 承接上一篇《MySQL 基础 ————高频函数总结》&#xff0c;本篇单独针对分组查询进行简单的总结和归纳&#xff0c;并为后续更为复杂的DQL 语句做好铺垫。 查询语句&#xff1a; SELECT AVG(salary) FROM teacher; 实际上是以全表的 salary 字段来求平均值。但是在实…

MySQL 基础 ———— 连接查询

引言 本篇文章承接《数据库与SQL语句》专栏&#xff0c;进入DQL的重要环节&#xff0c;可以说&#xff0c;这一部分的内容应该占据SQL语言的大部分使用场景。 本篇的连接查询知识&#xff0c;和后面的一些重要的查询知识总结&#xff0c;共同构成了在工作中80%的MySQL应用场景…

MySQL 基础 ———— 子查询

引言 承接《MySQL 基础 ———— 连接查询》&#xff0c;本文介绍和展示SQL中子查询的使用。 子查询是出现在其他语句中的select 语句&#xff0c;也称为内查询。外部的查询语句&#xff0c;称为主查询或外查询。 一、子查询的分类和支持的子句 按照子查询出现的位置&#…

MySQL 基础 ———— SQL语句的执行顺序与 LIMIT 子句

引言 到目前为止&#xff0c;已经总结了常见的SQL子句&#xff0c;包括 SELECT 、FROM、JOIN ... ON、WHERE、GROUP BY、HAVING、ORDER BY。 虽然SQL的书写顺序是固定的&#xff0c;但在MySQL引擎中执行的顺序并不完全和书写顺序一致。除了上述这些子句&#xff0c;下面将会介…

MySQL 基础———— UNION 联合查询

引言 联合查询与连接查询不同&#xff0c;通过UNION 关键字&#xff0c;我们可以将多个查询语句一同执行并将结果集展示出来&#xff0c;不涉及到任何关联关系。 UNION 的含义是“联合&#xff0c;并集&#xff0c;结合”&#xff0c;在MySQL中可以将多个查询语句的结果合并成…

MySQL 基础————常用数据类型

引言 从第一次学习mysql开始&#xff0c;不知道为什么MySQL的数据类型始终没有像Java 一样深入脑海&#xff0c;对某些数据类型的定义和用法&#xff0c;也并不清晰&#xff0c;这篇文章&#xff0c;就好好总结一番&#xff0c;将MySQL中几个常用的数据类型归纳一下。 一、类…

MySQL 基础 ———— SAVEPOINT 的应用

引言 savepoint 关键字用于在数据库事务中设置一个存储点&#xff0c;在一个较长的事务中暂存数据&#xff0c;如果在事务末尾执行回滚&#xff0c;可选择性的回滚到 savepoint 设置的暂存点。 本文承接上一篇博客《MySQL 基础 ————事务与隔离级别总结》&#xff0c;进一…

MySQL 基础 ———— 视图的应用与总结

引言 视图是一种虚拟表&#xff0c;和普通表的使用是一样的&#xff0c;视图的一大特点就是“临时性”&#xff0c;是通过表动态生成的数据&#xff0c;只保存SQL逻辑&#xff0c;不保存查询结果。 视图在实际生产中主要有两种应用场景&#xff1a; 1、多个地方用到同样的查…