Использование OpenID/Keycloak с Superset
Я хочу использовать keycloak для аутентификации моих пользователей в нашей среде Superset.
Superset использует flask-openid, как это реализовано в флеш-безопасности:
- http://flask-appbuilder.readthedocs.io/en/latest/_modules/flask_appbuilder/security/manager.html
- https://pythonhosted.org/Flask-OpenID/
Чтобы включить аутентификацию пользователя, отличную от обычной (база данных), вам необходимо переопределить параметр AUTH_TYPE в файле superset_config.py. Вам также нужно будет предоставить ссылку на вашу область openid-connect и включить регистрацию пользователей. Как я понимаю, это должно выглядеть примерно так:
from flask_appbuilder.security.manager import AUTH_OID
AUTH_TYPE = AUTH_OID
OPENID_PROVIDERS = [
{ 'name':'keycloak', 'url':'http://localhost:8080/auth/realms/superset' }
]
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
При такой конфигурации страница входа меняется на приглашение, где пользователь может выбрать нужного поставщика OpenID (в нашем случае keycloak). У нас также есть две кнопки, одна для входа (для существующих пользователей) и одна для регистрации в качестве нового пользователя.
Я ожидаю, что любая из этих кнопок приведет меня на мою страницу входа в Keycloak. Однако этого не происходит. Вместо этого я перенаправлен обратно на страницу входа.
В случае, когда я нажимаю кнопку регистрации, я получаю сообщение "Невозможно зарегистрировать вас в данный момент, повторите попытку позже". Когда я нажимаю кнопку входа, сообщение не отображается. Журналы Superset показывают запрос, который загружает страницу входа, но не запросы к keycloak. Я попробовал то же самое с помощью провайдера Google OpenID, который работает просто отлично.
Так как я не вижу запросов к keycloak, это заставляет меня думать, что я либо пропускаю настройки конфигурации где-то, либо использую неправильные настройки. Не могли бы вы помочь мне выяснить, какие настройки я должен использовать?
3 ответа
Мне удалось решить свой вопрос. Основная проблема была вызвана неправильным предположением, которое я сделал относительно плагина колба-openid, который использует суперсет. Этот плагин на самом деле поддерживает OpenID 2.x, но не OpenID-Connect (это версия, реализованная Keycloak).
В качестве обходного пути я решил переключиться на плагин flask-oidc. Переключение на нового провайдера аутентификации на самом деле требует определенных усилий по копанию. Чтобы интегрировать плагин, мне пришлось выполнить следующие шаги:
Настроить колбу-oidc для брелка
К сожалению, flask-oidc не поддерживает формат конфигурации, сгенерированный Keycloak. Вместо этого ваша конфигурация должна выглядеть примерно так:
{
"web": {
"realm_public_key": "<YOUR_REALM_PUBLIC_KEY>",
"issuer": "http://<YOUR_DOMAIN>/auth/realms/<YOUR_REALM_ID>",
"auth_uri": "http://<YOUR_DOMAIN>/auth/realms/<YOUR_REALM_ID>/protocol/openid-connect/auth",
"client_id": "<YOUR_CLIENT_ID>",
"client_secret": "<YOUR_SECRET_KEY>",
"redirect_urls": [
"http://<YOUR_DOMAIN>/*"
],
"userinfo_uri": "http://<YOUR_DOMAIN>/auth/realms/<YOUR_REALM_ID>/protocol/openid-connect/userinfo",
"token_uri": "http://<YOUR_DOMAIN>/auth/realms/<YOUR_REALM_ID>/protocol/openid-connect/token",
"token_introspection_uri": "http://<YOUR_DOMAIN>/auth/realms/<YOUR_REALM_ID>/protocol/openid-connect/token/introspect"
}
}
Flask-oidc ожидает, что конфигурация будет в файле. Я сохранил мой в client_secret.json
, Вы можете настроить путь к файлу конфигурации в вашем superset_config.py
,
Расширить диспетчер безопасности
Во-первых, вы должны убедиться, что фляга перестает использовать флеш-openid, и вместо нее начинает работать фляга-oidc. Для этого вам нужно будет создать собственный менеджер безопасности, который настраивает flask-oidc в качестве поставщика аутентификации. Я реализовал свой менеджер безопасности следующим образом:
from flask_appbuilder.security.manager import AUTH_OID
from flask_appbuilder.security.sqla.manager import SecurityManager
from flask_oidc import OpenIDConnect
class OIDCSecurityManager(SecurityManager):
def __init__(self,appbuilder):
super(OIDCSecurityManager, self).__init__(appbuilder)
if self.auth_type == AUTH_OID:
self.oid = OpenIDConnect(self.appbuilder.get_app)
self.authoidview = AuthOIDCView
Чтобы включить OpenID в Superset, ранее вам нужно было установить тип аутентификации AUTH_OID. Мой менеджер безопасности по-прежнему выполняет все поведение суперкласса, но переопределяет атрибут oid с объектом OpenIDConnect. Кроме того, он заменяет стандартное представление проверки подлинности OpenID пользовательским. Я реализовал мой так:
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib import quote
class AuthOIDCView(AuthOIDView):
@expose('/login/', methods=['GET', 'POST'])
def login(self, flag=True):
sm = self.appbuilder.sm
oidc = sm.oid
@self.appbuilder.sm.oid.require_login
def handle_login():
user = sm.auth_user_oid(oidc.user_getfield('email'))
if user is None:
info = oidc.user_getinfo(['preferred_username', 'given_name', 'family_name', 'email'])
user = sm.add_user(info.get('preferred_username'), info.get('given_name'), info.get('family_name'), info.get('email'), sm.find_role('Gamma'))
login_user(user, remember=False)
return redirect(self.appbuilder.get_url_for_index)
return handle_login()
@expose('/logout/', methods=['GET', 'POST'])
def logout(self):
oidc = self.appbuilder.sm.oid
oidc.logout()
super(AuthOIDCView, self).logout()
redirect_url = request.url_root.strip('/') + self.appbuilder.get_url_for_login
return redirect(oidc.client_secrets.get('issuer') + '/protocol/openid-connect/logout?redirect_uri=' + quote(redirect_url))
Мое представление переопределяет поведение в конечных точках /login и /logout. При входе в систему запускается метод handle_login. Требуется, чтобы пользователь прошел проверку подлинности поставщиком OIDC. В нашем случае это означает, что пользователь сначала будет перенаправлен на Keycloak для входа.
При аутентификации пользователь перенаправляется обратно в Superset. Далее мы смотрим, узнаем ли мы пользователя. Если нет, мы создаем пользователя на основе его информации о пользователе OIDC. Наконец, мы регистрируем пользователя в Superset и перенаправляем его на целевую страницу.
При выходе из системы нам потребуется аннулировать эти файлы cookie:
- Суперсет сессия
- Токен OIDC
- Печенье от Keycloak
По умолчанию Superset позаботится только о первом. Расширенный метод выхода из системы учитывает все три момента.
Настроить суперсет
Наконец, нам нужно добавить некоторые параметры в наш superset_config.py
, Вот как я настроил мой:
'''
AUTHENTICATION
'''
AUTH_TYPE = AUTH_OID
OIDC_CLIENT_SECRETS = 'client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
У меня были проблемы с библиотекой OIDC, поэтому я настроил ее немного иначе -
В Keycloak я создал новый
client
с участием
standard flow
а также
confidential
доступ.
Я также добавил
roles
утверждение токена в картографе, чтобы я мог сопоставить «Роли клиента» с ролями надмножества.
Для Superset я монтирую пользовательские файлы конфигурации в свой контейнер [k8s в моем случае].
/app/pythonpath/custom_sso_security_manager.py
import logging
import os
import json
from superset.security import SupersetSecurityManager
logger = logging.getLogger('oauth_login')
class CustomSsoSecurityManager(SupersetSecurityManager):
def oauth_user_info(self, provider, response=None):
logging.debug("Oauth2 provider: {0}.".format(provider))
logging.debug("Oauth2 oauth_remotes provider: {0}.".format(self.appbuilder.sm.oauth_remotes[provider]))
if provider == 'keycloak':
# Get the user info using the access token
res = self.appbuilder.sm.oauth_remotes[provider].get(os.getenv('KEYCLOAK_BASE_URL') + '/userinfo')
logger.info(f"userinfo response:")
for attr, value in vars(res).items():
print(attr, '=', value)
if res.status_code != 200:
logger.error('Failed to obtain user info: %s', res._content)
return
#dict_str = res._content.decode("UTF-8")
me = json.loads(res._content)
logger.debug(" user_data: %s", me)
return {
'username' : me['preferred_username'],
'name' : me['name'],
'email' : me['email'],
'first_name': me['given_name'],
'last_name': me['family_name'],
'roles': me['roles'],
'is_active': True,
}
def auth_user_oauth(self, userinfo):
user = super(CustomSsoSecurityManager, self).auth_user_oauth(userinfo)
roles = [self.find_role(x) for x in userinfo['roles']]
roles = [x for x in roles if x is not None]
user.roles = roles
logger.debug(' Update <User: %s> role to %s', user.username, roles)
self.update_user(user) # update user roles
return user
И в
/app/pythonpath/superset_config.py
Я добавил несколько конфигов -
from flask_appbuilder.security.manager import AUTH_OAUTH, AUTH_REMOTE_USER
from custom_sso_security_manager import CustomSsoSecurityManager
CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager
oauthSecretPair = env('OAUTH_CLIENT_ID') + ':' + env('OAUTH_CLIENT_SECRET')
AUTH_TYPE = AUTH_OAUTH
OAUTH_PROVIDERS = [
{ 'name':'keycloak',
'token_key':'access_token', # Name of the token in the response of access_token_url
'icon':'fa-address-card', # Icon for the provider
'remote_app': {
'api_base_url': env('KEYCLOAK_BASE_URL', 'http://CHANGEME'),
'client_id':env('OAUTH_CLIENT_ID'), # Client Id (Identify Superset application)
'client_secret':env('OAUTH_CLIENT_SECRET'), # Secret for this Client Id (Identify Superset application)
'client_kwargs':{
'scope': 'profile' # Scope for the Authorization
},
'request_token_url':None,
'access_token_url': env('KEYCLOAK_BASE_URL', 'http://CHANGEME') + '/token',
'authorize_url': env('KEYCLOAK_BASE_URL', 'http://CHANGEME') + '/auth',
}
}
]
# Will allow user self registration, allowing to create Flask users from Authorized User
AUTH_USER_REGISTRATION = True
# The default user self registration role
AUTH_USER_REGISTRATION_ROLE = "Gamma"
# This will make sure the redirect_uri is properly computed, even with SSL offloading
ENABLE_PROXY_FIX = True
Эти конфигурации ожидают несколько параметров env:
KEYCLOAK_BASE_URL
OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET
Я пытался следовать советам, основанным на комментариях в этом посте, но даже в этом случае в процессе все еще были другие сомнения, и мне удалось решить проблему, и она отлично работает, я хотел бы поделиться кодом для решения проблемы. суперсет-keycloak. Этот подход использует докер для развертывания расширенного приложения.