通过使用外部IDP代理的KeyCloak进行程序化用户名/密码访问

2022-04-08 00:00:00 oauth-2.0 openid-connect keycloak idp java

我正在使用Identity Brokering功能和外部IdP。因此,用户登录到外部IdP用户界面,然后KeyCloak Broker客户端从外部IdP接收JWT令牌,KeyCloak提供JWT供我们访问资源。我已经设置了Default Identitiy Provider功能,以便在登录时向用户显示外部IDP登录屏幕。这意味着用户及其密码存储在外部IdP上。

当我需要在测试中以编程方式使用"Direct Access Grant"(资源所有者密码授予)登录时,会出现这个问题。由于密码没有存储在KeyCloak上,登录时我总是从KeyCloak收到401未经授权的错误。当我试图更改用户密码时,它开始起作用了,所以问题是用户密码没有在KeyCloak上提供,并且使用"Direct Access Grant"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();
  }

有没有办法解决这个问题?例如,在"Direct Access Grant"上调用Identity Broker,以便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']

相关文章