使用外部 IDP 代理通过 KeyCloak 进行编程 username/password 访问

Programatic username/password access with KeyCloak using external IDP brokering

我正在使用 Identity Brokering feature and external IDP. So, user logs in into external IDP UI, then KeyCloak broker client receives JWT token from external IDP and KeyCloak provides JWT with which we access the resources. I've set up Default Identitiy Provider 功能,因此在登录时向用户显示外部 IDP 登录屏幕。这意味着用户及其密码存储在外部 IDP 上。

当我需要在测试中以编程方式使用 "Direct Access Grant"(资源所有者密码授予)登录时,就会出现问题。由于密码未存储在 KeyCloak 上,我在登录时总是从 KeyCloak 收到 401 Unauthorized 错误。当我尝试更改用户密码时它开始工作,所以问题是用户密码未在 KeyCloak 上提供并且使用 "Direct Access Grant" KeyCloak 不会在程序登录时调用外部 IDP。

我使用以下代码获取访问令牌,但每次我通过有效 username/password 时都会收到 401 错误。

org.keycloak.authorization.client.util.HttpResponseException: Unexpected response from server: 401 / Unauthorized

已为该客户端启用直接访问授权。

public static String login(final Configuration configuration) {
    final AuthzClient authzClient = AuthzClient.create(configuration);
    final AccessTokenResponse accessTokenResponse = authzClient.obtainAccessToken(USERNAME, PASSWORD);
    return accessTokenResponse.getToken();
  }

有什么办法可以解决吗?例如在 "Direct Access Grant" 上调用身份代理,以便 KeyCloak 向我们提供它的有效令牌?

问题是 KeyCloak 没有来自初始身份提供者的密码信息。他们有一个 token exchange feature 应该用于程序化令牌交换。

应该用

External Token to Interanal Token Exchange来实现。

这是 Python 中的示例代码(只需在占位符中放置正确的值):

def login():
    idp_access_token = idp_login()
    return keycloak_token_exchange(idp_access_token)

def idp_login():
    login_data = {
        "client_id": <IDP-CLIENT-ID>,
        "client_secret": <IDP-CLIENT-SECRET>,
        "grant_type": <IDP-PASSWORD-GRANT-TYPE>,
        "username": <USERNAME>,
        "password": <PASSWORD>,
        "scope": "openid",
        "realm": "Username-Password-Authentication"
    }
    login_headers = {
        "Content-Type": "application/json"
    }
    token_response = requests.post(<IDP-URL>, headers=login_headers, data=json.dumps(login_data))
    return parse_response(token_response)['access_token']

def keycloak_token_exchange(idp_access_token):
    token_exchange_url = <KEYCLOAK-SERVER-URL> + '/realms/master/protocol/openid-connect/token'
    data = {
        'grant_type': 'urn:ietf:params:oauth:grant-type:token-exchange',
        'subject_token': idp_access_token,
        'subject_issuer': <IDP-PROVIDER-ALIAS>,
        'subject_token_type': 'urn:ietf:params:oauth:token-type:access_token',
        'audience': <KEYCLOAK-CLIENT-ID>
    }
    response = requests.post(token_exchange_url, data=data,
                             auth=(<KEYCLOAK-CLIENT-ID>, <KEYCLOAK-CLIENT-SECRET>))
    logger.info(response)
    return parse_response(response)['access_token']

这个例子对我很有用,我只想添加更多关于用于令牌交换的 KEYCLOAK-CLIENT 的信息(对我来说 authorization_client)。我有 KEYCLOAK 作为 IDP ADFS 的代理。

  1. 首先,您需要启用令牌交换功能,在您的 keycloak 启动命令行中添加 2 个参数(取决于您的操作方式) -Dkeycloak.profile=预览 -Dkeycloak.profile.feature.token_exchange=enabled IDP(对我来说 ADFS)选项卡具有令牌交换权限的权限将可用。
  2. 给"token-exchange"提供者权限添加策略,给客户端KEYCLOAK-CLIENT
  3. 将之前的策略添加到 "token-exchange" 客户端权限

您可以使用 POSTMAN 测试身份验证流程:

External IDP ADFS Login Username/password Token Exchange