Keycloak x509 客户端认证配置

Keycloak x509 client authentication configuration

我需要一些技巧来了解如何使用 x509 证书对 Keycloak 执行客户端身份验证。

我们有一个简单的 Spring Boot Web App (API REST) 到 Kubernetes 集群中。
此 Web 应用程序通过 API 网关(大使)公开,目前通过浏览器重定向到 Keycloak 登录页面进行保护,用户可以在其中输入其用户名和密码。

但这不是我们想要的。
我们需要的是客户端身份验证(React Native Mobile App),其中:

  • 移动应用程序尝试调用我们的服务器 API REST 端点
  • 大使检查有效的访问令牌并(如果没有)以 403 http 状态响应(无浏览器重定向)
  • 移动应用程序然后重定向到 Keycloak 以执行身份验证
  • Keycloak 不显示用户名/密码登录页面,而是移动应用程序通过其浏览器传递 x509 用户证书。

很遗憾,我不明白如何使用用户数据(信息、角色 ecc)生成有效且受信任的 x509 证书,以便从 IdP 获取访问令牌。

还有...... IdP 如何检查和验证此客户端证书?是否需要在 Keycloak 上的某个位置安装对应的服务器证书?

将客户端证书传递给 keycloak 的正确形式是什么(例如 CURL)?
是否还需要传递私钥?为什么?


请查看 Keycloak 服务器管理指南第 6 节中的 X.509 客户端证书用户身份验证部分,其中描述:

  • 如何在 Wildfly 和 Keycloak 中启用和配置客户端证书身份验证
  • 如何将证书字段映射到用户属性
  • 使用 Keycloak/Wildfly(Keycloak 容器)的客户端证书身份验证工作流程。


很遗憾,我不明白如何使用用户数据(信息、角色等)生成有效且受信任的 x509 证书,以便从 IdP 获取访问令牌。

看看这篇文章来生成证书如何使用 OpenSSL 创建自签名证书

IdP 如何检查和验证此客户端证书?

Keycloak 文档是一个很好的起点,请查看"将 X.509 客户端证书身份验证添加到浏览器流程"和"将 X.509 客户端证书身份验证添加到直接授权流程"
如果您需要用户密钥的整个 DN,您可以在配置 X509 上使用此 RegEx:

  • 设置"提取用户身份的正则表达式":(.*)
  • 例如,添加一个名为 "usercertificate" 的用户属性并在其中复制 DN。

是否有必要在 Keycloak 的某个地方安装一个服务器证书副本?

不是所有的证书,您只能在名为 "usercertificate" 的用户用户属性中添加 DN。

将客户端证书传递给 keycloak 的正确形式是什么(例如 CURL)?

网址:https://localhost:9445/auth/realms//protocol/openid-connect/token

打开 Keycloak TLS 会话的代码示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public String openSession(
        File p12File,
        String passphrase,
        String clientId)
throws IOException, GeneralSecurityException {

    try (FileInputStream fis = new FileInputStream(p12File);) {
        KeyStore keyStore = KeyStore.getInstance("PKCS12");
        keyStore.load(new FileInputStream(p12File), passphrase.toCharArray());
       
        HttpClient httpClient = HttpClients
                .custom()
                .setSSLContext(new SSLContextBuilder()
                        .loadTrustMaterial(null, TrustAllStrategy.INSTANCE)
                        .loadKeyMaterial(keyStore, passphrase.toCharArray())
                        .build())
                .setSSLHostnameVerifier(NoopHostnameVerifier.INSTANCE)
                .build();
       
        HttpPost httpPost = new HttpPost(tokenEndpoint);
        httpPost.addHeader("Content-Type","application/x-www-form-urlencoded");

        String data ="grant_type=password" +"&client_id="+ clientId;
        StringEntity input = new StringEntity(data);
        httpPost.setEntity(input);

        HttpResponse response = httpClient.execute(httpPost);
        return IOUtils.toString(response.getEntity().getContent(),"UTF-8");
    }
}

是否还需要传递私钥?为什么?

没有