最近做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是无状态的,服务器不知道你是谁。当然可以每次请求都带上密码,不过这也太麻烦也不安全了。人们想到一个方法:
- 用户login
- 服务器生成回话(session),session中包含谁谁谁,什么时候登陆的信息,session有个sessionId,返回给浏览器
- 下一次再访问的时候,带上这个ID就行了。服务器根据session ID来判断访问者是谁。
cookie是一种特殊的存储技术,cookie按照域名存在浏览器端,当浏览器访问domainA的时候,会自动带上domainA的cookie。 很显然,上述sessionID放在cookie中最合适不过了。
用户logout的时候,清空会话,清空cookie中的sessionID
2. 基于token的验证
好了,token就是说我相信你说你是你,告诉我你是谁就行了
- 用户login
- 返回一个token(证明你是你)给浏览器
- 下次再访问的时候,带上这个token就行了。服务器根据token判断访问者是谁。
很显然,这个token存在哪儿都行,localstorage或者cookie。logout的时候,浏览器端删除掉token就行了。
3. 简单对比
从步骤上看,貌似二者没啥大区别,都需要获取/保存/传递一个标记,这个标记是sessionId或者token。但是在实现上还是有很大的不同。可以看到对于token而言,服务器不需要去查看你是谁,不需要保存你的会话,直接看token中的用户id就行。sessionID只是ID没有其他信息,而token则不一样,根据token的实现可以包含不同信息。
就好比去某个政府办事
- 服务员看你的身份证,给你一个编号,以后,进行任何操作,都出示编号后服务员去看查你是谁。
- 直接妹子给他看身份证
感觉还是很不一样的,实际上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的话,可以用httpOnly
flag禁止js进行获取,所以xss可以完全避免。但是这样的话js获取不到,服务器只能通过session一样的方式,在cookie中找token,这样会遇到xsrf问题,而且cookie还有其他的很多问题。
关于安全的,我补充两个地方:
- MITM: 经常想如果token被人截获了怎么办?其实想想如果真有这么一个人,无论是哪种方式都是无法保证安全的。所以https是必须的,不用考虑这种半途打劫的情况。
- 过期问题: session也有过期时间,所以会有自动登录选项,自动续期。token也有过期时间,过期后需要重新发行token。
6. JWT
来看看 token如何实现的,貌似jwt已经成为了事实上的标准。 jwt包含三个部分
以下内容基本来自于官网 https://jwt.io/
- Header
- Payload
- 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. 总结
感觉懂了很多