安全的数字化身份互连(三)--JWT与单点登录

什么是单点登录,指在多服务中只需登录一次,即可访问多个服务并且无需再次登录。多服务可指微服务内的不同服务,同企业不同的独立服务,跨企业的不同服务等等。

单系统中所有功能在一个系统上,用户身份可通过cookie+session认证,如果是集群部署,只需解决集群之间的session共享即可。企业随着业务的发展,为了合理利用资源和降低耦合性,往往采用SOA或微服务架构,那这种架构如何解决用户登录认证的问题?

我们先聊微服务架构的单点登录。微服务登录认证需要认证中心服务(本文中称为IAM)。只有IAM可拥有用户的密码并生成用户的会话标识,其他服务只接收IAM颁发的会话标识,并可通过会话标识直接或间接获取到用户的身份,之后完成用户的业务逻辑。为了完成微服务间的登录与认证往往需要gateway协助一起完成。具体做法如下:


1. 用户输入用户名和密码登录:iam校验密码的合法性并保存session和返回cookie

2. 用户访问业务服务时,先经过gateway,gateway获取cookie,调用IAM接口获取用户信息

3. gateway携带用户信息路由到业务服务中

用户每次操作服务都会先经过gateway,gateway都需要调用IAM的接口获取用户信息。有没有一种方式可以让gateway获取会话标识时,根据会话标识就获取用户的信息呢?为了做到这点需要会话标识拥有几个特点:

1. 自包含用户信息

2. 不可篡改

3. 可过期

JWT(JSON Web Token)

JWT完全可以符合这几个特点。JWT分三部分组成,Header,Payload,Signature。Payload可以包含用户信息和过期时间,Signature可以防止Payload被篡改。

IAM通过Payload生成Signature的方式分为两种,对称与不对称,如果采用对称签名的方式,意味着IAM要与业务服务分享同一个密钥,这会导致IAM可以生成JWT,业务服务也可以,这很危险。

那我们采用非对称签名的方式,IAM通过私钥生成JWT,业务服务提前获取IAM的公钥(服务可以在启动时调IAM API获取)。当一个请求携带JWT时,业务服务通过公钥验签,能验签通过说明一定是IAM颁发的JWT。通过Payload可以获取用户的信息,验证过期时间保证有效性。

多么美好的解决方案啊!IAM只需在用户登录时验证用户的密码生成JWT,对于IAM来说登录的工作就完成了,也不需要保存session了。

但安全问题也随之而来。

JWT因为Signature的不可篡改性,过期时间一旦生成就固定,这会导致几问题:

1. 用户使用系统还未结束但过期时间已到。

2. 用户点击了退出按钮,但通过JWT仍然可以访问系统。

3. 系统管理员发现用户在非法操作时,想强制下线用户,但在一定时间内无法实施。

对于cookie+session的方式:

session是有过期时间的,用户一直使用的场景下,可以延长session的过期时间来保证用户对系统的可用性。

用户退出时系统删除session,cookie中的sessionId将变为毫无意义的随机数。

当管理员发现用户非法操作时,删除session就能达到目的。

都挺简单的,但对于JWT就比较麻烦了。

第一个问题:

因JWT是保存在客户端的,一旦JWT过期,需要获取一个新的JWT来保证可用性,但又不能让用户重新登录,因此在IAM生成JWT时,多返回一个Refresh  Token。可以设置Jwt 的过期时间是半小时,Refresh Token的过期时间是一天。当JWT过期后,客户端使用Refresh Token调IAM api获取新的JWT。Refresh Token过期后,用户需重新登录。

第二个问题是用户正常退出:

一般情况下JWT保存在客户端,用户退出成功后,客户端删除JWT即可,如果用户保存了JWT并点击退出,然后携带JWT访问后端接口,会发现还是能访问。不过正常用户也不会自找麻烦退出后再通过JWT访问系统。

但第三个问题就严重了:

如果用户是攻击人或攻击人获取到了正常用户的JWT,攻击人就可以攻击系统了。当管理员发现了系统被攻击了,并找到了对应的用户账号。管理员想把该用户禁用,可以把该用户的账号锁住,无法登陆。并通知客户端,删除JWT。但攻击人还是可以操作系统,因为攻击人私自保存了JWT,通过工具调业务系统接口,一样可以访问系统进行非法操作。

解决这个问题有几种方式,

第一是缩短JWT的过期时间,并将用户加入黑名单。攻击人只能在较短的时间内非法操作系统,通过Refresh Token获取新的JWT时,因为用户在黑名单中,也获取不到新的JWT。这种方式较简单,但问题是,总有一段时间内攻击人可以攻击系统。而且如果设置JWT有效时间太短。会出现频繁获取新JWT,对性能也不太友好。

第二种方式是将用户锁定,并加入黑名单,由IAM来维护这份黑名单,并存入中间件(如Redis)中,其他服务接受到请求时,不仅需要对JWT验签,判断有效时间,还要访问redis,判断下该用户是否在黑名单中。个人感觉这种方式与session的方式差不太多,都需要通过一次网络传输,访问外部服务来判断这次请求是否可信(访问IAM或者访问redis),而且性能也是个问题,session的方式是业务服务只需要一次网络传输即可。但JWT+黑名单的方式是验签+一次网络传输。

也可以采用每个业务服务都维护这份黑名单,可以减少用户操作请求时的网络传输,只是维护成本会增加。

第三种方式是jwt存入白名单,和第二种方式差不多,不再讨论。

采用哪种方式看业务,安全要求极高的系统和安全要求不高的系统采用的方式肯定会有所差异

我们再来聊聊通企业内不同系统间的单点登录

一家大型企业不能保证所有服务都在一个微服务集群中。随着业务的发展,收购子公司或与其他公司合并也经常发生,如果其他公司本身就有自己的系统,这就会导致多个大型系统是完全独立的。一个公司的人要同时操作多个系统时,会出现每个系统都要求登录,登录方式、登录名、登录密码的要求可能还不一样,容易把人搞晕,效率低下,审计也增加了复杂度。

我们需要集中的身份管理服务(本文中称IDP),来对接其他的业务服务,保证用户只需登陆一次,就可以访问原本完全隔离的业务服务(本文中称SP)。要做到这件事可以采用业界标准的协议来完成

重点:推荐使用标准协议,在安全领域,一定要坚信自己的设计不完善,感觉完善可能是因为经验不够或别的原因。而标准协议是经过无数人的挑战也没挑出啥毛病,大多数场景下是可以相信的。就像密码学,加密算法完全可以公开,就算知道加密算法在不知道密钥的情况下也无法破解密文

标准协议有多种,可以采用OAuth2/OIDC,SAML等协议。

但不管SSO哪种协议都需要解决如下两个问题:

1. 只有IDP才有资格接触用户密码和验证密码的合法性。

2. SP获取用户的信息时,只信任IDP,其余皆不可信

本文采用SAML来介绍SSO。

SAML分两种方式,IdP initiated与SP initiated

前提:SP已经获取了IDP的回调地址与IDP的公钥


1.    用户访问SP页面。

2.    sp通过公钥加密数据(不同场景数据不一样),生成SAML Request。

3.    通过浏览器重定向到IDP中,并携带SAML Request。

4.    IDP通过私钥解密,获取对应数据。

5.    IDP提示用户进行登录。

6.    IDP验证用户的身份,获取用户的身份信息(有可能会有权限信息),通过私钥对信息生成签名,并一起生成SAML Response。

7.    通过浏览器重定向到SP中,并携带SAML Response。

8.    SP获取SAML Response,通过公钥对SAML Response的签名进行验证。获取用户信息。

9.    SP生成访问令牌或会话标识(不同服务不同做法),并返回给浏览器。

1.    用户访问IDP登录页。

2.    IDP提示用户进行登录。

3.    IDP验证用户的身份,获取用户的身份信息(有可能会有权限信息),通过私钥对信息生成签名,并一起生成SAML Response。

4.    通过浏览器重定向到SP中,并携带SAML Response。

5.    SP获取SAML Response,通过公钥对SAML Response的签名进行验证。获取用户信息。

6.    SP生成访问令牌或会话标识(不同服务不同做法),并返回给浏览器。

这两种方式用户都是在IDP的页面进行登录,只有IDP接触到了用户的密码,并由IDP验证密码的合法性。

因为非对称签名的特性:公钥只能验证对应私钥生成的签名。SP因保存了的IDP的公钥,SP能验证的签名一定是IDP生成的。

不管是Oauth2/OIDC,SAML也可以解决跨企业不同系统的单点登录问题。

在客户那实现单点登录时常遇见的一个问题是,经常有人会把IDP与SP搞混。牢记一点:用户身份在哪,哪就是IDP。用户需要访问的资源在哪,哪就是SP。

目前只解决了登录的问题,但授权的问题如何解决?下回再聊聊简单到复杂的授权。

©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容