本篇速览脑图

名词约定
全局会话
判定标志
SSO页面的session存在且未过期
局部会话
在各个子系统,是否已经登录过,这个我们称为局部会话
判定标志
子系统存在可行的token【未过期且有效】
ticket
SSO系统颁发给子系统的凭证,有此凭证且有效的话,表明SSO系统允许子系统去建立局部会话【生成token】
token
子系统的访问凭证,各个子系统的token是不一样的,具体视业务而定,子系统也需要配置相应的拦截器来检测token
SSO登录
当SSO登录页面登录成功后,会存储一份session,建立起会话,表示全局会话已存在 session我们这里就不再过多赘述,想了解更多的话可以参考本专栏往期内容
用户访问流程
直接访问子系统A分成了两种情况:SSO已登录或未登录【全局会话是否存在】
我们先列出时序图,后续分析起来对照时序图会更清晰一点
ps:时序图里边涉及到了一些代码细节,不清晰的地方可以先跳过,后续讲解代码的时候再着重解读
SSO已登录

注意:这里我们分析的是SSO已登录,也就是全局会话已存在的情况
- melo直接访问子系统A,此时会优先判断局部会话是否存在且有效【token】
- 若有效则直接放行,没有SSO什么事情了,业务正常执行
- 若无效,说明局部会话不存在,此时去判断全局会话
- 接下来会跳转到SSO页面,SSO页面调用SSO服务接口,判断全局会话是否存在,发现session存在且有效
现在全局会话校验成功了,接下来的问题就是如何建立局部会话?
- 局部会话依赖于全局会话,所以需要全局会话去颁发ticket给子系统A【相当于一个授权的过程,允许子系统去登录】
- 子系统拿到ticket后,校验是否是SSO颁发的且有效,有效则解析出ticket里边的凭证【比如学号】,然后根据学号在子系统A生成局部会话token,至此局部会话也建立成功了。
SSO未登录
SSO未登录的流程,跟上边大体是相同的,只不过在SSO页面判断全局会话不存在时【session为空】,此时需要跳转到SSO登录页面,登录成功生成session后 ,再去给子系统颁发ticket

细节
SSO如何跳转回子系统A
子系统A跳转到SSO的时候,需要传递参数redirect_url,后续根据这个url就能跳转回去
ticket如何传递给子系统A
一般是从SSO跳转到子系统A的时候,拼接在地址栏后边,比如melo.com?ticket=xxx
不同系统需要共用redis吗?
不需要的,我们用远程调用的方式,去调用各个系统的接口,各个系统接口内部,就能访问各自主机redis,而不需跨系统去访问其他主机的redis
安全优化
ticket如何防范被篡改?
ticket里边是有用户凭证的,黑客如果篡改了ticket里边的用户凭证,比如改成黑客自己的,那到子系统A登录的时候,登录的就是黑客的身份了。
此处melo的解决方法,其实是类似jwt,颁发ticket的时候,是用jwt的加密方式【结合数字签名】
此处不清晰的同学,可以参考本专栏往期内容
只要加密数字签名的私钥不泄露,黑客就没办法自行篡改凭证后,捏造出对应的数字签名。
ticket如何防盗用?
ticket拼接在地址栏,安全风险还是蛮高的,如果黑客拿到我们的ticket,岂不是能直接去子系统A登录了?
此处melo的解决方法是:SSO颁发ticket的时候,获取此时用户的ip,采用JWT机制,并在payload里边绑定用户的ip,到子系统A的时候,校验ticket有效性的时候,再次获取用户ip,并解析出payload里边的ip,对照两个ip是否一致,一致说明是同一个用户。
此方法的缺点是:黑客能够看到payload里边的ip,如果黑客伪造自己ip为payload里边的,这样就能通过我们的校验了。
ticket如何设置只允许使用一次?
ticket如果可以被多次使用,也会带来一定的风险。
此处melo的解决方法是:SSO颁发ticket的时候,就把ticket存储在redis中,并设置1min过期
子系统A验证ticket有效性的时候,远程调用SSO的verify接口【接口的功能是:判断SSO的redis中ticket是否已过期】,未过期则说明有效,并删除掉该ticket
- 若已过期,则说明已失效了,需要重新颁发
- 细心的同学也许会发现,远程调用其实就解决了,不同系统需要共用redis的问题,如果是直接在子系统A去验证的话,需要去访问SSO的redis【但由于防火墙问题,redis一般都是设置仅本机可访问的】
- 而如果用远程调用的话,是去调用sso的verify接口,此接口只需要判断本机上的redis是否存在ticket即可
防重放
比如我们有这样一个接口,查询支付订单的接口,调用该接口时出于安全性,需要绑定一个签名,签名由订单的场地id+时间段等信息,末尾再加上通讯双方约定好的私钥 secret ,用md5加密生成sign,作为接口的签名传递到服务端去校验
服务端用同样的信息和规则,结合 secret ,用md5加密后,判断两个签名是否一致
虽然这样解决了接口信息篡改的问题,黑客无法自行伪造签名来发请求,但如果黑客抓取到我们的一个合法请求后,其实是可以一直用这个合法请求,去疯狂调用我们接口的~
什么是重放攻击
所以,重放攻击就是:黑客拦截了我们的请求,获得我们发给服务端的一个合法请求,然后重复地发送该合法请求,若该请求耗时过长,极端调用可能会搞崩我们的系统
注意 — 跟幂等的区别
幂等是允许执行多次,只不过执行多次后的结果依然是一样的 而防重放,是不允许执行多次,从根头上就遏制住了
如何防止重放攻击?
原理其实很简单,就是让合法的请求,只能被执行一次,保证每次请求的唯一性
时间戳 timestamp
我们认为一次HTTP请求从发出到到达服务器的时间是不会超过60s的,当你发送一个请求时必须携带一个时间戳timestamp,假设值为20 。
当请求发送到服务器之后,服务器会取出服务器的当前时间, 假设为 now =100, 很明显 now -timestamp>60s,那么服务器就认为请求是不合法的。
防止时间戳被更改
一般情况下,黑客从抓包重放请求耗时远远超过了60s,但如果黑客修改了时间戳为当前时间,使得 now-timestamp < 60s 的话,似乎就能绕过我们的校验了诶?
所以此时我们会结合上文的 防篡改 机制 , 具体流程如下:
- 客户端对 传输的参数信息【时间戳等】+约定好的secret 进行 md5 加密,得到sign,一并传递给服务端
- 服务端重复该过程,对 收到的参数信息【时间戳等】+约定好的secret 进行 md5 加密,得到sign
- 判断是否跟客户端发送过来的sign一致,若一致则说明接口参数信息没有被篡改过
存在问题
如果黑客在60s再次发起请求的话,那还是防范不了的
随机数nonce
nonce的意思是仅一次有效的随机字符串,所以需要做到每次请求的 nonce 都不同,可以由时间戳来生成,结合 ip 地址等,进一步确保唯一性。
客户端请求接口的时候,生成随机数,发送到服务端,服务端吧 nonce 存储在redis里边,当重放请求到达时,验证发现 nonce 已经存在,则认为请求是非法的
存在问题
要保证历史全局唯一,有点麻烦,并且redis存储那么多,开销有点大
时间戳+随机数
综合来看,时间戳的问题是没法防止60s内的攻击,而随机数的问题在于要做到全局唯一,而且要存储很多 nonce ,耗费空间大
所以我们其实可以结合两者,具体流程如下:
- 客户端生成时间戳和随机数,并且作为sign的加密参数,传递给服务端
- 服务端先判断时间戳是否合法,若不合法,则直接返回【省去存储随机数的开销】
- 若时间戳合法,再将 nonce 存储到redis里边,并且设置 1min过期
注意这里设置 1min过期 的好处在于,只需要保证在这 1min内 ,nonce不会重复即可,这样客户端生成nonce也不用太过复杂
后续那些在60s内的攻击,虽然能绕过时间戳校验,但却因为 nonce 的存在【1min过期,所以能防1min内的,且占用内存不会过多】,会被认为是非法的。
而60s以外的攻击,在时间戳校验就直接GG了。
- 所以此方案,综合解决了两者的缺点:无法防止60s的攻击,以及存储开销大
原文链接:https://juejin.cn/post/7160224863964102669
创业项目群,学习操作 18个小项目,添加 微信:923199819 备注:小项目!
如若转载,请注明出处:https://www.zodoho.com/58441.html