9

我正在使用身份代理功能和外部 IDP。因此,用户登录到外部 IDP UI,然后 KeyCloak 代理客户端从外部 IDP 接收 JWT 令牌,并且 KeyCloak 提供 JWT 以供我们访问资源。我已经设置了默认身份提供程序功能,因此在登录时会向用户显示外部 IDP 登录屏幕。这意味着用户及其密码存储在外部 IDP 上。

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

我使用以下代码获取访问令牌,但每次传递有效的用户名/密码时都会出现 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();
  }

有什么办法可以修复吗?例如,在“直接访问授权”上调用身份代理,以便 KeyCloak 为我们提供它的有效令牌?

4

2 回答 2

1

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

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

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

外部 IDP ADFS 登录 用户名/密码 令牌交换

于 2020-02-27T08:55:06.560 回答
1

问题是 KeyCloak 没有来自初始身份提供者的密码信息。它们具有令牌交换功能,应用于程序化令牌交换。

应该使用外部令牌到内部令牌交换来实现它。

这是 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']
于 2018-02-13T18:02:38.417 回答