在实现会话管理之前,我们还是先来了解一下协议和会话的概念,连协议和会话都不知道是啥,还谈啥管理。
因为我们现在的会话,基本上都是基于HTTP
协议的,所以在讲解会话之前,我再带各位复习一下HTTP
协议。
HTTP:超文本传输协议(HyperText Transfer Protocol
),是一种用于分布式、协作式和超媒体信息系统的应用层协议,是一种在客户端(用户)和服务器端(网站)之间进行请求和响应的规范标准(TCP
),HTTP
是万维网中数据通信的基础。
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 Bis
(httpbis
)工作小组将HTTP/2
标准提议递交至IESG
进行讨论,于2015年2月17日被批准。HTTP/2
标准于2015年5月以RFC 7540
正式发表,取代HTTP 1.1
成为HTTP
的实现标准
基于 请求-响应 模式:
HTTP
协议规定:请求是从客户端发出的,最后由服务器端接受并响应该请求。
无状态保存:
HTTP
是一种不保存用户状态,即无状态(stateless
)的协议。HTTP
协议自身不对请求和响应之间的通信状态进行保存,也就是说在HTTP
这个级别,协议对于发送过的请求或响应都不做持久化处理。
HTTP
协议的无状态,指的是每当有新的请求发送时,就会有对应的响应产生,HTTP
协议本身并不保留之前一切的请求或响应的报文信息。这是为了更快地处理大量事务,确保协议的可伸缩性,而特意把HTTP
协议设计成如此简单的。可随着Web
的不断发展,因无状态而导致业务处理变得棘手的情况也增多了。比如:用户登录到一家购物网站,然后他跳转到该站的其他页面,也需要能继续保持登录状态。针对这个实例,网站为了能够掌握是谁发送的请求,需要保存用户的状态。HTTP/1.1
虽然是无状态的协议,但为了实现期望的保持状态的功能,于是就引入了Cookie
和Session
技术。有了Cookie
和Session
,再用HTTP
协议通信,就可以管理状态了。
无连接:
无连接的含义就是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并可以提高并发性能,不用和每个用户建立长久的连接,请求一次响应一次,服务端和客户端就中断了。
会话(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 Securit
y已经为我们提供了完善的会话管理功能,包括:会话固定攻击、会话超时检测以及会话并发控制等。
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
了:
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
策略。
可以在 Sevlet
容器中设置 Session
的有效期,如下设置有效期为3600
秒(默认):
server:
servlet:
session:
timeout: 3600s
会话超时之后,可以通过Spring Security
设置失效跳转路径:
http.sessionManagement(session -> session
.invalidSessionUrl("/login?error=INVALID_SESSION") // 失效跳转路径
.sessionCreationPolicy(SessionCreationPolicy.ALWAYS) // 创建策略
);
如果用户注销没有关闭浏览器,浏览器中的 cookie
不会被清除,注销登录时可以明确地删除名称为JSESSIONID
的 cookie
:
http.logout(logout -> logout
.deleteCookies("JSESSIONID")
);
注意:
这不能保证适用于每个servlet
容器,因此您需要在您的环境中对其进行测试。
下面的方法与容器无关,并且适用于任何支持标头的容器:
HeaderWriterLogoutHandler clearSiteData = new HeaderWriterLogoutHandler(new ClearSiteDataHeaderWriter(ClearSiteDataHeaderWriter.Directive.ALL));
http.logout(logout -> logout.addLogoutHandler(clearSiteData));
通过定义 CustomInvalidSessionStrategy
实现 InvalidSessionStrategy
接口,配置无效会话策略:
http
.sessionManagement(session -> session
.invalidSessionStrategy(new CustomInvalidSessionStrategy())
);
未完待续!!!!