资讯详情

资讯详情

单点登录SSO设计实战:从架构选型到安全防坑指南

单点登录这事我入行这几年几乎每个项目都会碰到。刚接触的时候觉得就是个一次登录到处通行的登录框后来真正上手设计才发现里面牵扯到的会话管理、协议选型、跨域策略、安全防护每一块都能写出一篇长文。尤其是当系统从一个单体应用拆成十几个微服务或者公司内部同时跑着旧版OA和新版业务平台的时候SSO就不再是要不要做的问题而是怎么做才能不踩坑的问题。这篇就把我实际设计SSO时踩过的坑、总结的经验、以及核心的原理逻辑摊开来聊聊给正在做类似方案的同行一个参考也帮刚接触这块的朋友少走点弯路。1. SSO的整体架构与核心思路拆解1.1 SSO到底解决什么问题先说痛点。我接过一个某内部管理系统改造的需求业务方提得特别直接我们员工每天上班要打开五六个系统每个系统都要单独输入一遍账号密码要命的是密码规则还不一样光记密码就够头疼了。其实这就是SSO最核心的价值场景。当一个组织内部存在多个相互独立的应用系统时每个系统各自维护一套用户凭证和会话状态用户就需要重复认证、重复登录。而SSO要做的事情就是把这个重复认证的过程收敛到一个统一的认证中心用户只要在认证中心登录一次就能访问所有接入的系统不需要每进一个系统就重新输一次账号密码。但这里要澄清一个常见的误解SSO不是让所有系统共享同一个登录密码也不是把用户信息复制到每个系统里。它是一种认证逻辑的集中化——真正验证你是谁的动作只发生一次后续的系统通过某种信任机制承认认证中心给出的这个人已经通过验证的结论从而放行。1.2 主流实现方案对比业内常见的SSO实现方案我大致分为三类各有适用场景。第一类是中心化认证服务CAS模式。这是最经典的做法专门部署一个独立的认证中心所有应用系统在需要登录时把用户重定向到认证中心认证中心完成登录后发放票据Ticket用户携带票据回到业务系统业务系统拿着票据向认证中心验证有效性。这种方式逻辑简单、通用性强对已有系统的改造也很友好适合架构相对传统、技术栈五花八门的场景。第二类是网关统一认证。把所有请求都经过一个API网关网关负责统一校验用户身份通过后再把请求转发给后端服务。这种方式对后端服务来说几乎是透明的服务自己不用关心用户从哪来怎么验证的问题。但它的局限在于网关只能处理走网关的请求如果有些系统不走网关比如老系统直接暴露了域名就绕过了统一的认证逻辑。第三类是基于Token的轻量认证常见实现为JWT。用户登录认证中心后拿到一个签名的Token后续访问各业务系统时带着Token系统验签通过即放行。这种方式无状态、易扩展特别适合微服务架构和前后端分离的项目。我在做新系统的时候基本都选这条路。三种方式可以有组合比如大前端走网关 JWT老系统走CAS适配器核心思想不变认证收敛、信任转发。1.3 为什么推荐独立认证中心我在设计的时候第一原则就是认证中心必须独立部署、单独维护。有些团队觉得既然都要做认证不如直接在某个已有的核心系统里加一个登录模块让它顺便承担认证中心的职责。我的建议是千万别这么干。原因有三一是认证中心是所有系统的信任锚点它一旦挂了所有接入的系统都登录不了这个风险必须用独立的部署和运维来隔离二是认证中心会涉及大量的安全加固、证书管理、审计日志混在业务系统里很难做好专项防护三是后续要接入新的应用、调整认证策略有一个独立的、职责单一的中心改动的影响面才可控。2. 关键设计细节与协议选型2.1 协议选型CAS、OAuth2/OIDC、SAML怎么选协议选型是SSO设计里最需要花时间思考的环节。我在早期项目中吃过看到什么都想用的亏后面才慢慢梳理清楚不同协议的定位。CASCentral Authentication Service是一个基于Ticket的认证协议现在很多开源框架也有对应实现。它的流程比较直白用户访问业务系统业务系统发现未认证重定向到认证中心认证中心验证完用户身份生成一个一次性Ticket通过URL回调传回业务系统业务系统再用这个Ticket到认证中心换取用户信息。CAS适合内部系统集成尤其是技术栈杂乱、需要快速统一登录入口的场景。OAuth2 OIDCOpenID Connect是目前最主流的选择。OAuth2本身是授权协议解决的是第三方应用能否访问我的资源的问题OIDC在OAuth2之上增加了身份认证层让客户端能可靠地获取用户的身份信息。对于互联网应用、移动端、跨企业授权场景OIDC基本是首选。SAMLSecurity Assertion Markup Language是另一套基于XML的认证授权协议在企业级应用里很常见尤其是和一些重型系统对接时对方的对接文档里可能就直接写了SAML 2.0。我的选型建议很简单接老系统、对方要求明确的按对方的协议来新项目、自己掌控技术栈的直接用OIDC既有老系统又需要弹性扩展的CAS做桥接、核心用OIDC是常见组合。2.2 会话管理设计单一登录态的维护方案SSO能实现登录一次全部通行本质是各应用系统都对同一个登录态表示信任。这个信任怎么建立具体有两种常见策略。一种是共享会话存储。认证中心把用户会话信息写到一个集中式的会话存储比如Redis各业务系统在处理请求时从某个地方拿到会话标识再去会话存储里验证。这种方式直觉、易实现但有一个问题所有系统都和会话存储产生强依赖会话存储一旦抖动或挂了影响面非常大。我在早期项目里用共享库的方式遇到一次Redis连接池被打满结果所有接入的系统集体登出排查了很久才定位到问题。之后再做SSO设计除非是特别小的内部工具否则不推荐直接共享会话存储给业务系统。另一种是票据/Token传递。认证中心在用户登录成功后颁发一个签名票据或Token各个系统通过验签来确认用户身份可信。这种方式下业务系统不需要实时访问认证中心只需要拿到公钥认证中心也不是每次请求的必经之路性能和可用性都好很多。JWT就是这种思想的典型产物。基于Token的方案还有个额外好处天然支持跨域和移动端因为它不依赖Cookie在浏览器侧的自动携带。从我的经验看会话管理设计的关键不是技术选型而是边界划分。认证中心负责发身份凭证业务系统负责验凭证两者职责清晰整个SSO才不会越来越乱。2.3 Token与票据的设计要点这块我单独拿出来说因为它最容易被忽略但恰恰是问题高发区。Token不要塞太多东西。我见过有人在JWT的Payload里放了用户的全量信息、权限列表甚至一些业务数据理由是反正签了名不怕被篡改。不怕被篡改没错但Token会变很大每次请求都带着它在网络上传输浪费带宽而且信息越多、泄露风险越大。正确的做法是Token只放必要且稳定的信息比如用户唯一ID、会话ID、签发时间、过期时间其他数据在业务系统需要时按ID去查。过期时间一定要合理。设置太短用户用着用着就掉线体验很差设置太长Token泄露了风险很大。我常用的策略是双Token一个短期访问Token比如30分钟到2小时用于正常访问一个长期刷新Token比如7到30天用于无感续期访问Token过期后用刷新Token换新的。这样既保证了体验又控制了泄露风险窗口。票据必须一次性使用。CAS模式里的Ticket尤其如此一旦校验过就作废防止重放攻击。我在给某系统的老接口对接时就发现开发因为图方便把Ticket的有效期设成了1小时而且不限制使用次数结果被安全测试直接发现可以多次重放。这类低级坑设计方案时就该堵死。3. 实操落地认证中心搭建与对接流程3.1 认证中心核心流程实现OIDC模式示例说完设计层面的东西来点实操的。这里我以OIDC模式为例展示一个认证中心的最小实现思路。需要说明的是生产环境直接用成熟的开源解决方案会省很多事了解底层流程可以帮你在排查问题时不至于无从下手。认证中心的核心接口有这几个。/authorize接收业务系统的登录请求如果用户未登录展示登录页如果已登录生成Authorization Code并重定向回业务系统。/token接收业务系统用Authorization Code换Token的请求校验后会返回ID Token和Access Token。/userinfo接收Token查询用户信息。业务系统接入时第一步就是把用户重定向到认证中心的/authorize地址带上client_id、redirect_uri、response_typecode等参数。https://auth.example.com/authorize? client_idmy-web-app redirect_urihttps://app.example.com/callback response_typecode scopeopenid profile statexyz123用户完成登录后认证中心重定向回redirect_uri并在URL后带上code和state。业务系统拿到code后在后端发起POST请求到/token换取Token。curl -X POST https://auth.example.com/token \ -H Content-Type: application/x-www-form-urlencoded \ -d grant_typeauthorization_code \ -d client_idmy-web-app \ -d client_secretyour-client-secret \ -d redirect_urihttps://app.example.com/callback \ -d codethe-authorization-code这里有两件事我反复跟同事们强调。第一code换取Token的操作必须在后端完成绝对不能在浏览器里做否则client_secret就直接暴露了。第二state参数一定要校验它用来防止跨站请求伪造CSRF你在发起登录时生成一个随机值存到会话里回调时比对是否一致不一致直接拒绝。换到Token后业务系统每次请求需要携带Access Token。Authorization: Bearer eyJhbGciOiJSUzI1NiIs...后端服务拿到Token后用认证中心下发的公钥验签确认Token有效后从Token里取出用户ID完成身份识别。3.2 前后端对接中的常见坑实际对接过程中后端一般问题不大真正的坑大多在前端。重定向丢失参数。有些前端框架在跳转时会把URL里的state或code弄丢尤其是某些SPA应用的路由模式配置不对时回调地址会被吞掉一部分。排查这类问题时先看浏览器的网络请求确认跳转链路上参数是否完整。跨域导致Cookie失效。SSO场景下认证中心和业务系统大概率是不同的域名登录态Cookie的域需要专门设置。如果你希望用户在访问任意一个系统时都保持登录状态通常需要把Cookie域设成顶级域名比如各子系统在app1.example.com、app2.example.com就把Cookie域设成.example.com。前端存储还是后端存储。Token放在前端localStorage还是后端HttpOnly Cookie业界各有说法。我的建议是如果业务系统本身就是纯后端渲染优先HttpOnly Cookie安全性更好如果是SPA应用优先把刷新Token放到HttpOnly Cookie里访问Token放内存中别放localStorage因为XSS攻击能把localStorage里的东西直接拿走。3.3 登出设计与登录同样重要很多设计SSO的人重点全放在怎么登录却把怎么登出忽略了。其实登出难多了因为登录只需要认证中心发一个通行凭证而登出需要把这个凭证在所有接入系统里全部作废。我在设计登出流程时有一个策略清单按优先级排列。认证中心本地销毁会话让Token通过过期机制自行失效。这个最简单但存在用户以为登出了Token其实还能用一段时间的问题。接入系统收到登出请求时调用认证中心的登出接口同时广播登出事件到其他接入系统。这个最彻底但实现复杂度高需要各系统都支持登出回调。对于纯前端场景登出时清理本地的Token同时跳转到认证中心的登出页面。实操层我最常用的是前端登出 后端入库黑名单的组合。前端把Token清掉后端把当前会话的TokenID记入黑名单有效期到Token过期为止。理论上Token还有效但实际上已经进黑名单了请求会被拒绝。这个方案实现成本低且能最大程度避免登出后Token仍可访问的隐患。4. 安全细节与风险防控4.1 常见攻击面与防护手段SSO认证中心掌握了所有系统的访问入口一旦有漏洞后果是灾难性的。我复盘过不少安全问题最常被利用的有下面几类。重放攻击Replay Attack。攻击者截获一个合法请求比如更换Token的请求在之后重复发送来欺骗服务端。防护手段很简单Authorization Code和Ticket必须一次性使用服务端要记录已消费过的Code或Ticket重复提交直接拒绝另外所有回调接口要做state参数校验避免攻击者构造回调URL。跨站脚本攻击XSS。如果业务系统存在XSS漏洞攻击者可以注入脚本窃取Token。防护思路是Token尽量放HttpOnly Cookie、前端脚本读不到对Token的存储位置做最小化处理别什么都往localStorage里塞同时业务系统自身做好输入过滤和输出转义。CSRF跨站请求伪造。这个在SSO场景里很容易被忽视。比如用户已经在认证中心登录了攻击者诱导用户访问一个恶意URL以用户的名义发起敏感操作。防护手段就是前面说的state参数和SameSiteCookie属性。开放重定向。认证中心的登录接口一般会接受一个redirect_uri参数登录成功后跳回这个地址。如果这里不做校验攻击者可以用认证中心的域名做跳板把用户引导到钓鱼网站。防护手段是必须在认证中心预注册合法的redirect_uri白名单凡是白名单里没有的地址一概不允许跳转。4.2 密钥管理与证书轮换SSO系统里签名密钥是最核心的资产它的安全性直接决定整个信任体系的安全。我见过有人把Github上的开源项目直接部署密钥也用的默认值这种做法等于把自家大门钥匙贴在门上。密钥管理上有几个基本要求密钥必须独立生成、独立存储不能硬编码在代码里更不能提交到代码仓库。生产环境的密钥要加密存储使用专门的密钥管理服务或者至少用环境变量注入并在部署时限制访问权限。证书/密钥要定期轮换。轮换是很多团队的痛点新老密钥并行一段时间再切换到新密钥就能避免服务中断。OIDC协议里的jwks端点天然支持多密钥并存轮换时把新密钥加进去等所有系统都拉取到新密钥后再删除旧密钥。我记得有一次帮某团队处理线上问题他们的JWT密钥一直没换过后来一个实习生误把密钥提交到了公开仓库好在发现及时全部重签并轮换才没出大事。从此我给自己定了个死规矩SSO相关密钥至少每3到6个月轮换一次每次发布前检查一遍。4.3 审计日志与风控SSO是个天然的审计中心因为所有系统的用户认证行为都汇聚在这里。这个资源不利用起来太可惜了。我在设计认证中心时会强制记录三类日志登录日志谁在什么时间、通过哪个IP、登录了哪个系统成功还是失败。登出日志谁在什么时间主动登出或被动失效。异常行为日志比如同一账号短时间内大量认证失败、同一个Token出现多个不同来源IP、Token在异地登录等。这些日志的价值有两个。一是出现问题时有迹可循能快速定位是在哪个环节被攻击了二是为后续的风控策略提供数据支撑比如识别撞库攻击、异常异地登录等。这一块的实操经验是日志不能只记录还要定人定岗去监控。否则日志存了几个月一次都没人看等于白存。我们是在日志平台里配置了几条关键告警规则只要有账号连续失败超过5次、或者非工作时间段有登录行为就会自动通知安全负责人。5. 常见问题排查与实战心得5.1 高频疑难问题速查下面这些是我被反复问到的问题整理成了速查表。现象可能原因排查思路与解法用户登录成功后跳回业务系统又变成未登录state参数校验失败或Cookie未携带检查回调链路中state是否保持一致检查Cookie域设置是否正确确认跳转有没有丢参数多系统只有一个能保持登录其他总是重新登录Cookie域不一致统一Cookie域为顶级域名确认各系统域名在同一个顶级域下登录后Token立即失效时钟不同步检查各服务器时间是否一致NTP同步一下再试刷新Token后访问Token还是被拒绝黑名单或会话状态不同步检查认证中心和各业务系统的会话状态是否一致Token ID是否走同一套校验逻辑用户登出后其他系统还能访问登出事件没广播或黑名单生效范围不对检查登出逻辑本地Token清理了没有、认证中心会话销毁了没有、其他系统的Token是否签发了新的偶尔出现登录回调地址错误开放重定向防护误伤检查redirect_uri白名单是否配置全了有没有因为域名多了www前缀这样的小差异导致校验失败5.2 踩坑记录与建议第一大坑使用共享会话存储把认证中心变成了性能瓶颈。我有一次给一个内部平台做SSO图省事直接让大家共享一份Redis会话。一开始系统少、用户量也不大问题不明显。随着接入的系统变多每次页面刷新都要去Redis确认会话Redis的连接数和网络响应成了瓶颈线上直接出现登录特别慢偶尔登出的现象。后来我们改成Token模式用JWT验签性能问题才彻底消散。经验SSO要长久稳定尽量让验签在业务本地完成而不是每次回认证中心查询。第二大坑Token过期策略没想清楚用户被频繁踢下线。最开始做某个SSO服务Access Token过期时间设置成30分钟用户稍微用久一点就到点被要求重新登录体验非常差。后来有人提议直接把过期时间改成24小时安全那边又不同意。最后我们用刷新Token的方案解决了这个矛盾前端无感刷新用户不再频繁被踢安全也稳了。第三大坑登出流程没设计用户假登出。这是新手上路最容易犯的错误。很多人以为把浏览器里的Token删掉就算登出了实际上如果Token本身还没失效攻击者之前拿到过照样能用。真正的登出必须让认证中心知道这个会话已经废了同时让所有接入系统都不要再接受这个会话的授权。5.3 给新接入系统的一些守则接入SSO的系统多了以后我发现如果不主动定义一些约束每个团队都会按自己的想法来导致整体方案走样。这里分享几条我这边一直在用的接入守则。认证中心统一下发SDK各业务系统不要自己实现验签细节。自己实现就意味着可能埋雷。业务系统只负责识别用户是谁和判断这个资源能不能访问不负责如何证明用户是用户。证明的事情交给认证中心。所有接入系统的client_id、client_secret等重要凭据要登记造册设置有效期且到期提醒防止有系统带着过期的凭据一直跑在生产上。统一使用标准库和标准协议不在协议层面做私有化扩展。私有化扩展会造成后续对接成本越来越高也容易出安全问题。6. 写在最后的额外建议SSO这个东西设计得好能很好地支撑整个平台的发展设计不好后期会天天被钉在“登录不好使”的工单上。我个人最大的体会是SSO本质上是信任问题的工程化。你要决定的是认证中心发给业务系统的凭证以什么形式存在、怎么传递、怎么验真、怎么作废。想清楚了这点设计出来的方案不管用什么协议、什么存储方案都不会歪到哪去。还有一点想多说一句如果是从零开始搭建新系统尽量直接采用成熟的认证服务和开源协议实现不要从零写一套自己的认证逻辑。一来认证这块的安全性要求太高从零写到能扛住攻防成本远超想象二来标准协议意味着有优秀的客户端库、完整的文档、活跃的社区遇到问题能找到人商量。我见过有人说自己写了一套“轻量SSO”觉得挺美结果每个接入方都来问“这个协议怎么对接”最后硬生生变成了一个内部私有协议苦不堪言。如果你们家现在正处在多个系统各自为战、登录体验混乱的状态我建议可以优先考虑先梳理现有系统的技术栈和登录方式再确定是走CAS快速改造还是一次性兼容OIDC然后拿一个非核心系统做试点跑通再逐步推广。这个节奏最稳也最容易让团队逐步适应新的认证模式。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →