SSO 全称 Single Sign On,就是所谓的“单点登录”。 对于一个复杂的业务系统来说,其中会有多个应用子系统,每个应用子系统提供独立的功能。 而每一个应用子系统都有着登录、验证和鉴权的需求,当用户需要同时使用多个子系统时,就需要分别执行多次登录操作,这无疑是非常繁琐的一件事。 是否可以让用户一处登录处处使用呢?这就是 SSO 要解决的问题。
既然要实现一次登录处处使用,那就要做到用户标记,最简单的做法就是首次登录为用户生成一个标识,例如,该标识存储到客户端缓存,例如浏览器的 cookie 里。 当另一个应用也需要验证登录,只要在相同的客户端缓存中读取到标识,服务端验证标识即可。
上述方案将 cookie 的生成和校验逻辑分散在各个子系统中,存在下面几个问题:
因此,基于上述这些问题,接下来的方案就呼之欲出了 — 构建统一的认证中心,这样做的好处是显而易见的。
下面是服务端处理用户请求的流程图:
方案2做到了业务系统与 SSO 认证系统的解耦,但是仍然存在一个显著问题,对于浏览器客户端来说一个 domain 的所有子 domain 可以共享 cookie,但是完全不同的域名之间则无法读写 cookie。 例如,sso.taobao.com、www.taobao.com 与 xxx.taobao.com 都可以共享 .taobao.com 域下的 cookie,但是所有浏览器都不会将 tmall.com 的 cookie 传递给 sso.taobao.com。 这意味着使用上面的方案将导致类似淘宝与天猫的跨域请求不能实现单点登录。 于是就有了进一步的可以支持跨域请求的方案:
我们看到上述的方案3已经可以解决跨域单点登录的问题,其逻辑也非常清晰,那么,在实际的线上系统中,是否采用了这样的交互方式呢? 我们来通过实例验证一下。
ITeye 是 2003 年创办的一个 java 技术社区,此前被 CSDN 低调收购,收购后 https://www.iteye.com/ 仍然被独立运营着,但是 https://www.iteye.com/ 新增了可以使用 csdn 账号登录的入口。 这就是典型的跨域单点登录了,因为实际的登录入口是 https://api.csdn.net。 那么,我们来抓包看看他是怎么做的。
抓包看到,iteye.com 的服务端果然返回了跳转到 http://api.csdn.net 的 302 response,并且带有 redirect_uri=http://www.iteye.com/auth/csdn/callback 的参数,用来通知 SSO 中心即 http://api.csdn.net 验证后的跳转地址,对应我们方案3的步骤1。
浏览器跳转后,http://api.csdn.net 返回了 Status Code:307 Internal Redirect,并且在 header 中带有 Non-Authoritative-Reason:HSTS。 这表示该地址仅接受 https 请求,浏览器收到该提示后,自动跳转到了对应的 https 地址。
方案3的步骤2,用户登录的过程我们这里不赘述,抓包可以看到,由于我们已经登录过 csdn,所以浏览器自动在 header 中加入了带有秘钥信息的 cookie。 https://api.csdn.net 验证秘钥后自动跳转回了 sourceurl,并且带上了 code 参数,这就是我们的方案3步骤3,而 code 参数就是我们上文中的 token。
浏览器接收到 302 返回后,自动跳转到带有 code 参数的 sourceurl。 服务端获取到 code 参数后调用接口验证成功,执行业务逻辑:返回 302 跳转到 https://www.iteye.com,同时设置了一系列 cookie。
至此,我们看到,整个过程与我们上面的方案3完全符合。