Session 其实分为客户端 Session 和服务器端 Session
当用户首次与 Web 服务器建立连接的时候,服务器会给用户分发一个 SessionID 作为标识。SessionID 是一个由 24 个字符组成的随机字符串。用户每次提交页面,浏览器都会把这个 SessionID 包含在 HTTP 头中提交给 Web 服务器,这样 Web 服务器就能区分当前请求页面的是哪一个客户端。这个 SessionID 就是保存在客户端的,属于客户端 Session。 其实客户端 Session 默认是以 cookie 的形式来存储的,所以当用户禁用了 cookie 的话,服务器端就得不到 SessionID。这时我们可以使用 url 的方式来存储客户端 Session。也就是将 SessionID 直接写在了 url 中,当然这种方法不常用。
SessionID 如何产生?由谁产生?保存在哪里?
Session 对象存在的意义:
Session 对象存储特定用户会话所需的属性及配置信息。这样,当用户在应用程序的 Web 页之间跳转时,存储在 Session 对象中的变量将不会丢失,而是在整个用户会话中一直存在下去。当用户请求来自应用程序的 Web 页时,如果该用户还没有会话,则 Web 服务器将自动创建一个 Session对象。当会话过期或被放弃后,服务器将终止该会话。Session 对象最常见的一个用法就是存储用户的首选项。例如,如果用户指明不喜欢查看图形,就可以将该信息存储在 Session 对象中。
sessionId 是一个会话的 key,浏览器第一次访问服务器会在服务器端生成一个 session,有一个 sessionId 和它对应。tomcat 生成的sessionId 叫做 JSESSIONID。
session 在访问 tomcat 服务器 HttpServletRequest 的 getSession(true) 的时候创建,tomcat 的 ManagerBase 类提供创建 session-id 的方法:随机数+时间+jvmid;
它存储在服务器的内存中,tomcat 的 StandardManager 类将 session 存储在内存中,也可以持久化到 file,数据库,memcache,Redis 等。客户端只保存 sessionid 到 cookie 中,而不会保存 session,session 销毁只能通过 invalidate 或超时,关掉浏览器并不会关闭session。
Session 会因为浏览器的关闭而删除吗?
Cookie 分为内存中 Cookie(也可以说是进程中Cookie)和硬盘中Cookie。大部分的 Session 机制都使用进程中 Cookie 来保存sessionId的,关闭浏览器后这个进程也就自动消失了,进程中的 Cookie 自然就消失了,那么 sessionId 也跟着消失了,再次连接到服务器时也就无法找到原来的 Session 了。 当然,我们可以在登陆时点击下次自动登录,比如说 CSDN 的 ”记住我一周”,或者我们的购物车信息可以在切换不同浏览器时依然可用。这就要用到我们上文提到的另一种 Cookie 了 — 硬盘中 Cookie,这时 session-id 将长期保存在硬盘上的 Cookie 中,直到失效为止。
Tomcat 中 Session 的创建
ManagerBase 是所有 session 管理工具类的基类,它是一个抽象类,所有具体实现 session 管理功能的类都要继承这个类,该类有一个受保护的方法,该方法就是创建 sessionId 值的方法: (tomcat 的 session 的 id 值生成的机制是一个 随机数 + 时间 + jvm 的 id 值,jvm 的 id 值会根据服务器的硬件信息计算得来,因此不同jvm的id值都是唯一的)
StandardManager 类是 tomcat 容器里默认的 session 管理实现类,它会将 session 的信息存储到 web 容器所在服务器的内存里。
PersistentManagerBase 也是继承 ManagerBase 类,它是所有持久化存储 session 信息的基类,PersistentManager 继承了PersistentManagerBase,但是这个类只是多了一个静态变量和一个 getName 方法,目前看来意义不大,对于持久化存储 session,tomcat 还提供了 StoreBase 的抽象类,它是所有持久化存储 session 的基类,另外 tomcat 还给出了文件存储 FileStore 和数据存储 JDBCStore 两个实现。
session 是解决 http 协议无状态问题的服务端解决方案,它能让客户端和服务端一系列交互动作变成一个完整的事务,能使网站变成一个真正意义上的软件。
会话 Cookie 和持久 Cookie 的区别
如果不设置过期时间,则表示这个 cookie 生命周期为浏览器会话期间,只要关闭浏览器窗口,cookie 就消失了。这种生命期为浏览会话期的 cookie 被称为会话 cookie。会话 cookie 一般不保存在硬盘上而是保存在内存里。 如果设置了过期时间,浏览器就会把 cookie 保存到硬盘上,关闭后再次打开浏览器,这些 cookie 依然有效直到超过设定的过期时间。 存储在硬盘上的 cookie 可以在不同的浏览器进程间共享,比如两个 IE 窗口。而对于保存在内存的 cookie,不同的浏览器有不同的处理方式。
Cookie/Session 使用注意事项
- cookie 大小有限制 4k
- cookie 不能跨浏览器
- cookie 不支持中文
- 如果是安全性较高的数据应存放在 session 中,因为 cookie 存放在客户端总会轻易被不法分子获取
- 如果是访问量特别大的网站,尽量不要在 session 中存储用户数据,因为每个用户存一个 session 会给服务器造成很大的压力
保存 SessionID 的几种方式
- 保存 session-id 的方式可以采用 cookie,这样在交互过程中浏览器可以自动的按照规则把这个标识发送给服务器。
- 由于 cookie 可以被人为的禁止,必须有其它的机制以便在 cookie 被禁止时仍然能够把 session-id 传递回服务器,经常采用的一种技术叫做 URL 重写,就是把 session-id 附加在 URL 路径的后面(网络在整个交互过程中始终保持状态,就必须在每个客户端可能请求的路径后面都包含这个 session-id),附加的方式也有两种:
- 一种是作为 URL 路径的附加信息
- 另一种是作为查询字符串附加在 URL 后面
- 另一种技术叫做表单隐藏字段。就是服务器会自动修改表单,添加一个隐藏字段,以便在表单提交时能够把 session-id 传递回服务器。
Session 什么时候被创建
一个常见的错误是以为 session 在有客户端访问时就被创建,然而事实是直到某 server 端程序(如 Servlet)调用 HttpServletRequest.getSession(true) 这样的语句时才会被创建。
Session 何时被删除
session 在下列情况下被删除:
- 程序调用
HttpSession.invalidate() - 距离上一次收到客户端发送的 session-id 时间间隔超过了 session 的最大有效时间
- 服务器进程被停止
再次注意关闭浏览器只会使存储在客户端浏览器内存中的 session cookie 失效,不会使服务器端的 session 对象失效。
getSession()/getSession(true)、getSession(false) 的区别
getSession()/getSession(true):当 session 存在时返回该 session,否则新建一个 session 并返回该对象getSession(false):当 session 存在时返回该 session,否则不会新建 session,返回 null
使用 isNew() 来判断用户是否为新旧用户的错误做法
public boolean isNew() 方法:如果会话尚未和客户程序(浏览器)发生任何联系,则这个方法返回 true,这一般是因为会话是新建的,不是由输入的客户请求所引起的。
但如果 isNew() 返回 false,只不过是说明他之前曾经访问该 Web 应用,并不代表他们曾访问过我们的 servlet 或 JSP 页面。
因为 session 是与用户相关的,在用户之前访问的每一个页面都有可能创建了会话。因此 isNew() 为 false 只能说用户之前访问过该 Web 应用,session 可以是当前页面创建,也可能是由用户之前访问过的页面创建的。
正确的做法是判断某个 session 中是否存在某个特定的 key 且其 value 是否正确。
Session Cookie 和 Session 对象的生命周期是一样的吗?
当用户关闭了浏览器虽然 session cookie 已经消失,但 session 对象仍然保存在服务器端。
是否只要关闭浏览器,Session 对象就消失了?
程序一般都是在用户做 log off 的时候发个指令去删除 session,然而浏览器从来不会主动在关闭之前通知服务器它将要被关闭,因此服务器根本不会有机会知道浏览器已经关闭。服务器会一直保留这个会话对象直到它处于非活动状态超过设定的间隔为止。
之所以会有这种错误的认识,是因为大部分 session 机制都使用会话 cookie 来保存 session-id ,而关闭浏览器后这个 session-id 就消失了,再次连接到服务器时也就无法找到原来的 session。
如果服务器设置的 cookie 被保存到硬盘上,或者使用某种手段改写浏览器发出的 HTTP 请求报头,把原来的 session-id 发送到服务器,则再次打开浏览器仍然能够找到原来的 session。
恰恰是由于关闭浏览器不会导致 session 被删除,迫使服务器为 session 设置了一个失效时间,当距离客户上一次使用 session 的时间超过了这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把 session 删除以节省存储空间。
由此我们可以得出如下结论: 关闭浏览器,只会是浏览器端内存里的 session cookie 消失,但不会使保存在服务器端的 session 对象消失,同样也不会使已经保存到硬盘上的持久化 cookie 消失。
Session 共享问题
当下的互联网网站为了提高网站安全性和并发量,服务端的部署的服务器的数量往往是大于或等于两台,多台服务器对外提供的服务是等价的,但是不同的服务器上面肯定会有不同的 web 容器,由上面的讲述我们知道 session 的实现机制都是 web 容器里内部机制,这就导致一个 web 容器里所生成的 session 的 id 值是不同的,因此当一个请求到了 A 服务器,浏览器得到响应后,客户端存下的是 A 服务器上所生成的 session 的 id,当在另一个请求分发到了 B 服务器,B 服务器上的 web 容器是不能识别这个 session 的 id 值,更不会有这个 sessionID 所对应记录下来的信息,这个时候就需要两个不同 web 容器之间进行 session 的同步。 一般大型互联公司的网站都是有一个个独立的频道所组成的,例如我们常用的百度,会有百度搜索,百度音乐,百度百科等等,我相信他们不会把这些不同频道都给一个开发团队完成,应该每个频道都是一个独立开发团队,因为每个频道的应用的都是独立的 web 应用,那么就存在一个跨站点的 session 同步的问题,跨站点的登录可以使用单点登录的(SSO) 的解决方案,但是不管什么解决方案,跨站点的 session 共享仍然是逃避不了的问题。
单点登录(Single Sign On)
单点登录,简称为 SSO,是比较流行的企业业务整合的解决方案之一。SSO的定义是在多个应用系统中,用户只需要登录一次就可以访问所有相互信任的应用系统。
概述
很早期的公司,一家公司可能只有一个 Server,慢慢的 Server 开始变多了。每个 Server 都要进行注册登录,退出的时候又要一个个退出。用户体验很不好!你可以想象一下,上豆瓣要登录豆瓣FM、豆瓣读书、豆瓣电影、豆瓣日记... 真的会让人崩溃的。我们想要另一种登录体验:一家企业下的服务只要一次注册,登录的时候只要一次登录,退出的时候只要一次退出,怎么做?
一次注册: 一次注册不难,想一下是不是只要 Server 之间同步用户信息就行了?实际上用户信息的管理才是 SSO 真正的难点,只是作为初学者,我们的难点在于实现 SSO 的技术!
一次登录与一次退出: 回头看看普通商场的故事,什么东西才是保持登录状态关键的东西?记录器(session)?那种叫做 cookie 的纸张?写在纸张上的 ID? 是 session 里面记录的信息跟那个 ID,cookie 不只是记录 ID 的工具而已。客户端持有 ID,服务端持有 session,两者一起用来保持登录状态。客户端需要用 ID 来作为凭证,而服务端需要用 session 来验证 ID 的有效性(ID可能过期、可能根本就是伪造的找不到对应的信息、ID下对应的客户端还没有进行登录验证等)。但是 session 这东西一开始是每个 server 自己独有的,豆瓣FM有自己的 session、豆瓣读书有自己的 session,而记录 ID 的 cookie 又是不能跨域的。所以,我们要实现一次登录一次退出,只需要想办法让各个 server 的共用一个 session 的信息,让客户端在各个域名下都能持有这个 ID 就好了。再进一步讲,只要各个 server 拿到同一个 ID,都能有办法检验出 ID 的有效性,并且能得到 ID 对应的用户信息就行了,也就是能检验 ID。
实现
服务器端
以 server 群如何生成、验证 ID 的方式大致分为两种:
- “共享 Cookie”:这个就是上面提到的共享 session 的方式,我倒觉得叫 “共享 session” 来得好一点,本质上 cookie 只是存储 session-id 的介质,session-id 也可以放在每一次请求的 url 里。毕竟 session 这项机制一开始就是一个 server 一个 session 的,把 session 拿出来让所有 server 共享确实有点奇怪。
- SSO-Token 方式:**因为共享 session 的方式不安全,所以我们不再以 session-id 作为身份的标识。**我们另外生成一种标识,把它取名 SSO-Token(或 Ticket),这种标识是整个 server 群唯一的,并且所有 server 群都能验证这个 token,同时能拿到 token 背后代表的用户的信息。
浏览器端
单点登录还有非常关键的一步,这一步跟 server 端验证 token 的方式无关。
使用最早的 “共享 session” 的方式还是现在的 “token” 方式,身份标识到了浏览器端都要面临这样的一个问题:用户登录成功拿到 token(或者是 session-id)后怎么让浏览器存储和分享到其它域名下?同域名很简单,把 token 存在 cookie 里,把 cookie 的路径设置成顶级域名下,这样所有子域都能读取 cookie 中的 token。这就是共享 cookie 的方式(这才叫共享 Cookie 嘛,上面那个应该叫共享 session)。比如:谷歌公司,google.com 是他的顶级域名,邮箱服务的 mail.google.com 和地图服务的 map.google.com 都是它的子域。但是,跨域的时候怎么办?谷歌公司还有一个域名,youtube.com.
Cookie 跨域问题:blog.csdn.net/chou_out_ma…
Cookie 属性之 Domain、Path
Cookie 有两个很重要的属性:domain 和 path
domain 告诉浏览器当前要添加的 cookie 的域名归属,如果没有明确指明则默认为当前域名,比如通过访问 www.vinceruan.info 添加的 cookie 的域名默认就是 www.vinceruan.info,通过访问 blog.vinceruan.info 所生成的 cookie 的域名就是 blog.vinceruan.info.
path 告诉浏览器当前要添加的 cookie 的路径归属,如果没有明确指明则默认为当前路径,比如通过访问 www.vinceruan.info/java/hotspo… 添加的 cookie 的默认路径就是 /java/ ,通过 blog.vinceruan.info/java/hotspot.html 生成的 cookie 的路径也是 /java/.
解决 Session 相关问题的技术方案
由上所述,session 一共有两个问题需要解决:
- session 的存储应该独立于 web 容器,也要独立于部署 web 容器的服务器
- 如何进行高效的 session 同步
在讲到解决这些问题之前,我们首先要考虑下 session 如何存储才是高效,是存储在内存、文件还是数据库?文件和数据库的存储方式都是将 session 的数据固化到硬盘上,操作硬盘的方式就是 IO,IO 操作的效率是远远低于操作内存的数据,因此文件和数据库存储方式是不可取的,所以将 session 数据存储到内存是最佳的选择。
因此最好的解决方案就是使用分布式缓存技术,例如:memcached 和 redis,将 session 信息的存储独立出来也是解决 session 同步问题的方法~
🤪以上内容对原文进行了一些拓展