使用外部 IDP 代理通过 KeyCloak 编程用户名/密码访问

Yur*_*kov 8 java oauth-2.0 openid-connect keycloak idp

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

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

我使用以下代码获取访问令牌,但每次传递有效的用户名/密码时都会出现 401 错误。

org.keycloak.authorization.client.util.HttpResponseException: Unexpected response from server: 401 / Unauthorized
Run Code Online (Sandbox Code Playgroud)

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

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

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

Yur*_*kov 2

问题是 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']
Run Code Online (Sandbox Code Playgroud)