HTTP无状态协议
是指该协议对事件的处理过程没有记忆能力,当后续的步骤需要上一步的信息时,则需要重传,即需要携带上一次的信息。
因此,对于存在依赖性的访问请求,则下一次的传递需要携带上一次传递的信息,依次不断的叠加,会导致传输的信息量会越来越大,服务器响应较慢。
HTTP无状态访问
在90年代,浏览器刚出现的时候,它的作用也仅仅是浏览一些文本而已,彼此之间不存在依赖关系(在后面的学习中,我们把这种依赖关系
理解为会话
)。因此,服务器不需要做任何的记录,浏览器请求什么,服务器就返回什么,彼此之间清清楚楚,不存在任何的爱恨情仇,双方的关系非常的融洽。
随着技术的发展,特别是当客户端与服务器进行动态交互的Web应用程序出现之后,HTTP的无状态特征严重影响了这类Web应用程序,Web应用的交互是双向,需要承前启后的,总不能每次都失忆。
就像夏洛特烦恼中,夏洛和大爷的对话:
夏洛:大爷,楼上322住的是马冬梅家吗?
大爷:马冬什么?
夏洛:马冬梅。
大爷:什么冬梅啊?
夏洛:马冬梅啊。
大爷:马什么梅啊?
夏洛:……行,大爷,您先凉快吧。
如简单的购物车程序也要知道用户到底在之前选择了什么商品,总不能所有用户都使用一个购物车吧,所以需要把每个用户都区分开,服务器需要记录每个用户的会话,又因为HTTP是无状态的,那么就要想办法为每个用户保持一个会话。
因此市面上出现了两种保持HTTP连接状态的技术:Cookie
和Session
。
Cookie是客户端
保持HTTP会话状态的技术,而Session是服务端
保持HTTP会话状态的技术。通常情况下,这两种技术是结合使用的。
下面,我们将分别学习Cookie、Session,并针对它们的不足,学习Token的原理与使用。
在学习Cookie之前,我们先考虑以下问题:
(1)什么是Cookie,Cookie的作用是什么?
(2)Cookie的工作机制是什么?
(3)Cookie的基本属性有哪些?
针对上面的问题,我们一一来做解答。
按照官方的定义:
Cookie是保存在客户端浏览器中的文本文件(key-value形式),这个文件与访问的特定的Web页面(文档)关联在一起,并且保存在本地的客户端中,Cookie 最根本的用途是帮助 Web 站点保存有关访问者的信息。
举个例子,当客户端浏览器访问服务端时,服务端会记录每个用户的访问信息并以Cookie文件
的形式保存在客户端,当用户再次访问服务端的特定页面时,服务端会首先检查客户端携带的Cookie中的用户身份信息,从而保持了会话的进行。
什么意思呢,我们举个生活中的例子:
当我们去银行办理储蓄业务时,柜员第一次给我们办了张银行卡,里面存放了身份证、密码、手机等个人信息。当我们下次再来这个银行时,银行机器能识别这种卡,从而能够直接办理业务。
那么问题来了,Cookie到底是如何起作用的呢?
事实上,当用户每次访问服务器时,Web应用程序都可以读取Cookie包含的信息,当用户再次访问该页面时,浏览器就会在本地硬盘上查找与该URL相关的Cookie,如果该 Cookie 存在(Cookie在不过期的情况下),浏览器就将它添加到request header
的Cookie
字段中,与http请求
一起发送到该站点。
我们以登录为例:
实现登陆我们就必须需要cookie, 使用 cookie 来保存用户的信息。
在请求某个域的时候,http 会自动将这个域的 Cookie 放到请求头当中。所以我们只需要在登陆用户成功后,将必要的信息通过设置 Set-Cookie 这个响应头返回给客户端。
浏览器会自动将该头的信息存储到当前域名下的 Cookie 中,当下次用户请求的时候,http 协议会自动将该 cookie 带上。我们就可以在每次的请求的请求头当中拿到该 Cookie, 然后去判断用户是否登陆,进而根据用户是否登陆进行相应的处理。
NOTE:
Cookie被添加到
request header
中是「浏览器的行为」,存储在cookie
的数据「每次」都会被浏览器「自动」放在http
请求中。Cookie过多的信息会增加网络流量,因此,我们必须考虑什么样的数据才能放入到Cookie中,我们用的最多的是身份验证信息。
因此,当用户第一次访问并登陆一个网站的时候,Cookie的设置以及发送会经历以下4个步骤:
(1)客户端发送一个请求数据
(2)服务器发送一个HttpResponse响应到客户端,该响应包含Set-Cookie头部
(3)客户端保存Cookie,之后向服务器发送请求时,HttpRequest请求中会包含一个Cookie的头部
(4)服务器返回响应数据
携带Cookie访问
以登录
为例,一般来说我们不会在用户登陆后将用户的用户名和密码设置到 Cookie 中,从而直接在请求头拿到用户名和密码
,然后去判断用户是否登陆,这些信息过于敏感,暴露出来十分危险。
通常情况下,正确的做法应该是不管用户是否登录,都应该通过 Cookie 给用户返回一个标志(通常称为session_id或者token 等,在下面的部分具体展开),并需要设置其过期时间
、httpOnly
、path
等。每次用户请求时,都根据这个标志在服务端去取根据这个标志存在服务端的用户的信息。
因此,Cookie一般由以下几部分构成
(1)Name/Value:
该属性是设置Cookie的名称及相对应的值,该值通常是保留在Cookie中的用户信息。对于认证Cookie,Value值包括Web服务器所提供的访问令牌(继续往下看,下面内容会学习令牌)。
(2)Expires属性:
该属性是设置Cookie的生存周期。
在默认情况下,Cookie是临时存在的。
当一个浏览器窗口打开时,可以设置Cookie,只要该浏览器窗口没有关闭,Cookie就一直有效,而一旦浏览器窗口关闭后,Cookie也就随之消失。
如果想要cookie在浏览器窗口之后还能继续使用,就需要为Cookie设置一个生存期。所谓生存期也就是Cookie的终止日期,在这个终止日期到达之前,浏览器都可以读取该Cookie。一旦终止日期到达之后,该cookie将会从cookie文件中删除。
(3)Path属性:
定义了Web站点上可以访问该Cookie的目录
。比如,设置为"/"
表示允许当前域名下的所有路径都可以使用该Cookie。
(4)Domain属性:
指定了可以访问该 Cookie 的 Web 站点
或域
,默认为当前域。
如当前域是www.simon.item,那么它的子域www.simon.item.count共享父域的Cookie,对于不同的域或者平行域则无法共享该Cookie。
(5)Secure属性:
secure
是 cookie 的安全标志,指定是否使用HTTPS
安全协议发送Cookie。
(6)HTTPOnly 属性 :
用于防止客户端脚本通过document.cookie
属性访问Cookie,有助于保护Cookie不被跨站脚本攻击窃取或篡改。
综上所述,服务器通过发送一个名为 Set-Cookie
(Cookie是一个对象,需要自己new出来) 的HTTP头来创建一个cookie,并作为 Response Headers 的一部分。每个Set-Cookie
表示一个 Cookie(如果有多个Cookie,需写多个Set-Cookie),每个属性也是以名/值
对的形式(除了secure
),属性间以分号加空格
隔开。格式如下:
Set-Cookie: name1=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name2=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
Set-Cookie: name3=value[; expires=GMTDate][; domain=domain][; path=path][; secure]
......
除了在服务器端
设置,还可以在客户端
设置Cookie
document.cookie = "test1=myCookie1;"
document.cookie = "test2=myCookie2; domain=.google.com.hk; path=/webhp"
document.cookie = "test3=myCookie3; domain=.google.com.hk; expires=Sat, 08 AUG 2021 16:00:00 GMT; secure"
document.cookie = "test4=myCookie4; domain=.google.com.hk; max-age=10800;"
NOTE:只有name/value
可以被发送至服务端,其余的参数仅仅是服务端给客户端的指示或者客户端自身的约束。
由于Cookie的出现可以解决HTTP的无状态,维持会话的正常进行,我们使用Cookie的应用场景通常有以下几种:
(1)购物车(网购)
(2)自动登录(登录账号时的自动登录)
(3)精准广告
平常浏览网页时有时会推出商品刚好是你最近浏览过,买过的类似东西,这些是通过cookie记录的。
虽然Cookie有很多优点,其缺点也很明显。
(1)每个域的Cookie总数是有限的,不同浏览器之间各有不同
如Firefox限制每个域最多50个cookie,IE限制50个,Chrome对于每个域的Cookie数量没有规定
(2)Cookie大小限制
大多数浏览器限制Cookie的大小为4KB
(3)安全性
Cookie文件中可能含有涉密信息,可能会导致信息泄露。
由于Cookie存储信息的大小不仅有限制,而且还存在信息安全问题,因此,必须想办法把一些具体的信息存储到服务端上。因此,Session的出现可以解决这个问题。
Session
在计算机中被称为会话控制
。
Session对象
可以存储特定用户会话所需的属性
及配置信息
,它通过给不同的用户发送session_id并放在Cookie中,然后具体的数据则是保存在session中。
如果用户已经登录,则服务器会在Cookie中保存一个session_id
,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息,从而保持会话连接的状态。
图解如下:
Cookie+Session
Session流程对应的后端代码如下:
package xdp.gacl.session;
import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
public class SessionDemo1 extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
response.setCharacterEncoding("UTF=8");
response.setContentType("text/html;charset=UTF-8");
//使用request对象的getSession()获取session,如果session不存在则创建一个
HttpSession session = request.getSession();
//将数据存储到session中
session.setAttribute("data", "小郎同学");
//获取session的Id
String sessionId = session.getId();
//判断session是不是新创建的
if (session.isNew()) {
response.getWriter().print("session创建成功,session的id是:"+sessionId);
}else {
response.getWriter().print("服务器已经存在该session了,session的id是:"+sessionId);
}
}
public void doPost(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
doGet(request, response);
}
}
随着访问服务器的用户数量的增多,服务器上保存的Session
也日益增多,这对服务器来说是个巨大的开销,对于单个服务器的Web应用汇总,大量的Session
会占用比较多的内存。
不仅如此,在分布式系统中,由于负载均衡对请求转发,这样就有可能导致同一个用户的请求分发到不同的服务器上,会出现不能获取不到Session的情况。
Session不一致原理图
Session不一致原理图
解决Session不一致的情况通常有三种方法。
1、反向代理hash一致性
这类方法是使用Nginx
的负载均衡算法其中的hash_ip
算法将ip
固定到某一台服务器上,这样就不会出现session
共享问题,因为同一个ip
访问下,永远是同一个服务器。
反向代理hash一致性
但是这样的做法也是不保险的,当绑定的机器挂了,请求还是会被分发到别的机器上去。
因此,可以对多个服务器间进行Session复制
,这样就可以保障每个服务器上都包好全部请求的session
2、Session复制
Session复制
多个服务器间进行Session复制非常占用内网的带宽,每个服务器都有相同的Session不仅导致服务器的空间利用降低
,而且受内存的限制,无法水平扩展
。
既然每个服务器都包含相同的Session,我们可以把Session统一存储管理。
3、共享Session服务器
该方法是把Session集中存储一个服务器上
,Session服务器是将有状态的Session信息与无状态的应用服务器相分离,所有的请求都来访问这个session服务器。
共享Session服务器
这种方法虽然不再需要复制,但是却增加了单点失败的可能性,如果负责Session的机器挂了,那么保存所有用户信息的Session将会丢失。有没有一种办法使得服务端存储这些session呢?
在这个基础上,Token就出现了。
通常意义上的token
是把session
中的内容都放到token中(可以明文形式或者用对称加密算法加密
(加密密钥放在服务器端), 然后再把这些内容做hash签名
(密钥在服务器),把内容和其签名拼接成一个字符串(这个就是token) 发送给客户端
。
客户端后续请求都带上这个token,服务器首先校验token
是否被篡改(根据hash签名
校验),如果没被篡改而且token 也没过期,就把token中的用户信息(可能还有权限信息等等)拿出来,直接使用,不需要像session一样去查询具体用户信息。
通常情况下,基于Token的身份验证的过程如下:
(1)用户通过用户名和密码发送请求。
(2)程序验证。
(3)程序返回一个签名的token 给客户端。
(4)客户端储存token,并且每次用于每次发送请求。
(5)服务端验证token并返回数据。
使用token访问
Token具有以下的优势:
(1)无状态、可扩展
在客户端存储的token是无状态的,并且能够被扩展。基于这种无状态和不存储Session信息,负载负载均衡器能够将用户信息从一个服务传到其他服务器上。
如果我们将已验证的用户的信息保存在Session中,则每次请求都需要用户向已验证的服务器发送验证信息(称为Session亲和性)。用户量大时,可能会造成一些拥堵。
但是不要着急。使用tokens之后这些问题都迎刃而解,因为服务端使用token可以使用秘钥找到用户的信息。
(2)安全性
请求中发送token而不再是发送cookie能够防止CSRF(跨站请求伪造)。即使在客户端使用cookie存储token,cookie也仅仅是一个存储机制而不是用于认证。不将信息存储在Session中,让我们少了对session操作。
token是有时效的,一段时间之后用户需要重新验证。我们也不一定需要等到token自动失效,token有撤回的操作,通过token revocataion可以使一个特定的token或是一组有相同认证的token无效。
可扩展性
使用token时,可以提供可选的权限给第三方应用程序。当用户想让另一个应用程序访问它们的数据,我们可以通过建立自己的API,得出特殊权限的tokens。
如使用微信登录微博
多平台跨域
只要用户有一个通过了验证的token,数据和资源就能够在任何域上被请求到。
下面,我们对Cookie、Session和Token做以下总结:
HTTP请求是无状态
的,就是说第一次和服务器连接并登陆成功后,第二次请求服务器仍然不知道当前请求的用户。Cookie出现就是解决了这个问题,第一次登陆后服务器返回一些数据(cookie)给浏览器,然后浏览器保存在本地,当用户第二次返回请求的时候,就会把上次请求存储的cookie数据自动携带给服务器。
如果关闭浏览器Cookie失效(Cookie就是保存在内存中)
如果关闭浏览器Cookie不失效(Cookie保存在磁盘中)
Session和Cookie的作用有点类似,都是为了存储用户相关的信息
。
不同的是,Cookie是存储在本地浏览器
,而Session存储在服务器
。存储在服务器的数据会更加的安全,不容易被窃取。但存储在服务器也有一定的弊端,就是会占用服务器的资源。
存储在服务端
通过cookie存储一个session_id,然后具体的数据则是保存在session中。
如果用户已经登录,则服务器会在cookie中保存一个session_id,下次再次请求的时候,会把该session_id携带上来,服务器根据session_id在session库中获取用户的session数据。就能知道该用户到底是谁,以及之前保存的一些状态信息。
1、Cookie与Session的区别
cookie数据存放在客户的浏览器上,session数据放在服务器上。
cookie不是很安全,别人可以分析存放在本地的cookie并进行cookie欺骗考虑到安全应当使用session。
session会在一定时间内保存在服务器上。当访问增多,会比较占用你服务器的性能考虑到减轻服务器性能方面,应当使用cookie。
单个cookie保存的数据不能超过4K,很多浏览器都限制一个站点最多保存20个cookie。因此使用cookie只能存储一些小量的数据。
所以开发人员的通常做法是:
将登陆信息等重要信息存放为Session 其他信息如果需要保留,可以放在Cookie中
2、Token和Session的区别
共同点:
都是保存了用户身份信息,都有过期时间。
不同点:
session翻译为会话,token翻译为令牌。
session是
空间换时间
,token是时间换空间
。session和session_id:服务器会保存一份,可能保存到缓存/数据库/文件。
token:服务器不需要记录任何东西,每次都是一个
无状态的请求
,每次都是通过解密来验证是否合法
。token 只是一个 key,不存放实际的数据,与这个 token 相关的数据还是存放在服务器上,例如 Session,Redis 等分布式缓存里,用 token 去请求对应的数据。session_id:一般是随机字符串,要到
服务器检索id的有效性
。出现请求:服务器重启内存中的session没了,数据库服务器挂了。token 和 cookie 本质上没啥区别,只不过 token 只是一个字符串,访问的时候可以放在 url 的参数,header 里等,不像 cookie 那么重量级,而且移动端访问的时候 token 更方便,仅此而已。