使用keycloak-python的change_current_realm()后出现Invalid_grant错误
问题场景
使用keycloak-python的change_current_realm()将Realm从master切换至new-sso后,调用create_user()时出现401错误,返回信息:
401: b'{"error":"invalid_grant","error_description":"Invalid user credentials"}'
报错代码示例:
from keycloak import KeycloakAdmin from keycloak import KeycloakOpenIDConnection server_url = "http://localhost:8080/" new_sso_realm = "new-sso" admin_username = 'admin' admin_password = 'admin' admin_client = 'admin-cli' master_realm = "master" keycloak_connection = KeycloakOpenIDConnection(server_url=server_url, username=admin_username, password=admin_password, client_id=admin_client, realm_name=master_realm) keycloak_admin = KeycloakAdmin(connection=keycloak_connection) keycloak_admin.change_current_realm(new_sso_realm) user_payload = {"username": "new_user", "enabled": True} keycloak_admin.create_user(user_payload, exist_ok=True)
但如果在切换Realm前调用keycloak_admin.get_realm(master_realm),代码就能正常执行创建用户。
原因分析
keycloak-python的KeycloakOpenIDConnection采用延迟令牌获取策略:初始化后不会立即向Keycloak请求令牌,而是等到第一次API调用时才会获取当前Realm(此处为master)的令牌并缓存。
当直接调用change_current_realm()时,连接对象只是修改了Realm名称,但缓存的令牌还未生成。此时调用create_user(),连接会尝试用未初始化的令牌上下文去请求new-sso Realm的接口,或者后续生成的令牌仍然绑定旧的Realm,导致invalid_grant错误。
而调用get_realm(master_realm)会触发第一次API调用,强制获取master Realm的令牌并缓存。切换Realm后,后续API调用会检测到Realm已变化,自动重新获取new-sso Realm的有效令牌,因此能正常执行。
解决方案
方案1:切换Realm后清除令牌缓存,强制重新认证
切换Realm后,手动清除连接对象的令牌缓存,让后续API调用重新获取新Realm的有效令牌:
keycloak_admin.change_current_realm(new_sso_realm) # 清除缓存的令牌,强制重新获取new-sso Realm的令牌 keycloak_admin.connection.token = None keycloak_admin.connection.refresh_token = None user_payload = {"username": "new_user", "enabled": True} keycloak_admin.create_user(user_payload, exist_ok=True)
方案2:初始化后提前触发令牌获取
在切换Realm前,手动触发令牌获取操作,避免延迟加载导致的上下文错误:
keycloak_admin = KeycloakAdmin(connection=keycloak_connection) # 提前获取master Realm的令牌 keycloak_admin.connection.get_token() keycloak_admin.change_current_realm(new_sso_realm) user_payload = {"username": "new_user", "enabled": True} keycloak_admin.create_user(user_payload, exist_ok=True)
方案3:直接初始化目标Realm的连接
如果无需先操作master Realm,可直接创建针对new-sso Realm的连接,跳过切换步骤:
keycloak_connection = KeycloakOpenIDConnection(server_url=server_url, username=admin_username, password=admin_password, client_id=admin_client, realm_name=new_sso_realm) keycloak_admin = KeycloakAdmin(connection=keycloak_connection) user_payload = {"username": "new_user", "enabled": True} keycloak_admin.create_user(user_payload, exist_ok=True)
注意事项
确保admin用户在new-sso Realm中拥有足够的权限(如realm-admin角色),且admin-cli客户端在new-sso Realm中已启用密码授权类型,否则即使切换Realm成功,仍可能出现权限错误。
内容的提问来源于stack exchange,提问作者Gооd_Mаn

