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

JupyterHub与Django OAuth2+DockerSpawner连接失败求助排查

问题:Django OAuth2 + DockerSpawner JupyterHub 认证失败及通信问题

环境配置

依赖版本

  • Django==4.2.13
  • django-cors-headers==4.4.0
  • django-oauth-toolkit==2.4.0

Django 设置

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'oauth2_provider',
    'corsheaders',
    'users.apps.UsersConfig',
]

MIDDLEWARE = [
    # ...
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'oauth2_provider.middleware.OAuth2TokenMiddleware',
    'corsheaders.middleware.CorsMiddleware',
]

CORS_ORIGIN_ALLOW_ALL = True
LOGIN_URL = '/admin/login/'
AUTHENTICATION_BACKENDS = (
    'oauth2_provider.backends.OAuth2Backend',
    'django.contrib.auth.backends.ModelBackend'
)

OAUTH2_PROVIDER = {
    'OIDC_ENABLED': True,
    'PKCE_REQUIRED': False,
    'SCOPES': {
        'openid': 'OpenID scope',
        'read': 'Read scope', 
        'write': 'Write scope', 
        'groups': 'Access to your groups'
    }
}

JupyterHub 配置

c = get_config()

c.JupyterHub.spawner_class = "dockerspawner.DockerSpawner"
c.DockerSpawner.image = 'jupyter/base-notebook:latest'
c.DockerSpawner.use_internal_ip = True
c.DockerSpawner.network_name = "jupyterhub-network"
c.DockerSpawner.notebook_dir = "/home/jovyan/work"
c.DockerSpawner.volumes = {"jupyterhub-user-{username}": "/home/jovyan/work"}
c.DockerSpawner.remove = True
c.DockerSpawner.debug = True
c.JupyterHub.hub_ip = "jupyterhub"
c.JupyterHub.hub_port = 8080
c.Authenticator.admin_users = ["admin"]
c.JupyterHub.authenticator_class = "oauthenticator.generic.GenericOAuthenticator"

# OAuth2 application info
c.GenericOAuthenticator.client_id = "Wg6aLxkQxt8D74FJ4U6vfOe781JeLNf1fUeeMKFJ"
c.GenericOAuthenticator.client_secret = "Xu4ySuo4N06M0mYcNeElcuWIstGCll8ZKX87HAMhnJSkDnJB91tx5fKL5AMBbfhcQu6jA5p9TuNGy4k3bAsxoPGeBPl3JLD7dJuC95eBc1OGwxCQ4ZpRuM1wB2oGqVJ4"
c.GenericOAuthenticator.authorize_url = "http://localhost:6678/o/authorize/"
c.GenericOAuthenticator.token_url = "http://localhost:6678/o/token/"
c.GenericOAuthenticator.userdata_url = "http://localhost:6678/o/userinfo/"
c.OAuthenticator.oauth_callback_url = "http://localhost:8000/hub/oauth_callback"
c.GenericOAuthenticator.scope = ["read", "openid"]
c.GenericOAuthenticator.username_claim = "openid"

# Authorization
c.OAuthenticator.allow_all = True
c.GenericOAuthenticator.admin_users = {"admin"}
c.GenericOAuthenticator.admin_groups = {"administrator"}

Docker Compose 配置

version: "3"

services:
  jupyterhub:
    build:
      context: .
      dockerfile: dockerfile.jupyterhub
      args:
        JUPYTERHUB_VERSION: latest
    restart: always
    container_name: jupyterhub
    networks:
      - jupyterhub-network
    volumes:
      - ./jupyterhub/config:/srv/jupyterhub
      - ./jupyterhub/data:/data
      - /var/run/docker.sock:/var/run/docker.sock
    ports:
      - "8000:8000"
    depends_on:
      - django

  django:
    build:
      context: .
      dockerfile: dockerfile
    restart: always
    container_name: django
    networks:
      - jupyterhub-network
    volumes:
      - ./:/app
    ports:
      - "6678:6678"
    command: "python manage.py runserver 0.0.0.0:6678"

networks:
  jupyterhub-network:
    name: jupyterhub-network

遇到的问题

JupyterHub 认证通信失败

JupyterHub 日志报错:

jupyterhub  | [E 2024-07-04 10:14:25.433 JupyterHub oauth2:683] Error fetching 599 POST http://localhost:6678/o/token/: HTTP 599: Failed to connect to localhost port 6678 after 0 ms: Connection refused
jupyterhub  | [E 2024-07-04 10:14:25.434 JupyterHub web:1875] Uncaught exception GET /hub/oauth_callback?code=yprl0rDglVnb1kJcIKAOzxy9Y5ty47&state=eyJzdGF0ZV9pZCI6ICJmYTYxNDE1ZTE4OWY0OGRlYTFlYzI3MDI5YWFkNTVlMCJ9 (::ffff:172.18.0.1)
jupyterhub  |     HTTPServerRequest(protocol='http', host='localhost:8000', method='GET', uri='/hub/oauth_callback?code=yprl0rDglVnb1kJcIKAOzxy9Y5ty47&state=eyJzdGF0ZV9pZCI6ICJmYTYxNDE1ZTE4OWY0OGRlYTFlYzI3MDI5YWFkNTVlMCJ9', version='HTTP/1.1', remote_ip='::ffff:172.18.0.1')
jupyterhub  |     Traceback (most recent call last):
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/tornado/web.py", line 1790, in _execute
jupyterhub  |         result = await result
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 210, in get
jupyterhub  |         user = await self.login_user()
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/jupyterhub/handlers/base.py", line 964, in login_user
jupyterhub  |         authenticated = await self.authenticate(data)
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/jupyterhub/auth.py", line 661, in get_authenticated_user
jupyterhub  |         authenticated = await maybe_future(self.authenticate(handler, data))
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 1061, in authenticate
jupyterhub  |         token_info = await self.get_token_info(handler, access_token_params)
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 906, in get_token_info
jupyterhub  |         token_info = await self.httpfetch(
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 718, in httpfetch
jupyterhub  |         return await self.fetch(
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 684, in fetch
jupyterhub  |         raise e
jupyterhub  |       File "/usr/local/lib/python3.10/dist-packages/oauthenticator/oauth2.py", line 663, in fetch
jupyterhub  |         resp = await self.http_client.fetch(req, **kwargs)
jupyterhub  |     tornado.curl_httpclient.CurlError: HTTP 599: Failed to connect to localhost port 6678 after 0 ms: Connection refused

Userinfo 接口 Token 无效

Postman 完成认证后访问 /o/userinfo/ 返回:

{"error": "invalid_token", "error_description": "The access token provided is expired, revoked, malformed, or invalid for other reasons."}

解决方案

1. 修复容器间通信问题

容器内的localhost指向自身而非宿主机,同一 Docker 网络下需用服务名访问。修改 JupyterHub 配置中的 OAuth 地址:

c.GenericOAuthenticator.authorize_url = "http://django:6678/o/authorize/"
c.GenericOAuthenticator.token_url = "http://django:6678/o/token/"
c.GenericOAuthenticator.userdata_url = "http://django:6678/o/userinfo/"

同时更新 Django 的ALLOWED_HOSTS,确保包含容器服务名:

# Django settings.py
ALLOWED_HOSTS = ['localhost', 'django', '127.0.0.1']

2. 修复 Userinfo 接口 Token 无效问题

确认 Token 权限范围

确保授权流程中请求的 scope 包含openid(JupyterHub 配置已设置,但需验证 Django OAuth 应用是否允许该 scope)。

配置 Django OIDC 用户信息端点

在 Djangosettings.py的OAUTH2_PROVIDER中添加端点配置,可选自定义序列化器映射用户信息:

OAUTH2_PROVIDER = {
    # ... 现有配置
    'OIDC_USERINFO_ENDPOINT': '/o/userinfo/',
    'USERINFO_SERIALIZER': 'users.serializers.UserInfoSerializer',
}

自定义序列化器示例:

# users/serializers.py
from oauth2_provider.serializers import UserInfoSerializer

class UserInfoSerializer(UserInfoSerializer):
    def get_user_info(self, user):
        return {
            'username': user.username,
            'email': user.email,
            'openid': user.username, # 匹配 JupyterHub 的 username_claim
        }

验证 Token 传递方式

请求/o/userinfo/时,需在请求头中正确携带 Token:

Authorization: Bearer <你的access_token>

3. 额外检查项

  • 查看 Django 容器日志,确认服务正常启动:docker logs django
  • 测试 JupyterHub 容器与 Django 容器的连通性:docker exec jupyterhub ping django
  • 检查 Django admin 中 OAuth2 应用的redirect_uris是否包含http://localhost:8000/hub/oauth_callback
  • 确保 JupyterHub 的oauth_callback_url与 Django 应用的redirect_uris完全一致(协议、域名、端口、路径均需匹配)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.21 17:39:51