无状态的 HTTP
首先我们知道HTTP协议是无状态的,也就是说每一次请求都是独立的,和上一次请求没有关系,也不会携带信息,那么我们访问网站经常需要存储用户信息,怎么办呢,cookie/ session机制就是一种在客户端与服务器之间保持状态的解决方案。
session中文意思为“回话”,代表服务器与浏览器的一次会话过程,这个过程是连续的,也可以时断时续的,它保存了本次客户端与服务端的通信信息。且session数据是存放在服务端的。
cookie
Cookie(复数形态Cookies),中文名称为“小型文本文件”或“小甜饼”。指某些网站为了辨别用户身份而储存在用户本地终端(Client Side)上的数据(通常经过加密)。 ——摘自维基。
原理图:
第一次访问网站的时候,浏览器发出请求,服务器响应请求后,会将cookie放入到响应请求中,在浏览器第二次发请求的时候,会把cookie带过去,服务端会辨别用户身份,当然服务器也可以修改cookie内容
如何通俗的解释:
Cookie 是浏览器访问服务器后,服务器传给浏览器的一段数据。
浏览器需要保存这段数据,不得轻易删除。
此后每次浏览器访问该服务器,都必须带上这段数据。
所以就能得出cookie的特点:
服务器通过设置Set-Cookie 响应头来设置 cookie
浏览器得到 cookie 后,每次同源的请求的请求头都会带上 cookie
服务器读取 cookie 就知道了登录用户的信息(如账户名等)
cookie 实际上存储在本地计算机的硬盘里
cookie 的最大储存量一般只有4K
那么cookie有哪些缺点呢:
Cookie很容易被用户篡改( Session 可以解决这个问题,防止用户篡改)
Cookie 的默认有效期理论上在用户关闭页面后就失效,实际上在在20分钟左右,不同浏览器策略不同。但是后端可以强制设置有效期(如何设置见下文)。
-
Cookie 也有一定的同源策略,不过跟 AJAX 的同源策略稍微有些不同。如:
当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上 baidu.com 对应的 Cookie
当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的Cookie,还会带上 qq.com 的 Cookie
另外 Cookie 还可以根据路径做限制,请自行了解,这个功能用得比较少。
有了Cookie,我们就可以实现这两件事 :
第一个作用是识别用户身份。如:
比如用户 A 用浏览器访问了http://a.com,那么http://a.com 的服务器就会立刻给 A 返回一段数据「uid=1」(这就是 Cookie)。当 A 再次访问 http://a.com的其他页面时,就会附带上「uid=1」这段数据。这样服务端就知道 A 是谁了。第二个作用是记录历史。如:
假设 http://a.com 是一个购物网站,当 A 在上面将商品 B1 、B2 加入购物车时,JS 可以改写 Cookie,改为「uid=1; cart=B1,B2」,表示购物车里有 B1 和 B2 两样商品了。这样一来,当用户关闭网页,过三天再打开网页的时候,依然可以看到 B1 、B2躺在购物车里,因为浏览器并不会无缘无故地删除这个 Cookie。(需要注意的是:上面的例子只是为了让大家了解 Cookie 的作用而构想出来的,实际的网站使用 Cookie 时会更谨慎一些。)
cookie不可跨域
我就几个例子你就懂了,当我打开百度的网页,我要设置一个cookie的时候,我的指令如下
javascript:document.cookie='myname=laihuamin;path=/;domain=.baidu.com';复制代码
javascript:document.cookie='myname=huaminlai;path=/;domain=.google.com';复制代码
当我将这两个语句都放到浏览器控制台运行的时候,你会发现一点,注意,上面两个cookie的值是不相同的,看清楚
显而易见的是,真正能把cookie设置上去的只有domain是.baidu.com的cookie绑定到了域名上,所以上面所说的不可跨域性,就是不能在不同的域名下用,每个cookie都会绑定单一的域名
cookie参数
cookie/session部分
Session是对于服务端来说的,客户端是没有Session一说的。Session是服务器在和客户端建立连接时添加客户端连接标志,最终会在服务器软件(Apache、Tomcat、JBoss)转化为一个临时Cookie发送给给客户端,当客户端第一请求时服务器会检查是否携带了这个Session(临时Cookie),如果没有则会添加Session,如果有就拿出这个Session来做相关操作。
session由谁创建?什么样的结构?谁来维护?
Session 整体的工作过程。 大概分为这几个步骤:
浏览器第一次请求网站, 服务端生成 Session ID。
把生成的 Session ID 保存到服务端存储中。
把生成的 Session ID 返回给浏览器,通过 set-cookie。
浏览器收到 Session ID, 在下一次发送请求时就会带上这个 Session ID。
服务端收到浏览器发来的 Session ID,从 Session 存储中找到用户状态数据,会话建立。
-
此后的请求都会交换这个 Session ID,进行有状态的会话。
** 当浏览器一次请求时,服务器会从检查cookie里面是否包含sessionId,如果包含说明我们之前创建过session了,就去查找相应的session,如果没查到就创建一个新的;如果cookie里面不包含sessionId,说明之前没有创建过,就开始创建session,创建后的session由tomcat来维护。**
session的创建:
当我们浏览器第一次访问服务器时,tomcat的ManagerBase类提供创建sessionid的方法,ManagerBase是所有session管理工具类的基类,它是一个抽象类,所有具体实现session管理功能的类都要继承这个类,该类有一个受保护的方法,该方法就是创建sessionId值的方法:tomcat的session的id值生成的机制是一个随机数加时间加上jvm的id值,jvm的id值会根据服务器的硬件信息计算得来,因此不同jvm的id值都是唯一的,创建完session和sessionId后,将sessionId返回给cookie,下次请求时,通过sessionId来查找相应的session信息,就找到了之前保存的用户信息了。
session的销毁:
当回话过期,过着需要销毁时,程序调用HttpSession.invalidate();程序关闭。并且session并不会因浏览器的关闭直接销毁,迫使服务器为seesion设置了一个失效时间,当距离客户端上一次使用session的时间超过这个失效时间时,服务器就可以认为客户端已经停止了活动,才会把session删除以节省存储空间。
存储结构:创建的session存储在服务器tomcat内存里,当服务器进程被停止或者重启的时候,内存里的session也会被清空,那么我们session相关的信息怎么存储呢,首先它必须被同步操作,因为在多线程环境下session是线程间共享的,而web服务器一般情况下都是多线程的(为了提高性能还会用到池技术);其次,这个数据结构必须容易操作,最好是传统的键值对的存取方式,而且我们也会经常使用setAttribute(String name, Object value, boolean notify),查看源码可以发现其存储结构为concurrentHashMap。protected Map attributes = new ConcurrentHashMap();就很明确了,原来tomcat使用了一个ConcurrentHashMap对象存储数据,这是java的concurrent包里的一个类。它刚好满足了我们所猜测的两点需求:同步与易操作性。 那么tomcat又是用什么数据结构来存储所有的session对象呢?果然还是ConcurrentHashMap(在管理session的org.apache.catalina.session. ManagerBase类里):
protected Map sessions = new ConcurrentHashMap();
传输安全
最后再聊聊传输安全,有一种叫做 Session ID 劫持的,假如 Session ID 是基于 HTTP 协议传输的,因为是明文传输,那么它就可能被中间的路由器劫持。 攻击者得到 Session ID 后,把它带到自己的请求中,就能够进入你的账户。
所以一些 Web 框架还提供了 Session 的一些安全保护,比如间隔时间内动态刷新 Session ID,加上 Token 等。但这些也无法完全保证不被中间人看到。 其实从这个角度也间接体现了为什么 HTTPS 这么重要。
session存在的问题
安全性,session劫持,这个前面已经举过例子了;
增加服务器压力,因为session是直接存储在服务器的内存中的;
如果存在多台服务器的话,还存在session同步问题,当然如果只有一台tomcat服务器的话,也就没有session同步的事情了,然而现在一般的应用都会用到多台tomcat服务器,通过负载均衡,同一个会话有可能会被分配到不同的tomcat服务器,因此很可能出现session不一致问题;解决session同步问题,实际上主要是保证能够抽离出一块共享空间存放session信息,且这块空间不同的tomcat服务器都可以访问到;一般这块共享的空间可以是数据库,或者某台服务器的内存空间,甚至硬盘空间,或者客户端的cookie也是可以的;
无cookie保存sessionid机制
重写url带sessionid
如果客户端禁用了cookie,那么您可以在每个URL后面添加一些额外的数据来区分会话,服务器能够根据这些数据来关联session标识符。举例来说,http://www.51gjie.com/java?sessionid=12345, session标识符为sessionid=12345,服务器可以用这个数据来识别客户端。
相比而言,重写URL是更好的方式来,就算浏览器不支持cookies也能工作,但缺点是您必须为每个URL动态指定session ID,就算这是个简单的HTML页面。
隐藏表单识别sessionid
如果客户端禁用了cookie,那么您可以发送一个隐藏的HTML表单域和一个唯一的session ID,就像下面这样: <input type="hidden" name="sessionid" value="12345">这个条目意味着,当表单被提交时,指定的名称和值将会自动包含在GET或POST数据中。每当浏览器发送一个请求,session_id的值就可以用来保存不同浏览器的轨迹。
这种方式可能是一种有效的方式,但点击<A HREF>标签中的超链接时不会产生表单提交事件,因此隐藏表单域也不支持通用会话跟踪。
cookie 和 session的关系:
cookie只是实现session的其中一种方案。虽然是最常用的,但并不是唯一的方法。禁用cookie后还有其他方法存储,比如放在url中
现在大多都是Session + Cookie,但是只用session不用cookie,或是只用cookie,不用session在理论上都可以保持会话状态。可是实际中因为多种原因,一般不会单独使用
用session只需要在客户端保存一个id,实际上大量数据都是保存在服务端。如果全部用cookie,数据量大的时候客户端是没有那么多空间的。
如果只用cookie不用session,那么账户信息全部保存在客户端,一旦被劫持,全部信息都会泄露。并且客户端数据量变大,网络传输的数据量也会变大
token:
token 也称作令牌,由uid+time+sign[+固定参数]
token 的认证方式类似于临时的证书签名, 并且是一种服务端无状态的认证方式, 非常适合于 REST API 的场景. 所谓无状态就是服务端并不会保存身份认证相关的数据。
组成
uid: 用户唯一身份标识
time: 当前时间的时间戳
sign: 签名, 使用 hash/encrypt 压缩成定长的十六进制字符串,以防止第三方恶意拼接
固定参数(可选): 将一些常用的固定参数加入到 token 中是为了避免重复查库
存放
token在客户端一般存放于localStorage,cookie,或sessionStorage中。在服务器一般存于数据库中
前端可以在每次请求的时候带上 Token 证明自己的合法地位。如果这个 Token 在服务端持久化(比如存入数据库),那它就是一个永久的身份令牌。
token认证流程
token 的认证流程与cookie很相似:
用户登录,成功后服务器返回Token给客户端。
客户端收到数据后保存在客户端
客户端再次访问服务器,将token放入headers中
服务器端采用filter过滤器校验。校验成功则返回请求数据,校验失败则返回错误码
当讨论基于token的身份验证时,一般都是说的JSON Web Tokens(JWT)。虽然有着很多不同的方式实现token,但是JWT已经成为了事实上的标准,所以后面会将JWT和token混用。
jwt token 工作流程如下:
用户输入登陆凭据;
服务器验证凭据是否正确,然后返回一个经过签名的token;
客户端负责存储token,可以存在local storage,或者cookie中;
对服务器的请求带上这个token;
服务器对JWT进行解码,如果token有效,则处理该请求;
一旦用户登出,客户端销毁token。
token相对cookie的优势
无状态
基于token的验证是无状态的,这也许是它相对cookie来说最大的优点。后端服务不需要记录token。每个令牌都是独立的,包括检查其有效性所需的所有数据,并通过声明传达用户信息。
服务器唯一的工作就是在成功的登陆请求上签署token,并验证传入的token是否有效。
防跨站请求伪造(CSRF)
举个CSRF攻击的例子,在网页中有这样的一个链接: [图片上传失败...(image-50f42c-1584891040652)],假设你已经通过银行的验证并且cookie中存在验证信息,同时银行网站没有CSRF保护。一旦用户点了这个图片,就很有可能从银行向tom这个人转1000块钱。
但是如果银行网站使用了token作为验证手段,攻击者将无法通过上面的链接转走你的钱。(因为攻击者无法获取正确的token)
跨域请求和同源策略参考:https://www.yinchengli.com/2016/09/16/cross-domain/
多站点使用
cookie绑定到单个域。foo.com域产生的cookie无法被bar.com域读取。使用token就没有这样的问题。这对于需要向多个服务获取授权的单页面应用程序尤其有用。
使用token,使得用从myapp.com获取的授权向myservice1.com和myservice2.com获取服务成为可能。
支持移动平台
好的API可以同时支持浏览器,iOS和Android等移动平台。然而,在移动平台上,cookie是不被支持的。
性能
一次网络往返时间(通过数据库查询session信息)总比做一次HMACSHA256计算的Token验证和解析要费时得多。
uuid
UUID是Universally Unique Identifier的缩写,它是在一定的范围内(从特定的名字空间到全球)唯一的机器生成的标识符
uuid 可以用来生成token
比如可以根据用户及设备等信息生成一个唯一的token,uid用于识别用户,uuid用于识别设备
参考:https://juejin.im/post/59d1f59bf265da06700b0934(cookie)
http://www.sanshi30.cn/blog/articles/93.html (session)
https://www.jianshu.com/p/ce9802589143 (cookie、session)