随着企业的发展,系统越来越多,每个系统都要单独登录,用户要记住很多套账号密码,体验很差,管理也很麻烦。这时候就需要SSO单点登录了,用户只需要登录一次,就可以访问所有互信的系统,不用重复登录。SSO单点登录看起来简单,但是真正做好并不容易,里面有很多坑,我这几年做了几个SSO项目,踩了不少坑,也积累了一些经验。今天就来聊聊SSO单点登录的原理、常见的实现方案,以及我总结的一些最佳实践和踩坑经验。
一、什么是SSO,为什么需要SSO
SSO(Single Sign-On,单点登录)是一种身份认证方案,用户只需要登录一次,就可以访问所有互信的应用系统,不需要在每个系统都重复登录。对应的还有SLO(Single Logout,单点登出),用户在一个系统登出,所有系统都自动登出。
为什么需要SSO呢?主要有几个原因:
第一,提升用户体验。如果一个企业有十几个系统,用户每个系统都要单独登录,要记住很多套账号密码,经常忘记密码,体验非常差。有了SSO,用户只需要登录一次,就能访问所有系统,不用重复登录,体验好了很多。
第二,提升管理效率。没有SSO的时候,每个系统都要单独管理用户,用户入职、离职、调岗,要在每个系统都操作一遍,很麻烦,也容易出错,比如用户离职了,某个系统的账号忘了禁用,就会有安全风险。有了SSO,用户统一在认证中心管理,入职、离职、调岗只需要操作一次,所有系统都生效,管理效率高了很多,也更安全。
第三,提升安全性。账号密码多了,用户为了好记,往往会在不同系统用相同的密码,一个系统密码泄露了,所有系统都有风险。而且每个系统都自己做认证,水平参差不齐,容易出安全问题。有了SSO,统一在认证中心做认证,安全策略统一管理,比如密码强度、多因素认证、登录异常检测等,都可以统一做,安全性更高。
所以,只要企业的系统超过两三个,就应该考虑做SSO了,这是提升用户体验、管理效率和安全性的必要手段。
二、SSO的核心原理
SSO的核心原理其实很简单,就是有一个统一的认证中心(Authentication Server,也叫Identity Provider,IdP),所有的应用系统(也叫Service Provider,SP)都信任这个认证中心,用户的登录和认证都在认证中心完成,认证中心给用户颁发一个统一的身份凭证,用户拿着这个凭证就可以访问所有信任认证中心的应用系统。
具体的流程是这样的:
- 用户第一次访问应用系统A,应用系统A发现用户没有登录,就把用户重定向到认证中心的登录页面。
- 用户在认证中心输入账号密码,认证中心验证通过后,给用户颁发一个全局的登录凭证(比如存在认证中心域名下的Cookie里),然后把用户重定向回应用系统A,同时带上一个临时的授权码或者令牌。
- 应用系统A拿到授权码或者令牌后,向认证中心验证,验证通过后,就知道用户是谁了,给用户创建应用系统A自己的本地会话(比如存在应用系统A域名下的Cookie里),用户就可以正常访问应用系统A了。
- 这时候用户再访问应用系统B,应用系统B发现用户没有登录,也把用户重定向到认证中心。
- 认证中心发现用户已经登录过了(因为认证中心域名下的Cookie还在),就不用用户再输入账号密码了,直接把用户重定向回应用系统B,同时带上授权码或者令牌。
- 应用系统B拿到授权码或者令牌后,向认证中心验证,验证通过后,创建本地会话,用户就可以正常访问应用系统B了。
这样,用户只在第一次访问的时候输入了一次账号密码,后面访问其他系统都不用再登录了,这就是单点登录。
这里有两个关键的点:
- 全局会话:存在认证中心域名下,记录用户在认证中心的登录状态,只要这个会话有效,用户就不用再输入账号密码。
- 本地会话:存在各个应用系统自己的域名下,记录用户在该应用系统的登录状态,用户访问该应用系统的时候,先检查本地会话,本地会话有效就不用再去认证中心了。
因为Cookie是不能跨域名的,所以认证中心的Cookie和各个应用系统的Cookie是分开的,这也是为什么需要重定向和授权码的原因,就是为了在不同域名之间传递用户的身份信息。
三、常见的SSO实现方案
SSO有几种常见的实现方案,各有优缺点,适用于不同的场景。
1. CAS(Central Authentication Service,中央认证服务)
CAS是耶鲁大学开发的一个开源的SSO协议,也是企业级SSO最常用的方案之一,特别是在Java生态里用得很多。CAS的流程就是我上面讲的那个标准流程,用授权码(Ticket)来传递身份信息,认证中心叫CAS Server,应用系统叫CAS Client。
CAS的优点:
- 协议成熟,文档完善,社区活跃,有很多开源的实现,比如Apereo CAS。
- 安全性高,授权码是一次性的,用完就失效,而且有有效期,不容易被窃取。
- 支持各种认证方式,用户名密码、短信验证码、多因素认证、LDAP、数据库等都支持。
- 支持代理授权,可以让一个应用系统代表用户去访问另一个应用系统。
CAS的缺点:
- 协议相对复杂,实现起来有一定难度,特别是CAS Client的集成,每个应用系统都要集成CAS Client,有一定的工作量。
- 主要是面向Web应用,对移动端APP、前后端分离的SPA应用支持不太好,需要做一些改造。
- 跨域处理比较麻烦,需要用重定向,对纯API的服务不太友好。
2. OAuth2.0 + OpenID Connect(OIDC)
OAuth2.0本身是一个授权框架,不是认证协议,但是在它的基础上发展出了OpenID Connect(简称OIDC),OIDC是一个认证协议,可以用来做SSO。现在越来越多的SSO方案都是基于OAuth2.0 + OIDC的,特别是互联网公司和云服务,比如Google、Facebook、微信、阿里云的登录,都是基于OAuth2.0 + OIDC的。
OAuth2.0 + OIDC的流程和CAS类似,也是用户在认证中心登录,然后用授权码换令牌,但是它返回的不只是访问令牌(Access Token),还有一个ID Token,ID Token是JWT格式的,里面包含了用户的身份信息,应用系统拿到ID Token,验证签名通过后,就知道用户是谁了,不用再去认证中心验证。
OAuth2.0 + OIDC的优点:
- 协议更现代,更灵活,支持各种客户端类型,Web应用、移动端APP、SPA、服务端都支持。
- 基于RESTful API,实现简单,调试方便,各种语言都有成熟的SDK。
- ID Token是JWT格式,自包含,应用系统可以自己验证,不用每次都去认证中心,性能更好。
- 生态好,现在大部分的云服务、第三方登录都是基于OAuth2.0 + OIDC的,集成方便。
- 支持授权和认证分离,既可以做身份认证,也可以做资源授权。
OAuth2.0 + OIDC的缺点:
- 协议相对复杂,OAuth2.0有四种授权模式,OIDC又在上面加了很多概念,新手容易搞混。
- JWT令牌一旦签发,在过期之前无法主动撤销,除非做黑名单,这一点不如CAS的会话管理灵活。
- 安全性依赖于实现,如果实现不好,容易出安全问题,比如不验证签名、不验证audience、不验证过期时间等。
3. SAML(Security Assertion Markup Language,安全断言标记语言)
SAML是一个基于XML的认证授权协议,主要用在企业级的SSO里,特别是和企业的身份管理系统(比如Active Directory、LDAP)集成的时候用得很多。SAML的历史比较久,协议也比较复杂,现在新的系统用得越来越少了,但是很多老的企业系统还在用。
SAML的优点:
- 企业级支持好,和企业的身份管理系统集成方便,很多企业的SSO都是基于SAML的。
- 安全性高,有完善的签名和加密机制。
- 支持联邦身份,可以在不同的企业之间做SSO。
SAML的缺点:
- 协议复杂,基于XML,实现和调试都比较麻烦。
- 主要面向Web应用,对移动端和API支持不好。
- 比较重,性能不如OAuth2.0 + OIDC。
- 现在新的系统用得越来越少,生态在萎缩。
4. 基于共享Cookie的简单方案
如果所有的应用系统都在同一个一级域名下,比如都是*.example.com,那可以用最简单的方案,就是把登录态存在一级域名的Cookie里,所有子域名都能读到这个Cookie,这样就实现了单点登录。比如登录在sso.example.com,Cookie存在.example.com下,那么app1.example.com、app2.example.com都能读到这个Cookie,就不用重复登录了。
这种方案的优点:
- 非常简单,实现成本低,不用复杂的协议。
- 性能好,不用重定向,不用授权码,直接读Cookie就行。
缺点:
- 只能在同一个一级域名下用,跨域名就不行了。
- 安全性差,所有系统都共享同一个Cookie,一个系统出了XSS漏洞,Cookie被偷了,所有系统都受影响。
- 不好做权限隔离,每个系统的权限不一样,但是登录态是共享的,需要额外处理。
- 不支持移动端APP和纯API服务。
这种方案只适合内部系统少、都在同一个域名下、对安全要求不高的场景,稍微复杂一点的场景就不建议用了。
四、SSO最佳实践和踩坑经验
讲完了原理和方案,接下来重点聊聊我总结的一些最佳实践和踩坑经验,这些都是实际项目中踩过的坑,很有价值。
1. 协议选择:优先选OAuth2.0 + OIDC
如果是新做SSO,我优先推荐用OAuth2.0 + OIDC,这是现在的主流趋势,生态好,灵活,支持各种客户端。CAS也可以,特别是如果你的系统都是Java Web应用,而且已经有CAS的使用经验,那CAS也没问题。SAML除非是要和老的企业系统集成,否则不建议新系统用。共享Cookie的简单方案只适合非常简单的内部场景,不推荐。
选OAuth2.0 + OIDC的时候,尽量用成熟的开源实现或者商业产品,不要自己从零写,比如Keycloak、IdentityServer、Auth0、Okta等,这些都是成熟的产品,安全有保障,自己写很容易出安全漏洞。
2. 安全第一:这些点一定要注意
SSO是整个系统的入口,一旦出了安全问题,所有系统都受影响,所以安全是第一位的,这些点一定要注意:
- 所有通信都用HTTPS:这个不用多说,SSO的所有请求,包括登录页面、重定向、令牌请求、API调用,都必须用HTTPS,防止中间人攻击窃取令牌或者账号密码。HTTP的SSO是完全不安全的,绝对不能用。
- 授权码一次性使用,有效期短:不管是CAS还是OAuth2.0,授权码都必须是一次性的,用完就失效,而且有效期要短,比如5分钟,防止授权码被窃取后重放。
- 验证令牌的所有字段:应用系统验证JWT令牌的时候,一定要验证所有关键字段,包括签名(signature)、签发者(iss)、受众(aud)、过期时间(exp)、生效时间(nbf),不能只验证签名就完了,不然会有安全漏洞。比如不验证aud,别人拿其他应用的令牌也能访问你的应用。
- state参数防CSRF:OAuth2.0的授权请求一定要带state参数,回调的时候验证state,防止CSRF攻击。这个之前讲OAuth2.0的时候也提到过,很重要。
- redirect_uri严格校验:认证中心一定要严格校验回调地址,必须和注册的完全一致,或者在白名单里,不能用前缀匹配或者模糊匹配,不然攻击者可以构造恶意回调地址窃取授权码。这是SSO最常见的安全漏洞之一。
- 密码安全:用户的密码一定要用强哈希算法存储,比如bcrypt、Argon2,绝对不能明文存储,也不能用MD5、SHA1这种弱哈希。还要有密码强度要求,防止用户用太简单的密码。
- 多因素认证(MFA):对安全要求高的系统,建议支持多因素认证,比如短信验证码、邮箱验证码、TOTP(谷歌验证器)、硬件令牌等,就算密码泄露了,还有第二道防线。
- 登录异常检测:认证中心应该有登录异常检测,比如异地登录、频繁登录失败、陌生设备登录等,检测到异常要告警或者要求额外验证,防止暴力破解和账号被盗。
- 登出要彻底:单点登出(SLO)的时候,不仅要清除认证中心的全局会话,还要通知所有应用系统清除本地会话,不然用户虽然在认证中心登出了,但是某个应用系统的本地会话还在,别人还是能访问。SLO的实现比较麻烦,但是一定要做,特别是对安全要求高的系统。
3. 会话管理:全局会话和本地会话要分清
SSO里有两个会话,全局会话(在认证中心)和本地会话(在各个应用系统),这两个要分清,管理策略也要不一样。
- 全局会话的有效期可以长一点,比如7天或者30天,这样用户不用频繁登录,体验好。但是全局会话要有滑动过期,也就是用户每次活跃的时候,会话有效期自动延长,但是也要有一个绝对过期时间,比如不管活不活跃,最多30天就要重新登录,防止会话永久有效。
- 本地会话的有效期可以短一点,比如2小时或者8小时,因为本地会话是应用系统自己的,就算过期了,用户访问的时候会自动跳转到认证中心,认证中心的全局会话还在的话,会自动登录回来,用户无感知,所以本地会话短一点也不影响体验,但是更安全,因为本地会话泄露了的话,有效期短危害小。
- 本地会话要和全局会话联动,用户在认证中心登出了,所有本地会话都要失效;用户的全局会话过期了,本地会话也应该跟着失效,不能本地会话还在继续用。这个可以通过后台的令牌校验或者SLO通知来实现。
- 要支持主动踢人,管理员可以主动让某个用户下线,清除他的所有会话,这个在用户账号被盗或者离职的时候很有用。
4. 令牌管理:JWT和不透明令牌的选择
OAuth2.0里有两种令牌,一种是JWT(自包含令牌),一种是不透明令牌(随机字符串,需要去认证中心校验),这两种各有优缺点,要根据场景选择。
- JWT的优点是自包含,应用系统可以自己验证,不用每次都去认证中心,性能好,适合高并发的场景。缺点是一旦签发,过期前无法主动撤销,除非做黑名单,而且令牌里不能存太多敏感信息,因为JWT是Base64编码的,谁都能解码看。
- 不透明令牌的优点是可以主动撤销,认证中心可以随时让令牌失效,而且令牌本身不包含信息,更安全。缺点是每次验证都要去认证中心,性能有损耗,需要认证中心高可用。
我的建议是,访问令牌(Access Token)用JWT,有效期短一点,比如15分钟到2小时,这样就算无法撤销,有效期短危害也小,而且性能好。刷新令牌(Refresh Token)用不透明令牌,存在认证中心,可以主动撤销,而且刷新令牌只在认证中心用,不会频繁校验,性能影响不大。这样兼顾了性能和安全性。
另外,JWT里不要存敏感信息,比如密码、手机号、身份证号等,因为JWT不是加密的,谁拿到都能解码看。JWT里只存必要的身份信息,比如用户ID、用户名、角色、过期时间等就够了。
5. 系统集成:尽量用标准SDK,不要自己造轮子
各个应用系统集成SSO的时候,尽量用标准的SDK或者中间件,不要自己从零实现协议,OAuth2.0、OIDC、CAS这些协议都有成熟的SDK,各种语言都有,直接用就行,自己实现很容易出安全漏洞,也浪费时间。
如果是老系统集成SSO,尽量不要改老系统的代码,可以用反向代理或者网关来做SSO集成,比如Nginx、Kong、Spring Cloud Gateway等,在网关层统一做SSO认证,后端的老系统不用改,这样侵入性小,也安全。
集成的时候要做好测试,特别是各种异常场景,比如授权码过期、令牌过期、签名错误、用户取消授权等,都要测试到,要有友好的错误提示,不要直接抛异常给用户。
6. 高可用和性能:认证中心不能挂
认证中心是整个SSO的核心,一旦挂了,所有系统都没法登录了,所以认证中心一定要高可用,不能有单点故障。
- 认证中心要集群部署,至少两台,前面加负载均衡,一台挂了另一台还能用。
- 认证中心的会话要共享,存在Redis或者数据库里,不能存在本地内存里,不然集群部署的时候会话不同步。
- 数据库也要高可用,主从复制,读写分离,防止数据库挂了。
- 要有降级方案,比如认证中心挂了的时候,可以允许某些内部系统用本地账号临时登录,或者有备用的认证方式,不能所有系统都完全不可用。
- 性能方面,认证中心的登录接口、令牌接口要做好性能优化,加缓存,加限流,防止被暴力破解或者DDoS攻击打挂。JWT验证要高效,不要每次都去认证中心,用本地验签。
7. 用户体验:不要让用户觉得麻烦
SSO的目的之一就是提升用户体验,所以在设计的时候要多从用户角度考虑,不要让用户觉得麻烦。
- 登录页面要简洁友好,支持多种登录方式,用户名密码、手机号验证码、扫码登录、第三方登录等,让用户有选择。
- 登录成功后的跳转要正确,用户从哪个系统来的,登录成功后要跳回哪个系统,而且要回到用户原来想访问的页面,不要都跳到首页。
- 会话过期的时候要有友好的提示,不要直接跳到登录页面,用户会懵,可以提示"您的登录已过期,请重新登录",然后自动跳转到登录页面。
- 移动端体验要好,移动端APP集成SSO的时候,要用授权码模式PKCE,不要用隐式模式,登录页面要适配移动端,操作要方便。
- 要有统一的账号管理页面,用户可以在那里修改密码、绑定手机/邮箱、查看登录记录、管理授权的应用等,不用每个系统都做一套。
五、写在最后
SSO单点登录最佳实践:我总结了这些经验。
以上就是我对SSO单点登录的原理、实现方案,以及最佳实践和踩坑经验的总结,内容比较多,但是都是实际项目中总结出来的,希望能给大家一些参考。
SSO看起来简单,就是登录一次访问所有系统,但是真正做好并不容易,里面涉及到安全、性能、高可用、用户体验等很多方面的问题,需要仔细设计和实现。特别是安全,SSO是整个系统的入口,安全怎么重视都不为过,一个小的漏洞可能就会导致所有系统都被攻破。
如果你的企业还没有做SSO,系统越来越多,用户体验越来越差,管理越来越麻烦,那建议尽快做SSO,这是提升效率和安全的必要手段。如果要做,优先选OAuth2.0 + OIDC,用成熟的开源产品或者商业方案,不要自己从零造轮子,安全有保障,也节省时间。
技术是为业务服务的,SSO也是一样,不要为了技术而技术,要根据企业的实际情况,选择合适的方案,解决实际的问题,提升用户体验和管理效率,这才是SSO的价值所在。
最后用一句话结尾:"认证是系统的大门,门守好了,里面才安全。"愿我们都能做好SSO,守好系统的大门,让用户用得方便,让系统安全可靠。
评论(0)
暂无评论,快来抢沙发~
评论功能仅对会员开放,请先登录
登录