将 KeyCloak(OpenID Connect) 与 Apache SuperSet 一起使用

Posted

技术标签:

【中文标题】将 KeyCloak(OpenID Connect) 与 Apache SuperSet 一起使用【英文标题】:Using KeyCloak(OpenID Connect) with Apache SuperSet 【发布时间】:2019-05-29 08:03:29 【问题描述】:

我从Using OpenID/Keycloak with Superset 开始,并按照说明做了所有事情。但是,这是一个旧帖子,并非一切正常。我还尝试通过将其安装为 FAB 插件来实现自定义安全管理器,以便在我的应用程序中实现它,而无需编辑现有的超集代码。

我正在运行 KeyCloak 4.8.1.Final 和 Apache SuperSet v 0.28.1

正如博文中所解释的,SuperSet 不能很好地与开箱即用的 KeyCloak 配合使用,因为它使用 OpenID 2.0 而不是 KeyCloak 提供的 OpenID Connect。

第一个区别是pull request4565合并后,就不能再做:

from flask_appbuilder.security.sqla.manager import SecurityManager

相反,您现在必须使用:(根据 UPDATING.md 文件)

from superset.security import SupersetSecurityManager

在上面提到的帖子中,海报展示了如何分别创建管理器和查看文件,但没有说明放在哪里。我将管理器类和视图类放在同一个文件中,命名为manager.py,并将它放在 FAB 附加结构中。

from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
import logging

class OIDCSecurityManager(SupersetSecurityManager):
    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

CUSTOM_SECURITY_MANAGER = OIDCSecurityManager

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))

我在这个文件中设置了CUSTOM_SECURITY_MANAGER 变量,而不是superset_config.py。这是因为它在那里时不起作用,它没有加载自定义安全管理器。阅读Decorator for SecurityManager in flask appbuilder for superest 后,我将变量移到了那里。

我的client_secret.json 文件如下所示:


    "web": 
        "realm_public_key": "<PUBLIC_KEY>",
        "issuer": "https://<DOMAIN>/auth/realms/demo",
        "auth_uri": "https://<DOMAIN>/auth/realms/demo/protocol/openid-connect/auth",
        "client_id": "local",
        "client_secret": "<CLIENT_SECRET>",
        "redirect_urls": [
            "http://localhost:8001/*"
        ],
        "userinfo_uri": "https://<DOMAIN>/auth/realms/demo/protocol/openid-connect/userinfo",
        "token_uri": "https://<DOMAIN>/auth/realms/demo/protocol/openid-connect/token",
        "token_introspection_uri": "https://<DOMAIN>/auth/realms/demo/protocol/openid-connect/token/introspect"
    

realm_public_key:我在 Realm Settings > Keys > Active 然后在表格中的“RS256”行中获得了这个密钥。 client_id:本地(我用于本地测试的客户端) client_secret:我在 Clients > local (from the table) > Credentials > Secret 得到这个

所有的 url/uri 值都是从我用来设置它的第一个提到的帖子中调整的。 &lt;DOMAIN&gt; 是 AWS CloudFront 的默认域,因为我在 EC2 上运行 KeyCloak,不想费心设置自定义 HTTPS 域来简单地启动和运行。

最后,我的superset_config.py 文件的一部分看起来像这样:

ADDON_MANAGERS = ['fab_addon_keycloak.manager.OIDCSecurityManager']
AUTH_TYPE = AUTH_OID
OIDC_CLIENT_SECRETS = '/usr/local/lib/python3.6/site-packages/fab_addon_keycloak/fab_addon_keycloak/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
OPENID_PROVIDERS = [
    'name': 'KeyCloak',
    'url': 'https://<DOMAIN>/auth/realms/demo/account'
]

在原始帖子中,没有提到OPENID_PROVIDERS 环境变量,所以我不确定在此处为 URL 输入什么。我放了那个,因为这是您在 KeyCloak 上登录客户端控制台的 URL。

当我运行 SuperSet 时,我没有收到任何错误。我可以看到自定义安全管理器已加载。当我导航到登录屏幕时,我必须选择我的提供商,我没有得到登录表单。我选择 KeyCloak,因为显然没有其他内容,然后单击 Login。当我单击登录时,我可以看到浏览器的地址栏中加载了一些内容,但没有任何反应。据我了解,我应该被重定向到 KeyCloak 登录表单,然后在成功登录后返回我的应用程序,但没有任何反应。我在某处遗漏了什么吗?

编辑

因此,经过更多挖掘,我的自定义视图类似乎已加载,但是该类中的方法不会覆盖默认行为。不知道为什么会发生这种情况或如何解决它。

【问题讨论】:

【参考方案1】:

我最终自己弄清楚了。

我最终得到的解决方案没有使用 FAB 插件,但您也不必编辑现有代码/文件。

我已将 manager.py 文件重命名为 security.py,现在看起来像这样:

from flask import redirect, request
from flask_appbuilder.security.manager import AUTH_OID
from superset.security import SupersetSecurityManager
from flask_oidc import OpenIDConnect
from flask_appbuilder.security.views import AuthOIDView
from flask_login import login_user
from urllib.parse import quote
from flask_appbuilder.views import ModelView, SimpleFormView, expose
import logging

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))

class OIDCSecurityManager(SupersetSecurityManager):
    authoidview = AuthOIDCView
    def __init__(self,appbuilder):
        super(OIDCSecurityManager, self).__init__(appbuilder)
        if self.auth_type == AUTH_OID:
            self.oid = OpenIDConnect(self.appbuilder.get_app)

我将 security.py 文件放在我的 superset_config_py 文件旁边。

JSON 配置文件保持不变。

然后我更改了 superset_config.py 文件以包含以下几行:

from security import OIDCSecurityManager
AUTH_TYPE = AUTH_OID
OIDC_CLIENT_SECRETS = <path_to_configuration_file>
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
AUTH_USER_REGISTRATION = True
AUTH_USER_REGISTRATION_ROLE = 'Gamma'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager

就是这样。

现在,当我导航到我的网站时,它会自动转到 KeyCloak 登录屏幕,并在成功登录后重定向回我的应用程序。

【讨论】:

超集文件夹中已经存在一个名为 security.py 的文件。你编辑过同一个文件吗? 嗨@DeepaMG,不,我没有编辑那个文件。我的主目录“/superset/”中有一个文件夹,其中包含我的 superset_config.py 文件。我将 security.py 和 JSON 文件放在 superset_config.py 文件旁边。如果您的 superset_config.py 文件位于已包含 security.py 文件的 superset 包目录中,您可以将其命名为其他名称(例如 oidc_security.py)并相应地更改您的 superset_config.py 文件中的引用。 是的,我搞定了。 @sj-meyer 我现在得到的是 1. 在点击超集的 URL 时,它会将我重定向到 keycloak。 2. 输入凭据后,我可以看到超集。问题是我需要在 keycloak 和 superset 中拥有相同的用户凭据。我不能只在 keycloak 中有凭据吗? 我不确定,我不这么认为。我已经设置好了,如果用户登录 KeyCloak 但在超集上不存在,则会自动创建用户。在 KeyCloak 上创建新用户时也会发生什么,它将检查超集,如果有一个用户具有相同的电子邮件地址(我认为是用户名),它会将您登录到该现有用户。在我看来,用户是在超集中创建的这一事实是一件好事。它允许您利用超集的内置角色来管理权限。 但是我将超集 iframe 到我的另一个应用程序中,该应用程序已经与缺少用户的 keycloak 集成。我不想在超集中再次创建相同的凭据。

以上是关于将 KeyCloak(OpenID Connect) 与 Apache SuperSet 一起使用的主要内容,如果未能解决你的问题,请参考以下文章

Keycloak, openId-connect userInfo

使用 jumbojett/OpenID-Connect-PHP 库的 KeyCloak 身份验证流程

keycloak 错误 http://localhost:8080/auth/realms/claim-dev/protocol/openid-connect/token

Keycloak 及其不同的适配器是不是实现了 Openid Connect Backchannel 注销规范

在 Keycloak 中使用 OpenID Connect 的两个用例有啥区别? (客户端与应用程序)

移动应用程序 + REST 后端中的 OpenID Connect 身份验证流程(使用 KeyCloak)