Superset 与 Keycloak 的集成在用户名验证后崩溃,出现错误 500

问题描述 投票:0回答:1

我终于成功让 Apache Superset 将用户验证路由到 Keycloak(弹出 keycloak 登录屏幕,正在使用、验证和使用 KC 用户)。在 Keycloak 中设置与 Superset 角色完全匹配的角色,并且在 Keycloak 客户端配置中正确定义 Superset URL。但是,用户登录后,会收到错误 500 页面。

KC 客户端配置已正确设置所有 SS URL 定义。KC Client URL Definitions

KC角色设置为与SS角色匹配:KC client roles

用户已创建并分配了匹配的 KC 角色:KC user roles details

从显示的字体/图形来看,错误 500 似乎是 Supeset 错误 500(与 nginx/kc 错误相反)。

从客户端检查网络流量,我使用 oidc_callback 引用调用超集 URL:

client network inspect

并且 docker 日志显示对“unknonwn 主机”的引用:

superset_app          | 10.30.61.186 - - [10/Aug/2023:14:09:06 +0000] "GET /superset/welcome/ HTTP/1.0" 302 201 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
superset_app          | 10.30.61.186 - - [10/Aug/2023:14:09:06 +0000] "GET /login/ HTTP/1.0" 302 1449 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/115.0.0.0 Safari/537.36"
superset_app          | 127.0.0.1 - - [10/Aug/2023:14:09:10 +0000] "GET /health HTTP/1.1" 200 2 "-" "curl/7.74.0"
superset_app          | (504, b'Unknown Host')
superset_app          | Traceback (most recent call last):
superset_app          |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1517, in full_dispatch_request
superset_app          |     rv = self.dispatch_request()
superset_app          |   File "/usr/local/lib/python3.8/site-packages/flask/app.py", line 1503, in dispatch_request
superset_app          |     return self.ensure_sync(self.view_functions[rule.endpoint])(**req.view_args)
superset_app          |   File "/usr/local/lib/python3.8/site-packages/flask_oidc/__init__.py", line 657, in _oidc_callback
superset_app          |     plainreturn, data = self._process_callback('destination')
superset_app          |   File "/usr/local/lib/python3.8/site-packages/flask_oidc/__init__.py", line 689, in _process_callback
superset_app          |     credentials = flow.step2_exchange(code)
superset_app          |   File "/usr/local/lib/python3.8/site-packages/oauth2client/_helpers.py", line 133, in positional_wrapper
superset_app          |     return wrapped(*args, **kwargs)
superset_app          |   File "/usr/local/lib/python3.8/site-packages/oauth2client/client.py", line 2053, in step2_exchange
superset_app          |     resp, content = transport.request(
superset_app          |   File "/usr/local/lib/python3.8/site-packages/oauth2client/transport.py", line 280, in request
superset_app          |     return http_callable(uri, method=method, body=body, headers=headers,
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/__init__.py", line 1724, in request
superset_app          |     (response, content) = self._request(
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/__init__.py", line 1444, in _request
superset_app          |     (response, content) = self._conn_request(conn, request_uri, method, body, headers)
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/__init__.py", line 1366, in _conn_request
superset_app          |     conn.connect()
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/__init__.py", line 1156, in connect
superset_app          |     sock.connect((self.host, self.port))
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/socks.py", line 504, in connect
superset_app          |     self.__negotiatehttp(destpair[0], destpair[1])
superset_app          |   File "/usr/local/lib/python3.8/site-packages/httplib2/socks.py", line 465, in __negotiatehttp
superset_app          |     raise HTTPError((statuscode, statusline[2]))
superset_app          | httplib2.socks.HTTPError: (504, b'Unknown Host')
superset_app          | 2023-08-10 14:09:32,111:ERROR:superset.views.base:(504, b'Unknown Host')

我的 superset_config.py 的授权处理部分是:

from security import OIDCSecurityManager
from flask_appbuilder.security.manager import AUTH_OID

logger.info('@keycloak config ..')
AUTH_TYPE = AUTH_OID
curr  =  os.path.abspath(os.getcwd())
OIDC_CLIENT_SECRETS =  curr + '/docker/pythonpath_dev/client_secret.json'
OIDC_ID_TOKEN_COOKIE_SECURE = False
OIDC_REQUIRE_VERIFIED_EMAIL = False
OIDC_ID_TOKEN_COOKIE_NAME = 'pergamino'
AUTH_USER_REGISTRATION = True
#AUTH_USER_REGISTRATION_ROLE = 'Gamma'
AUTH_USER_REGISTRATION_ROLE = 'Public'
CUSTOM_SECURITY_MANAGER = OIDCSecurityManager


我的 security.py 是:

from flask import redirect, request, abort
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
import json
from base64 import b64decode
import os

logger = logging.getLogger()

AFIP_SERVICE_ID = os.environ.get("AFIP_SERVICE_ID", "pergamino")

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():
            if not oidc.get_access_token():
                logger.warn('Not found access_token')
                return self.logout()

            roles = findAccessRoles(sm)
            logger.debug('findAccessRoles %s' % roles)

            if not roles or len(roles) == 0:
                logger.warn('Not found roles')
                return self.logout()

            info = oidc.user_getinfo(
                    ['preferred_username', 'given_name', 'family_name', 'email', 'sub'])
            email = oidc.user_getfield('email')
            if email is None:
                logger.debug('email no informado, se intenta crear un valor')
                username = oidc.user_getfield('username') or info.get('preferred_username')
                email = username + "@afip.gob.ar"

            user = sm.auth_user_oid(email)

            if user is None:
                logger.debug('crear usuario %s' % email)
                user = sm.add_user(info.get('preferred_username'), info.get(
                    'given_name'), info.get('family_name'), email, roles[0])

                if len(roles) > 1:
                    user.roles = roles
                    sm.update_user(user)
            elif len(roles) != len(user.roles) or not all(e in roles for e in user.roles):
                logger.debug('actualizar usuario %s' % email)
                user.roles = roles
                sm.update_user(user)

            logger.debug('user.roles %s' % user.roles)

            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)

def findAccessRoles(sm):
    try:
        pre, tkn, post = sm.oid.get_access_token().split('.')
        # logger.debug("get_access_token split . {},{},{}".format(pre, tkn, post))
        if not tkn:
            return None

        idp_token_decode = decode_base64(tkn)
        logger.debug('idp_token_decode %s' % idp_token_decode)
        access_token = json.loads(idp_token_decode)
        logger.debug('access_token in key resource_access.%s.roles' % AFIP_SERVICE_ID)
        rolesAccess = access_token['resource_access'][AFIP_SERVICE_ID]['roles']

    except Exception as e:
        logger.error('Failed to parser access_token: '+ str(e))
        return None

    logger.debug('rolesAccess %s' % rolesAccess)
    roles = None

    if rolesAccess or len(rolesAccess) > 0:
        roles = []
        for r in rolesAccess:
            find = sm.find_role(r.capitalize())
            if find:
                roles.append(find)
    return roles

def decode_base64(data):
    '''
    FIX -> OIDC + Python3 + Incorrect padding during base64decode
    '''
    missing_padding = 4-len(data) % 4
    if missing_padding:
        data = f"{data}{'=' * missing_padding:}"
    return b64decode(data)
authentication flask keycloak apache-superset
1个回答
0
投票

我也面临同样的500内部服务器错误,你解决了吗?

© www.soinside.com 2019 - 2024. All rights reserved.