cookie? session? JWT? :)

Javascript Aug 06, 2020

套话是:HTTP是一个无状态协议,既每次请求之间相互独立,下一次请求无法得知下一次和上上次请求的状态,那么如何将其关联起来呢? 比方说我们登录过一次网站,当关掉浏览器再一次访问网站的时候,我们希望在一定时间内再次访问该网站能够不需要再次输入账号密码就可以登录(刨除浏览器帮你记住账号密码😄)

cookie是啥?它已经是http协议中的一部分了,其实它只是一对key/value值。
我们从头开始缕一下流程:

  1. 首先你第一次访问https://www.hellohub.cn/, 你的浏览器中没有携带任何和cookie有关的字段请求服务器。
  2. hellohub的服务器接到了你的请求,然后在返回给你页面的同时,会返回一个字段Set-Cookie,这个字段其实就是服务器告诉你的浏览器:“你设置一个cookie吧,这样的话等你下次登录的时候,我就知道你是谁了,咱不用再费劲登录了。”
  3. 如果你没有对你的浏览器做什么特殊的操作的话,比方说turn off cookie的话,你的浏览器没收到set-cookie的字段之后,就会言听计从的设置一个相应的cookie。
  4. 从此以后,只要cookie没有过期,你用浏览器向hellohub发起的每次请求都会带上这个cookie,你们无须多言就能够彼此相识 peace~

上代码,这里我参考了文章最后的reference中的代码:(用express来写一个简单的后台服务器吧~)
cache
这里面我认为比较重要的代码是第21行的

 res.cookie('name', 'haochen', {maxAge: 60 * 1000, httpOnly: true});

如果你没有res.cookie的话,作为服务器的第一次response将不会带有Set-Cookie这个字段,更不要说你的浏览器在之后的请求中会带着cookie啦。
我们这里面用了cookie-parser这个模块,在express的官网中也提到过,当你将其作为中间件使用的时候,就可以通过res.cookie对cookie进行操作啦。
这里面说几个res.cookie(name, value [, options])中的options字段:

  • path:表示 cookie 影响到的路径,匹配该路径才发送这个 cookie。
  • encode: 一个同步的方法用来加密cookie的,默认的是encodeURIComponent。
  • expires 和 maxAge:告诉浏览器这个 cookie 什么时候过期,expires 是 UTC 格式时间,maxAge 是 cookie 多久后过期的相对时间。当不设置这两个选项时,如果用户关闭浏览器,cookie就被清除。一般用来保存 session 的 session_id。(我这里设置的是1min),下图是在devtools中查看设置了maxAge和没设置maxAge的对比。session那个表示当你关闭浏览器的时候自动消失,有一串时间的那个代表着cookie的过期时间。
    cache4
  • secure:当 secure 值为 true 时,cookie 在 HTTP 中是无效,在 HTTPS 中才有效。
  • httpOnly:浏览器不允许脚本操作 document.cookie 去更改 cookie。一般情况下都应该设置这个为 true,这样可以避免被 xss 攻击拿到 cookie。当你设置了httpOnly的时候通过命令document.cookie没办法拿到cookie的~不信你试试

我们将服务器run起来,然后用浏览器访问localhost:3000,看到如下页面:
cache1-1
这是我们第一次打开这个页面,页面显示“welcome to my channel first time”!再打开devtools中的Application查看cookie,看到我们的cookie在浏览器中被设置了~其中详细的标明了这个cookie的一些性质,httpOnly啦,Size啦

cache2-1
从network中我们能够看见这个cookie是由我们的第一次response设置哒~
好咧,让我们再发一次请求,刷新页面,此时打开network看这次请求的request中,我们看到了Cookie: name=haochen,这证明第二次请求我们带上了cookie,并且此时页面中的内容“welcome to my channel again babe”也显示这是我们第二次访问该网站了。
cache3

signedCookie

cookie的升级版本,虽然说cookie很容易被js修改,但是你可以签名呀~
比方说你的cookie中存放的是:

{
   userName: haochen
}

你可以用一个secret-key(放在服务器端)做一下签名,这样你发给浏览器的cookie将变为

{
   userName: haochen,                                                          userName.sig: s%3AZ3WInpnSKQqdO3Jjnh...
 }

类似这样的, 如果有人串改了cookie中的内容,和你在服务器端进行解密得到的结果不一样,你就知道有人不怀好心了。

session

session是啥?说白了也是为了让你的http请求“有状态”,即可以让服务器记住你,那相比于cookie,为啥需要seesion呢?
因为cookie存放在浏览器端,也就是客户端,客户可以通过很多方式修改cookie,很容易被伪造,这就有可能搞出一些事情出来,所以我们需要session,将重要的信息存放在服务端。此外如果cookie中存放的数据太大了的话,你想你每次请求都要带上它,会不会非常影响速度?
那么使用session的流程是什么样子呢?

  1. 首先你第一次访问https://www.hellohub.cn/, 你的浏览器中没有携带任何和cookie有关的字段请求服务器。(同cookie)
  2. hellohub的服务器接到了你的请求,然后在返回给你页面的同时,会返回一个字段Set-Cookie。你的浏览器会设置Set-Cookie中的内容,到这里和设置cookie的流程一样,
  3. 但是此时cookie中存放的却是sesson_id, 与此同时,服务器端会新建一个session(服务器这里创建的session有很多中储存方式,包括:1.内存。2.cookie本身。3.redis等缓存中。4.数据库中),它的id对应着通过cookie给你传递的这个id。简单来说这回服务器第一次接收到你的请求的时候,会创建一个session,并将这个session的id发给你作为cookie中的内容,这样你每次请求都会带上session_id,之后服务器就可以通过这个id找到你们之间建立的session啦。

这边我们使用一个模块叫做express-session,上代码:
cache5
然后开启服务,打开浏览器,此时可以看到页面上显示的:欢迎第一次
来这里,刷新之后(在1min之内)页面将显示“第2次来此页面”,再刷新的话次数会+1,这里就不上图了,以下有几点需要注意:

  • 需要注意,就是这种session cookie的方式,当用户关闭浏览器的时候,浏览器中存放的session_id就会消失。比方说我在上面的代码中,将
   cookie: { maxAge: 60 * 1000, httpOnly: false }

变成为

   cookie: {httpOnly: false }

其实就是将maxAge去掉,此时存在浏览器中的cookie将在浏览器关闭的同时消失,此时服务器就失去了对浏览器身份的追踪,需要重新做身份认证。其他的几个点如下:

  • 因为可能会出现客户端浏览器禁用cookie,会导致无法设置session_id,有另外一种方式在客户端设置session_id的方式就是将其设置在url中,比如:http://..../xxx;sessionid=123123123(当然session最好做一下加密)
  • 因为当并发过多的时候,可能在服务器端会存储大量的session,这样非常占内存,要给session设置一个生命周期,让它在一定时间内销毁~
  • 上面的代码是将session存储在内存,如果重启服务器将丢失所有的session,正常来说可能会放到数据库中。其他存储的方式请参考cookie和session, node实践

cookie-session

cookie的超级变换形态,已知cookie是将内容存在服务端,session是将内容存放在服务器端,那么我们不管每次通讯的数据量大小了,我就想每次都让请求都带上session中的内容,我就想把数据全存在客户端的cookie中,那么可以用cookie-session,简单来说,就是把所有的东西都存在cookie里面,4kb呢,够用了~
既然把数据都放在cookie里面了,那肯定最大的问题就是安全问题了,signedCookie我们将id做个签名就好了,这边我们需要来一点更凶的方式来进行加密,没错就是对称加密。其实就是将session中的信息用公钥加密发给客户端,当客户端带着这份信息来向服务端请求的时候,再用私钥来解密。That's it!

JWT(JSON Web Token)

JWT也是用来做认证的,上面的这些方法现在都算是“传统”的认证方式了,缺点就是扩展性不是很好,比方说你有很多个服务器啥的,或者需要跨域请求,需要想到共享session之类的,想想就头疼。有人可能会说上面的signedCookie不是挺好吗,把东西都存在cookie中,不用担心多个服务器的session共享,但是他只有4kb啊,并且不能跨域。所以JWT came out,它的思路和signedCookie差不多,但是他可以跨域,因为它存在了请求的header中。

简单来说呢,比方说需要存储关于用户的信息其实就是一个JSON,我们将它加密,加密之后的结果是一长串字母,比方说123123.456456.789789,当然真实的JWT比这长很多,超长的。这串字母用‘.‘隔开,分别代表了Header.Payload.Signature这三个部分,这三个部分每部分其实都是一个JSON经过加密之后的样子,具体比方说Header,signature中解密之后的JSON长啥样,可以看这里cookie和session, node实践,无非就是些加密,签名信息。中间的Payload中解密之后的JSON中除了一些官方的字段以外,你也可以自定义一些字段加进去。。
然后这串字母被放到你每次请求的HEADER中的Authorization

Authorization: Bearer <token>

这样就既能跨域,又将信息存在了客户端,安全可靠,还让服务端的扩展性更强。Perfect 🎉

reference:
1.cookie和session, node实践
2. JWT入门教程

Great! You've successfully subscribed.
Great! Next, complete checkout for full access.
Welcome back! You've successfully signed in.
Success! Your account is fully activated, you now have access to all content.