http 是无状态的,即我们的一次请求结束后,下一次请求,服务端他并不知道是哪个用户发来的。
我们在业务开发中通常是不需要关注是哪个客户端发来的,更多的是关注是哪个用户发来的。
基于这个特点,我们在处理业务逻辑时,就得想方设法地在下一次请求时让服务端知道我是哪个用户。
为什么是下一次呢?
因为首先我们得先登录,才能告诉下一次请求是哪个,否则我们的很多业务就没法开展,这就是所谓的会话管理。
那我们在项目里通常是怎么去管理我们的会话呢?
下面介绍常用的三种方式:
一、基于 server 端 session 的管理
在早期的 web 应用中,我们通常都是使用这种方式来管理会话,它也叫服务端 session 管理,这里快速给大家介绍下它的处理逻辑:
- 用户第一次访问应用时,服务端就会给他创建一个对象,用来存储这次会话需要存储的数据,同时会分配一个唯一的 sessionid(字符串)给这次请求,这样就把用户和 sessionid 进行了关联。
- 服务端处理完第一步之后,就会把 sessionid 以 cookie 的方式返回给客户端,因为客户端每次请求都会带上 cookie,所以后续的请求服务端都能从 cookie 里面得到 sessionid,以此来知道请求是哪个用户发来的。
- session 是有失效时间的,比如 30 分钟,当失效时间到了,服务端就会销毁之前创建的 session,下次客户端再拿 sessionid 来请求也就找不到了,从而达到过期的效果。
- 但是只要客户端在失效期内再次访问,session 的失效时间就会被再次延长,以此来保证用户的良好体验。
- 单纯的 session 是不具备会话管理的,这里面最关键的就是把登陆用户和 sessionid 进行关联,如果不进行关联就不能起到会话管理的作用,在项目里面的体现就是:用户登录后就进行session里面写登录用户信息,从而实现关联,用户退出就删除 session 里面的用户信息,从而破坏关联。
我在网上找了一张流程图,感兴趣可以看下:
这种方式有一个比较大的优点就是安全性好,因为客户端和服务端保持会话的只有 sessionid,只要这个 id 足够的随机,那么别人就很难伪造冒充。
即使伪造了 id 也得是后台存在这个 id 才行。
也不是说这种方式就绝对安全,除非他们通过 CSRF(跨站请求伪造)或者 http 劫持的方式,这又是另个话题了。
这种方式也存在缺点:
- 在线用户量很多的时候,这些会话信息会比较占用空间。
- 当应用采用集群部署时,会遇到多台服务器之间 session 共享的问题,因为很可能处理请求的那台服务器,不是创建 session 的那台服务器。
- 假如你有多个应用,想共享 session 时,还得处理跨域的问题。
前两个问题现在已经有比较成熟的解决方案了,那就是可以使用 Redis 这种中间服务来托管 session 的增删改查。
对于第三个问题,解决起来就比较麻烦了,前后端都需要做处理,因为它最关键部分是 cookie,所以只要保证多个应用之间的 cookie 能互通跨域就 OK。
这也是为啥现在的新系统,都很少使用这种方式了,有比较多的局限性。
这种方式也不是不能用了,毕竟与后来出现的两种管理方式比,这种管理方式他的安全性相对来说是最高的。
比较适合单体服务网站使用。
由于第一种方式会增加服务端的负担和架构的复杂性,所以后来的人想,那我把用户的登录凭证直接存到客户端这样就不会增加服务端的负担了哇。
于是就有了第二种,cookie-based 的管理方式。
即:当用户登录成功后,直接把用户的信息写到 cookie 里面,这样的话,上面的前两个缺点都解决了。
他的流程大致为:
- 用户发起登录请求,登录信息验证通过后,把用户信息处理成一个登录凭证,这个凭证需要有创建时间和过期时间,便于服务端处理过期。
- 服务端把登录凭证进行签名,加密等操作(这步非常关键),保证其相对安全,然后写入 cookie。
- 登录后发起的请求,服务端就能根据 cookie 里面的登录凭证来知道是哪个用户发起的请求了。
我又找了一张流程图,请看:
这种方式最大的优点就是实现了服务端的无状态化,就是服务端不用再去管理会话了,服务端只需要处理 创建和验证 cookie 里面的登陆凭证就好了。
但是也存在缺点:
- cookie 是有大小限制的,存储不了太多数据,所以对写入的数据量有非常大的要求。
- cookie 也依旧存在跨域的问题。
最关键的是,不是所有客户端都是浏览器,如果是 APP 这类不太好管理 cookie 的客户端,这两种方式都不太适用。
于是人们便又有了新方式!
三、token-based 的管理方式
这是目前用的比较多的方式,他本质上和第二种没太大区别,只不过第二种登凭证是放 cookie 里面,token-based 是丢给客户端自己管理。
只需要下次请求时把 token(登录凭证) 放请求头里,或者和服务端约定好的地方,只要能获取到的地方,就能达到验证的目的,从而进行会话的管理。
看下流程图:
这种方式服务端不再通过固定的 cookie 进行 token 传递,所以他的灵活性更大,我们只需要:
- 存储好服务端返回的 token,得保证每次调用接口的时候能拿到 token。
- 每次调用接口时,把 token 加到和服务端约定的地方,比如请求头里面即可。
这种方式解决了第一种方式遗留的的三个问题:资源占用,共享,跨域。
但是也让想伪造 token 的朋友们,更加容易伪造了,所以这种方式最关键的部分就是签名了,签名被破译就等于完全打开了你家的大门。
四、总结
市面上在处理 token 的问题上,用得比较多就是 JWT 标准。
JWT 本身并没有任何技术实现,他只是定义了 token 的生成的过程和方法,因此不同开发语言也就有了非常多开源的库,实际开发中没太大必要自己再去重复造一个 JWT 的轮子。
第三种方式使用起来固然便捷,但是相对第一种和第二种来说,token 被伪造的可能性高出不是一点半点。
最关键的地方就是签名的算法,相关的加密 Key 千万千万不能泄露了。
这三种方式都有一个终结问题,就是 CSRF 攻击(跨站请求伪造)。
其原理就是通过各种方法偷偷拿到别人的登录凭证,然后伪造请求。
其解决方案针对不同的攻击方式解决方案不一样,完全取决于开发人员对这种攻击方式的了解程度,所以亲们,有空多看看 CSRF 这方面的知识吧!
这里提供一种非常具有代表性的攻击方式:
假如你的 token 是放在 cookie 里面的,正常情况下别人是很难拿到的。
但是假如你的网站里,用户有填写外链的地方,比如评论,此时如果其他地方填写了他的服务地址,又或者你挂了外网的图片地址等。
这样你网站在请求这些外网地址的时候,也就间接的泄露了你的 cookie,假如这个地址是别人的恶意地址,后果就可想而知了。
这就是一种典型的 CSRF 跨站攻击方式。
安全无小事,防范需谨慎!
原文链接:https://mp.weixin.qq.com/s/Njss-eZz6LMgpcRsmDFbOw