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

时间:2018-10-31 18:20:18

标签: java customization keycloak

我看过这两篇文章为这个问题提供了解决方案,但是它们没有提供关于如何为像我这样的非Java开发人员做的足够详细的信息:

Keycloak add extra claims from database / external source

How to register a custom ProtocolMapper in Keycloak?

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

期望的过程 from 1st link

  
      
  1. 用户登录
  2.   
  3. 调用我的自定义协议映射器,在这里我覆盖了transformAccessToken方法
  4.   
  5. 在这里,我将协议映射器所在的客户端作为服务登录到keycloak中。在这里别忘了使用其他客户端ID   而不是您要为其构建协议映射器的那个,您将输入   否则无休止的递归。
  6.   
  7. 我将访问令牌放入协议映射器,并调用应用程序的其余端点以获取额外的声明,这是   安全的。
  8.   
  9. 获取端点返回的信息并将其添加为额外声明
  10.   

实现目标的步骤 from 2nd link

  

实现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>
     

完成所有这些操作后,将调用自定义transformAccessToken()方法   每次对URL的请求   http://:/ auth / realms / testrealm / protocol / openid-connect / token

阅读本文后,我有几个问题:

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

谢谢大家的时间。 如果我想念他们的答案,请告诉我。

编辑:

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

编辑2 此处描述的方法可以解决问题,但缺少细节。 Keycloak create a custom identity provider mapper

2 个答案:

答案 0 :(得分:10)

希望本逐步指南对您有帮助

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

仅作总结-为其他内容提供快速概述-每个步骤将在以后更详细地描述

  1. 您将基于以下内容实现CustomProtocolMapper类 AbstractOIDCProtocolMapper

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

  3. jboss-deployment-structure.xml必须可以使用 内置于类中的keycloak

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

现在确定更多细节:-)

创建自定义映射器

我将您的Maven pom.xmlpom)上传给您-只需将其导入您的IDE中,所有依赖项都应自动加载。依赖关系仅为provided,以后将在运行时直接从keycloak中使用

相关的是keycloak.version属性-当前所有密钥库相关性都已加载到版本4.5.0.Final

现在,我创建了一个名为CustomOIDCProtocolMapper的自定义协议映射器类。查找“完整”代码here

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

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

服务文件

服务文件对于Keycloak 重要很重要,以查找您的自定义实现

org.keycloak.protocol.ProtocolMapper

内放置 fileName \src\main\resources\META-INF\services\的文件

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

com.stackoverflow.keycloak.custom.CustomOIDCProtocolMapper

部署结构XML

在自定义映射器中,您将使用keycloak中的文件。为了使用它们,我们需要通知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>

构建并部署您的扩展程序

构建扩展文件(mvn clean package的jar文件-将jar放在/opt/jboss/keycloak/standalone/deployments/中,然后重新启动密钥斗篷

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

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

仅作为信息-密钥斗篷尚未完全正式支持-因此界面可能会在更高版本中更改

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

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

答案 1 :(得分:1)

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

==>您可以在this repository中找到完整的代码。

(1)自定义协议映射器

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

  1. 实现AbstractOIDCProtocolMapper及其方法setClaim(以及其他方法)的Java类。
  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发送第二个身份验证请求。使用remote-claims-client(客户端ID +机密)针对第二个Keycloak客户端(例如client_credentials)发出请求。
  4. 自定义协议映射器收到客户端remote-claims-client的访问令牌。
  5. 自定义协议映射器将请求发送到远程端点。 Authorization: Bearer <access token>标头已添加到请求标头中。
  6. 远程端点接收请求并验证JWT令牌。在许多情况下,应进一步限制访问。例如,仅允许为相应的remote-claims-client铸造(“写”)令牌。
  7. 远程端点返回自定义远程声明数据。
  8. 自定义协议映射器接收自定义的远程声明数据,并将其放入用户的访问令牌中。
  9. Keycloak将具有自定义声明的访问令牌返回给用户。

步骤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);

免责声明

我是链接存储库的所有者/作者。但是,我并不是从零开始,而是使用其他多个存储库作为基础/灵感。参见repo's README