一、Cookie 在前端中的实践
1.搭建Demo环境
找个文件夹,npm init,然后如果没有安装过express,再npm install express -D,然后新建一个main.js,执行node main.js即可启动服务。注意如果修改了js脚本,需要重新执行node main.js。
const express = require('express')
const app = express()
app.listen(3000, err => {
if (err) {
return console.log(err)
}
console.log('---- 打开 http://localhost:3000 吧----')
})
app.get('/', (req, res) => {
res.send('<h1>hello world!</h1>')
})
2.在介绍 Cookie 是什么之前,我们来看看 Cookie 是如何工作的
- 首先,我们假设当前域名下还是没有 Cookie 的
- 接下来,浏览器发送了一个请求给服务器(这个请求是还没带上 Cookie 的)
- 服务器设置 Cookie 并发送给浏览器(当然也可以不设置)
- 浏览器将 Cookie 保存下来
- 接下来,以后的每一次请求,都会带上这些 Cookie,发送给服务器
app.get('/', (req, res) => {
// 服务器接收到请求,在给响应设置一个 Cookie
// 这个 Cookie 的 name 为 testName
// value 为 testValue
res.cookie('testName', 'testValue')
res.send('<h1>hello world!</h1>')
})
改一下代码,就可以验证以上逻辑了,第一次Request Headers 并没有 Cookie 这个字段,后面刷新就一直有了。当然,如果我们多设置几个cookie,请求时也会汇总带上。
app.get('/', (req, res) => {
res.cookie('testName', 'testValue')
res.cookie('testName22', 'testValue')
res.cookie('testName33', 'testValue')
res.cookie('myName', 'cuixu')
res.send('<h1>hello world!</h1>')
})
3.什么是 Cookie
说了这么多,大家应该知道 Cookie 是什么吧。整理一下有以下几个点:
- Cookie 就是浏览器储存在用户电脑上的一小段文本文件
- Cookie 是纯文本格式,不包含任何可执行的代码
- Cookie 由键值对构成,由分号和空格隔开
- Cookie 虽然是存储在浏览器,但是通常由服务器端进行设置
- Cookie 的大小限制在 4kb 左右
4.Cookie 的属性选项
expires / max-age 都是控制 Cookie 失效时刻的选项。
// 这个 Cookie 设置十秒后失效
res.cookie('testName0', 'testValue0', {
expires: new Date(Date.now() + 10000)
})
在控制台输入下面的代码
console.log(`现在的 cookie 是:${document.cookie}`)
setTimeout(() => {
console.log(`5 秒后的 cookie 是:${document.cookie}`)
}, 5000)
setTimeout(() => {
console.log(`10 秒后的 cookie 是:${document.cookie}`)
}, 10000)
可以发现,10秒失效后,自动就没了,当然以后发送请求也不会再带上这个失效的 Cookie 了。
expires 是 http/1.0 协议中的选项,在新的 http/1.1 协议中 expires 已经由 max-age 选项代替,两者的作用都是限制 Cookie 的有效时间。expires 的值是一个时间点 (Cookie 失效时刻 = expires),而 max-age 的值是一个以秒为单位时间段 (Cookie 失效时刻 = 创建时刻 + max-age)。如果同时设置了 max-age 和 expires,以 max-age 的时间为准。
res.cookie('testName0', 'testValue0', {
// express 这个参数是以毫秒来做单位的
// 实际发送给浏览器就会转换为秒
// 十秒后失效
maxAge: 10000
})
name
、domain
和 path
可以标识一个唯一的 Cookie。domain
和 path
两个选项共同决定了 Cookie 何时被浏览器自动添加到请求头部中发送出去。具体是什么原理请看 Cookie 的作用域和作用路径 这个章节。如果没有设置这两个选项,则会使用默认值。domain
的默认值为设置该 Cookie 的网页所在的域名,path
默认值为设置该 Cookie 的网页所在的目录。
secure 选项用来设置 Cookie 只在确保安全的请求中才会发送。当请求是 HTTPS 或者其他安全协议时,包含 secure 选项的 Cookie 才能被保存到浏览器或者发送至服务器。默认情况下,Cookie 不会带 secure 选项(即为空)。所以默认情况下,不管是 HTTPS 协议还是 HTTP 协议的请求,Cookie 都会被发送至服务端。
httpOnly这个选项用来设置 Cookie 是否能通过 js 去访问。默认情况下,Cookie 不会带 httpOnly 选项(即为空),客户端是可以通过 js 代码去访问(包括读取、修改、删除等)这个 Cookie 的。当 Cookie 带 httpOnly 选项时,客户端则无法通过 js 代码去访问(包括读取、修改、删除等)这个 Cookie。
5.设置 Cookie
明确一点:Cookie 可以由服务端设置,也可以由客户端设置。看到这里相信大家都可以理解了吧。在网页即客户端中我们也可以通过 js 代码来设置 Cookie。
- 设置
document.cookie = 'name=value'
- 可以设置 Cookie 的下列选项:expires、domain、path,各个键值对之间都要用 ; 和 空格 隔开:
document.cookie='name=value; expires=Thu, 26 Feb 2116 11:50:25 GMT; domain=sankuai.com; path=/';
- 只有在 https 协议的网页中,客户端设置 secure 类型的 Cookie 才能成功
- 客户端中无法设置 HttpOnly 选项
- Cookie 的 name、path 和 domain 是唯一标识一个 Cookie 的。我们只要将一个 Cookie 的 max-age 设置为 0,就可以删除一个 Cookie 了。
let removeCookie = (name, path, domain) => {
document.cookie = `${name}=; path=${path}; domain=${domain}; max-age=0`
}
6.Cookie 的作用域
在说这个作用域之前,我们先来对域名做一个简单的了解。
子域,是相对父域来说的,指域名中的每一个段。各子域之间用小数点分隔开。放在域名最后的子域称为最高级子域,或称为一级域,在它前面的子域称为二级域。
以下图为例,news.163.com 和 sports.163.com 是子域,163.com 是父域。当 Cookie 的 domain 为 news.163.com,那么访问 news.163.com 的时候就会带上 Cookie;当 Cookie 的 domain 为 163.com,那么访问 news.163.com 和 sports.163.com 就会带上 Cookie
7.Cookie 的作用路径
当 Cookie 的 domain 是相同的情况下,也有是否带上 Cookie 也有一定的规则。在子路径内可以访问访问到父路径的 Cookie,反过来就不行。
看看例子,还是先修改 main.js
app.get('/parent', (req, res) => {
res.cookie('parent-name', 'parent-value', {
path: '/parent'
})
res.send('<h1>父路径!</h1>')
})
app.get('/parent/childA', (req, res) => {
res.cookie('child-name-A', 'child-value-A', {
path: '/parent/childA'
})
res.send('<h1>子路径A!</h1>')
})
app.get('/parent/childB', (req, res) => {
res.cookie('child-name-B', 'child-value-B', {
path: '/parent/childB'
})
res.send('<h1>子路径B!</h1>')
})
参考文章
二、一文带你看懂cookie,面试前端不用愁
1.存放哪些数据
当客户端要发送http请求时,浏览器会先检查下是否有对应的cookie。有的话,则自动地添加在request header中的cookie字段。注意,每一次的http请求时,如果有cookie,浏览器都会自动带上cookie发送给服务端。那么把什么数据放到cookie中就很重要了,因为很多数据并不是每次请求都需要发给服务端,毕竟会增加网络开销,浪费带宽。所以对于那设置“每次请求都要携带的信息(最典型的就是身份认证信息)”就特别适合放在cookie中,其他类型的数据就不适合了。
2.localStorage和sessionStorage
在较高版本的浏览器中,js提供了两种存储方式:sessionStorage和globalStorage。在H5中,用localStorage取代了globalStorage。
sessionStorage用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问,并且当会话结束后,数据也随之销毁。所以sessionStorage仅仅是会话级别的存储,而不是一种持久化的本地存储。
localStorage是持久化的本地存储,除非是通过js删除,或者清除浏览器缓存,否则数据是永远不会过期的。
浏览器的支持情况:IE7及以下版本不支持web storage,其他都支持。不过在IE5、IE6、IE7中有个userData,其实也是用于本地存储。这个持久化数据放在缓存中,只有不清理缓存,就会一直存在。
三、知乎 COOKIE和SESSION有什么区别?
由于HTTP协议是无状态的协议,所以服务端需要记录用户的状态时,就需要用某种机制来识具体的用户,这个机制就是Session.典型的场景比如购物车,当你点击下单按钮时,由于HTTP协议无状态,所以并不知道是哪个用户操作的,所以服务端要为特定的用户创建了特定的Session,用用于标识这个用户,并且跟踪用户,这样才知道购物车里面有几本书。这个Session是保存在服务端的,有一个唯一标识。在服务端保存Session的方法很多,内存、数据库、文件都有。集群的时候也要考虑Session的转移,在大型的网站,一般会有专门的Session服务器集群,用来保存用户会话,这个时候 Session 信息都是放在内存的,使用一些缓存服务比如Memcached之类的来放 Session。
思考一下服务端如何识别特定的客户?这个时候Cookie就登场了。每次HTTP请求的时候,客户端都会发送相应的Cookie信息到服务端。实际上大多数的应用都是用 Cookie 来实现Session跟踪的,第一次创建Session的时候,服务端会在HTTP协议中告诉客户端,需要在 Cookie 里面记录一个Session ID,以后每次请求把这个会话ID发送到服务器,我就知道你是谁了。有人问,如果客户端的浏览器禁用了 Cookie 怎么办?一般这种情况下,会使用一种叫做URL重写的技术来进行会话跟踪,即每次HTTP交互,URL后面都会被附加上一个诸如 sid=xxxxx 这样的参数,服务端据此来识别用户。
Cookie其实还可以用在一些方便用户的场景下,设想你某次登陆过一个网站,下次登录的时候不想再次输入账号了,怎么办?这个信息可以写到Cookie里面,访问网站的时候,网站页面的脚本可以读取这个信息,就自动帮你把用户名给填了,能够方便一下用户。这也是Cookie名称的由来,给用户的一点甜头。
所以,总结一下:Session是在服务端保存的一个数据结构,用来跟踪用户的状态,这个数据可以保存在集群、数据库、文件中;Cookie是客户端保存用户信息的一种机制,用来记录用户的一些信息,也是实现Session的一种方式。
四、彻底理解cookie,session,token
1.发展史
很久很久以前,Web 基本上就是文档的浏览而已, 既然是浏览,作为服务器, 不需要记录谁在某一段时间里都浏览了什么文档,每次请求都是一个新的HTTP协议, 就是请求加响应, 尤其是我不用记住是谁刚刚发了HTTP请求, 每个请求对我来说都是全新的。这段时间很嗨皮。
但是随着交互式Web应用的兴起,像在线购物网站,需要登录的网站等等,马上就面临一个问题,那就是要管理会话,必须记住哪些人登录系统, 哪些人往自己的购物车中放商品, 也就是说我必须把每个人区分开,这就是一个不小的挑战,因为HTTP请求是无状态的,所以想出的办法就是给大家发一个会话标识(session id), 说白了就是一个随机的字串,每个人收到的都不一样, 每次大家向我发起HTTP请求的时候,把这个字符串给一并捎过来, 这样我就能区分开谁是谁了
这样大家很嗨皮了,可是服务器就不嗨皮了,每个人只需要保存自己的session id,而服务器要保存所有人的session id ! 如果访问服务器多了, 就得由成千上万,甚至几十万个。
这对服务器说是一个巨大的开销 , 严重的限制了服务器扩展能力, 比如说我用两个机器组成了一个集群, 小F通过机器A登录了系统, 那session id会保存在机器A上, 假设小F的下一次请求被转发到机器B怎么办? 机器B可没有小F的 session id啊。
有时候会采用一点小伎俩: session sticky , 就是让小F的请求一直粘连在机器A上, 但是这也不管用, 要是机器A挂掉了, 还得转到机器B去。
那只好做session 的复制了, 把session id 在两个机器之间搬来搬去, 快累死了。
后来有个叫Memcached的支了招: 把session id 集中存储到一个地方, 所有的机器都来访问这个地方的数据, 这样一来,就不用复制了, 但是增加了单点失败的可能性, 要是那个负责session 的机器挂了, 所有人都得重新登录一遍, 估计得被人骂死。
也尝试把这个单点的机器也搞出集群,增加可靠性, 但不管如何, 这小小的session 对我来说是一个沉重的负担
于是有人就一直在思考, 我为什么要保存这可恶的session呢, 只让每个客户端去保存该多好?
可是如果不保存这些session id , 怎么验证客户端发给我的session id 的确是我生成的呢? 如果不去验证,我们都不知道他们是不是合法登录的用户, 那些不怀好意的家伙们就可以伪造session id , 为所欲为了。
嗯,对了,关键点就是验证 !
比如说, 小F已经登录了系统, 我给他发一个令牌(token), 里边包含了小F的 user id, 下一次小F 再次通过Http 请求访问我的时候, 把这个token 通过Http header 带过来不就可以了。
不过这和session id没有本质区别啊, 任何人都可以可以伪造, 所以我得想点儿办法, 让别人伪造不了。
那就对数据做一个签名吧, 比如说我用HMAC-SHA256 算法,加上一个只有我才知道的密钥, 对数据做一个签名, 把这个签名和数据一起作为token , 由于密钥别人不知道, 就无法伪造token了。
这个token 我不保存, 当小F把这个token 给我发过来的时候,我再用同样的HMAC-SHA256 算法和同样的密钥,对数据再计算一次签名, 和token 中的签名做个比较, 如果相同, 我就知道小F已经登录过了,并且可以直接取到小F的user id , 如果不相同, 数据部分肯定被人篡改过, 我就告诉发送者: 对不起,没有认证。
Token 中的数据是明文保存的(虽然我会用Base64做下编码, 但那不是加密), 还是可以被别人看到的, 所以我不能在其中保存像密码这样的敏感信息。
当然, 如果一个人的token 被别人偷走了, 那我也没办法, 我也会认为小偷就是合法用户, 这其实和一个人的session id 被别人偷走是一样的。
这样一来, 我就不保存session id 了, 我只是生成token , 然后验证token , 我用我的CPU计算时间获取了我的session存储空间 !
解除了session id这个负担, 可以说是无事一身轻, 我的机器集群现在可以轻松地做水平扩展, 用户访问量增大, 直接加机器就行。 这种无状态的感觉实在是太好了!
以下参考前端应该知道的web登录
前面说到sessionId的方式本质是把用户状态信息维护在server端,token的方式就是把用户的状态信息加密成一串token传给前端,然后每次发请求时把token带上,传回给服务器端;服务器端收到请求之后,解析token并且验证相关信息;
所以跟第一种登录方式最本质的区别是:通过解析token的计算时间换取了session的存储空间
业界通用的加密方式是jwt(json web token),jwt的具体格式如图:
简单的介绍一下jwt,它主要由3部分组成:
header 头部
{
"alg": "HS256",
"typ": "JWT"
}
payload 负载
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022,
"exp": 1555341649998
}
signature 签名
header里面描述加密算法和token的类型,类型一般都是JWT;
payload里面放的是用户的信息,也就是第一种登录方式中需要维护在服务器端session中的信息;
signature是对前两部分的签名,也可以理解为加密;实现需要一个密钥(secret),这个secret只有服务器才知道,然后使用header里面的算法按照如下方法来加密:
HMACSHA256(
base64UrlEncode(header) + "." +
base64UrlEncode(payload),
secret)
总之,最后的 jwt = base64url(header) + "." + base64url(payload) + "." + signature
jwt可以放在response中返回,也可以放在cookie中返回,这都是具体的返回方式,并不重要。
客户端发起请求时,官方推荐放在HTTP header中:
Authorization: Bearer <token>
这样子确实也可以解决cookie跨域的问题,不过具体放在哪儿还是根据业务场景来定,并没有一定之规。
五、Web技术:Token与Session究竟是什么呢
本文通俗易懂,基本是上述内容的重述。文末总结有点意思:
wa: 先不提问,我再说几个结论,在提问。。
- token 是无状态的,后端不需要记录信息,每次请求每次解密就行。
- session 是有状态的,需要后端每次去检索id的有效性。不同的session都需要进行保存哦。但让也可以设置单点登录,减少保存的数据。
- session与token的问题是空间与时间博弈,为什么这么说呢,是因为token不需要保存,直接获取,每次访问都需要进行解密。
好了就先说这几条吧,以后有了在补充。
琪琪:总结了这么多了,那我们开始提问了。首先,为啥客户端ios与Andriod基本上没见过用session的?
ff: 这个我来回答吧。因为在这些客户端上啊,原生接口都是每一次建立一个会话,这就出问题了,这样会导致登录功能失效了,登录每次把信息放到session中,session都不一样了,每次登录都成新的一个人了,这就不ok了。
琪琪:哦哦原来客户端是每次用原生接口都是新建立一个会话,好尴尬的设计。那我们采用什么方式解决这个问题呢?
ff: 在用户登录后,重点哦我们可以通过cookie嘛,我们在app端也可以是存储cookie的,我们知道cookie将sessionID保存好,返回给客户端,服务器最后也是通过SessionId来标识的。但是利用token的话,内容我们可以自定义,并且不用再服务端进行保存。方便我们处理。
琪琪:那么就是token可以保存到cookie中,如果禁止的话我们也可以保存到body中每次都请求上或者header中。