all articles

basic understanding of cookie&session&token

2017-03-17 @sunderls

js

最近做welogger.com,想要用service worker来进行强缓存,因为访问特别慢,考虑用app shell模型来提升体验。但是出现了一个问题,API的csrf_token放在html中被缓存了的话,会跟随session的实效而失效。为了解决这个问题,貌似可以用token的方式来解决,laravel提供了passport来实现,但是自己对这些东西不是很了解,所以网上搜集了资料, 做一些学习笔记。

主要参考: https://auth0.com/blog/cookies-vs-tokens-definitive-guide/

1. 基于cookie的验证

http是无状态的,服务器不知道你是谁。当然可以每次请求都带上密码,不过这也太麻烦也不安全了。人们想到一个方法:

  1. 用户login
  2. 服务器生成回话(session),session中包含谁谁谁,什么时候登陆的信息,session有个sessionId,返回给浏览器
  3. 下一次再访问的时候,带上这个ID就行了。服务器根据session ID来判断访问者是谁。

cookie是一种特殊的存储技术,cookie按照域名存在浏览器端,当浏览器访问domainA的时候,会自动带上domainA的cookie。 很显然,上述sessionID放在cookie中最合适不过了。

用户logout的时候,清空会话,清空cookie中的sessionID

2. 基于token的验证

好了,token就是说我相信你说你是你,告诉我你是谁就行了

  1. 用户login
  2. 返回一个token(证明你是你)给浏览器
  3. 下次再访问的时候,带上这个token就行了。服务器根据token判断访问者是谁。

很显然,这个token存在哪儿都行,localstorage或者cookie。logout的时候,浏览器端删除掉token就行了。

3. 简单对比

从步骤上看,貌似二者没啥大区别,都需要获取/保存/传递一个标记,这个标记是sessionId或者token。但是在实现上还是有很大的不同。可以看到对于token而言,服务器不需要去查看你是谁,不需要保存你的会话,直接看token中的用户id就行。sessionID只是ID没有其他信息,而token则不一样,根据token的实现可以包含不同信息。

就好比去某个政府办事

  1. 服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。
  2. 直接妹子给他看身份证

感觉还是很不一样的,实际上2看上去要简单的多。

4. token的优势

这一部分就参考的这篇文章了。

4.1 无状态,可扩展性

如前所述,服务器端不存储会话,可扩展性强,如果有多台服务器,会话信息存储地方不只一个的话,就需要某种同步机制,或者想办法知道用户和session存储地的对应关系。

4.2 跨域问题

浏览器有跨域问题,如果有好几个不同的网站的话,cookie-session的话需要在login的时候给所有的domain写上cookie,通常做法应该是有个sso endpoint,登陆成功后跳转到专门的页面,页面上用iframe之类的分别调用各个domain的写cookie endpoint。

如果用了token,只要不同的网站采用相同的验证机制,就ok了。

4.3 token里面可以存储信息

这个待会儿来看jwt(json web token)的实现。

4.4 性能

token不用查db,然后用户角色的划分,也可以通过token中的数据进行查看。

4.5 支持非浏览器环境

嗯,app之类的。

5. token的问题

5.1 token的size

sessoinId只存了id,很小。token则比较大,不过这个问题应该不大吧?

5.2 token的存储

如果存在localStorage的话,不像cookie,localStorage不支持子域名获取主域名的localStorage。如果存在cookie中,会受到4kb的限制。 这一点上,感觉localStorage的域名问题比较大,如何解决?

5.3 xss和xsrf

xss简单上说就是外部脚本闯进来了,情况比较多,比如用户的输入没有进行检查啊,用的第三方脚本出问题了的话,token就暴露在敌人眼前了。这个常见的框架应该都有保证,不要乱用应该就ok,第三方脚本也不要乱用。

xsrf是说用户在恶意网站B上面点来点去,不小心操纵了网站A的数据。比如登陆了A,A上允许post到一个url然后删除账号,结果网站B悄悄做了一个隐藏的form,用户点击的时候不经意触发了这个url,由于浏览器会带上cookie,所以直接成功了。

所以在post等重要的请求的时候,服务器需要验证这个请求是不是从网站自身上发出的。证明这个的办法是,在页面中放上一个自己才有的随机字符串(csrf_token),然后请求的时候带上这个token,服务器端进行验证。这样其他网站不能生成这个token所以也就无解了。

如果网站B上悄悄放上A的iframe,然后引诱用户去点呢? 这是另外一个问题了,这个就需要通过设置http header X-Frame-Options来禁止被用作iframe了。

如果token存在localStorage中,根据浏览器机制,token是不会被其他人知道的,所以不会出现xsrf问题,但是会出现xss的问题,xss问题可以通过http header csp来处理。

如果是存在cookie的话,可以用httpOnlyflag禁止js进行获取,所以xss可以完全避免。但是这样的话js获取不到,服务器只能通过session一样的方式,在cookie中找token,这样会遇到xsrf问题,而且cookie还有其他的很多问题。

关于安全的,我补充两个地方:

  1. MITM: 经常想如果token被人截获了怎么办?其实想想如果真有这么一个人,无论是哪种方式都是无法保证安全的。所以https是必须的,不用考虑这种半途打劫的情况。
  2. 过期问题: session也有过期时间,所以会有自动登录选项,自动续期。token也有过期时间,过期后需要重新发行token。

6. JWT

来看看 token如何实现的,貌似jwt已经成为了事实上的标准。 jwt包含三个部分

以下内容基本来自于官网 https://jwt.io/

  1. Header
  2. Payload
  3. Signature

三个部分分别处理后形成这样一个token: xxxx.yyyy.zzzz header

{
  "alg": "HS256", // 算法
  "typ": "JWT"    // token类型
}

上述信息Base64Url转换后,形成xxxx

Payload 中包含很多claim,这些claim是一些我是谁,我能干嘛的一些声明(claim)。这些claim分三种(Reserved, Public, private),种类细分暂时是TODO,后来我再看。

{
  "sub": "1234567890",
  "name": "John Doe",
  "admin": true
}

上述信息Base64Url转换后,形成yyyy

Signature 需要将上述xxxx,yyyy部分加一起,通过header中指定的加密方法,然后加上一个secret,hash过后得到。 比如上述信息的话:

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret)

这样得到zzzz的部分

很显然,server保存的secret,所以外部是不能自己模拟产生一个合法的token。简单想想,其实就是你写了个证明,由某个机构给你改了个章。你可以自己修改其中的信息,但是章你盖不出来,所以文件没有效力,这也是为什么填文件资料填错涂改的时候,需要在涂改的部分旁边,加上一个章。 嗯。

7. 总结

感觉懂了很多。