vlambda博客
学习文章列表

前后端分离下的无状态会话管理

引言:

    你点击了一个网站或图片,突然收到银行短信:你有一笔转账信息。对,你的钱应该在无意中被转跑了! 

    在普通用户看起来,这是一个隐藏很深,而且给人的感觉是一个很神秘又很高级的黑客或者木马手段。        

    再听听术语:CSRF,中文翻译:跨站请求攻击。是不是果然很高级?

    实际上呢?


<img >


    其实这就是所谓的CSRF跨站请求攻击,实现原理很简单,只需要具备以下几个前提条件:

1、一个没有安全防御的网站,这个网站有很多用户,而且有转账功能(比如:银行网站),然后抓取到这个网站转账的链接。

2、把转账链接,隐藏到这些用户经常可能会点到的地方或者其他网站,就等着收钱就可以了。

 

    如果用户之前有登录过那个银行网站,你不小心点到这个链接的时候,浏览器会自动把你的身份信息一起带过去,并且告诉系统,你要给我转1000块。因为浏览器认证过,所以网站会认为是真正的用户在操作。

    这就是由于浏览器生成的session,会自动存活在当前浏览器,并会被每个请求自动携带导致的安全问题。

 构造代码 → 伪装代码 → 发送给受害者 → 受害者打开 → 受害者执行了恶意代码 → 攻击完成

    防御CSRF攻击方式和手段有很多。今天主题是无状态会话管理,所以,网站采用token替代session认证,也是其中之一!

    token不存放在cookie中,那么攻击者就无法得到这个随机生成的token,也就没有办法执行CSRF攻击了。

 当然.token的随机性和加密机制天生就具备这个特性,所以就不继续展开了。

    下面就今天的主要话题来展开来说一下,token机制下无状态会话管理的相关话题。


    在后端渲染时代,我们通常利用session,cooike等机制来管理用户的会话。随着终端设备的多样化,和分布式架构的兴起,这种有状态的会话已经不再能简单满足当下的需求了。

    随之而来的是无状态会话技术,更加灵活而简单。

    但是在切换过度时,很多人还是会习惯性的拿有状态会话的惯性思维去理解无状态会话。

    本篇文章主要目的是 介绍无状态会话管理在前后端分离架构下的实际落地方案以及在实际生产应用当中出现的一些问题解答和解决方案。

术语解释

 后文三个常用术语的理解释义。


1

会话

——是服务器判断客户端身份的标识的一个具体场景。

2

有状态会话

——以session为例,是在服务端存储一个对象,用来存储所有访问过该服务端的客户端用户信息(也可以存储其他信息),从而实现保持用户会话状态的一个过程,但是由于是存储在服务器内存的,所以当服务器重启时,内存会被销毁,存储的用户信息也就随之消失了。

——会话在服务器和客户端之间有强依赖关系并持续维持的过程。

3

无状态会话

——以token为例,用户每次登录时建立的唯一标识,伴随整个用户交互周期。利用加密和层级管理,和客户端保持对应关系.每次客户端发起请求都需携带,由服务端进行解析后认证。适用于项目级的前后端分离(前后端代码运行在不同的服务器下),服务端不保存具体会话,每次客户端请求时都要根据秘钥解析token来认证用户信息,对服务端不是强依赖关系。

——会话在服务器和客户端是相互独立存在。

场景描述

 一次性验证

     由于公司业务发展的需要,所有系统需要对用户的注册逻辑进行安全性调整。

    需要用户在系统注册时,填写一个安全邮箱,系统会自动发送一封激活邮件到这个邮箱,用户只有在自己接收到这个邮件并且点击邮件正文里附带的一个激活链接,账户才能正式开通使用。

    这个需求在通报给开发人员的时候,开发人员在设计时,通常需要考虑和解决以下几个问题:

      这个激活链接不能是固定的,因为每个链接都必须要能识别出当前点击的用户身份信息

    这个链接只能是一次性的,而且需要具有时效性(一般只能允许几小时之内才能激活,这个是为了尽可能的避免用户信息泄露)

     这个链接不能被随意篡改(需要防止恶意激活系统内的其他账户)

    这种场景就和 jwt的token特性非常贴近.jwt 的payload 中固定的参数,iss 签发者和 exp过期时间正是为其做准备的。


用户登录保持

    由于公司业务发展迅速,系统经常升级,导致用户投诉说每天都要频繁登录。开发人员分析后得出一个结论,是由于当前用户认证状态是由session保持的,而session在系统中,又是存到到jvm内存中。所以当服务器重启后,jvm内存释放导致session丢失。所以用户每次在系统重启后都需要重新进行登录认证。

    这种场景虽然可以由redis或者其他介质存放session,来避免服务器重启导致会话丢失。但是毕竟治标不治本,而且通过redis来维持会话,也会导致一系列其他问题。需要长期维持一个会话状态.那这种情况下,最简单的方式就是换成无状态会话。

分布式系统中的会话保持

    由于公司业务体量升级,导致活跃用户暴增,导致服务器扛不住高峰流量而总是宕机。现有架构已经无法满足当前用户量的访问,于是公司决定利用分布式或集群来分担服务器压力。

    这个时候,开发人员就会发现,用户登录总是失效。有时候明明已经登录了,再点击其他页面的时候却又弹出需要登录的页面,根本无法完成一个正常操作.用户体验极差,导致频繁投诉。

     经过开发人员排查后发现,由于系统架构采用了负载均衡策略,而会话保持策略依旧使用的session。

    导致第一个请求到了A服务器,有可能同个用户的第N个请求,会被负载到B服务器上做操作。虽然减轻了服务器压力,但是B服务器没有保存到用户的session认证信息,所以用户总是要在登录,使用,再登录中循环,导致了大量用户的投诉。

    这个问题有很多解决方案,最容易想到的就是,怎么在多个服务器当中去共享session和保持会话。策略很多,利用redis+消息传播机制什么的(但由于不推荐,而且不是本篇文章目的.就不再此过多解释了,有兴趣的可以去网上查一下)。

    如果换成了token方式,那就不存在这个问题了,服务端都有同一把秘钥,不同客户端的用户传到服务器的token都是同把秘钥加密后的数据,所以多个服务器只需要临时解析就可以得知当前用户的身份。


落地方案

.完整流程

.生命周期

    我们把会话的生命周期分成三个阶段.初始化阶段,过程阶段以及销毁阶段.


1

初始化阶段

 是由客户端发起做登录认证,当服务端对用户认证通过以后,通过秘钥把用户信息进行统一加密,并生成一个token串,返回给客户端进行本地保存。在此期间,服务端是不需要对当前会话进行维持和保存操作的。

2

过程阶段

    客户端每次访问服务接口,都需要在heard上携带此token.服务端接受请求时用秘钥解析token串来获得当前用户信息。这个过程,如果服务端每次接收到请求都去解析一次协议里的token,会增加很大重复工作量,这里可以选择几种方案。

    这个过程,如果服务端每次接收到请求都去解析一次协议里的token,会增加很大重复工作量,这里可以选择几种方案。

    1.网关方式,前置网关去统一解析.统一鉴权.网关通过解析当前用户信息给到具体服务。这种方式可以把鉴权抽离到前置服务进行统一管理,但是缺点也很明显,就是业务服务保持裸奔状态。但是如果业务服务能保证不暴露在公网,是可以忽略这个问题的。

    2.使用开源社区第三方的鉴权框架做统一处理.优点就是可以直接使用别人集成好的成熟方案,缺点是对于鉴权来说,用第三方框架还是太重了,几个链式过滤器和几个注解就能完成的事情,依赖框架有点儿得不偿失。

 3.过滤器方式,以通用能力进行请求拦截并解析,并封装成组件形式集成到每个服务去统一使用,这种好处就是在保证每个服务的接口都是受控状态,且单个服务可以抽离到其他环境进行独立部署和使用,可以更好的保证服务的独立性和完整性。也是目前使用和推荐的方式,轻量级架构无依赖。

3

销毁阶段

    这个是用户在退出登录的阶段,当前用户的整个生命周期随之结束.只需要客户端去删除掉本地token即可,不需要服务端做任何操作。

    伴随着用户的客户端登出操作,总有人会联想到有状态管理会话时的操作,发现几个看似不太合理的地方.接下来就对几个比较常见的问题展开讲一下。

问题

    在用户登出操作时,客户端会通知服务,由服务端使session进行失效。现在无状态管理会话时,同样的操作,客户端直接删除本地token就完成了,消失的服务端操作会让很多刚接触这种模式的人产生很大的不安全感。  

    其实这个问题大可不必过于纠结,对比session的退出机制,唯一的区别在session是可以由客户端主动发起session失效,而无状态token不能。

    对于用户或者客户端没有主动发起登出动作的情况来说,两种方式都一样。

    接下来就经常遇到的两个风险相关的热点问题分析一下。

1

网络传输中的token泄露

    这个问题,基本上和用什么架构和什么技术没什么关系了.就算在前后端没有分离的时期,session也会遇到这个问题,网络数据泄露问题是网络安全层面的问题,一般解决方案是利用网络协议进行加密处理,可直接利用https安全协议就可以解决。

2

客户端的token泄露

    这个担忧主要来源于token会不会存在客户端泄露的风险.其实这个问题session时期也会存在,没必要过份担心.这个问题出现的前提条件来自于客户端本地保存方案,太容易被人为获取到了.甚至在浏览器中通过F11就能直接取到。

 但是没必要过度担心...

    太容易获取到token这个问题出现的前提只有用户没有主动退出登录或者系统没有设置自动登出机制造成的.用户账户本身没有退出,也就是说在客户端还是可以无需登录直接访问的,这种情况下,再去获取token看来并没有多大意义.拿token单独去伪造接口请求去访问数据的麻烦程度也不如我直接在当前用户界面或者电脑上进行脚本操作来的轻松。

    黑客木马之类的,主要是个人领域的问题,电脑或者浏览器防护安全问题。就好比银行卡和密码都丢了还不挂失,账户的钱是不是有风险一样。

    这种情况,如果想完全规避掉是没多大必要的,但可以从侧面降低这些风险带来的负面影响。比方说服务端生成token时,设置的有效期短一点(这个应该在保证用户体验的前提下)。

    密码的修改或重置需要伴随token的重新生成等都是降低风险的手段。

结论

    如果抛开前后端分离架构和多终端设备,还有一个比较推荐采用无状态的模式管理会话的原因,就是分布式的服务架构体系。

    当用户的请求每次都被随机到一个服务器的时候,如果要实现session共享,还需要做一系列额外的操作,来在所有服务器之间保持和共享这个会话状态.繁琐的同时,还会因为额外的网络开销导致性能上的下降。

    所以,若是在新的微服务分布式架构下,尽量利用无状态模式来进行会话管理。