Keycloak 使用自定义协议映射器从数据库/外部源添加额外声明

Keycloak add extra claims from database / external source with custom protocol mapper

我已经看到那两个 post 给出了这个问题的解决方案,但他们没有提供足够详细的信息来说明如何为像我这样的非 Java 开发人员提供解决方案:

这里是他们解决方案的概述,如果提供更多详细信息,可能会对其他人有所帮助。

预期进程

  1. User logs in
  2. My custom protocol mapper gets called, where I overwrite the transformAccessToken method
  3. Here I log in the client where the protocol mapper is in into keycloak, as a service. Here don't forget to use another client ID instead the one you're building the protocol mapper for, you'll enter an endless recursion otherwise.
  4. I get the access token into the protocol mapper and I call the rest endpoint of my application to grab the extra claims, which is secured.
  5. Get the info returned by the endpoint and add it as extra claims

实现步骤

Implement the ProtocolMapper interface and add the file "META-INF/services/org.keycloak.protocol.ProtocolMapper" containing the reference to the class.

At this point Keycloak recognizes the new implementation. And you should be able to configure it via the admin console.

To add some data to the token add the following interfaces

org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper

and implement the methods according to the interface

Then add the file "META-INF/jboss-deployment-structure.xml" with the following content

<?xml version="1.0" encoding="UTF-8"?>
<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

And after doing all this the custom transformAccessToken() method is called on every request to URL http://:/auth/realms/testrealm/protocol/openid-connect/token

看完这篇我有几个问题:

  1. 你如何“实现 ProtocolMapper”
  2. 你在哪里添加前面提到的文件? (在我的 Keycloak 安装文件夹中看不到任何 META-INF/ 目录)
  3. 你如何以及在哪里“添加以下接口”
  4. 自定义 transformAccessToken() 是什么样的

谢谢大家的宝贵时间。 如果我错过了总结他们的答案,请告诉我。

编辑:

我开始悬赏,希望有人能够给我详细的步骤,说明如何在 Keycloak 3.4.3 中从数据库添加额外声明(对于非 Java 开发人员来说足够详细)

编辑 2 此处描述的方法可以解决问题,但缺乏细节。

希望这一步guide对你有帮助

我正在使用 Keycloak 4.5.0 - 因为我安装了这个较新的版本 - 但我应该不会有太大的不同。我在例子中实现了一个OIDCProtocolMapper

只是总结一下 - 对于其他人的 quick 概述 - 每个步骤稍后都会更详细地描述

  1. 你实现了一个 CustomProtocolMapper class 基于 AbstractOIDCProtocolMapper

  2. META-INF/services 文件与 名称 org.keycloak.protocol.ProtocolMapper 必须可用并且 包含您的映射器名称

  3. jboss-deployment-structure.xml 需要可用 classes

  4. 中的密钥斗篷 built
  5. Jar文件部署在 /opt/jboss/keycloak/standalone/deployments/

好的,现在更多详细信息:-)

创建您的自定义映射器

我给你上传了我的 maven pom.xml (pom) - 只需将它导入你的 IDE 并且所有依赖项应该会自动加载。依赖项只是 provided,稍后将在运行时直接从 keycloak 使用

相关的是 keycloak.version 属性 - 所有 keycloak 依赖项当前都在版本 4.5.0.Final

中加载

现在我创建了一个名为 CustomOIDCProtocolMapper 的自定义协议映射器 Class。查找 "full" 代码 here

它应该扩展AbstractOIDCProtocolMapper并且需要实现所有的抽象方法。也许您想拥有一个 SAML 协议映射器,那么它是另一个基础 class (AbstractSAMLProtocolMapper)

一个相关的方法是 transformAccessToken - 在这里我为 AccessToken 设置了一个额外的 Claim。您在这里需要您的逻辑,但是是的 - 取决于您的数据库等。;-)

服务文件

服务文件对于 keycloak 找到您的自定义实现很重要

\src\main\resources\META-INF\services\

中放置一个 fileName org.keycloak.protocol.ProtocolMapper 的文件

在此文件中,您写入自定义提供者的名称 - 因此 keycloak 知道此 class 可用作协议映射器
在我的示例中,文件内容只有一行

com.Whosebug.keycloak.custom.CustomOIDCProtocolMapper

部署结构XML

在您的自定义映射器中,您使用来自 keycloak 的文件。为了使用它们,我们需要告知 jboss 这种依赖性。 因此在 \src\main\resources\META-INF\ 中创建一个文件 jboss-deployment-structure.xml 内容:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="org.keycloak.keycloak-services" />
        </dependencies>
    </deployment>
</jboss-deployment-structure>

Build 并部署您的扩展

Build 你的扩展的 jar 文件 (mvn clean package) - 并将 jar 放入 /opt/jboss/keycloak/standalone/deployments/ 并重新启动 keycloak

在日志文件中,您应该会看到它何时部署以及(希望没有)错误消息

现在您可以使用您的映射器了——在我的示例中,我可以在 keycloak admin ui 和 select Whosebug Custom Protocol Mapper 下拉菜单中创建一个映射器

正如信息 - keycloak 并未完全正式支持 - 因此接口可能会在以后的版本中发生变化

我希望它是可以理解的,并且您将能够成功实现自己的映射器

编辑: 导出的 eclipse 文件结构 zip

我正在使用 Custom Protocol Mapper1 发送 authenticated2 GraphQL 查询3 到外部系统并将 JSON 响应数据放入用户的访问令牌(智威汤逊)。它目前与 Keycloak 10 一起运行。

==> 您可以在 this repository.

中找到完整代码

(1) 自定义协议映射器

正如其他人所说,您的项目至少需要 3 个文件。

  1. A Java class 实现了 AbstractOIDCProtocolMapper 及其方法 setClaim (以及其他)。
  2. 包含部署依赖项的 jboss-deployment-structure.xml 文件。
  3. 包含自定义协议映射器全名的 org.keycloak.protocol.ProtocolMapper 文件。

文件夹结构如下:

$ tree src/ -A
src/
└── main
    ├── java
    │   └── com
    │       └── thohol
    │           └── keycloak
    │               └── JsonGraphQlRemoteClaim.java
    └── resources
        └── META-INF
            ├── jboss-deployment-structure.xml
            └── services
                └── org.keycloak.protocol.ProtocolMapper

(2) 已验证的远程请求

如果远程端点需要身份验证,我们可以从Keycloak获取访问令牌。完整的流程如下所示(尤其是步骤 3-6):

  1. 用户向 Keycloak 发送身份验证请求(即“登录”)。该请求是针对特定的 Keycloak 客户端发出的,例如 login-client.
  2. 因为 login-client 配置为使用自定义协议映射器,它的代码在处理用户的身份验证请求时执行。
  3. 自定义协议映射器向 Keycloak 发送第二个身份验证请求。使用 client_credentials(客户端 ID + 秘密)针对 second Keycloak 客户端(例如,remote-claims-client)发出请求。
  4. 自定义协议映射器收到客户端 remote-claims-client 的访问令牌。
  5. 自定义协议映射器向远程端点发送请求。一个 Authorization: Bearer <access token> header 添加到请求 headers.
  6. 远程端点接收请求并验证 JWT 令牌。在许多情况下,应该进一步限制访问。例如,只允许为相应的 remote-claims-client.
  7. 铸造(“写入”)的令牌
  8. 远程端点returns自定义远程声明数据。
  9. 自定义协议映射器接收自定义远程声明数据并将其放入用户的访问令牌中。
  10. Keycloak returns 一个对用户具有自定义声明的访问令牌。

步骤 3/4 可以作为简单的 HTTP POST 请求在 Java 中实现(省略错误处理!):

// Call remote service
HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(keycloakAuthUrl);
URI uri = uriBuilder.build();

HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "grant_type=client_credentials&client_id=remote-claims-client&client_secret=dfebc62a-e8d7-4ab3-9196-258ddb5684ab";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));

// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_FORM_URLENCODED);

// Call
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());

// Process Response
JsonNode json = return new ObjectMapper().readTree(response.body());
String accessToken = json.findValue("access_token").asText();

(3) 对外部请求使用 GraphQL 查询

GraphQL 查询本质上是一个 HTTP POST 请求,带有 body like

{
    "query": "query HeroName($episode: Episode) {
        hero(episode: $episode) {
            name
        }
    }",
    "variables": {
        "episode" : "JEDI"
    }
}

这可以从 Java 发送,例如(省略错误处理!):

HttpClient httpClient = HttpClient.newHttpClient();
URIBuilder uriBuilder = new URIBuilder(remoteUrl);
URI uri = uriBuilder.build();

HttpRequest.Builder builder = HttpRequest.newBuilder().uri(uri);
String queryBody = "{
    \"query\": \"query HeroName($episode: Episode) {
        hero(episode: $episode) {
            name
        }
    }\",
    \"variables\": {
        \"episode\" : \"JEDI\"
    }
}";
builder.POST(HttpRequest.BodyPublishers.ofString(queryBody));

// Build headers
builder.header(HttpHeaders.CONTENT_TYPE , MediaType.APPLICATION_JSON);
builder.header(HttpHeaders.AUTHORIZATION, "Bearer " + accessToken);

// Call
HttpResponse<String> response = httpClient.send(builder.build(), HttpResponse.BodyHandlers.ofString());

// Process Response and add to token
JsonNode json = return new ObjectMapper().readTree(response.body());
clientSessionCtx.setAttribute("custom_claims", json);

免责声明

我是链接库的owner/author。但是,我并没有从头开始,而是使用了多个其他存储库作为 basis/inspiration。见 repo's README.