苏格拉底曰:我唯一知道的,就是自己一无所知

源头

最近在翻阅Springboot Security板块中的会话管理器过滤器SessionManagementFilter源码的时候,发现其会对单用户的多会话进行校验控制,比如其下的某个策略ConcurrentSessionControlAuthenticationStrategy,节选部分代码

	public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) { // 获取单用户的多会话
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false); // 一系列判断
int sessionCount = sessions.size();
int allowedSessions = getMaximumSessionsForThisUser(authentication); ....
.... // session超出后的操作,一般是抛异常结束filter的过滤
allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
}

笔者一般的思维是认为单个校验通过的用户有单一的会话,为何会有多个会话呢?那多个会话其又是如何管理的呢?带着疑问探究下HttpSession的概念

何为HttpSession

通俗的理解应该是基于HTTP协议而产生的服务器级别的对象。其独立于客户端发的请求,并不是客户端每一次的请求便会创建此对象,也不是客户端关闭了就会被注销。

故其依赖于HTTP服务器的运行,是独立于客户端的一种会话。目的也是保存公共的属性供页面间跳转的参数传递。

如何使用HttpSession

HttpSession主要是通过HttpServletRequest#getSession()方法来创建,且只依赖于此方法的创建。一般都是用户校验通过后,应用才会调用此方法保存一些公共的属性,方便页面间传递。

HttpSession的实现机制

为了理解清楚上述的疑问,那么HttpSession的实现机制必须深入的了解一下。因为其依赖于相应的HTTP服务器,就以Springboot内置的Tomcat服务器作为分析的入口吧。

代码层

笔者以唯一入口HttpServletRequest#getSession()方法为源头,倒推其代码实现逻辑,大致梳理了下Tomcat服务器的HTTP请求步骤

	AbstractEndpoint作为服务的创建入口,其子类NioEndpoint则采用NIO思想创建TCP服务并运行多个Poller线程用于接收客户端(浏览器)的请求-->
通过Poller#processSocket()方法调用内部类SocketProcessor来间接引用AbstractProtocol内部类ConnectionHandler处理具体的请求-->
HTTP相关的请求则交由AbstractHttp11Protocol#createProcessor()方法创建Http11Processor对象处理---->
Http11Processor引用CoyoteAdapter对象来包装成org.apache.catalina.connector.Request对象来最终处理创建HttpSession-->
优先解析URL中的JSESSIONID参数,如果没有则尝试获取客户端Cookie中的JSESSIONID键值,最终存入至相应Session对象属性sessionId中,避免对来自同一来源的客户端重复创建HttpSession

基于上述的步骤用户在获取HttpSession对象时,会调用Request#doGetSession()方法来创建,具体的笔者不分析了。

总而言之,HttpSession的关键之处在于其对应的sessionId,每个HttpSession都会有独一无二的sessionId与之对应,至于sessionId的创建读者可自行分析,只需要知道其在应用服务期间会对每个HttpSession创建唯一的sessionId即可。

保存方式

上述讲解了HttpSession的获取方式是基于sessionId的,那么肯定有一个出口去保存相应的键值对,仔细一看发现其是基于cookie去实现的,附上Request#doGetSession()方法关键源码

    protected Session doGetSession(boolean create) {

        .....
..... // session不为空且支持cookie机制
if (session != null
&& context.getServletContext()
.getEffectiveSessionTrackingModes()
.contains(SessionTrackingMode.COOKIE)) {
// 默认创建Key为JSESSIONID的Cookie对象,并设置maxAge=-1
Cookie cookie =
ApplicationSessionCookieConfig.createSessionCookie(
context, session.getIdInternal(), isSecure()); response.addSessionCookieInternal(cookie);
} if (session == null) {
return null;
} session.access();
return session;
}

很明显,由上述的代码可知,HttpSession的流通还需要依赖Cookie机制的使用。此处谈及一下Cookie对象中的maxAge,可以看下其API说明

    /**
* Sets the maximum age of the cookie in seconds.
* <p>
* A positive value indicates that the cookie will expire after that many
* seconds have passed. Note that the value is the <i>maximum</i> age when
* the cookie will expire, not the cookie's current age.
* <p>
* A negative value means that the cookie is not stored persistently and
* will be deleted when the Web browser exits. A zero value causes the
* cookie to be deleted.
*
* @param expiry
* an integer specifying the maximum age of the cookie in
* seconds; if negative, means the cookie is not stored; if zero,
* deletes the cookie
* @see #getMaxAge
*/
public void setMaxAge(int expiry) {
maxAge = expiry;
}

默认maxAge值为-1,即当浏览器进程重开之前,此对应的JSESSIONID的cookie值都会在访问服务应用的时候被带上。

由此处其实可以理解,如果多次重开浏览器进程并登录应用,则会出现单用户有多个session的情况。所以才有了限制Session最大可拥有量

HttpSession的管理

这里浅谈下Springboot Security中对Session的管理,主要是针对单个用户多session的情况。由HttpSecurity#sessionManagement()来进行相应的配置

    @Override
protected void configure(HttpSecurity http) throws Exception {
// 单用户最大session数为2
http.sessionManagement().maximumSessions(2);
}

经过上述的配置,便会引入两个关于session管理的过滤链,笔者按照过滤顺序分开浅析

ConcurrentSessionFilter

主要是针对过期的session进行相应的注销以及退出操作,看下关键的处理代码

	public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest request = (HttpServletRequest) req;
HttpServletResponse response = (HttpServletResponse) res; // 获取HttpSession
HttpSession session = request.getSession(false); if (session != null) {
SessionInformation info = sessionRegistry.getSessionInformation(session
.getId()); if (info != null) {
// 如果设置为过期标志,则开始清理操作
if (info.isExpired()) {
// 默认使用SecurityContextLogoutHandler处理退出操作,内含session注销
doLogout(request, response); // 事件推送,默认是直接输出session数过多的信息
this.sessionInformationExpiredStrategy.onExpiredSessionDetected(new SessionInformationExpiredEvent(info, request, response));
return;
}
else {
// Non-expired - update last request date/time
sessionRegistry.refreshLastRequest(info.getSessionId());
}
}
} chain.doFilter(request, response);
}

前文也提及,如果服务应用期间,要注销session,只能调用相应的session.invalid()方法。直接看下SecurityContextLogoutHandler#logout()源码

	public void logout(HttpServletRequest request, HttpServletResponse response,
Authentication authentication) {
Assert.notNull(request, "HttpServletRequest required");
if (invalidateHttpSession) {
HttpSession session = request.getSession(false);
if (session != null) {
// 注销
session.invalidate();
}
} if (clearAuthentication) {
SecurityContext context = SecurityContextHolder.getContext();
context.setAuthentication(null);
} // 清理上下文
SecurityContextHolder.clearContext();
}

SessionManagementFilter

笔者只展示ConcurrentSessionControlAuthenticationStrategy策略类用于展示session的最大值校验

	public void onAuthentication(Authentication authentication,
HttpServletRequest request, HttpServletResponse response) {
// 获取当前校验通过的用户所关联的session数量
final List<SessionInformation> sessions = sessionRegistry.getAllSessions(
authentication.getPrincipal(), false); int sessionCount = sessions.size();
// 最大session支持,可配置
int allowedSessions = getMaximumSessionsForThisUser(authentication); if (sessionCount < allowedSessions) {
// They haven't got too many login sessions running at present
return;
} if (allowedSessions == -1) {
// We permit unlimited logins
return;
} if (sessionCount == allowedSessions) {
HttpSession session = request.getSession(false); if (session != null) {
// Only permit it though if this request is associated with one of the
// already registered sessions
for (SessionInformation si : sessions) {
if (si.getSessionId().equals(session.getId())) {
return;
}
}
}
// If the session is null, a new one will be created by the parent class,
// exceeding the allowed number
}
// 超出对应数的处理
allowableSessionsExceeded(sessions, allowedSessions, sessionRegistry);
}

继续跟踪allowableSessionsExceeded()方法

	protected void allowableSessionsExceeded(List<SessionInformation> sessions,
int allowableSessions, SessionRegistry registry)
throws SessionAuthenticationException {
// 1.要么抛异常
if (exceptionIfMaximumExceeded || (sessions == null)) {
throw new SessionAuthenticationException(messages.getMessage(
"ConcurrentSessionControlAuthenticationStrategy.exceededAllowed",
new Object[] { Integer.valueOf(allowableSessions) },
"Maximum sessions of {0} for this principal exceeded"));
} // Determine least recently used session, and mark it for invalidation
SessionInformation leastRecentlyUsed = null; for (SessionInformation session : sessions) {
if ((leastRecentlyUsed == null)
|| session.getLastRequest()
.before(leastRecentlyUsed.getLastRequest())) {
leastRecentlyUsed = session;
}
}
// 2.要么设置对应的expired为true,最后交由上述的ConcurrentSessionFilter来处理
leastRecentlyUsed.expireNow();
}

关于session的保存,大家可以关注RegisterSessionAuthenticationStrategy注册策略,其是排在上述的策略之后的,就是先判断再注册,很顺畅的逻辑。笔者此处就不分析了,读者可自行分析

小结

HttpSession是HTTP服务中比较常用的对象,理解它的含义以及应用逻辑可以帮助我们更好的使用它。以苏格拉底的话来说就是我唯一知道的,就是自己一无所知

浅析HttpSession的更多相关文章

  1. SQL Server on Linux 理由浅析

    SQL Server on Linux 理由浅析 今天的爆炸性新闻<SQL Server on Linux>基本上在各大科技媒体上刷屏了 大家看到这个新闻都觉得非常震精,而美股,今天微软开 ...

  2. 【深入浅出jQuery】源码浅析--整体架构

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  3. 高性能IO模型浅析

    高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型. (2)同步非阻塞IO(Non-blocking  ...

  4. netty5 HTTP协议栈浅析与实践

      一.说在前面的话 前段时间,工作上需要做一个针对视频质量的统计分析系统,各端(PC端.移动端和 WEB端)将视频质量数据放在一个 HTTP 请求中上报到服务器,服务器对数据进行解析.分拣后从不同的 ...

  5. java使用websocket,并且获取HttpSession,源码分析

    转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...

  6. Jvm 内存浅析 及 GC个人学习总结

    从诞生至今,20多年过去,Java至今仍是使用最为广泛的语言.这仰赖于Java提供的各种技术和特性,让开发人员能优雅的编写高效的程序.今天我们就来说说Java的一项基本但非常重要的技术内存管理 了解C ...

  7. 从源码浅析MVC的MvcRouteHandler、MvcHandler和MvcHttpHandler

    熟悉WebForm开发的朋友一定都知道,Page类必须实现一个接口,就是IHttpHandler.HttpHandler是一个HTTP请求的真正处理中心,在HttpHandler容器中,ASP.NET ...

  8. 【深入浅出jQuery】源码浅析2--奇技淫巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  9. HttpSession与Hibernate中Session的区别

    一.javax.servlet.http.HttpSession是一个抽象接口 它的产生:J2EE的Web程序在运行的时候,会给每一个新的访问者建立一个HttpSession,这个Session是用户 ...

随机推荐

  1. BZOJ_1895_Pku3580 supermemo_Splay

    BZOJ_1895_Pku3580 supermemo_Splay Description 给出一个初始序列fA1;A2;:::Ang,要求你编写程序支持如下操作: 1. ADDxyD:给子序列fAx ...

  2. Python练习:哥德巴赫猜想

    哥德巴赫猜想 哥德巴赫 1742 年给欧拉的信中哥德巴赫提出了以下猜想:任一大于 2 的偶数都可写成两个质数之和.但是哥德巴赫自己无法证明它,于是就写信请教赫赫有名的大数学家欧拉帮忙证明,但是一直到死 ...

  3. FOFA爬虫大法——API的简单利用

    FOFA是一款网络空间搜索引擎,它通过进行网络空间测绘,帮助研究人员或者企业迅速进行网络资产匹配,例如进行漏洞影响范围分析.应用分布统计.应用流行度等. 何为API?如果你在百度百科上搜索,你会得到如 ...

  4. [Objective-C语言教程]简介(1)

    中文名:扩充C的面向对象编程语言 外文名:Objective-C 简 写:ObjC&OC 创始人:布莱德·考克斯 创始时间:1980年代 Objective-C,通常写作ObjC或OC和较少用 ...

  5. (翻译)W3C的Turtle文档

    主要翻译如下页面,https://www.w3.org/TR/turtle/,对该页面中Turtle的内容部分进行翻译,希望对使用Turtle的朋友们有所帮助. 1 简介 2 Turtle语言 2.1 ...

  6. SpringBoot之旅第一篇-初探

    一.SpringBoot是什么? 微服务,应该是近年来最火的概念,越来越多的公司开始使用微服务架构,面试中被问到的微服务的概率很高,不管对技术的追求,还是为了进更好的公司,微服务都是我们开发人员的必须 ...

  7. TiDB之mac上搭建及调试技巧

    此文目的 由于本人最近已经成为TiDB的粉丝,所以就开始各种研究TiDB的源码,研究源码这个事情,首先就需要在自己电脑上不断的调试及修改.TiDB本身的代码是非常容易编译和调试的,但是要把PD.TiK ...

  8. ElasticSearch入门 附.Net Core例子

    1.什么是ElasticSearch? Elasticsearch是基于Lucene的搜索引擎.它提供了一个分布式,支持多租户的全文搜索引擎,它具有HTTP Web界面和无模式JSON文档. Elas ...

  9. 初探奥尔良(Orleans)

    由于工作上关系目前经常被各种并发数据问题搞得焦头烂额,要么要性能舍弃数据上得一致性,要么要一致性但是却得到了特别糟糕的响应.难道鱼和熊掌真的无法兼得吗? 然后找到了类似奥尔良这种基于Actor模型的k ...

  10. jQuery拼图小游戏

    jQuery拼图小游戏 最后样式 核心代码部分 <script type="text/javascript" > $(function () { $("td& ...