在Apache SuperSet中使用KeyCloak(OpenID Connect)

时间:2019-01-02 17:02:44

标签: flask openid keycloak apache-superset flask-appbuilder

我从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。

第一个区别是合并合并拉取请求4565后,您将无法再执行以下操作:

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:我在“领域设置”>“键”>“活动”中获得了此键,然后在表格的“ RS256”行中获得了此键。
  • client_id:本地(我用于本地测试的客户端)
  • client_secret:我是在“客户端”>“本地”(来自表)>“凭据”>“秘密”

所有的url / uri值都从我之前设置的所有提到的第一篇文章中进行了调整。 <DOMAIN>是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 个答案:

答案 0 :(得分:2)

我最终自己弄清楚了。

我最终得到的解决方案没有使用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登录屏幕,成功登录后,我将被重定向回我的应用程序。