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

为Jupyter添加Flask前置认证层以实现远程访问的技术咨询

需求与现有配置

我希望在Jupyter前端添加Flask认证层,实现从任意地点安全访问Jupyter。Flask与Jupyter部署在同一服务器上,用户通过Flask认证后,请求需转发至Jupyter服务。以下是我当前的各项配置,寻求完善方案以确保认证后请求能正常转发至Jupyter。

Jupyter Server配置

c.ServerApp.ip = '127.0.0.1'
c.ServerApp.port = 8888
c.ServerApp.open_browser = False
c.ServerApp.token = ''  # 禁用token认证
c.ServerApp.password = ''  # 禁用密码认证
c.ServerApp.disable_check_xsrf = True  # 禁用XSRF检查
c.ServerApp.trust_xheaders = True  # 允许反向代理头
c.ServerApp.allow_remote_access = True
c.ServerApp.allow_origin = '*'

Nginx配置

server {
    listen 443 ssl;
    server_name jupyter.mywebsite.com;

    ssl_certificate /etc/letsencrypt/live/jupyter.mywebsite.com/fullchain.pem; # Certbot
    ssl_certificate_key /etc/letsencrypt/live/jupyter.mywebsite.com/privkey.pem; # Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # Certbot SSL选项
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://127.0.0.1:7000/;  # Flask服务
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # Jupyter WebSocket支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

server {
    listen 80;
    server_name jupyter.mywebsite.com mywebsite.com www.mywebsite.com;

    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl;
    server_name mywebsite.com www.mywebsite.com;

    ssl_certificate /etc/letsencrypt/live/mywebsite.com/fullchain.pem; # Certbot
    ssl_certificate_key /etc/letsencrypt/live/mywebsite.com/privkey.pem; # Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # Certbot SSL选项
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem;

    location / {
        proxy_pass http://127.0.0.1:7000;  # Flask应用
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;

        # WebSocket支持
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_read_timeout 3600s;
        proxy_send_timeout 3600s;
    }
}

Flask配置代码

# 代理Jupyter请求
@app.after_request
def adjust_csp_for_jupyter(response):
    if "/jupyter/" in request.path:
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; script-src 'self' https://cdn.jsdelivr.net 'unsafe-eval'; "
            "style-src 'self' https://cdnjs.cloudflare.com; img-src 'self' data:;"
        )
    return response


JUPYTER_BASE_URL = "http://127.0.0.1:8888"  # 内部Jupyter地址

@app.route('/jupyter/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@login_required
def proxy_to_jupyter(path):
    jupyter_url = f"{JUPYTER_BASE_URL}/{path}"
    try:
        response = requests.request(
            method=request.method,
            url=jupyter_url,
            headers={key: value for key, value in request.headers.items() if key.lower() not in ['host', 'cookie']},
            data=request.get_data(),
            cookies=request.cookies,
            allow_redirects=False,  # 手动处理重定向
        )
        excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(name, value) for name, value in response.headers.items() if name.lower() not in excluded_headers]
        return Response(response.content, response.status_code, headers)
    except requests.RequestException as e:
        abort(502, f"连接Jupyter失败: {str(e)}")

@app.route('/api/kernels/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@login_required
def proxy_websocket_to_jupyter(path):
    # WebSocket代理代码(使用flask-sock或类似库)
    pass
完善方案指导

1. 实现WebSocket代理功能

Jupyter依赖WebSocket与内核通信,当前Flask中的WebSocket路由为空,需用flask-sock库实现代理:

  • 安装依赖:pip install flask-sock
  • 修改Flask代码:
from flask_sock import Sock

# 初始化Sock
sock = Sock(app)

@sock.route('/api/kernels/<path:path>')
@login_required
def proxy_websocket(path):
    import websocket
    try:
        # 连接到Jupyter的WebSocket服务
        ws = websocket.create_connection(f"{JUPYTER_BASE_URL.replace('http', 'ws')}/{path}")
        # 转发客户端与Jupyter之间的消息
        while True:
            # 接收客户端消息
            client_msg = sock.receive()
            if client_msg is None:
                break
            ws.send(client_msg)
            # 接收Jupyter返回的消息
            jupyter_msg = ws.recv()
            sock.send(jupyter_msg)
    except Exception as e:
        abort(502, f"WebSocket连接失败: {str(e)}")
    finally:
        if 'ws' in locals():
            ws.close()

2. 优化Jupyter Server安全性配置

当前部分配置存在安全风险,调整如下:

c.ServerApp.ip = '127.0.0.1'
c.ServerApp.port = 8888
c.ServerApp.open_browser = False
c.ServerApp.token = ''
c.ServerApp.password = ''
# 不要禁用XSRF检查,而是让Jupyter信任代理转发的头
c.ServerApp.disable_check_xsrf = False
c.ServerApp.trust_xheaders = True
# 限制允许的来源为你的实际域名,而非通配符
c.ServerApp.allow_origin = 'https://jupyter.mywebsite.com https://mywebsite.com'
# 仅允许来自Flask的请求(127.0.0.1)
c.ServerApp.allow_remote_access = False
c.ServerApp.allow_ip = ['127.0.0.1']

3. 调整Flask代理的头与重定向处理

  • Host头转发:将Host设置为Jupyter的内部地址,避免域名不匹配
  • Cookie处理:过滤掉Flask的会话Cookie,只转发Jupyter相关的Cookie
  • 重定向处理:手动处理Jupyter的重定向,将路径替换为Flask的代理前缀

修改proxy_to_jupyter函数:

@app.route('/jupyter/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE'])
@login_required
def proxy_to_jupyter(path):
    jupyter_url = f"{JUPYTER_BASE_URL}/{path}"
    # 构建转发头:保留原请求头,替换Host为Jupyter的内部地址
    forward_headers = {
        key: value for key, value in request.headers.items()
        if key.lower() not in ['host', 'cookie']
    }
    forward_headers['Host'] = '127.0.0.1:8888'
    
    # 过滤Cookie:只保留Jupyter相关的(如果有的话)
    jupyter_cookies = {}
    for name, value in request.cookies.items():
        if name.startswith('_jupyter_'):
            jupyter_cookies[name] = value
    
    try:
        response = requests.request(
            method=request.method,
            url=jupyter_url,
            headers=forward_headers,
            data=request.get_data(),
            cookies=jupyter_cookies,
            allow_redirects=False,
        )
        
        # 处理重定向:将Jupyter的重定向路径替换为Flask的代理前缀
        if response.status_code in [301, 302, 303, 307, 308]:
            redirect_url = response.headers['Location']
            if redirect_url.startswith(JUPYTER_BASE_URL):
                redirect_url = redirect_url.replace(JUPYTER_BASE_URL, '/jupyter')
                response.headers['Location'] = redirect_url
        
        excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection']
        headers = [(name, value) for name, value in response.headers.items() if name.lower() not in excluded_headers]
        return Response(response.content, response.status_code, headers)
    except requests.RequestException as e:
        abort(502, f"连接Jupyter失败: {str(e)}")

4. 优化Nginx配置

  • 合并重复的配置块,减少冗余
  • 确保WebSocket代理的Connection头设置正确(使用$connection_upgrade变量)

修改Nginx配置:

# 在http块中添加以下变量定义
map $http_upgrade $connection_upgrade {
    default upgrade;
    '' close;
}

# 然后在所有location块中修改WebSocket设置:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection $connection_upgrade;
proxy_read_timeout 3600s;
proxy_send_timeout 3600s;

5. 完善Content-Security-Policy(CSP)头

Jupyter需要更多的资源权限,调整CSP规则以兼容Jupyter的功能:

@app.after_request
def adjust_csp_for_jupyter(response):
    if "/jupyter/" in request.path:
        response.headers["Content-Security-Policy"] = (
            "default-src 'self'; "
            "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net; "
            "style-src 'self' 'unsafe-inline' https://cdnjs.cloudflare.com; "
            "img-src 'self' data: blob:; "
            "connect-src 'self' ws://127.0.0.1:8888 wss://jupyter.mywebsite.com; "
            "frame-src 'self'; "
            "font-src 'self' https://cdnjs.cloudflare.com;"
        )
    return response

6. 强化Flask认证的安全性

  • 确保Flask的会话Cookie启用secure、HttpOnly和SameSite属性:
app.config.update(
    SESSION_COOKIE_SECURE=True,
    SESSION_COOKIE_HTTPONLY=True,
    SESSION_COOKIE_SAMESITE='Lax'
)
  • 仅使用HTTPS传输认证会话,避免明文泄露风险

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 19:04:58