flask - 将 KeyCloak(OpenID Connect) 与 Apache SuperSet 结合使用

标签 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 而不是 OpenID Connect,而后者正是 KeyCloak 提供的。

第一个区别是pull request 4565被合并后,不能再做:

from flask_appbuilder.security.sqla.manager import SecurityManager

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

在上面提到的帖子中,海报展示了如何分别创建管理器和查看文件,但没有说把它放在哪里。我将管理器和 View 类放在同一个名为 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”行中得到了这个 key 。
  • client_id :本地(我用于本地测试的客户端)
  • client_secret :我在客户端> 本地(从表中)> 凭据> 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,因为显然没有别的,然后单击登录。当我单击登录时,我可以看到浏览器的地址栏中加载了一些内容,但没有任何 react 。我的理解是我应该被重定向到 KeyCloak 登录表单,然后在成功登录后返回到我的应用程序,但没有任何 react 。我在某处遗漏了什么吗?

    编辑

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

    最佳答案

    我最终自己弄清楚了。

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

    关于flask - 将 KeyCloak(OpenID Connect) 与 Apache SuperSet 结合使用,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/54010314/

    相关文章:

    google-app-engine - 带有 OpenID 的 App Engine remote_api

    Kubernetes Keycloak Gatekeeper 坏网关

    python - 如何让这个 Flask 应用程序更加 DRY(连接到许多 oauth API)?

    jwt - 为什么 id_token 通过带有片段标识符而不是查询字符串的 url 传递?

    python - Flask:如何在 Response 中设置状态码

    facebook - 我可以使用 Facebook 凭据让用户访问我的网站吗?

    javascript - OAuth 2.0 OpenID Connect 环回和 Keycloak

    keycloak - kcadm 添加新的协议(protocol)映射器

    python - Flask:NameError:未定义全局名称 'redirect'

    python - Flask:为什么 app.route() 装饰器应该总是在最外面?