概述
单点登录,Single Sign On,SSO,是指在多系统应用群中登录一个系统,便可在其他所有系统中得到授权而无需再次登录,包括单点登录与单点注销两部分。
先看看单系统的登录实现方案。
单系统登录机制
Web应用采用B-S架构,HTTP作为无状态通信协议,浏览器每次请求,服务器会独立处理,不与之前或之后的请求产生关联。
浏览器第一次请求服务器,服务器创建一个会话,并将会话ID(SessionId)作为响应的一部分发送给浏览器,浏览器存储会话ID,并在后续请求中带上会话ID,服务器取得请求中的会话ID就能判断是不是同一个用户。
服务器在内存中保存会话对象,浏览器保存会话ID有两种方式:
- 作为参数:将会话ID作为请求参数(需要在前端代码里实现逻辑),服务器接收请求后,解析参数获得会话ID,并借此判断是否来自同一会话,不推荐
- Cookie:浏览器维护会话ID,每次发送请求时浏览器自动发送会话ID
Cookie是浏览器用来存储少量数据的一种机制,数据以K/V形式存储,浏览器发送请求时自动附带Cookie信息。
访问Tomcat服务器时,浏览器中可看到名为JSESSIONID的cookie,这就是Tomcat会话机制维护的会话ID。
验证登陆请求成功后,Tomcat在会话对象中设置登录状态如下:
java
HttpSession session = request.getSession();
session.setAttribute("isLogin", true);
再次访问时,查看登录状态
java
HttpSession session = request.getSession();
session.getAttribute("isLogin");
每次请求受保护资源时都会检查会话对象中的登录状态,只有isLogin=true的会话才能访问,登录机制因此而实现。
单系统登录解决方案的核心是Cookie,Cookie携带会话ID在浏览器与服务器之间维护会话状态。但Cookie是有限制的,浏览器发送请求时会自动携带与该域(通常对应网站的域名)匹配的Cookie,而不是所有Cookie。
同域名共享Cookie
将Web应用群中所有子系统的域名统一在顶级域名下,如*.google.com,然后将它们的Cookie域都设置为google.com,理论上可行,早期很多多系统登录就采用这种方案。
存在局限:
- 应用群域名得统一;
- 不同语言和服务器,Cookie的默认Key值不同。Tomcat为
JSESSIONID,PHP的叫PHPSESSID,为实现跨语言多系统登录,需手动统一Key名称,且得保证多端多语言命名一致。但依旧存在问题:Cookie/Session数据存储不互通。不过也有解决方案:引入集中式缓存组件,如Redis; - Cookie不够安全:Cookie被黑客窃取,就能直接冒充用户身份。
登录
相比于单系统登录,SSO需要一个独立的认证中心,只有认证中心能接受用户的用户名密码等安全信息,其他系统不提供登录入口,只接受认证中心的间接授权。
核心组件:
- Identity Provider(IdP,身份提供商):
- 负责用户认证和令牌颁发
- 存储用户凭据和属性
- 示例:Keycloak、Okta、Active Directory
- Service Provider(SP,服务提供商):
- 提供实际业务功能的系统
- 信任IdP的认证结果
- 示例:企业邮箱、Wiki、ERP系统
- 令牌(Token):
- IdP颁发的认证凭证
- 可以是Session ID、JWT、SAML Assertion等
间接授权通过令牌实现,SSO认证中心验证用户的用户名密码没问题,创建授权令牌,在接下来的跳转过程中,授权令牌作为参数发送给各个子系统,子系统拿到令牌,即得到授权,可以借此创建局部会话,局部会话登录方式与单系统的登录方式相同。

应用系统2 身份提供商(IdP) 应用系统1 用户浏览器 应用系统2 身份提供商(IdP) 应用系统1 用户浏览器 #mermaid-svg-6g2ZcohWfpONKryB{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-6g2ZcohWfpONKryB .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-6g2ZcohWfpONKryB .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-6g2ZcohWfpONKryB .error-icon{fill:#552222;}#mermaid-svg-6g2ZcohWfpONKryB .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-6g2ZcohWfpONKryB .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-6g2ZcohWfpONKryB .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-6g2ZcohWfpONKryB .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-6g2ZcohWfpONKryB .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-6g2ZcohWfpONKryB .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-6g2ZcohWfpONKryB .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-6g2ZcohWfpONKryB .marker{fill:#333333;stroke:#333333;}#mermaid-svg-6g2ZcohWfpONKryB .marker.cross{stroke:#333333;}#mermaid-svg-6g2ZcohWfpONKryB svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-6g2ZcohWfpONKryB p{margin:0;}#mermaid-svg-6g2ZcohWfpONKryB .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6g2ZcohWfpONKryB text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-6g2ZcohWfpONKryB .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6g2ZcohWfpONKryB .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-6g2ZcohWfpONKryB .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-6g2ZcohWfpONKryB .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-6g2ZcohWfpONKryB #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-6g2ZcohWfpONKryB .sequenceNumber{fill:white;}#mermaid-svg-6g2ZcohWfpONKryB #sequencenumber{fill:#333;}#mermaid-svg-6g2ZcohWfpONKryB #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-6g2ZcohWfpONKryB .messageText{fill:#333;stroke:none;}#mermaid-svg-6g2ZcohWfpONKryB .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6g2ZcohWfpONKryB .labelText,#mermaid-svg-6g2ZcohWfpONKryB .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-6g2ZcohWfpONKryB .loopText,#mermaid-svg-6g2ZcohWfpONKryB .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-6g2ZcohWfpONKryB .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-6g2ZcohWfpONKryB .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-6g2ZcohWfpONKryB .noteText,#mermaid-svg-6g2ZcohWfpONKryB .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-6g2ZcohWfpONKryB .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6g2ZcohWfpONKryB .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6g2ZcohWfpONKryB .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-6g2ZcohWfpONKryB .actorPopupMenu{position:absolute;}#mermaid-svg-6g2ZcohWfpONKryB .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-6g2ZcohWfpONKryB .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-6g2ZcohWfpONKryB .actor-man circle,#mermaid-svg-6g2ZcohWfpONKryB line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-6g2ZcohWfpONKryB :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 用户访问第二个应用系统 1. 访问应用系统1 2. 检测到未登录,重定向到IdP 3. 访问IdP登录页面 4. 显示登录表单 5. 提交用户名和密码 6. 验证凭据,创建全局会话 7. 返回令牌(Token)和SSO会话Cookie 8. 携带令牌访问SP1 9. 验证令牌有效性 10. 令牌有效,返回用户信息 11. 登录成功,创建局部会话 12. 访问应用系统2 13. 检测到未登录,重定向到IdP 14. 携带SSO会话Cookie访问IdP 15. 检测到已登录(全局会话有效) 16. 直接返回令牌(无需重新登录) 17. 携带令牌访问SP2 18. 验证令牌 19. 令牌有效 20. 登录成功
简要描述:
- 用户访问系统1的受保护资源,系统1发现用户未登录,跳转至认证中心,并将自己的地址作为参数
- 认证中心发现用户未登录,将用户引导至登录页面
- 用户输入用户名密码提交登录申请
- 认证中心校验用户信息,创建用户与认证中心之间的会话,称为全局会话,同时创建授权令牌,返回给浏览器
- 浏览器带着令牌访问系统1
- 系统1拿到令牌,去认证中心校验令牌是否有效
- 认证中心校验令牌,返回有效,注册系统1
- 系统1使用该令牌创建与用户的会话,称为局部会话,返回受保护资源
- 用户访问系统2的受保护资源
- 系统2发现用户未登录,跳转至认证中心,并将自己的地址作为参数
- 认证中心发现用户已登录,跳转回系统2的地址,并附上令牌
- 系统2拿到令牌,去认证中心校验令牌是否有效
- 认证中心校验令牌,返回有效,注册系统2
- 系统2使用该令牌创建与用户的局部会话,返回受保护资源
用户登录成功之后,会与SSO认证中心及各个子系统建立会话,用户与SSO认证中心建立的会话称为全局会话,用户与各个子系统建立的会话称为局部会话,局部会话建立之后,用户访问子系统受保护资源将不再通过SSO认证中心,全局会话与局部会话有如下约束关系:
- 局部会话存在,全局会话一定存在
- 全局会话存在,局部会话不一定存在
- 全局会话销毁,局部会话必须销毁
总结
- 全局会话(Global Session):
- 在IdP侧创建
- 使用SSO Session Cookie(通常设置为顶级域名)
- 控制所有SP的访问权限
- 局部会话(Local Session):
- 在SP侧创建
- 可以是Session或JWT
- 与会话超时时间相关
会话超时策略:
| 会话类型 | 超时时间 | 刷新策略 |
|---|---|---|
| 全局会话 | 较长(如8小时) | 用户活动时可刷新 |
| 局部会话 | 较短(如30分钟) | 使用Refresh Token刷新 |
| 记住我会话 | 很长(如30天) | 使用持久化Cookie |
注销
SLO,Single Log Out,在一个子系统中注销,所有子系统的会话都将被销毁,

认证中心一直监听全局会话的状态,一旦全局会话销毁,监听器将通知所有注册系统执行注销操作。简要说明:
- 用户向系统1发起注销请求
- 系统1根据用户与系统1建立的会话id拿到令牌,向SSO认证中心发起注销请求
- SSO认证中心校验令牌有效,销毁全局会话,同时取出所有用此令牌注册的系统地址
- SSO认证中心向所有注册系统发起注销请求
- 各注册系统接收SSO认证中心的注销请求,销毁局部会话
- SSO认证中心引导用户至登录页面
应用系统2 身份提供商(IdP) 应用系统1 用户浏览器 应用系统2 身份提供商(IdP) 应用系统1 用户浏览器 #mermaid-svg-gn5BLa1OqEKalBWU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-gn5BLa1OqEKalBWU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-gn5BLa1OqEKalBWU .error-icon{fill:#552222;}#mermaid-svg-gn5BLa1OqEKalBWU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-gn5BLa1OqEKalBWU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-gn5BLa1OqEKalBWU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-gn5BLa1OqEKalBWU .marker.cross{stroke:#333333;}#mermaid-svg-gn5BLa1OqEKalBWU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-gn5BLa1OqEKalBWU p{margin:0;}#mermaid-svg-gn5BLa1OqEKalBWU .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gn5BLa1OqEKalBWU text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-gn5BLa1OqEKalBWU .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gn5BLa1OqEKalBWU .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-gn5BLa1OqEKalBWU .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-gn5BLa1OqEKalBWU .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-gn5BLa1OqEKalBWU #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-gn5BLa1OqEKalBWU .sequenceNumber{fill:white;}#mermaid-svg-gn5BLa1OqEKalBWU #sequencenumber{fill:#333;}#mermaid-svg-gn5BLa1OqEKalBWU #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-gn5BLa1OqEKalBWU .messageText{fill:#333;stroke:none;}#mermaid-svg-gn5BLa1OqEKalBWU .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gn5BLa1OqEKalBWU .labelText,#mermaid-svg-gn5BLa1OqEKalBWU .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-gn5BLa1OqEKalBWU .loopText,#mermaid-svg-gn5BLa1OqEKalBWU .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-gn5BLa1OqEKalBWU .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-gn5BLa1OqEKalBWU .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-gn5BLa1OqEKalBWU .noteText,#mermaid-svg-gn5BLa1OqEKalBWU .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-gn5BLa1OqEKalBWU .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gn5BLa1OqEKalBWU .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gn5BLa1OqEKalBWU .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-gn5BLa1OqEKalBWU .actorPopupMenu{position:absolute;}#mermaid-svg-gn5BLa1OqEKalBWU .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-gn5BLa1OqEKalBWU .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-gn5BLa1OqEKalBWU .actor-man circle,#mermaid-svg-gn5BLa1OqEKalBWU line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-gn5BLa1OqEKalBWU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 点击登出 2. 销毁局部会话 3. 发送登出请求(携带Token) 4. 销毁全局会话 5. 发送登出通知(后端调用) 6. 销毁局部会话 7. 返回登出成功页面
SLO实现挑战:
- 前端无法保证通知所有SP:如果用户关闭浏览器,可能无法发送登出请求
- 后端轮询验证:SP每次请求都到IdP验证会话状态
- 使用短寿命的局部会话:减少会话不同步的时间窗口
实现
SSO可使用不同协议来实现。
SAML
特点:
- 使用XML格式的SAML Assertion
- 适用于企业级应用(如SAP、Oracle ERP)
- 支持SP发起和IdP发起2种流程
实现要点:
- IdP需配置SP元数据(Metadata)
- SP需要配置IdP元数据
- 使用
X.509证书进行签名和加密
OIDC
特点:
- 使用JSON格式的ID Token(JWT)
- 适用于现代Web应用和移动应用
- 支持Authorization Code Flow + PKCE
实现要点:
- 使用Discovery端点自动获取配置
- 验证ID Token的签名和声明
- 使用Refresh Token维持长期会话
Keycloak
参考Keycloak。
Authentik
官网,Python生态下、开源(GitHub,22.2K Star,1.7K Fork)身份提供商,主打灵活的工作流引擎和现代化的管理体验。
核心技术:基于Flows(流程)和Stages(阶段)的可编程认证管道。传统IdP的登录流程是硬编码的,而Authentik将登录过程拆解为一系列可编排的Stage,管理员可以像搭积木一样自由组合。
#mermaid-svg-jvisk5WSq1RIsZFm{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-jvisk5WSq1RIsZFm .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-jvisk5WSq1RIsZFm .error-icon{fill:#552222;}#mermaid-svg-jvisk5WSq1RIsZFm .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-jvisk5WSq1RIsZFm .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-jvisk5WSq1RIsZFm .marker{fill:#333333;stroke:#333333;}#mermaid-svg-jvisk5WSq1RIsZFm .marker.cross{stroke:#333333;}#mermaid-svg-jvisk5WSq1RIsZFm svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-jvisk5WSq1RIsZFm p{margin:0;}#mermaid-svg-jvisk5WSq1RIsZFm .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-jvisk5WSq1RIsZFm .cluster-label text{fill:#333;}#mermaid-svg-jvisk5WSq1RIsZFm .cluster-label span{color:#333;}#mermaid-svg-jvisk5WSq1RIsZFm .cluster-label span p{background-color:transparent;}#mermaid-svg-jvisk5WSq1RIsZFm .label text,#mermaid-svg-jvisk5WSq1RIsZFm span{fill:#333;color:#333;}#mermaid-svg-jvisk5WSq1RIsZFm .node rect,#mermaid-svg-jvisk5WSq1RIsZFm .node circle,#mermaid-svg-jvisk5WSq1RIsZFm .node ellipse,#mermaid-svg-jvisk5WSq1RIsZFm .node polygon,#mermaid-svg-jvisk5WSq1RIsZFm .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-jvisk5WSq1RIsZFm .rough-node .label text,#mermaid-svg-jvisk5WSq1RIsZFm .node .label text,#mermaid-svg-jvisk5WSq1RIsZFm .image-shape .label,#mermaid-svg-jvisk5WSq1RIsZFm .icon-shape .label{text-anchor:middle;}#mermaid-svg-jvisk5WSq1RIsZFm .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-jvisk5WSq1RIsZFm .rough-node .label,#mermaid-svg-jvisk5WSq1RIsZFm .node .label,#mermaid-svg-jvisk5WSq1RIsZFm .image-shape .label,#mermaid-svg-jvisk5WSq1RIsZFm .icon-shape .label{text-align:center;}#mermaid-svg-jvisk5WSq1RIsZFm .node.clickable{cursor:pointer;}#mermaid-svg-jvisk5WSq1RIsZFm .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-jvisk5WSq1RIsZFm .arrowheadPath{fill:#333333;}#mermaid-svg-jvisk5WSq1RIsZFm .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-jvisk5WSq1RIsZFm .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-jvisk5WSq1RIsZFm .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jvisk5WSq1RIsZFm .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-jvisk5WSq1RIsZFm .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jvisk5WSq1RIsZFm .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-jvisk5WSq1RIsZFm .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-jvisk5WSq1RIsZFm .cluster text{fill:#333;}#mermaid-svg-jvisk5WSq1RIsZFm .cluster span{color:#333;}#mermaid-svg-jvisk5WSq1RIsZFm div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-jvisk5WSq1RIsZFm .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-jvisk5WSq1RIsZFm rect.text{fill:none;stroke-width:0;}#mermaid-svg-jvisk5WSq1RIsZFm .icon-shape,#mermaid-svg-jvisk5WSq1RIsZFm .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-jvisk5WSq1RIsZFm .icon-shape p,#mermaid-svg-jvisk5WSq1RIsZFm .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-jvisk5WSq1RIsZFm .icon-shape .label rect,#mermaid-svg-jvisk5WSq1RIsZFm .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-jvisk5WSq1RIsZFm .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-jvisk5WSq1RIsZFm .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-jvisk5WSq1RIsZFm :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 策略引擎
认证Flow(可自定义编排)
绑定
绑定
绑定
Stage 1
Identification
用户名/邮箱识别
Stage 2
Password
密码验证
Stage 3
MFA
TOTP/WebAuthn
Stage 4
Consent
用户授权同意
Stage 5
User Login
建立会话
Password Policy
密码强度校验
Expression Policy
Python表达式
Reputation Policy
IP信誉评分
架构图
#mermaid-svg-8vSJHAsuf6k5alN5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-8vSJHAsuf6k5alN5 .error-icon{fill:#552222;}#mermaid-svg-8vSJHAsuf6k5alN5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-8vSJHAsuf6k5alN5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-8vSJHAsuf6k5alN5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-8vSJHAsuf6k5alN5 .marker.cross{stroke:#333333;}#mermaid-svg-8vSJHAsuf6k5alN5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-8vSJHAsuf6k5alN5 p{margin:0;}#mermaid-svg-8vSJHAsuf6k5alN5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster-label text{fill:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster-label span{color:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster-label span p{background-color:transparent;}#mermaid-svg-8vSJHAsuf6k5alN5 .label text,#mermaid-svg-8vSJHAsuf6k5alN5 span{fill:#333;color:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 .node rect,#mermaid-svg-8vSJHAsuf6k5alN5 .node circle,#mermaid-svg-8vSJHAsuf6k5alN5 .node ellipse,#mermaid-svg-8vSJHAsuf6k5alN5 .node polygon,#mermaid-svg-8vSJHAsuf6k5alN5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-8vSJHAsuf6k5alN5 .rough-node .label text,#mermaid-svg-8vSJHAsuf6k5alN5 .node .label text,#mermaid-svg-8vSJHAsuf6k5alN5 .image-shape .label,#mermaid-svg-8vSJHAsuf6k5alN5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-8vSJHAsuf6k5alN5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-8vSJHAsuf6k5alN5 .rough-node .label,#mermaid-svg-8vSJHAsuf6k5alN5 .node .label,#mermaid-svg-8vSJHAsuf6k5alN5 .image-shape .label,#mermaid-svg-8vSJHAsuf6k5alN5 .icon-shape .label{text-align:center;}#mermaid-svg-8vSJHAsuf6k5alN5 .node.clickable{cursor:pointer;}#mermaid-svg-8vSJHAsuf6k5alN5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-8vSJHAsuf6k5alN5 .arrowheadPath{fill:#333333;}#mermaid-svg-8vSJHAsuf6k5alN5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-8vSJHAsuf6k5alN5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-8vSJHAsuf6k5alN5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8vSJHAsuf6k5alN5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-8vSJHAsuf6k5alN5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8vSJHAsuf6k5alN5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster text{fill:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 .cluster span{color:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-8vSJHAsuf6k5alN5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-8vSJHAsuf6k5alN5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-8vSJHAsuf6k5alN5 .icon-shape,#mermaid-svg-8vSJHAsuf6k5alN5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-8vSJHAsuf6k5alN5 .icon-shape p,#mermaid-svg-8vSJHAsuf6k5alN5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-8vSJHAsuf6k5alN5 .icon-shape .label rect,#mermaid-svg-8vSJHAsuf6k5alN5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-8vSJHAsuf6k5alN5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-8vSJHAsuf6k5alN5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-8vSJHAsuf6k5alN5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储
Authentik Outposts-Go
Authentik Core-Django
用户/应用
用户浏览器
应用A
OIDC
应用B
SAML
应用C
Proxy
管理UI/用户自助UI
Flow Engine
认证流程编排
Provider Engine
OIDC、SAML、Proxy、LDAP
Policy Engine
动态策略评估
内置用户目录
Users、Groups、Roles
事件与审计日志
Proxy Outpost
反向代理认证
LDAP Outpost
LDAP协议暴露
RAC Outpost
远程访问控制
PostgreSQL
Redis
优缺点分析
| 优点 | 缺点 |
|---|---|
| Flow/Stage引擎极其灵活,可定制任意认证管道 | Python+Django技术栈,性能不如Go/Rust系 |
| 现代美观的管理UI,用户体验好 | Flow编排概念有一定学习曲线 |
| 内置Proxy Provider(类似Authelia的反向代理认证) | 部分高级功能需要商业版(Enterprise) |
| 内置用户目录、组、角色管理 | Outpost架构增加部署复杂度 |
| 支持OIDC+SAML+LDAP+Proxy | 社区相对年轻,生产案例不如Keycloak多 |
| Docker Compose一键部署 | 文档好看但深度不够 |
| 支持表达式策略(Python表达式做动态决策) | 资源消耗中等(PostgreSQL+Redis+Core+Outpost) |
渲染错误: Mermaid 渲染失败: No diagram type detected matching given configuration for text:
渲染错误: Mermaid 渲染失败: No diagram type detected matching given configuration for text:
CAS
Central Authentication Service简称,项目主页,耶鲁大学使用Java编写、开源(GitHub,11.3K Star,4K Fork)统一认证服务。

颤抖吧。
使用Ticket机制,包括TGT(Ticket Granting Ticket)、ST(Service Ticket)
流程:
- 用户访问SP,SP重定向到CAS Server
- CAS Server验证凭据,颁发TGT(存储在CAS Server)
- CAS Server返回ST(Service Ticket)给SP
- SP使用ST到CAS Server验证并获取用户信息
- 用户访问其他SP时,使用已有TGT直接获取ST
Dex
官网,Go语言编写、开源(GitHub,10.9K Star,1.9K Fork)OIDC Provider,CoreOS开发(现由CNCF维护)。
定位:Identity Broker,协议转换器,不存储用户,只做身份代理(Connector/Broker),将上游的各种身份源(LDAP、GitHub、SAML IdP等)统一转化为标准OIDC接口。
上游身份源 (LDAP/GitHub/SAML IdP) Dex (OIDC Provider) 应用 (如K8s Dashboard) 用户浏览器 上游身份源 (LDAP/GitHub/SAML IdP) Dex (OIDC Provider) 应用 (如K8s Dashboard) 用户浏览器 #mermaid-svg-tJS16EYpO2ztFLXD{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-tJS16EYpO2ztFLXD .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-tJS16EYpO2ztFLXD .error-icon{fill:#552222;}#mermaid-svg-tJS16EYpO2ztFLXD .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-tJS16EYpO2ztFLXD .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-tJS16EYpO2ztFLXD .marker{fill:#333333;stroke:#333333;}#mermaid-svg-tJS16EYpO2ztFLXD .marker.cross{stroke:#333333;}#mermaid-svg-tJS16EYpO2ztFLXD svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-tJS16EYpO2ztFLXD p{margin:0;}#mermaid-svg-tJS16EYpO2ztFLXD .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tJS16EYpO2ztFLXD text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-tJS16EYpO2ztFLXD .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tJS16EYpO2ztFLXD .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-tJS16EYpO2ztFLXD .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-tJS16EYpO2ztFLXD .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-tJS16EYpO2ztFLXD #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-tJS16EYpO2ztFLXD .sequenceNumber{fill:white;}#mermaid-svg-tJS16EYpO2ztFLXD #sequencenumber{fill:#333;}#mermaid-svg-tJS16EYpO2ztFLXD #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-tJS16EYpO2ztFLXD .messageText{fill:#333;stroke:none;}#mermaid-svg-tJS16EYpO2ztFLXD .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tJS16EYpO2ztFLXD .labelText,#mermaid-svg-tJS16EYpO2ztFLXD .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-tJS16EYpO2ztFLXD .loopText,#mermaid-svg-tJS16EYpO2ztFLXD .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-tJS16EYpO2ztFLXD .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-tJS16EYpO2ztFLXD .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-tJS16EYpO2ztFLXD .noteText,#mermaid-svg-tJS16EYpO2ztFLXD .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-tJS16EYpO2ztFLXD .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tJS16EYpO2ztFLXD .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tJS16EYpO2ztFLXD .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-tJS16EYpO2ztFLXD .actorPopupMenu{position:absolute;}#mermaid-svg-tJS16EYpO2ztFLXD .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-tJS16EYpO2ztFLXD .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-tJS16EYpO2ztFLXD .actor-man circle,#mermaid-svg-tJS16EYpO2ztFLXD line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-tJS16EYpO2ztFLXD :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} alt 选择 LDAP Connector 选择 GitHub Connector 1. 访问受保护资源 2. 302重定向到Dex授权端点 3. 请求OIDC授权(client_id、scope、redirect_uri) 4. 返回"选择登录方式"页面 (Connectors:LDAP/GitHub/SAML) 5a. 输入用户名密码 6a. 用LDAP Bind验证凭证 7a. 返回用户信息 (DN、Groups) 5b. 点击GitHub登录 6b. 302到GitHub OAuth 7b. GitHub授权 8b. 回调带Code,Dex换取UserInfo 9. 签发OIDC ID Token和Access Token 10. 302重定向到App的redirect_uri?code=xxx 11. 携带code 12. 用code换取Token 13. 返回ID Token (JWT)
#mermaid-svg-uP7j1JfSanGSAqWa{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-uP7j1JfSanGSAqWa .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-uP7j1JfSanGSAqWa .error-icon{fill:#552222;}#mermaid-svg-uP7j1JfSanGSAqWa .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-uP7j1JfSanGSAqWa .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-uP7j1JfSanGSAqWa .marker{fill:#333333;stroke:#333333;}#mermaid-svg-uP7j1JfSanGSAqWa .marker.cross{stroke:#333333;}#mermaid-svg-uP7j1JfSanGSAqWa svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-uP7j1JfSanGSAqWa p{margin:0;}#mermaid-svg-uP7j1JfSanGSAqWa .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-uP7j1JfSanGSAqWa .cluster-label text{fill:#333;}#mermaid-svg-uP7j1JfSanGSAqWa .cluster-label span{color:#333;}#mermaid-svg-uP7j1JfSanGSAqWa .cluster-label span p{background-color:transparent;}#mermaid-svg-uP7j1JfSanGSAqWa .label text,#mermaid-svg-uP7j1JfSanGSAqWa span{fill:#333;color:#333;}#mermaid-svg-uP7j1JfSanGSAqWa .node rect,#mermaid-svg-uP7j1JfSanGSAqWa .node circle,#mermaid-svg-uP7j1JfSanGSAqWa .node ellipse,#mermaid-svg-uP7j1JfSanGSAqWa .node polygon,#mermaid-svg-uP7j1JfSanGSAqWa .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-uP7j1JfSanGSAqWa .rough-node .label text,#mermaid-svg-uP7j1JfSanGSAqWa .node .label text,#mermaid-svg-uP7j1JfSanGSAqWa .image-shape .label,#mermaid-svg-uP7j1JfSanGSAqWa .icon-shape .label{text-anchor:middle;}#mermaid-svg-uP7j1JfSanGSAqWa .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-uP7j1JfSanGSAqWa .rough-node .label,#mermaid-svg-uP7j1JfSanGSAqWa .node .label,#mermaid-svg-uP7j1JfSanGSAqWa .image-shape .label,#mermaid-svg-uP7j1JfSanGSAqWa .icon-shape .label{text-align:center;}#mermaid-svg-uP7j1JfSanGSAqWa .node.clickable{cursor:pointer;}#mermaid-svg-uP7j1JfSanGSAqWa .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-uP7j1JfSanGSAqWa .arrowheadPath{fill:#333333;}#mermaid-svg-uP7j1JfSanGSAqWa .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-uP7j1JfSanGSAqWa .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-uP7j1JfSanGSAqWa .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uP7j1JfSanGSAqWa .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-uP7j1JfSanGSAqWa .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uP7j1JfSanGSAqWa .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-uP7j1JfSanGSAqWa .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-uP7j1JfSanGSAqWa .cluster text{fill:#333;}#mermaid-svg-uP7j1JfSanGSAqWa .cluster span{color:#333;}#mermaid-svg-uP7j1JfSanGSAqWa div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-uP7j1JfSanGSAqWa .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-uP7j1JfSanGSAqWa rect.text{fill:none;stroke-width:0;}#mermaid-svg-uP7j1JfSanGSAqWa .icon-shape,#mermaid-svg-uP7j1JfSanGSAqWa .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-uP7j1JfSanGSAqWa .icon-shape p,#mermaid-svg-uP7j1JfSanGSAqWa .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-uP7j1JfSanGSAqWa .icon-shape .label rect,#mermaid-svg-uP7j1JfSanGSAqWa .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-uP7j1JfSanGSAqWa .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-uP7j1JfSanGSAqWa .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-uP7j1JfSanGSAqWa :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 上游身份源
Dex-Go二进制/K8s Pod
只支持OIDC的应用
Connectors-身份源适配器
Kubernetes API
--oidc-issuer-url
ArgoCD
OIDC端点
/.well-known/openid-configuration
/auth
/token
/userinfo
LDAP Connector
SAML Connector
OIDC Connector
级联
Active Directory
ADFS、SAML IdP
优缺点分析
| 优点 | 缺点 |
|---|---|
| 极其轻量:单个Go二进制,无JVM依赖 | 无用户管理UI:无管理后台,纯配置文件驱动 |
| CNCF生态,Kubernetes原生(Helm/Operator) | 不存储用户:无用户注册、无用户Profile管理 |
| Connector插件机制:轻松对接10+种上游身份源 | 无RBAC/ABAC授权能力 |
| 启动快、内存占用极低(几十MB) | 无MFA、无审计日志等企业级特性 |
| 完美适合GitOps管理(配置即代码) | 功能单一,只适合做"协议桥接" |
| 无状态设计(Token签发后可水平扩展) | 社区相对小众,文档偏简略 |
实战
集成LDAP
LDAP常作为SSO系统的用户目录后端。
集成架构:
#mermaid-svg-d59X6DcshX3kk6U5{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-d59X6DcshX3kk6U5 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-d59X6DcshX3kk6U5 .error-icon{fill:#552222;}#mermaid-svg-d59X6DcshX3kk6U5 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-d59X6DcshX3kk6U5 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-d59X6DcshX3kk6U5 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-d59X6DcshX3kk6U5 .marker.cross{stroke:#333333;}#mermaid-svg-d59X6DcshX3kk6U5 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-d59X6DcshX3kk6U5 p{margin:0;}#mermaid-svg-d59X6DcshX3kk6U5 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-d59X6DcshX3kk6U5 .cluster-label text{fill:#333;}#mermaid-svg-d59X6DcshX3kk6U5 .cluster-label span{color:#333;}#mermaid-svg-d59X6DcshX3kk6U5 .cluster-label span p{background-color:transparent;}#mermaid-svg-d59X6DcshX3kk6U5 .label text,#mermaid-svg-d59X6DcshX3kk6U5 span{fill:#333;color:#333;}#mermaid-svg-d59X6DcshX3kk6U5 .node rect,#mermaid-svg-d59X6DcshX3kk6U5 .node circle,#mermaid-svg-d59X6DcshX3kk6U5 .node ellipse,#mermaid-svg-d59X6DcshX3kk6U5 .node polygon,#mermaid-svg-d59X6DcshX3kk6U5 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-d59X6DcshX3kk6U5 .rough-node .label text,#mermaid-svg-d59X6DcshX3kk6U5 .node .label text,#mermaid-svg-d59X6DcshX3kk6U5 .image-shape .label,#mermaid-svg-d59X6DcshX3kk6U5 .icon-shape .label{text-anchor:middle;}#mermaid-svg-d59X6DcshX3kk6U5 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-d59X6DcshX3kk6U5 .rough-node .label,#mermaid-svg-d59X6DcshX3kk6U5 .node .label,#mermaid-svg-d59X6DcshX3kk6U5 .image-shape .label,#mermaid-svg-d59X6DcshX3kk6U5 .icon-shape .label{text-align:center;}#mermaid-svg-d59X6DcshX3kk6U5 .node.clickable{cursor:pointer;}#mermaid-svg-d59X6DcshX3kk6U5 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-d59X6DcshX3kk6U5 .arrowheadPath{fill:#333333;}#mermaid-svg-d59X6DcshX3kk6U5 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-d59X6DcshX3kk6U5 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-d59X6DcshX3kk6U5 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d59X6DcshX3kk6U5 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-d59X6DcshX3kk6U5 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d59X6DcshX3kk6U5 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-d59X6DcshX3kk6U5 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-d59X6DcshX3kk6U5 .cluster text{fill:#333;}#mermaid-svg-d59X6DcshX3kk6U5 .cluster span{color:#333;}#mermaid-svg-d59X6DcshX3kk6U5 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-d59X6DcshX3kk6U5 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-d59X6DcshX3kk6U5 rect.text{fill:none;stroke-width:0;}#mermaid-svg-d59X6DcshX3kk6U5 .icon-shape,#mermaid-svg-d59X6DcshX3kk6U5 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-d59X6DcshX3kk6U5 .icon-shape p,#mermaid-svg-d59X6DcshX3kk6U5 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-d59X6DcshX3kk6U5 .icon-shape .label rect,#mermaid-svg-d59X6DcshX3kk6U5 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-d59X6DcshX3kk6U5 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-d59X6DcshX3kk6U5 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-d59X6DcshX3kk6U5 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 登录请求
LDAP Bind
验证成功
返回Token
携带Token
验证Token
用户
SSO服务器
如Keycloak
LDAP服务器
如OpenLDAP
应用系统
用户联邦配置示例:Keycloak集成LDAP
json
{
"name": "ldap",
"providerId": "ldap",
"parentId": "master",
"config": {
"connectionUrl": ["ldap://ldap.example.com:389"],
"bindDn": ["cn=admin,dc=example,dc=com"],
"bindCredential": ["admin_password"],
"baseDn": ["dc=example,dc=com"],
"userDnSuffix": ["ou=people"],
"usernameLDAPAttribute": ["uid"],
"rdnLDAPAttribute": ["uid"],
"uuidLDAPAttribute": ["entryUUID"],
"userObjectClasses": ["inetOrgPerson"],
"customUserSearchFilter": [""],
"searchScope": ["1"],
"validatePasswordPolicy": ["false"],
"useTruststoreSpi": ["ldapsOnly"],
"connectionPooling": ["true"],
"pagination": ["true"],
"allowKerberosAuthentication": ["false"],
"useKerberosForPasswordAuthentication": ["false"]
}
}
代码示例:使用Python进行LDAP认证
py
import ldap
def authenticate_with_ldap(username, password):
"""使用LDAP进行用户认证"""
ldap_server = "ldap://ldap.example.com"
base_dn = "dc=example,dc=com"
user_dn = f"uid={username},ou=people,{base_dn}"
try:
# 1. 建立LDAP连接
conn = ldap.initialize(ldap_server)
conn.protocol_version = 3
conn.set_option(ldap.OPT_REFERRALS, 0)
# 2. 绑定(验证用户凭据)
conn.simple_bind_s(user_dn, password)
# 3. 认证成功,查询用户信息
search_filter = f"(uid={username})"
result = conn.search_s(base_dn, ldap.SCOPE_SUBTREE, search_filter)
# 4. 提取用户属性
if result:
user_entry = result[0][1]
user_info = {
'dn': result[0][0],
'cn': user_entry.get('cn', [b''])[0].decode('utf-8'),
'mail': user_entry.get('mail', [b''])[0].decode('utf-8'),
'sn': user_entry.get('sn', [b''])[0].decode('utf-8'),
}
return user_info
return None
except ldap.INVALID_CREDENTIALS:
print("认证失败:用户名或密码错误")
return None
except ldap.SERVER_DOWN:
print("LDAP服务器不可用")
return None
finally:
conn.unbind_s()
# 使用示例
user_info = authenticate_with_ldap("john", "password123")
if user_info:
print(f"认证成功:{user_info}")