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

2022-09-03 00:58:29

我看过这两篇帖子,它们给出了这个问题的解决方案,但它们没有提供足够详细的信息,说明如何为像我这样的非Java开发人员做到这一点:

按键斗篷添加来自数据库/外部源的额外声明

如何在Keycloak中注册自定义协议映射器?

以下是他们的解决方案的回顾,如果包含更多详细信息,可以帮助其他人。

从第一个链接预期的进程

  1. 用户登录
  2. 我的自定义协议映射器被调用,我在其中覆盖了 transformAccessToken 方法
  3. 在这里,我登录了协议映射器作为服务进入密钥保护的客户端。在这里,不要忘记使用另一个客户端ID而不是您正在为其构建协议映射器的客户端ID,否则您将输入无休止的递归。
  4. 我将访问令牌放入协议映射器中,并调用应用程序的其余终结点来获取额外的声明,这是受保护的。
  5. 获取终结点返回的信息,并将其添加为额外声明

从第二个链接实现它的步骤

实现 ProtocolMapper 接口并添加包含对该类的引用的文件 “META-INF/services/org.keycloak.protocol.ProtocolMapper”。

此时,Keycloak 识别新的实现。您应该能够通过管理控制台对其进行配置。

若要向令牌添加一些数据,请添加以下接口

org.keycloak.protocol.oidc.mappers.OIDCAccessTokenMapper

并根据接口实现方法

然后添加包含以下内容的文件“META-INF/jboss-deployment-structure.xml

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

完成所有这些操作后,在每次请求URL时都会调用自定义转换AccessToken()http://:/auth/realms/testrealm/protocol/openid-connect/token

读完这篇文章后,我有几个问题:

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

谢谢大家抽出宝贵时间。让我知道,如果我错过了总结他们的答案。

编辑:

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

编辑 2这里描述的方法可以做到这一点,但缺乏细节。密钥保护创建自定义身份提供程序映射器


答案 1

我希望这个分步指南可以帮助您

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

只是为了总结一下 - 为了快速概述其他人 - 每个步骤将在后面更详细地描述

  1. 实现基于AbstractOIDCProtocolMapper

  2. 具有该名称的 META-INF/服务文件必须可用,并且包含映射器的名称org.keycloak.protocol.ProtocolMapper

  3. jboss-deployment-structure.xml需要可用才能使用类中内置的键斗篷

  4. Jar 文件部署在/opt/jboss/keycloak/standalone/deployments/

好吧,现在更多细节:-)

创建自定义映射器

我上传了我的maven(pom) - 只需将其导入到您的IDE中,所有依赖项都应该自动加载。这些依赖项只是,稍后将在运行时直接从 keycloak 使用。pom.xmlprovided

相关是属性 - 所有键斗篷依赖项当前都加载在版本中keycloak.version4.5.0.Final

现在我创建了一个名为.在此处查找“完整”代码CustomOIDCProtocolMapper

它应该扩展并需要实现所有抽象方法。也许你想有一个SAML协议映射器,那么它是另一个基类(AbstractOIDCProtocolMapperAbstractSAMLProtocolMapper)

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

服务文件

服务文件对于密钥保护符查找自定义实现非常重要

将文件名所在的文件放在其中org.keycloak.protocol.ProtocolMapper\src\main\resources\META-INF\services\

在此文件中,您写入自定义提供程序的名称 - 因此keycloak知道该类可用作协议映射器
在我的示例中,文件内容仅为一行

com.stackoverflow.keycloak.custom.CustomOIDCProtocolMapper

部署结构 XML

在自定义映射器中,使用按键保护符中的文件。为了使用它们,我们需要通知jboss这个依赖关系。因此,在内容中创建一个文件:jboss-deployment-structure.xml\src\main\resources\META-INF\

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

生成和部署扩展

构建扩展 () 的 jar 文件 - 并放置 in 并重新启动钥匙斗篷mvn clean packagejar/opt/jboss/keycloak/standalone/deployments/

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

现在您可以使用您的映射器 - 在我的示例中,我可以在keycloak管理ui中创建一个映射器,并从下拉列表中进行选择Stackoverflow Custom Protocol Mapper

就像信息一样 - 这不是完全官方支持的keycloak - 所以接口可能会在更高版本中更改

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

编辑:导出的日食文件结构zip


答案 2

我正在使用自定义协议映射器1经过身份验证的2GraphQL 查询3 发送到外部系统,并将 JSON 响应数据放入用户的访问令牌 (JWT) 中。它目前与Keycloak 10一起运行。

==> 您可以在此存储库中找到完整的代码。

(1) 自定义协议映射器

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

  1. 实现及其方法(以及其他方法)的Java类。AbstractOIDCProtocolMappersetClaim
  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) 经过身份验证的远程请求

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

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

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

// 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

{
    "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);

免責聲明

我是链接存储库的所有者/作者。但是,我不是从头开始,而是使用多个其他存储库作为基础/灵感。请参阅存储库的自述文件


推荐