前言
最近公司业务在拓展,如移动端需要使用公司的用户体系和公司内部的产品线需集成各应用子系统,考虑到身份验证、用户信息授权可扩展性、可复用性等问题,避免用户需要针对每个应用系统都进行独立的登录,我们研发团队对单点登录方案的讨论进入了议程。
什么是单点登录
单点登录(Single Sign-On, SSO)是一种身份验证机制,允许用户一次登录即可访问多个应用程序或系统,无需为每个应用程序或系统分别输入认证凭据,便可在其他所有系统中得到授权,无需再次登录。
单点登录解决什么问题
用户侧
- 用户只需要一次身份验证即可访问多个应用程序或系统,从而简化了登录过程,提高了用户体验。
- 减少了用户忘记密码或用户名的风险,因为只需要记住一个凭据。
- 提升安全性,因为用户无需在每个应用程序或系统中输入密码,从而减少了密码泄露或人为错误的可能性。
公司侧
- 减少了应用程序或系统中身份验证模块的开发和维护工作,因为这些模块可以集成到单个身份提供者中(如统一认证中心)。
- 简化了用户管理和授权,使开发者可以使用单一的用户体系中心来管理和授权用户对多个应用程序或系统的访问。
- 降低了开发成本和时间,因为开发者无需为每个应用程序或系统编写自己的身份验证逻辑,而是可以重用已有的用户体系。
单点登录的原理和实现方案
基本原理
- 用户登录:用户在任何一个应用程序或系统中进行身份验证,并提供他们的凭据。
- 认证系统验证:该凭据被发送到认证系统进行验证。如果凭据有效,则认证系统会为用户生成数字签名的令牌(如token或ticket)。
- 令牌分发:认证系统将令牌返回给应用程序或子系统。
- 应用程序或系统授权:应用程序或系统使用令牌验证用户的身份,并授权其访问相应资源或服务。
- 跨域系统访问:用户可以通过同一令牌访问多个跨域应用程序或系统,而无需重复进行身份验证。
实现方案
单点登录的实现方案一般包含:Cookies、分布式Session方式、统一认证授权方式(JWT、OAuth2.0)等。目前解决跨域问题的比较常用的方案是分布式Session及统一认证授权方式。
Cookie单点登录
基于Cookie的单点登录是最简单的单点登录实现方式,使用一个共享的Cookie作为用户身份验证凭据,并在多个应用程序或系统之间进行共享。当用户首次登录某个应用程序时,该应用程序会生成一个加密Cookie,并将其发送到用户的浏览器中(后端接口返回响应头设置cookie)。随后,当用户尝试访问其他应用程序时,这些应用程序请求头会自动带上cookie,后端使用其中的信息验证用户身份。这种方式虽然实现简单,但在Android和iOS应用程序中通常不直接使用cookie来处理数据(我猜测是因为不同的设备和操作系统可能对cookie的支持程度不同),因为cookie是一种在web浏览器中使用的机制,它的使用场景在浏览器端。同时它不支持跨域访问,如果需要在各应用网站共享cookie,其对应的多个站点的顶级域名必须相同。
分布式Session实现单点登录
其实这种方式的原理和cookie差不多,分布式Session实现单点登录原理是将用户认证信息保存于Session中,即以Session内存储的值为用户凭证,一般采用Cache中间件实现(如Redis)。用户再次登录时,应用服务端获取分布式Session来校验用户信息。如图所示:
graph LR;
A[用户验证信息Session];
A-->B[应用系统1];
A-->C[应用系统2];
A-->D[应用系统3];
一般情况下都是基于Redis实现Session共享,将Session存储于Redis上,然后将整个系统的全局Cookie Domain设置于顶级域名(如example.com)上,这样SessionID就能在各个子系统间共享。这种方式也有一个问题,共享Session无法处理跨顶级域名(如example.cn、example.com、example.net等)。
使用JWT实现单点登录
JWT (JSON Web Token)是一个开放标准,它是一个含签名并携带用户相关信息的加密串。使用JWT进行身份验证时,客户端将用户的登录信息发送给服务器,服务器通过验证后生成一个加密字符串,并将其返回给客户端。客户端在随后的请求头中携带该加密字符串,服务器接收到请求后解析加密字符串,验证其中的签名和有效期,然后根据携带的信息判断是否授权访问。
JWT(JSON Web Token)字符串主要包含以下三部分:
- Header(头部):由两部分组成,分别是令牌类型(通常为JWT)和使用的加密算法(例如HMAC SHA256或RSA)。
- Payload(载荷):也称为Claims,包含了一些有关用户或实体的信息,例如身份验证状态、权限或其他元数据。Payload可以包含多个声明(Claim),每个声明以键值对的形式出现,并且包含标准声明和自定义声明。标准声明包括iss(令牌颁发者)、exp(令牌过期时间)、sub(令牌主题)等。
- Signature(签名):由Header、Payload和秘钥生成的签名,用于验证令牌是否合法和未被篡改。签名通常使用Base64编码后的Header和Payload和一个秘钥进行哈希运算生成。
其他网页应用结合JWT与分布式session,实现多域多空间单点登录。通过JWT生成和校验令牌,将刷新令牌存储在redis中,网关统一校验令牌,校验通过后将用户信息设置在请求头中,应用在拦截器中获取到用户信息后即可验证通过。
不同域中的网站共用一套密钥并且实时同步用户信息,通过JWT生成和校验令牌,用户登录其中一个域后,前端获取JWT加密串并写入Local Storage中,当用户跳到到其他域的网站时前端传入该加密串,后端网关校验,由此实现免登录访问其他域资源,如下图所示:
sequenceDiagram
浏览器->>网关: 登录
Note left of 浏览器: 用户登录
网关->>用户体系:Nginx转发
Note right of 用户体系: 生成JWT加密串
用户体系-->>浏览器:返回JWT给浏览器
Note left of 浏览器: 用户成功登录后,点击访问跨域子系统
浏览器->>网关: 前端请求头传JWT,请求跨域子系统
网关->>跨域子系统:网关拦截到请求,做JWT校验成功后才可访问跨域子系统
跨域子系统-->>浏览器: 成功
浏览器->>网关: 登录
Note left of 浏览器: 用户未成功登录或session过期
Note right of 网关: 网关JWT校验失败,不能访问跨域子系统
网关-->>浏览器: 响应失败
使用OAuth2.0实现单点登录
OAuth 2.0 是一种认证授权机制,主要用来颁发令牌(ticket),OAuth的核心就是向第三方应用颁发令牌,OAuth2.0对应下图中的统一认证系统:
如图,通过统一认证授权方式实现单点登录,需要有一个统一的认证系统。用户第一次访问应用系统时,由于还未登录,被引导到认证系统中进行登录,认证系统接受用户名密码等安全信息,生成访问令牌(ticket)。用户通过ticket访问应用系统,应用系统接受到请求之后会访问认证系统检查ticket的合法性,如果检查通过,用户就可以在不用再次登录的情况下访问应用系统资源。
具体流程如下:
journey
title
section 用户登录
用户在任何一个应用程序或系统中进行身份验证,并提供他们的凭据:done,des1
section 认证系统
将用户的凭据发送到认证中心系统进行验证。如果凭据有效,则身份提供者会为用户生成数字签名的令牌:des2
section 令牌分发
认证系统将令牌返回给应用程序或系统。:des3
section 系统授权
使用令牌验证用户的身份,并授权其访问相应资源或服务(如用户信息)。:des3
section 跨域访问
同一令牌跨域访问,无需重复进行身份验证。:des3
需要注意的是,为了实现这种方式的单点登录,所有涉及的子应用或系统都必须能够与认证中心进行集成,并使用相同的身份验证和授权协议(如OAuth),如果想实现更安全的认证方式,可以使用多因素身份验证(密码、短信验证码等)。通过OAuth2.0认证实现单点登录,为开发人员提供了一个通用的身份验证框架,提高开发人员的效率,解决了跨顶级域名单点登录问题。
总结
实现单点登录的主流的技术方案很多,并不是说哪种方案最好,技术方案服务于业务,为业务赋能,我们需要针对具体应用场景来制定最优解决方案。