不要再问我cookie和session的区别,因为没法比

上周第三轮面试被深问cookie和session给我问懵逼了,面试也毫无悬念地挂了。痛定思痛,准备写一篇博客好好理清HTTP的会话技术。

cookie

HTTP是无状态的协议,同一个用户发送连续两个请求报文,服务器并不能通过读取所谓的上下文去得知这个用户是否是一个“老用户”,究其原因还是因为服务器没有为客户端保存任何可以标识身份的信息。
而什么是cookie,它是一个HTTP的请求头,这个请求头在request中就是cookie,而在response中就是set-Cookie。当客户端(浏览器)访问某个服务器时,服务器根据已保存cookie的domain和path决定捎带哪些cookie信息,传送给服务器。而服务器拿到cookie集合一般会遍历这个每一个cookie,并且找到感兴趣的cookie进行身份验证——可以看作是上下文加载。而如果没有找到感兴趣的cookie,则大概有两种可能:这是个新的用户,或者用户的cookie被清除或已经过期了。那么就在response中addCookie,浏览器收到后保存这个cookie

上面的行为并不是某种标准,因为后端程序员编程的内容就包含上述部分。例如从HTTPServletRequest中拿到cookies然后遍历,以及向“将要发送给客户端”的空HTTPServletResponse中种cookie(addCookie,底层对应设置set-cookie)

说白了,cookie就是HTTP的一个header,它的值是以键值对的形式组织的,而且只能是ASCII字符编码,如果某些字符不能被ASCII编码(中文就不能被ASCII编码),那么可以考虑往response种下之前进行一次编码。

                   //URL编码
                    str = URLEncoder.encode(str, "utf-8");
                    c.setValue(str);   

下次从request拿到浏览器传来的cookie时进行一次解码

                    //URL解码
                     String str = c.getValue();
                     time = URLDecoder.decode(str, "utf-8");

既然str不能被ASCII编码,那么我就将它使用utf-8编码,编码后str便可以可以被作为ASCII字符形式保存

另外cookie的值如果是明文的,容易被盗取。那么同理可以再“种之前”和“拿到后”进行加密和解密。cookie是HTTP报文频繁携带的数据,因此还可以考虑进行相应的压缩和解压缩。

cookie的属性项最常见的除了name/value,还有过期时间expires和最长存活时长max-age,如果不指定以上两个属性,那么cookie默认就是会话cookie,一旦会话结束就会被销毁,浏览器不会将其持久化到本地。

path:当前cookie是在服务器的哪一个路径生成的,并不是客户端保存路径,而是服务器创建cookie设置的(程序员设置的),当浏览器访问服务器某个路径时,根据path判断该不该携带这个cookie。假如我把path设置为项目虚拟路径根路径(setPath(request.getContextPath())),那么你浏览器只有访问我这个项目,总是会带上该cookie
domain:当前cookie是在哪一个域名下生成的,如果设置一级域名相同,那么多个服务器之间cookie可以共享这个cookie
例如:SetDomain(“.baidu.com”),那么访问tieba.baidu.com时,这个cookie也会被拿到。

cookie一般存储的内容有限,毕竟cookie是存储在客户端的,不同的浏览器具有不同的标准(对cookie数量的限制、大小的限制等),使得服务器不好统一管理。

如何加强cookie的可靠性
【1】防止在cookie存放敏感信息,即使存放也应该进行加密,如使用HTTPS
【2】加防篡改验证码和登录随机验证码
服务器为用户存放两个cookie,一个是用户信息,另一个是对用户信息加随机数等加密参数产生的验证码,当客户端传来cookie时服务器需要进行验证。
【3】对cookie的name、value(或全部属性)进行加密处理
【4】强制要求开启HTTPS连接,服务器传送cookie时设置secure为true,表示创建的cookie只能在HTTPS连接中被浏览器传递到服务器端进行会话验证,如果是HTTP连接则不会传递该信息。

另外,cookie开启httpOnly可以防止JS读取cookie,而开启secure则保证了只有HTTPS服务才会传递cookie。

理解cookie的实质

上面说了一大堆,你能看出我对cookie的理解是什么吗?
cookie它就是一个header、一个技术、一个工具。它是HTTP标准的header,也就是实现了HTTP规范的服务器和浏览器公认的、用于实现状态化的header。(你也可以使用一个自定义header去实现同样的效果,但是毕竟不是规范,不是所有浏览器和服务器都能理解你的header)。
所以,cookie是有状态的还是无状态的?它就是一种工具,有状态还是无状态程序员说了算。

在做登录功能时,抛开各种框架,就拿最底层的来说,我们需要注册一个filter或者拦截器,这个东西你可以看作是一个AOP切面,用户访问主页、个人页面等**“需要上下文关系”的页面**,都需要验证身份,那么如果我直接去访问某个页面必然会被拦截,如果浏览器的cookie不满足要求,一般会被重定位到登录页面。

那怎么验证身份呢?
【1】浏览器出示凭证,服务器不保存各种会话信息,验证浏览器出示凭证的合法性和真实性则放行,从服务器的角度,这种情况一般看作无状态实现的,一般称为token。
【2】服务器保存会话信息,并且在浏览器上保存一个指向会话信息的唯一标识,浏览器出示唯一标识,服务器去查询。服务器保存了会话信息,因此这种实现被看作有状态的,一般称为session。

如果你拿到request递上来的cookies,检查没毛病后直接就放行,相当于验证了浏览器携带的token。如果cookie中存的是一个指针,而真正的会话信息保存在服务器,那么你就需要根据指针拿到对应的会话信息,即根据浏览器携带的sessionId拿到session。
那我既给cookie存指针又存放数据呢?那我给cookie存数据还有再给服务器保存一份副本呢?都可以啊,你是程序员,而cookie就是一个工具,你爱怎么用怎么用——cookie本质上就是HTTP用于实现传递会话信息以实现有状态的一个HTTP标准header,本质上就是一个工具,真正其决定作用的是使用它的程序员

session

很多面试题喜欢把cookie和session进行对比,其实我早就觉得有点不舒服了,因为这二者压根不是一个维度的东西啊。因为cookie算是一种实现HTTP有状态的标准header,而session更像一种实现有状态的思想——建立一种虚拟的会话。
cookie是什么?客户端向服务器打招呼,然后把自己的cookie传过去,服务器一看它有我之前给他存的cookie,那我们应该认识,那我就替你恢复一下上下文吧——解析cookie,相当于恢复和当前用户的会话上下文。
如果说cookie传递的是值,那么session传递是一个指针、一个索引。
session可以看作是一种思想,实现方式有很多:基于cookie的、基于URL的、基于post表单的、会话信息保存在单机的、会话信息保存在中间件的…
没有session之前,每次服务器想要“恢复上下文”,客户端必须通过cookie传递大量上下文信息(极大浪费带宽,接收cookie需要更多流量和时间),而且结构不灵活(KV、ASCII等),而基于cookie的session中,你cookie不需要存储过多信息或敏感信息,我session替你分担一下压力。
服务器向客户端种下cookie时,额外种下了一个保存的sessionId的cookie,这个sessionId指向服务器的一个session对象,这个session对象一般由服务器软件的session集合集中管理。一个session就可以看作一个mapList,最重要的是,这是一个更加抽象的会话对象——它可以保持任何类型的会话信息,不论是字符串还是对象。

Tomcat的Manager类有一个session集合的成员,Manager类将会管理所有session的声明周期,session过期则回收session对象,服务器关闭则将session对象序列化到服务器磁盘上,用户可以根据sessionId拿到对应的session对象

session对象是保存到服务器上的,过多的session对象给服务器的压力会很大,所以每个session对象不总是保存在服务器的内存中,通常会根据服务器的钝化策略持久化到磁盘上。(保证浏览器cookie的sessionId过来,能拿到session对象就没问题)

从上面的讨论来看,cookie和session其实不适合放在一起对比,session和cookie哪一个安全?你还不如问我cookie存放值和存放指针哪一个安全嘞
cookie的一部分内容存放在服务器,传输的时候顶多看见一个sessionId,但是不论是cookie存放的值还是sessionId都有可能被盗取。而且如果真想要会话信息不被窃取,最好还是强制HTTP使用加密的HTTPS

HTTPS对URL、header、body都会进行加密,而且可以保证完整性。

与其对比session和cookie,不如对比token和session
token可以看作:客户端主动出示证件然后服务器放行,而session可以看作浏览器出示一个证件的号码,然后服务器去查询这个号码拿到会话信息才放行。(本质上,不管是token还是sessionId,都可以基于cookie传递,而且都是没有直接含义的字符串)

token、session终究也是一种抽象的思想,不同的产品有不同的实现,谈论也都是抽象的,如果非要具体到某一个细节,应该指明哪一个产品

使用场景

一般面试问cookie和session,你完全可以看作:使用值和传递指针。
一般传递指针优先于传递值,随便想想就能想一些:
【1】会话信息一般都不少,而且cookie是作为频繁传递的header,必然造成大量带宽浪费(传递cookie前可以考虑压缩内容)

HTTP2 开启 头部压缩,对头部使用压缩算法进行压缩,而对于频繁传递且不变化的值user-agent、cookie等保存在头信息表,使用指针代替值进行传递

【2】cookie传递值,如果不进行加密肯定会被看到如name=abd&age=123,最好传递cookie前进行加密(或者使用HTTPS)
【3】使用cookie保存值只能是ascii不灵活,如果我想要保存一个对象,还需要在传递前转换为cookie支持的格式,肯定没使用session灵活
【4】另一方面,cookie是保存在客户端的,(这种情况下,服务器一般不保存会话信息)那么相当于服务器就失去对会话信息的控制权,为了方式不安全的cookie,服务器必然需要某种检查、校验机制(数字签名、时间戳等,基本上就是token的思想了)

当然了,cookie存值也是可以的,因为如果所有会话信息都保存在服务器,那么服务器内存也不够用啊。另一方面,如果服务器是分布式部署的,那么session对象将可能无法及时同步,导致一个持有sessionId的用户总是无法正常自动登录。

一种解决方案是让每个集群服务器拿到所有session、另一种方案是当存在session未命中则进行一次session同步(相当于第一种方案的延迟同步)、一种主流的方案是使用中间件如redis作为第三方session容器,每次从中间件去拿session。

一般不把所有会话信息(上下文信息)放入session,一般将cookie放不下的信息、敏感信息如密码、账户(一般都是抽象为一个相应的对象)等放入session,session由服务器软件的session容器集中保存(这里仅指代单机存储session的场景)。

cookie一般放什么
一些指针信息(如sessionId)、一些视频小网站没有登录业务但是也要保存用户的浏览记录,那么观看历史记录、搜索历史记录都可以保存到cookie中(可能就是一个名字,也可能是一条记录的id)、自定义设置(背景图片、个性栏等)、一些不重要的临时信息(例如浏览器型号、手机型号)。
还可以实现一些小功能如拿到用户上一次访问时间、统计用户访问总数、拿到用户上一次登录的ip地址等等。(说白了就是读取上下文相关的功能)

cookie存放的东西五花八门,而且不同的网站都有不同的存放需求,你可以随便打开个网页抓个包,看看cookie里面都有什么

至于token,它可以保存到URL传递,也可以保存到cookie或其他的头部,它是一个字符串,看作一个凭证,通常由用户标识符+时间戳+签名组成,服务器使用私钥对它签名并且保存在客户端,当客户端回传token时,服务器再次计算签名,如果没错则视为当前用户通过验证并放行,对于要求状态的页面,浏览器总是需要携带这个服务器签发的token。一般将基于token的验证看作无状态的,服务器不记录哪些用户已经登录,它的唯一职责就是签发token以及验证token,每个token都是独立的。
token更适合用于权限管理的系统,如:一个网站的业务就是普通用户看普通内容,VIP看普通内容+VIP内容,那么服务器唯一要做的就是拦截普通用户和为氪金用户签发token,这里面不需要太多上下文信息去保存,只需要做好访问控制即可。(session也能做,但是如果业务就是单纯访问控制,还是token做比较好:服务器压力小、逻辑简单、安全性高一些…)

JWT就是json格式的token,有具体的结构:header+payload+signature。其实本质上就是一个约定好格式的json字符串。

因此,一般说**HTTP实现有状态(保存和用户会话的上下文)**的具体方案,无非就是cookie+session或者cookie+token和cookie+session+token,当然取决于程序员如何使用cookie这个工具去实现会话状态。

cookie/session/token总结

cookie是一种工具,每次HTTP客户端与HTTP服务器再次建立连接时,无状态的HTTP服务为了加载上下文,客户端必须把上下文数据通过cookie传送给服务器。但是上下文数据通常很大,而且不同的服务器给客户端保存的上下文数据各种各样,那么将上下文保存在服务器一方,并且将相应指针保存给客户端,保存在服务器的上下文就是会话的抽象对象即session。而token的主要目的用于授权,服务器向客户端颁发令牌token,当客户端携带令牌访问某些需要权限的网页时,服务器检查token并决定是否放行,可以将token看作一种用于证明身份的上下文数据。

总结:HTTP协议是无状态的,加载状态信息的方式是传递上下文数据(会话信息),而cookie是其中一种方式(还可以通过改写URL或者隐藏表单提交)。而session是保存在服务器的会话信息,其中sessionId和token和普通的cookie数据一样,都是字符串,都可以看作上下文数据,只不过业务功能不同罢了。

CSRF 攻击

CSRF 跨域伪造请求攻击,攻击者利用浏览器的缺点——无法判断打开某个网页是否是某个人的真实行为

用户访问A网站并拿到了cookie,而当用户访问B网站时误点某一个连接,这个连接指向A网站,例如:用个人账户发布某些虚假信息,访问A网站时浏览器自动将cookie传送过去,于是请求得到响应。

攻击者的攻击原理就是骗得浏览器信任,导致浏览器cookie传递给服务器,加载了上下文后攻击者间接盗取了被攻击者的登录信息/会话信息。
防御方案:
【1】限制访问来源——拿到请求头referer,判断请求来源的合法性和可信度
【2】URL中添加token——攻击者虽然能够劫持cookie,但是它并不知道cookie的内容。
服务器随机产生token(如对cookie的内容进行摘要计算和加密或者是生成一个随机数)存入session或cookie。前端发送请求时先拿到token,然后将token放入url。服务器执行请求前验证token ,攻击者无法伪造token(也就是说请求中总是需要包含正确的token,攻击者只是劫持cookie,但是并不知道token,更无法从cookie中拿到token,而防止了攻击者去人为伪造被攻击用户的请求),因此可以防范csrf

总结:通过在cookie中放入csrf_token可以防止CSRF攻击(不一定是cookie,只要是前端能够能够读取这个token。为了让前端能够解析cookie,不设置HTTPOnly选项)。
csrf_token经过加密后保存在cookie中,同源页面每次发送请求时都会在请求头或者参数中加入cookie中读取到的token。而CSRF攻击每次只能拿到一大堆cookie而不能解析其中的内容,更不可能拿到其中的token去构造合法的http请求。token经过加密存储在cookie中,只有同源网页才能获取到即可以规避非法来源的请求

简而言之,想要获取目标网页,必须从当前网页拿到token上下文,而CSRF攻击者无法解析cookie中的token,没有上下文无法请求目标网页,最终的下场就是被拦截器拦截。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值