9

我想在我们的 Superset 环境中使用 keycloak 来验证我的用户。

Superset 使用的是 flask-openid,在 flask-security 中实现:

要启用不同于常规用户身份验证(数据库)的用户身份验证,您需要覆盖 superset_config.py 文件中的 AUTH_TYPE 参数。您还需要提供对您的 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 的请求,这让我认为我要么在某处丢失了配置设置,要么我使用了错误的设置。你能帮我弄清楚我应该使用哪些设置吗?

4

2 回答 2

17

2020 年 3 月 2 日更新

@sjmeyer 编写了更新指南,适用于 Superset 0.28.1 及更高版本。我自己没有尝试过,但感谢@nawazxy 确认此解决方案有效。


我设法解决了我自己的问题。主要问题是由于我对超集正在使用的 flask-openid 插件所做的错误假设造成的。这个插件实际上支持OpenID 2.x,但不支持 OpenID-Connect(这是 Keycloak 实现的版本)。

作为一种解决方法,我决定切换到flask-oidc插件。切换到新的身份验证提供程序实际上需要一些挖掘工作。要集成插件,我必须按照以下步骤操作:

为 keycloak 配置 flask-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.

扩展安全管理器

首先,您需要确保烧瓶停止使用 flask-openid 广告开始使用 flask-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

要在 Superset 中启用 OpenID,您之前必须将身份验证类型设置为 AUTH_OID。我的安全管理器仍然执行超类的所有行为,但是用 OpenIDConnect 对象覆盖了 oid 属性。此外,它将默认的 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 无效:

  1. 超集会议
  2. OIDC 令牌
  3. Keycloak设置的cookies

默认情况下,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'
于 2017-12-13T07:02:25.833 回答
1

我在使用 OIDC 库时遇到了一些问题,因此我对其进行了一些不同的配置 -

在 Keycloak 中,我创建了一个新client的 withstandard flowconfidentialaccess。
我还在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

这些配置需要一些环境参数 -

KEYCLOAK_BASE_URL
OAUTH_CLIENT_ID
OAUTH_CLIENT_SECRET
于 2021-09-11T04:53:14.150 回答