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
相关产品推荐
相关产品推荐

