IAM开源解决方案:Keycloak

概述

官网,由Red Hat开源(GitHub,35.4K Star,8.6K Fork)基于Java+Quarkus、功能全面的IAM解决方案。

核心原理:作为一个统一的身份代理(Identity Broker),集中管理用户、角色、权限和会话。它既是身份提供者(IdP),也能作为代理集成外部身份源(如LDAP、社交登录)。

核心架构:
#mermaid-svg-ktYkE9omAcFoDaLl{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-ktYkE9omAcFoDaLl .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-ktYkE9omAcFoDaLl .error-icon{fill:#552222;}#mermaid-svg-ktYkE9omAcFoDaLl .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-ktYkE9omAcFoDaLl .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-ktYkE9omAcFoDaLl .marker{fill:#333333;stroke:#333333;}#mermaid-svg-ktYkE9omAcFoDaLl .marker.cross{stroke:#333333;}#mermaid-svg-ktYkE9omAcFoDaLl svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-ktYkE9omAcFoDaLl p{margin:0;}#mermaid-svg-ktYkE9omAcFoDaLl .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-ktYkE9omAcFoDaLl .cluster-label text{fill:#333;}#mermaid-svg-ktYkE9omAcFoDaLl .cluster-label span{color:#333;}#mermaid-svg-ktYkE9omAcFoDaLl .cluster-label span p{background-color:transparent;}#mermaid-svg-ktYkE9omAcFoDaLl .label text,#mermaid-svg-ktYkE9omAcFoDaLl span{fill:#333;color:#333;}#mermaid-svg-ktYkE9omAcFoDaLl .node rect,#mermaid-svg-ktYkE9omAcFoDaLl .node circle,#mermaid-svg-ktYkE9omAcFoDaLl .node ellipse,#mermaid-svg-ktYkE9omAcFoDaLl .node polygon,#mermaid-svg-ktYkE9omAcFoDaLl .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-ktYkE9omAcFoDaLl .rough-node .label text,#mermaid-svg-ktYkE9omAcFoDaLl .node .label text,#mermaid-svg-ktYkE9omAcFoDaLl .image-shape .label,#mermaid-svg-ktYkE9omAcFoDaLl .icon-shape .label{text-anchor:middle;}#mermaid-svg-ktYkE9omAcFoDaLl .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-ktYkE9omAcFoDaLl .rough-node .label,#mermaid-svg-ktYkE9omAcFoDaLl .node .label,#mermaid-svg-ktYkE9omAcFoDaLl .image-shape .label,#mermaid-svg-ktYkE9omAcFoDaLl .icon-shape .label{text-align:center;}#mermaid-svg-ktYkE9omAcFoDaLl .node.clickable{cursor:pointer;}#mermaid-svg-ktYkE9omAcFoDaLl .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-ktYkE9omAcFoDaLl .arrowheadPath{fill:#333333;}#mermaid-svg-ktYkE9omAcFoDaLl .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-ktYkE9omAcFoDaLl .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-ktYkE9omAcFoDaLl .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ktYkE9omAcFoDaLl .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-ktYkE9omAcFoDaLl .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ktYkE9omAcFoDaLl .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-ktYkE9omAcFoDaLl .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-ktYkE9omAcFoDaLl .cluster text{fill:#333;}#mermaid-svg-ktYkE9omAcFoDaLl .cluster span{color:#333;}#mermaid-svg-ktYkE9omAcFoDaLl 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-ktYkE9omAcFoDaLl .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-ktYkE9omAcFoDaLl rect.text{fill:none;stroke-width:0;}#mermaid-svg-ktYkE9omAcFoDaLl .icon-shape,#mermaid-svg-ktYkE9omAcFoDaLl .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-ktYkE9omAcFoDaLl .icon-shape p,#mermaid-svg-ktYkE9omAcFoDaLl .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-ktYkE9omAcFoDaLl .icon-shape .label rect,#mermaid-svg-ktYkE9omAcFoDaLl .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-ktYkE9omAcFoDaLl .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-ktYkE9omAcFoDaLl .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-ktYkE9omAcFoDaLl :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 存储
支持
提供
集成
代理
管理
自助服务
Keycloak Server
数据库

MySQL、PG等
协议支持

OIDC、OAuth 2.0、

SAML 2.0、CAS等
客户端适配器

Java等
用户联合

LDAP、AD、Kerberos
外部IdP

Google、Facebook

OIDC、SAML
管理员
管理控制台

Web UI
用户
账户管理控制台

核心概念:

  1. Realm:领域,租户隔离单位,每个Realm有独立的用户、客户端、角色配置,类似于"租户"或"域"
  2. Client:客户端,需认证的应用系统,可以是Web应用、移动应用、后端服务,支持不同访问类型(Access Type):
    • confidential:需要客户端密钥(适用于服务端应用)
    • public:不需要客户端密钥(适用于SPA、移动应用)
    • bearer-only:仅验证Token,不发起登录(适用于微服务)
  3. User:用户,可存储在Keycloak内部数据库中,也可从外部LDAP/AD联合导入
  4. Role:角色有两类
    • Realm Role:全局角色(如adminuser
    • Client Role:特定客户端的角色(如client_a_admin
  5. Group:组,用户集合,可继承角色和属性
  6. Identity Provider(身份提供商):外部认证源(如Google、Facebook、另一个Keycloak实例),支持OIDC、SAML 2.0、社交登录

Keycloak完整支持以下协议:

协议 支持情况 说明
OIDC 完整支持 作为IdP和客户端
OAuth2.0 完整支持 支持所有授权模式
SAML2.0 完整支持 作为IdP,支持SP-initiated和IdP-initiated
WS-Federation 支持 主要用于Windows集成
SCIM2.0 支持 用户和组管理API
LDAP 支持 用户联合
Kerberos 支持 与ActiveDirectory集成
FIDO2/WebAuthn 支持 无密码认证(实验性)

身份代理

即Identity Brokering,允许将外部身份提供商(IdP)的用户导入到Keycloak。

工作流程:
外部 IdP (Google, Facebook) Keycloak (身份代理) 服务提供商 (您的应用) 用户 外部 IdP (Google, Facebook) Keycloak (身份代理) 服务提供商 (您的应用) 用户 #mermaid-svg-v1B1Ed2o9Km9Tjin{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-v1B1Ed2o9Km9Tjin .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-v1B1Ed2o9Km9Tjin .error-icon{fill:#552222;}#mermaid-svg-v1B1Ed2o9Km9Tjin .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-v1B1Ed2o9Km9Tjin .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-v1B1Ed2o9Km9Tjin .marker{fill:#333333;stroke:#333333;}#mermaid-svg-v1B1Ed2o9Km9Tjin .marker.cross{stroke:#333333;}#mermaid-svg-v1B1Ed2o9Km9Tjin svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-v1B1Ed2o9Km9Tjin p{margin:0;}#mermaid-svg-v1B1Ed2o9Km9Tjin .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v1B1Ed2o9Km9Tjin text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-v1B1Ed2o9Km9Tjin .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-v1B1Ed2o9Km9Tjin .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-v1B1Ed2o9Km9Tjin #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-v1B1Ed2o9Km9Tjin .sequenceNumber{fill:white;}#mermaid-svg-v1B1Ed2o9Km9Tjin #sequencenumber{fill:#333;}#mermaid-svg-v1B1Ed2o9Km9Tjin #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-v1B1Ed2o9Km9Tjin .messageText{fill:#333;stroke:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v1B1Ed2o9Km9Tjin .labelText,#mermaid-svg-v1B1Ed2o9Km9Tjin .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .loopText,#mermaid-svg-v1B1Ed2o9Km9Tjin .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .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-v1B1Ed2o9Km9Tjin .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-v1B1Ed2o9Km9Tjin .noteText,#mermaid-svg-v1B1Ed2o9Km9Tjin .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-v1B1Ed2o9Km9Tjin .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v1B1Ed2o9Km9Tjin .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v1B1Ed2o9Km9Tjin .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-v1B1Ed2o9Km9Tjin .actorPopupMenu{position:absolute;}#mermaid-svg-v1B1Ed2o9Km9Tjin .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-v1B1Ed2o9Km9Tjin .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-v1B1Ed2o9Km9Tjin .actor-man circle,#mermaid-svg-v1B1Ed2o9Km9Tjin line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-v1B1Ed2o9Km9Tjin :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 1. 访问应用 2. 重定向到 Keycloak 登录 3. 显示登录页面(包含外部 IdP 选项) 4. 选择"使用 Google 登录" 5. 发起 OIDC/SAML 认证请求 6. 显示 Google 登录页面 7. 输入 Google 凭据 8. 返回认证响应(Token/Assertion) 9. 提取用户信息,创建/链接本地账号 10. 返回 Token(Keycloak 签发的) 11. 登录成功

配置示例:将Google配置为身份代理

bash 复制代码
# 1. 在 Google Cloud Console 创建 OAuth 2.0 客户端
# - 获取 Client ID 和 Client Secret
# - 设置授权重定向 URI:http://localhost:8080/auth/realms/my-realm/broker/google/endpoint

# 2. 在 Keycloak 管理控制台添加 Google 身份提供商
# - 导航到:Configure -> Identity Providers -> Add provider -> Google
# - 输入 Client ID 和 Client Secret
# - 配置:
#   - Default Scopes: openid profile email
#   - Email: 启用"Trust Email"和"Store Tokens"
#   - Account Linking: 启用"Automatic Account Linking"(根据邮箱自动链接)

# 3. (可选)配置 First Broker Login Flow(首次代理登录流程)
# - 用于在新用户首次通过外部 IdP 登录时收集额外信息
# - 可配置为:
#   - 自动创建账号(如果邮箱已验证)
#   - 显示账号关联页面(如果用户已存在)
#   - 显示额外信息收集表单(如要求用户同意条款)

自定义First Broker Login Flow:

xml 复制代码
<!-- 使用 Keycloak 的 Authentication Flow 自定义首次代理登录 -->
<!-- 在管理控制台:Authentication -> Flows -> First Broker Login -->

<!-- 1. 显示账号关联页面(如果用户已存在) -->
<execution id="idp-review-profile" requirement="REQUIRED">
  <authenticator>Idp Review Profile</authenticator>
</execution>

<!-- 2. 显示额外信息收集表单(如果需要) -->
<execution id="idp-auto-link" requirement="REQUIRED">
  <authenticator>Idp Auto Link</authenticator>
</execution>

<!-- 3. 自动创建账号(如果邮箱已验证) -->
<execution id="idp-create-user" requirement="REQUIRED">
  <authenticator>Idp Create User</authenticator>
</execution>

实战

启动

从GitHub Release页面下载.zip压缩包,解压缩,确保系统安装JDK 11以上版本,进入bin目录后,打开命令行工具并输入kc.bat start-dev启动Keycloak。启动成功后,访问 http://localhost:8080

IdP

Keycloak作为IdP时,OIDC配置示例:

js 复制代码
// Node.js 应用:使用 keycloak-connect 中间件
const Keycloak = require('keycloak-connect');
const express = require('express');
const session = require('express-session');

const app = express();
// 1. 配置Session,必需,用于存储Token
const memoryStore = new session.MemoryStore();
app.use(session({
  secret: 'session-secret',
  resave: false,
  saveUninitialized: true,
  store: memoryStore,
}));

// 2. 初始化
const keycloak = new Keycloak({
  store: memoryStore,
});
app.use(keycloak.middleware());

// 3. 受保护的路由
app.get('/dashboard', keycloak.protect(), (req, res) => {
  // req.kauth.grant 包含 Token 信息
  const token = req.kauth.grant.access_token;
  res.json({
    message: '欢迎访问控制台',
    user: req.kauth.grant.id_token.content.preferred_username,
  });
});

// 4. 基于角色的访问控制
app.get('/admin', keycloak.protect('realm:admin'), (req, res) => {
  res.json({ message: '欢迎访问管理页面' });
});
app.listen(3000, () => console.log('应用运行在端口 3000'));

keycloak.json配置文件:

json 复制代码
{
  "realm": "my-realm",
  "auth-server-url": "http://localhost:8080/auth/",
  "ssl-required": "external",
  "resource": "my-client",
  "public-client": true,
  "confidential-port": 0
}

SPI

Keycloak提供丰富的SPI用于扩展功能,核心SPI:

  1. User Storage:实现UserStorageProvider接口,可用于自定义用户存储(如从REST API、自定义数据库读取用户)
  2. Login Forms:实现Authenticator接口,可用于自定义登录页面和认证流程
  3. Email:实现EmailSenderProvider接口,可用于自定义邮件发送(如使用SendGrid、AWS SES)
  4. Event Listener:实现EventListenerProvider接口,可用于监听Keycloak事件(如用户登录、注册、密码重置)
  5. Identity Provider:实现IdentityProvider接口,可用于自定义外部身份提供商

自定义User Storage示例:

java 复制代码
// 自定义用户存储 SPI:从 REST API 读取用户
public class CustomUserStorageProvider implements UserStorageProvider, UserLookupProvider, CredentialInputValidator {
    private final KeycloakSession session;
    private final ComponentModel model;
    private final RestApiClient restApiClient;
    
    public CustomUserStorageProvider(KeycloakSession session, ComponentModel model) {
        this.session = session;
        this.model = model;
        this.restApiClient = new RestApiClient(model.get("apiUrl"));
    }
    // 根据用户名查找用户
    @Override
    public UserModel getUserByUsername(String username, RealmModel realm) {
        // 调用 REST API 查找用户
        UserDto userDto = restApiClient.findUserByUsername(username);
        if (userDto == null) {
            return null;
        }
        
        // 将 REST API 返回的用户转换为 Keycloak UserModel
        return new UserModel() {
            @Override
            public String getId() {
                return userDto.getId();
            }
            @Override
            public String getUsername() {
                return userDto.getUsername();
            }
            // ... 实现其他方法
        };
    }
    
    // 验证用户凭据(密码)
    @Override
    public boolean isValid(UserModel user, CredentialInput input) {
        if (!(input instanceof UserPasswordCredentialModel)) {
            return false;
        }
        String password = ((UserPasswordCredentialModel) input).getValue();
        // 调用 REST API 验证密码
        return restApiClient.verifyPassword(user.getUsername(), password);
    }
}

部署自定义SPI:

bash 复制代码
mvn clean package
# 复制到Keycloak的providers目录
cp target/custom-user-storage-spi.jar $KEYCLOAK_HOME/standalone/deployments/
# 重启Keycloak
$KEYCLOAK_HOME/bin/standalone.sh

管理控制台配置User Federation,导航路径:User Federation->Add provider->Custom User Storage SPI,配置API URL、凭据等

高可用

高可用部署架构:
#mermaid-svg-31SsJjTylmQT1US1{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-31SsJjTylmQT1US1 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-31SsJjTylmQT1US1 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-31SsJjTylmQT1US1 .error-icon{fill:#552222;}#mermaid-svg-31SsJjTylmQT1US1 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-31SsJjTylmQT1US1 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-31SsJjTylmQT1US1 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-31SsJjTylmQT1US1 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-31SsJjTylmQT1US1 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-31SsJjTylmQT1US1 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-31SsJjTylmQT1US1 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-31SsJjTylmQT1US1 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-31SsJjTylmQT1US1 .marker.cross{stroke:#333333;}#mermaid-svg-31SsJjTylmQT1US1 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-31SsJjTylmQT1US1 p{margin:0;}#mermaid-svg-31SsJjTylmQT1US1 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-31SsJjTylmQT1US1 .cluster-label text{fill:#333;}#mermaid-svg-31SsJjTylmQT1US1 .cluster-label span{color:#333;}#mermaid-svg-31SsJjTylmQT1US1 .cluster-label span p{background-color:transparent;}#mermaid-svg-31SsJjTylmQT1US1 .label text,#mermaid-svg-31SsJjTylmQT1US1 span{fill:#333;color:#333;}#mermaid-svg-31SsJjTylmQT1US1 .node rect,#mermaid-svg-31SsJjTylmQT1US1 .node circle,#mermaid-svg-31SsJjTylmQT1US1 .node ellipse,#mermaid-svg-31SsJjTylmQT1US1 .node polygon,#mermaid-svg-31SsJjTylmQT1US1 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-31SsJjTylmQT1US1 .rough-node .label text,#mermaid-svg-31SsJjTylmQT1US1 .node .label text,#mermaid-svg-31SsJjTylmQT1US1 .image-shape .label,#mermaid-svg-31SsJjTylmQT1US1 .icon-shape .label{text-anchor:middle;}#mermaid-svg-31SsJjTylmQT1US1 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-31SsJjTylmQT1US1 .rough-node .label,#mermaid-svg-31SsJjTylmQT1US1 .node .label,#mermaid-svg-31SsJjTylmQT1US1 .image-shape .label,#mermaid-svg-31SsJjTylmQT1US1 .icon-shape .label{text-align:center;}#mermaid-svg-31SsJjTylmQT1US1 .node.clickable{cursor:pointer;}#mermaid-svg-31SsJjTylmQT1US1 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-31SsJjTylmQT1US1 .arrowheadPath{fill:#333333;}#mermaid-svg-31SsJjTylmQT1US1 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-31SsJjTylmQT1US1 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-31SsJjTylmQT1US1 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-31SsJjTylmQT1US1 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-31SsJjTylmQT1US1 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-31SsJjTylmQT1US1 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-31SsJjTylmQT1US1 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-31SsJjTylmQT1US1 .cluster text{fill:#333;}#mermaid-svg-31SsJjTylmQT1US1 .cluster span{color:#333;}#mermaid-svg-31SsJjTylmQT1US1 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-31SsJjTylmQT1US1 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-31SsJjTylmQT1US1 rect.text{fill:none;stroke-width:0;}#mermaid-svg-31SsJjTylmQT1US1 .icon-shape,#mermaid-svg-31SsJjTylmQT1US1 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-31SsJjTylmQT1US1 .icon-shape p,#mermaid-svg-31SsJjTylmQT1US1 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-31SsJjTylmQT1US1 .icon-shape .label rect,#mermaid-svg-31SsJjTylmQT1US1 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-31SsJjTylmQT1US1 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-31SsJjTylmQT1US1 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-31SsJjTylmQT1US1 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 负载均衡器

Nginx、HAProxy
Keycloak Node 1
K Node 2
K Node 3
数据库

PG等
Infinispan

分布式缓存

关键配置:

  1. 负载均衡器配置:使用sticky session(可选,但推荐),配置健康检查端点:/auth/realms/master
  2. 数据库配置:使用高可用数据库(如MySQL Galera Cluster、PostgreSQL Patroni),配置连接池,如HikariCP
  3. 分布式缓存Infinispan配置:用于缓存用户、角色、Token,配置为分布式模式replicationinvalidation
  4. Stateless部署(推荐用于大规模部署):使用外部会话存储(如Redis),每个请求都可以路由到任意Keycloak节点

Docker Compose高可用部署示例:

yaml 复制代码
# docker-compose-ha.yml
version: '3.8'
services:
  # 负载均衡器
  nginx:
    image: nginx:alpine
    ports:
      - "8080:8080"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf
    depends_on:
      - keycloak1
      - keycloak2
  # Keycloak 节点1
  keycloak1:
    image: quay.io/keycloak/keycloak:latest
    environment:
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: password
      KC_HOSTNAME: localhost
      KC_HOSTNAME_PORT: 8080
      KC_HOSTNAME_STRICT: "false"
      KC_HTTP_ENABLED: "true"
      JGROUPS_DISCOVERY_PROTOCOL: JDBC_PING
      JGROUPS_DISCOVERY_PROPERTIES: datasource_jndi_name=java:jboss/datasources/KeycloakDS
    depends_on:
      - postgres
  # Keycloak 节点2
  keycloak2:
    image: quay.io/keycloak/keycloak:latest
    environment:
      # 与keycloak1相同env
      KEYCLOAK_ADMIN: admin
      KEYCLOAK_ADMIN_PASSWORD: admin
      KC_DB: postgres
      KC_DB_URL: jdbc:postgresql://postgres:5432/keycloak
      KC_DB_USERNAME: keycloak
      KC_DB_PASSWORD: password
      KC_HOSTNAME: localhost
      KC_HOSTNAME_PORT: 8080
      KC_HOSTNAME_STRICT: "false"
      KC_HTTP_ENABLED: "true"
      JGROUPS_DISCOVERY_PROTOCOL: JDBC_PING
      JGROUPS_DISCOVERY_PROPERTIES: datasource_jndi_name=java:jboss/datasources/KeycloakDS
    depends_on:
      - postgres
  # PG
  postgres:
    image: postgres:14
    environment:
      POSTGRES_DB: keycloak
      POSTGRES_USER: keycloak
      POSTGRES_PASSWORD: password
    volumes:
      - postgres_data:/var/lib/postgresql/data
  # (可选)Redis用于外部缓存
  redis:
    image: redis:alpine
    ports:
      - "6379:6379"
volumes:
  postgres_data:

生产环境最佳实践:

  • 启用Metrics和监控:使用Prometheus和Grafana监控Keycloak,监控关键指标:CPU、内存、数据库连接池、缓存命中率
  • 定期备份:备份数据库、备份Keycloak配置文件,如standalone.xmlkeycloak.conf

不推荐做法:

  • 在有状态模式下运行多个节点:应使用stateless模式或配置外部缓存
  • 暴露管理控制台到公网:应仅允许从内网或VPN访问管理控制台