Spring Security 6.x 系列(13)—— 会话管理及源码分析

2024-01-02 19:14:41

一、会话概念

在实现会话管理之前,我们还是先来了解一下协议和会话的概念,连协议和会话都不知道是啥,还谈啥管理。

1.1 http 协议

因为我们现在的会话,基本上都是基于HTTP协议的,所以在讲解会话之前,我再带各位复习一下HTTP协议。

1.1.1 概念

HTTP:超文本传输协议(HyperText Transfer Protocol),是一种用于分布式、协作式和超媒体信息系统的应用层协议,是一种在客户端(用户)和服务器端(网站)之间进行请求和响应的规范标准(TCP),HTTP是万维网中数据通信的基础。

1.1.2 起源&发展

HTTP的发展是由蒂姆·伯纳斯-李于1989年在欧洲核子研究组织(CERN)所发起。HTTP的标准制定由万维网协会(World Wide Web Consortium,W3C)和互联网工程任务组(Internet Engineering Task Force,IETF)进行协调,最终发布了一系列的RFC。其中最著名的是1999年6月公布的 RFC 2616,定义了HTTP协议中现今广泛使用的一个版本—— HTTP 1.1

2014年12月,互联网工程任务组(IETF)的Hypertext Transfer Protocol Bishttpbis)工作小组将HTTP/2标准提议递交至IESG进行讨论,于2015年2月17日被批准。HTTP/2标准于2015年5月以RFC 7540正式发表,取代HTTP 1.1成为HTTP的实现标准

1.1.3 特点

  • 基于 请求-响应 模式:

    HTTP协议规定:请求是从客户端发出的,最后由服务器端接受并响应该请求。

  • 无状态保存:

    HTTP是一种不保存用户状态,即无状态(stateless)的协议。HTTP协议自身不对请求和响应之间的通信状态进行保存,也就是说在HTTP这个级别,协议对于发送过的请求或响应都不做持久化处理。

    HTTP协议的无状态,指的是每当有新的请求发送时,就会有对应的响应产生,HTTP协议本身并不保留之前一切的请求或响应的报文信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把HTTP协议设计成如此简单的。可随着Web的不断发展,因无状态而导致业务处理变得棘手的情况也增多了。比如:用户登录到一家购物网站,然后他跳转到该站的其他页面,也需要能继续保持登录状态。针对这个实例,网站为了能够掌握是谁发送的请求,需要保存用户的状态。HTTP/1.1虽然是无状态的协议,但为了实现期望的保持状态的功能,于是就引入了CookieSession技术。有了CookieSession,再用HTTP协议通信,就可以管理状态了。

  • 无连接:

    无连接的含义就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并可以提高并发性能,不用和每个用户建立长久的连接,请求一次响应一次,服务端和客户端就中断了。

1.2 会话概念

会话(Session):在计算机中,尤其是在网络应用中,称为会话控制,它是以无状态的HTTP协议来维持用户状态的一种解决方案。

我们知道HTTP是无状态的协议,这意味着每次客户端检索网页时,都要单独打开一个服务器连接,因此服务器不会存储先前客户端请求的任何信息。

HTTP 本身的无状态使得用户在与服务器的交互过程中,每个请求之间都没有关联性。这意味着用户的访问没有身份记录,站点也无法为用户提供个性化的服务,而Session的诞生解决了这个难题。服务器通过与用户约定,发起每个请求时都要携带一个id类的信息,从而让不同请求之间有了关联,而id又可以很方便地绑定具体用户,所以我们可以把不同请求归类到同一用户。基于这个方案,为了让用户每个请求都携带同一个id,在不妨碍体验的情况下,cookie是很好的载体。当用户首次访问系统时,系统会为该用户生成一个sessionid,并添加到cookie中。在该用户的会话期内,每个请求都自动携带该cookie,因此系统可以很轻易地识别出这是来自哪个用户的请求。

jsessionid就是客户端用来保存sessionid的变量,主要是针对j2ee实现的web容器。

尽管 cookie 非常有用,但有时用户会在浏览器中禁用它,这么做可能是出于安全考虑,也可能是为了保护个人隐私。

在这种情况下,基于cookie实现的 sessionld 自然就无法正常使用了。因此,有些服务还支持用 URL重写 的方式来实现类似的体验,例如:http://www.baidu.com;jessionid=xxx URL重写原本是为了兼容禁用 cookie 的浏览器而设计的,但也容易被黑客利用。黑客只需访问一次系统,将系统生成的sessionld提取并拼凑在URL上,然后将该URL发给一些取得信任的用户。只要用户在session有效期内通过此URL进行登录,该sessionld就会绑定到用户的身份,黑客便可以轻松享有同样的会话状态,完全不需要用户名和密码,这就是典型的会话固定攻击。

我们只需在两个浏览器中用同一个账号登录就会发现,默认情况下,我们的系统并未有任何会话并发的限制,也就是一个账户能在多处同时登录,这并不是一个好的策略。而Spring Security已经为我们提供了完善的会话管理功能,包括:会话固定攻击会话超时检测以及会话并发控制等。

1.3 HttpSession

HttpSession 是一个服务端的概念,服务端生成的 HttpSession 都会有一个对应的 sessionid,这个 sessionid 会通过 cookie 传递给前端,前端以后每次发送请求的时候,都会带上这个 sessionid 参数。服务端看到这个 sessionid 就会把这个前端请求和服务端的某一个 HttpSession 对象对应起来,形成会话的感觉。

浏览器关闭并不会导致服务端的 HttpSession 失效,想让服务端的 HttpSession 失效,要么手动调用 HttpSession#invalidate()方法,要么等到 session 自动过期,要么重启服务端。

但是为什么有的人会感觉浏览器关闭之后 session 就失效了呢?这是因为浏览器关闭之后,保存在浏览器里边的 sessionid 就丢了(默认情况下)。所以当浏览器再次访问服务端的时候,服务端会给浏览器重新分配一个 sessionid,这个 sessionid 和之前的 HttpSession 对应不上,所以用户就会感觉 session 失效。

但是我们也可以通过手动配置,让浏览器重启之后 sessionid 不丢失,但是这样会带来安全隐患,所以一般不建议。

Spring Boot 为例,服务端生成 sessionid 之后,返回给前端的响应头是这样的:

在这里插入图片描述

在服务端的响应头中有一个 Set-Cookie 字段,该字段指示浏览器更新 sessionid,同时大家注意还有一个 HttpOnly 属性,这个表示通过 JS 脚本无法读取到 Cookie 信息,这样能有效的防止 XSS 攻击。

下一次在浏览器中发送对某个接口的请求时候,就会自觉的携带上这个 sessionid 了:

在这里插入图片描述

二、会话配置

2.1 创建策略

Spring Security 中的 SessionCreationPolicy 枚举类,声明了会话的创建策略:

public enum SessionCreationPolicy {

	/**
	 * Always create an {@link HttpSession}
	 */
	ALWAYS,

	/**
	 * Spring Security will never create an {@link HttpSession}, but will use the
	 * {@link HttpSession} if it already exists
	 */
	NEVER,

	/**
	 * Spring Security will only create an {@link HttpSession} if required
	 */
	IF_REQUIRED,

	/**
	 * Spring Security will never create an {@link HttpSession} and it will never use it
	 * to obtain the {@link SecurityContext}
	 */
	STATELESS

}

通过以下选项准确控制会话合适创建及 Spring Security 合适与之交互:

策略名称描述
ALWAYS如果没有存 Session 就创建一个
NEVER不会创建 Session ,但是如果应用中其他地方创了,那么 Spring Security 将会使用它
IF_REQUIRED如果需要就创建一个 Session(默认)
STATELESS绝对不会创建 Session ,也不使用 Session ,每个请求都需要重新进行身份验证

通过以下方式对改策略进行配置:

// 会话创建策略
http.sessionManagement(session -> session
        .sessionCreationPolicy(SessionCreationPolicy.ALWAYS)
        );

默认情况下, Spring Security 会为每个登录成功的用户创建一个 Session 也就是使用 IF_REQUIRED 策略。

2.2 检测超时

2.2.1 超时时间

可以在 Sevlet 容器中设置 Session 的有效期,如下设置有效期为3600秒(默认):

server:
  servlet:
    session:
      timeout: 3600s

2.2.2 失效跳转路径

会话超时之后,可以通过Spring Security 设置失效跳转路径:

http.sessionManagement(session -> session
                .invalidSessionUrl("/login?error=INVALID_SESSION") //  失效跳转路径
                .sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // 创建策略
        );

如果用户注销没有关闭浏览器,浏览器中的 cookie 不会被清除,注销登录时可以明确地删除名称为JSESSIONIDcookie

http.logout(logout -> logout
        .deleteCookies("JSESSIONID")
    );

注意:
这不能保证适用于每个 servlet 容器,因此您需要在您的环境中对其进行测试。

下面的方法与容器无关,并且适用于任何支持标头的容器:

HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL));
http.logout(logout -> logout.addLogoutHandler(clearSiteData));

2.3 无效会话策略

通过定义 CustomInvalidSessionStrategy 实现 InvalidSessionStrategy 接口,配置无效会话策略:

http
    .sessionManagement(session -> session
        .invalidSessionStrategy(new CustomInvalidSessionStrategy())
    );

未完待续!!!!

文章来源:https://blog.csdn.net/ctwy291314/article/details/135338912
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。