Session基本概念
服务器第一次接收到请求时,开辟了一块 Session 空间(创建了Session对象),同时生成一个 sessionId ,并通过响应头的 Set-Cookie:JSESSIONID=XXXXXXX
命令,向客户端发送要求设置 Cookie 的响应; 客户端收到响应后,在本机客户端设置了一个 JSESSIONID=XXXXXXX
的 Cookie 信息,该 Cookie 的过期时间为浏览器会话结束
接下来客户端每次向同一个网站发送请求时,请求头都会带上该 Cookie信息(包含 sessionId ), 然后,服务器通过读取请求头中的 Cookie 信息,获取名称为 JSESSIONID 的值,得到此次请求的 sessionId。
Session生命周期
Session存储在服务器端,一般放置在服务器的内存中(为了高速存取),Session在用户访问第一次访问服务器时创建,需要注意只有访问JSP、Servlet等程序时才会创建Session,只访问HTML、IMAGE等静态资源并不会创建Session,可调用request.getSession(true)强制生成Session
。Session什么时候失效?
- 服务器会把长时间没有活动的Session从服务器内存中清除,此时Session便失效。Tomcat中Session的默认失效时间为30分钟。
- 调用Session的invalidate方法。
Session对浏览器的要求:虽然Session保存在服务器,对客户端是透明的,它的正常运行仍然需要客户端浏览器的支持。这是因为Session需要使用Cookie作为识别标志。HTTP协议是无状态的,Session不能依据HTTP连接来判断是否为同一客户,因此服务器向客户端浏览器发送一个名为JSESSIONID的Cookie,它的值为该Session的id(也就是HttpSession.getId()的返回值)。Session依据该Cookie中的JSESSIONID来识别是否为同一用户。
该Cookie为服务器自动生成的,它的maxAge属性一般为-1,表示仅当前浏览器内有效,并且各浏览器窗口间不共享,关闭浏览器就会失效。
为什么session只能显示在一个浏览器,根源就在cookie,关闭浏览器的时候,会话cookie被关闭,sessionid被丢失,再次访问的时候由于找不到sessionid只能创建新的sessionid,因此同一机器的两个浏览器窗口访问服务器时,会生成两个不同的Session。但是由浏览器窗口内的链接、脚本等打开的新窗口除外。这类子窗口(在链接上右击,在弹出的快捷菜单中选择在新窗口中打"时,子窗口便可以访问父窗口的Session)会共享父窗口的Cookie,因此会共享一个Session,以下是一个子窗口的示例:
所以说Session并没有被销毁,只是我们打开另一个浏览器使用的是一个新的session,我们用新的session而不用旧的,相当于抛弃了它,实际上它还默默的在服务器端活30分钟。相反request和response每次请求后确确实实被销毁了。
Session常用方法
HttpServletRequest 接口中提供了两个方法来创建 HttpSession 实例,并进行一些简单的操作,通过看Session的源码可以看出:
public interface HttpSession { long getCreationTime(); String getId(); long getLastAccessedTime(); ServletContext getServletContext(); void setMaxInactiveInterval(int var1); //设置 Session 的有效时间 int getMaxInactiveInterval(); /** @deprecated */ @Deprecated HttpSessionContext getSessionContext(); Object getAttribute(String var1); /** @deprecated */ @Deprecated Object getValue(String var1); Enumeration<String> getAttributeNames(); /** @deprecated */ @Deprecated String[] getValueNames(); void setAttribute(String var1, Object var2); /** @deprecated */ @Deprecated void putValue(String var1, Object var2); void removeAttribute(String var1); /** @deprecated */ @Deprecated void removeValue(String var1); void invalidate(); boolean isNew();
Session的缺点
虽然Session很好的避免了Cookie的通信复杂度,但是对于像京东天猫这样服务器就需要记录上亿个session id,这对服务器来说显然不友好,因为线上为了应对大量请求,我们一定是使用服务器集群去分散处理请求的,那么如果用户UA第一次请求了服务器F1,生成了session id,下次请求被路由到了服务器F2,而F2上没有UA的。
- 这个时候服务器说,那我这样吧,我把用户请求的服务器记录下来,粘住特定的session id请求到特定的服务器上,这就是session 粘连策略
这样也有风险,万一特定的这个服务器挂了,这个服务器上记录的session就都丢失了,某些特定用户就永远登录不上了
- 这个时候服务器说,那这样吧,只要有session产生就复制到各个服务器上,这样就有保障了,这就是session的复制策略
但是这样复制来复制去太麻烦,太耗费性能了,如果有一个统一的机器负责管理session就好了,而且这个统一的机器最好是个集群,这样防止单点挂掉的风险。
- 这样就引入了中间件集群这种方式去解决问题,例如引入Redis集群去处理,这就是session 共享策略。
但是这样仍然将session置于一个不确定不稳定的状态,因为第三方的集群也可能有宕机的危险
Token机制
那么如果服务端不对session进行记录,只是验证会话有效性呢?也就是客户端第一次请求后服务端依据用户数据计算出一个token签名然后返回给客户端,客户端第二次请求的时候把数据和token签名一起发来,服务端再用相同算法对数据进行签名,得出的token签名一致即认证通过。这就是token的概念。
Token解决购物车问题
继续延续上文的购物车问题,有了token之后可以不必考虑session的存储问题,分布式集群问题,按照如下图的流程,我们在
从上图中可以看出,服务端不存储任何内容,token 只存储在浏览器中,服务端却没有存储,server 会有一套校验机制,校验这个 token 是否合法。那么token既然没有存储到服务端,不像 session 那样根据 sessionId 找到 userid,怎么定位请求的用户呢? 其实token 本身携带 uid 信息。
当然其实我们可以看的出,Token并没有在服务端存储数据,那么怎么记录购物车信息呢,其实Token是一种轻量级的用户认证方案,只能认证当前用户登录的有效性,而并不能追踪用户行为,所以可以说Token在用户验证领域很专业,但是用途比较单一,如果要解决这里的购物车问题,还需要token结合一些数据存储技术,例如数据库或者redis,token负责验证当前用户,数据库或者redis负责进行购物车存取操作。
Token的基本概念
token 验证是无状态的,服务器不记录哪些用户登录了或者哪些 JWT 被发布了,而是每个请求都带上了服务器需要验证的 token,token 放在了 Authorization header 中,形式是 Bearer { JWT }
,但是也可以在 post body
里发送,甚至作为 query parameter
。JWT的结构如下;
可以看到 token 主要由三部分组成:
- header:指定了签名算法。
- payload:可以指定用户 id,过期时间等非敏感数据。
- Signature:签名,server 根据 header 知道它该用哪种签名算法,再用密钥根据此签名算法对 head+payload 生成签名,这样一个 token 就生成了。
当 server 收到浏览器传过来的 token 时,它会首先取出 token 中的 header+payload,根据密钥生成签名,然后再与 token 中的签名比对,如果成功则说明签名是合法的,即 token 是合法的。而且你会发现 payload 中存有我们的 userId,所以拿到 token 后直接在 payload 中就可获取 userid,避免了像 session 那样要从 redis 去取的开销。
整体的验证流程如下:
- 用户输入登录信息
- 服务器判断登录信息正确,返回一个 token
- token 存储在客户端,大多数通常在 local storage,但是也可以存储在 session storage 或者 cookie 中。
- 接着发起请求的时候将 token 放进 Authorization header,或者同样可以通过上面的方式。
- 服务器端解码 JWT 然后验证 token,如果 token 有效,则处理该请求。
- 一旦用户登出,token 在客户端被销毁,不需要经过服务器端。
可以看的出,token就是一个令牌的概念。
Token的缺点
既然 token 这么好,那为什么我们依然大多数使用场景还是Session?token也有它自身的缺点:
- token 太长了,token 是 header,payload 编码后的样式,所以一般要比 sessionId 长很多,很有可能超出 cookie 的大小限制(cookie 一般有大小限制的,如 4kb)。如果你在 token 中存储的信息越长,那么 token 本身也会越长,这样的话由于你每次请求都会带上 token,对请求来是个不小的负担。
- token不太安全,我们说 token 是存在浏览器的,既然它太长放在 cookie 里可能导致 cookie 超限,那就只好放在 local storage 里。这样会造成安全隐患,因为 local storage 这类的本地存储是可以被 JS 直接读取的。
所以 token 更适合一次性的命令认证,设置一个比较短的有效期
应用场景的划分
详细的比较下Session、Cookie以及Token的区别和应用场景。
Session和Cookie的使用场景区别
Cookie 和 Session都是⽤来跟踪浏览器⽤户身份的会话⽅式,但是两者的应⽤场景不太⼀样。
- Cookie⼀般⽤来保存⽤户信息,通过这种方式不需要用户反复去输入用户信息去登录
- 我们在 Cookie 中保存已经登录过得⽤户信息,下次访问⽹站的时候⻚⾯可以⾃动帮你登录的⼀些基本信息给填了;
- ⼀般的⽹站都会有保持登录也就是说下次你再访问⽹站的时候就不需要重新登录了,这是因为⽤户登录的时候我们可以存放了⼀个 Token 在 Cookie中,下次登录的时候只需要根据 Token 值来查找⽤户即可(为了安全考虑,重新登录⼀般要将 Token重写);
- 登录⼀次⽹站后访问⽹站其他⻚⾯不需要重新登录。
- Session 的主要作⽤就是通过服务端记录⽤户的状态。 典型的场景是购物⻋,当你要添加商品到购物⻋的时候,系统不知道是哪个⽤户操作的,因为 HTTP 协议是⽆状态的。服务端给特定的⽤户创建特定的 Session 之后就可以标识这个⽤户并且跟踪这个⽤户了。
- Cookie 数据保存在客户端(浏览器端),Session 数据保存在服务器端。Cookie 存储在客户端中,⽽Session存储在服务器上,相对来说 Session 安全性更⾼。如果要在Cookie 中存储⼀些敏感信息,不要直接写⼊ Cookie 中,最好能将 Cookie 信息加密然后使⽤到的时候再去服务器端解密
可以通俗的这么理解,Cookie在客户端保存用户信息,知道用户干了什么事儿,Session则在服务端给用户创建了相应的数据存档,下次只要对应用户登录就能读档,那么要想找到用户正确的访问内容需要二位配合了。
JWT 和 Session+Cookies 基于身份验证的区别
JWT 和 Session Cookies 都提供安全的用户身份验证,但是它们有以下几点不同
- 密码签名,JWT 具有加密签名,而 Session Cookies 则没有。
- 连接状态:JWT 是无状态的,因为声明被存储在客户端,而不是服务端内存中。身份验证可以在本地进行,而不是在请求必须通过服务器数据库或类似位置中进行。 这意味着可以对用户进行多次身份验证,而无需与站点或应用程序的数据库进行通信,也无需在此过程中消耗大量资源。
- 可扩展性,Session Cookies 是存储在服务器内存中,这就意味着如果网站或者应用很大的情况下会耗费大量的资源。由于 JWT 是无状态的,在许多情况下,它们可以节省服务器资源。因此 JWT 要比 Session Cookies 具有更强的可扩展性。
- 是否可跨域,JWT 支持跨域认证,Session Cookies 只能用在单个节点的域或者它的子域中有效。如果它们尝试通过第三个节点访问,就会被禁止。如果你希望自己的网站和其他站点建立安全连接时,这是一个问题。使用 JWT 可以解决这个问题,使用 JWT 能够通过多个节点进行用户认证,也就是我们常说的跨域认证。
需要说明的是,JWT仅仅是一种用户身份认证的解决方案,如果我们进行购物车的设置,JWT搞不定。
总结一下
这篇Blog参照了很多内容,共写了两天,有很多地方也很纠结没有想通,现在进行一下思路整理吧:
- cookie就是由服务端生成返回给客户端的,并且在第一次生成时默认给放置了一个Jsession id
- 浏览器请求服务端时,通过Jsessionid找到对应的session,然后进行对应存取操作,这个查找过程是tomcat帮我们做的,我们在代码里不需要关心,也就是说我们不用调用什么通过sessionid获取session对象之类的方法。
- 一个客户端浏览器对应一个目标站点【localhost】只产生一个session、一个cookie,我们追踪用户会话,前提也是在这个范围内讨论的,如何跨域生效、如何跨浏览器记忆之类的问题不在讨论范围内,我们只专注于当前客户端浏览器对于当前目标站点是如何进行会话追踪的
- JSP中的内置对象session就是和Servle中的这个HttpSession生成的,所以之前我们讨论JSP的作用域时其实也大概提了下,只是当时还不知道为什么session的作用域是一个浏览器,并且为什么能跨请求,现在我们知道了,是因为整个浏览器请求目标站点范围内,用的sessionid是同一个,如果cookie一直存在,那么HttpSession就还是那一个,所以作用范围才是这么大。而request只不过是doGet的一个局部变量,当然会请求一次后作用域失效。
- 对于用户身份验证这种场景,最好用token,因为session操作起来太复杂,没必要为了验证用户身份搭建一套redis,而如果有其它诉求,例如追踪用户的一些信息,还是要使用cookie+session,因为token不在服务端存储数据。
- cookie+session这种组合用途也要细分,cookie由于存储在客户端本地,所以最好不要放一些敏感信息,而cookie由于存放在客户端本地,所以可以存放很长时间,所以cookie适合防止要求长期存储但不敏感的数据,而session由于运行在服务器内存中或者redis集群中,而且通常有个过期时间,所以适合存放一些短期但重要的数据
- 当禁用cookie时,需要URL重写技术对Session进行跟踪,这里不做更多讨论
- 隐藏表单域提交也属于一种会话追踪技术,这里也不做更多的讨论
本篇Blog提到的购物车的例子,由于购物车的信息在各大电商网站比较重要,所以一般会落库并进行数据分析,所以不一定是存储在session或者cookie中,这里只是举个例子,具体以业务场景为准。