You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Apache Superset与Jumpcloud OIDC集成OAuth错误求助

Apache Superset与Jumpcloud OIDC集成问题排查

问题背景

使用Helm部署Apache Superset,尝试通过OIDC协议与Jumpcloud集成以实现身份验证,过程中出现两类错误,请求协助排查。


两端配置信息

Jumpcloud端配置

login url - https://superset.dev/login
redirect url - https://superset.dev/oauth-authorized/jumpcloud

Superset端配置

from superset.security import SupersetSecurityManager
from superset.security.manager import SupersetSecurityManager
from superset.security.manager import SupersetSecurityManager
from flask_appbuilder.security.manager import AUTH_OAUTH
import json
import logging
from base64 import b64decode

AUTH_TYPE = AUTH_OAUTH
AUTH_USER_REGISTRATION = False
ENABLE_PROXY_FIX = True
PROXY_FIX_CONFIG = {"x_for": 1, "x_proto": 1, "x_host": 1, "x_port": 0, "x_prefix": 1}

OAUTH_PROVIDERS = [
    {
        'name': 'jumpcloud',
        'icon': 'fa-address-card',
        'remote_app': {
            'client_id': 'abc',
            'client_secret': 'xyz',
            'client_kwargs':
            {
                'scope': 'openid'               # Scope for the Authorization
            },
            'server_metadata_url': 'https://oauth.id.jumpcloud.com/.well-known/openid-configuration',
            'api_base_url':'https://oauth.id.jumpcloud.com/',
            'access_token_url':'https://oauth.id.jumpcloud.com/oauth2/token',
            'authorize_url':'https://oauth.id.jumpcloud.com/oauth2/auth',
            'jwks_uri':'https://oauth.id.jumpcloud.com/.well-known/jwks.json',
            'userinfo_uri': 'https://oauth.id.jumpcloud.com/userinfo'
        }
    }
]

class CustomSsoSecurityManager(SupersetSecurityManager):
    def oauth_user_info(self, provider, response=None):
        print(provider)
        if provider == "jumpcloud":
            id_token = self.appbuilder.sm.oauth_remotes[provider].token
            logging.debug(f"ID Token: {id_token}")
            user_groups = get_user_group(id_token)
            me = self.appbuilder.sm.oauth_remotes[provider].userinfo()
            role_map = {
                "admin": "Admin"
            }
            roles = [role_map[key] for key in user_groups if key in role_map]
            user_payload = {
                "name": me["preferred_username"],
                "email": me["email"],
                "id": me["email"],
                "username": me["email"].split("@")[0],
                "first_name": me["given_name"],
                "last_name": me["family_name"],
            }
            if len(roles) > 0:
                user_payload["role"] = roles[0]

            return user_payload

    def auth_user_oauth(self, userinfo):
        if "username" in userinfo:
            user = self.find_user(username=userinfo["username"])
        elif "email" in userinfo:
            user = self.find_user(email=userinfo["email"])
        else:
            user = False
            logging.error("User info does not have username or email {0}".format(userinfo))

        if not user and not self.auth_user_registration:
            logging.debug("user does not exist on the DB and not self user registration, go away")
            return None

        if not user:
            if userinfo.get("role"):
                default_role = self.find_role(userinfo["role"])
            else:
                aur = self.auth_user_registration_role
                default_role = self.find_role(aur)

            user = self.add_user(
                username=userinfo["username"],
                first_name=userinfo.get("first_name", ""),
                last_name=userinfo.get("last_name", ""),
                email=userinfo.get("email", ""),
                role=default_role,
            )
            logging.debug("Adding user with role={} ".format(default_role))
            if not user:
                logging.error("Error creating a new OAuth user %s" % userinfo["username"])
                return None
            else:
                logging.debug("Success!")

        logging.debug("final user before update ={}".format(user))
        self.update_user_auth_stat(user)
        return user

CUSTOM_SECURITY_MANAGER = CustomSsoSecurityManager

遇到的错误

错误1

DEBUG:urllib3.connectionpool:https://oauth.id.jumpcloud.com:443 "POST /oauth2/token HTTP/1.1" 200 None
ERROR:flask_appbuilder.security.views:Error returning OAuth user info: 'oauth_token'

错误2

[ERROR] Error handling request
Traceback (most recent call last):
  File "/usr/local/lib/python3.8/site-packages/gunicorn/workers/gthread.py", line 326, in handle_request
    resp.write_file(respiter)
  File "/usr/local/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 385, in write_file
    if not self.sendfile(respiter):
  File "/usr/local/lib/python3.8/site-packages/gunicorn/http/wsgi.py", line 375, in sendfile
    self.sock.sendfile(respiter.filelike, count=nbytes)
  File "/usr/local/lib/python3.8/socket.py", line 482, in sendfile
    return self._sendfile_use_sendfile(file, offset, count)
  File "/usr/local/lib/python3.8/socket.py", line 346, in _sendfile_use_sendfile
    self._check_sendfile_params(file, offset, count)
  File "/usr/local/lib/python3.8/socket.py", line 460, in _check_sendfile_params
    raise ValueError(
ValueError: count must be a positive integer (got 0)

排查与修复建议

  1. 解决oauth_token错误

    • 调整token获取方式:oauth_user_info方法中应使用传入的response参数获取token,替换id_token = self.appbuilder.sm.oauth_remotes[provider].token为id_token = response.get('id_token'),避免调用未初始化的token对象。
    • 补充get_user_group函数:当前代码未定义该函数,需实现解析id_token群组信息的逻辑,否则会触发运行时错误。
    • 扩展OAuth Scope:仅openid权限不足以获取完整用户信息,修改client_kwargs中的scope为openid profile email groups。
  2. 修复gunicorn响应错误

    • 该错误是OAuth认证失败导致响应体为空的连锁问题,解决第一个错误后大概率会自动消失。若仍存在,可在Helm配置中添加GUNICORN_CMD_ARGS: "--disable-sendfile"禁用sendfile功能。
  3. 优化OAuth配置

    • 已配置server_metadata_url,可删除重复的access_token_url、authorize_url、jwks_uri,让系统自动从元数据中读取,避免配置冲突。
    • 验证Jumpcloud端redirect url与Superset回调路径完全一致,包括协议、域名和路径。

内容的提问来源于stack exchange,提问作者surya

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.06 00:01:02