【从 HTTP 到 HTTPS】Flask 多项目迁移到 Nginx 子路径完整实战

一、背景与痛点

1.1 原始部署方式(不安全)

我有三个 Flask 项目,分别运行在同一台阿里云服务器的不同端口上:

  • 项目 A:http://your-server-ip:5001/

  • 项目 B:http://your-server-ip:5003/

  • 项目 C:http://your-server-ip:5005/(含 /admin 后台)

用户直接通过 HTTP + IP + 端口 访问,存在以下问题:

  • 明文传输,密码、卡密等敏感信息易被窃听

  • 无域名,IP 地址变化后需要通知所有用户

  • 端口暴露,容易被扫描攻击

  • 浏览器不安全提示,影响用户体验

1.2 目标

  • 申请一个单域名 SSL 证书 (如 example.com),仅支持该域名,不支持泛域名

  • 让所有 Flask 项目通过 HTTPS + 域名 + 子路径 访问:

    • https://example.com/5001/ → 原 5001 端口项目

    • https://example.com/5003/ → 原 5003 端口项目

    • https://example.com/5005/ → 原 5005 端口项目(含 /admin

  • 保留原有 HTTP 访问方式不变(供旧客户端过渡,但最终要废弃)

1.3 技术挑战

  • Flask 默认生成的 URL(重定向、静态文件、表单 action)不包含子路径前缀

  • AJAX 请求路径硬编码,导致登录后跳转 404

  • 静态文件 404

  • 会话失效后 AJAX 请求不跳转登录页

  • Nginx 代理时前缀处理混乱

二、最终架构图

复制代码
客户端 HTTPS 请求
    ↓
Nginx (443端口,SSL终结)
    ├─ /5001/  → 代理到 127.0.0.1:5001 (保留前缀)
    ├─ /5003/  → 代理到 127.0.0.1:5003
    └─ /5005/  → 代理到 127.0.0.1:5005
            ↓
每个 Flask 应用使用蓝图设置 url_prefix='/5005'
            ↓
Flask 内部路由正常处理 /admin/login 等
            ↓
模板中使用 url_for 自动生成带前缀的链接

三、Nginx 配置(核心:proxy_pass 末尾无斜杠)

以 5005 端口项目为例,其他项目类似。

nginx

复制代码
server {
    listen 443 ssl;
    server_name example.com;   # 替换为你的域名
    ssl_certificate /path/to/your/cert.pem;
    ssl_certificate_key /path/to/your/cert.key;
    # ... 其他 SSL 参数(可参考常规配置)...

    # 静态文件直接由 Nginx 托管(可选)
    location /5005/static/ {
        alias /home/your_user/your_project/static/;   # 替换为实际路径
    }

    # 反向代理到 Flask
    location /5005/ {
        proxy_pass http://127.0.0.1:5005;   # 注意:末尾没有斜杠!
        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;
    }
}

# HTTP 重定向到 HTTPS
server {
    listen 80;
    server_name example.com;
    return 301 https://$server_name$request_uri;
}

关键点proxy_pass 末尾不加 /,这样 Nginx 会将完整的 /5005/admin/login 原样传给 Flask。如果加了 /,会去掉前缀导致路由 404。

四、Flask 代码改造(蓝图 + 统一前缀)

4.1 安装依赖(无额外库,Flask 自带蓝图)

4.2 修改主文件 main.py

python 复制代码
from flask import Flask, Blueprint, render_template, redirect, url_for, session, jsonify, request
from functools import wraps

app = Flask(__name__)
app.secret_key = 'your-secret-key-here'   # 替换为随机字符串

# 创建蓝图,设置 URL 前缀
bp = Blueprint('main', __name__, url_prefix='/5005')

# ------------------ 登录验证装饰器(支持 AJAX 401)------------------
def login_required(f):
    @wraps(f)
    def decorated_function(*args, **kwargs):
        if not session.get('logged_in'):
            # 判断是否为 AJAX 请求
            if request.headers.get('X-Requested-With') == 'XMLHttpRequest':
                return jsonify({'success': False, 'redirect': url_for('main.login_page')}), 401
            return redirect(url_for('main.login_page'))
        return f(*args, **kwargs)
    return decorated_function

# ------------------ 路由(原有 @app.route 全部换成 @bp.route)------------------
@bp.route('/admin/login', methods=['GET'])
def login_page():
    return render_template('login.html')

@bp.route('/admin/login', methods=['POST'])
def login():
    # 验证逻辑(示例)
    username = request.form.get('username')
    password = request.form.get('password')
    # ... 数据库验证 ...
    session['logged_in'] = True
    return redirect(url_for('main.admin_page'))   # 注意:必须加 main. 前缀

@bp.route('/admin')
@login_required
def admin_page():
    return render_template('admin.html')

# ... 其他路由(/admin/keys、/api/verify 等)

# 注册蓝图
app.register_blueprint(bp)

if __name__ == '__main__':
    app.run(host='127.0.0.1', port=5005)   # 监听本地,仅允许 Nginx 代理

改动说明

  • @app.route 全部替换为 @bp.route

  • 所有 redirect(url_for('xxx')) 改为 redirect(url_for('main.xxx'))

  • 添加 AJAX 友好的 login_required 装饰器

4.3 模板修改(admin.html

全局 AJAX 处理 401
javascript 复制代码
$.ajaxSetup({
    statusCode: {
        401: function(xhr) {
            var response = xhr.responseJSON;
            if (response && response.redirect) {
                window.location.href = response.redirect;
            } else {
                window.location.href = '/5005/admin/login';
            }
        }
    }
});
所有 AJAX 请求使用绝对路径(带 /5005
javascript 复制代码
// 删除卡密
function deleteKey(id) {
    $.post('/5005/admin/keys/' + id + '/delete', function(res) {
        if (res.success) loadKeys();
    });
}
链接和表单使用 url_for
html 复制代码
<a href="{{ url_for('main.change_password') }}">修改密码</a>
<form action="{{ url_for('main.login') }}" method="post">...</form>
静态文件使用 /5005/static/ 绝对路径或 CDN
html 复制代码
<link href="/5005/static/css/bootstrap.min.css" rel="stylesheet">

4.4 静态文件权限

bash 复制代码
sudo chmod -R 755 /home/your_user/your_project/static
sudo chown -R www-data:www-data /home/your_user/your_project/static

五、迁移步骤总结

  1. 申请 SSL 证书 (单域名,如 example.com

  2. 修改 Nginx 配置 :添加 location /5005/ 代理(无斜杠)和静态文件 location

  3. 修改 Flask 代码 :引入蓝图,设置 url_prefix,修改装饰器支持 AJAX 401

  4. 修改模板 :全局 AJAX 设置,将硬编码路径改为 /5005/xxxurl_for

  5. 重启服务sudo systemctl reload nginx,重启 Flask(使用 gunicorn)

  6. 测试 :访问 https://example.com/5005/admin/login,登录,操作增删改查

六、常见错误与解决

错误现象 原因 解决方案
访问 /5005/admin 返回 404 Nginx proxy_pass 末尾加了 / 去掉 /
登录后跳转到 /admin(缺少前缀) redirect(url_for('admin_page')) 未加蓝图名 改为 url_for('main.admin_page')
静态文件 404 alias 路径错误或权限不足 检查路径,执行 chmod
AJAX 请求返回 HTML 登录页 后端对 AJAX 返回 302,前端无法处理 后端返回 401 + JSON,前端全局处理
删除/编辑提示"记录不存在" 前端 URL 拼接错误(双斜杠或缺少 id) 使用硬编码 /5005/admin/keys/${id}/delete

七、性能优化建议

  • 静态文件由 Nginx 直接托管,不要经过 Flask

  • 使用 gunicorn 替代 flask run,设置 worker 数量为 CPU 核心数×2+1

  • 为 Nginx 开启 gzip 压缩(配置文件已包含)

  • 配置 proxy_bufferingproxy_cache 提升响应速度

八、结语

通过本方案,您的 Flask 应用从 不安全的 HTTP + IP 端口 平滑迁移到了 HTTPS + 域名子路径,既保证了通信安全,又保留了多项目共存的灵活性。整个改造过程仅需修改 Nginx 配置和少量 Flask 代码,无需重构业务逻辑。

下一步:可以进一步使用 Docker 容器化部署,或者接入 CI/CD 自动更新 SSL 证书。

如果本文对您有帮助,欢迎点赞、收藏、转发。如有问题,请在评论区留言。

相关推荐
lunzi_082614 小时前
【学习笔记】《Python编程 从入门到实践》第1章:Python环境搭建与Hello World(完整版)
笔记·python·学习
zhz521414 小时前
Spring Boot + 腾讯 Kona 实现 TLCP 8443 国密 HTTPS 排障实录(奇安信浏览器已通)
spring boot·后端·https·国密·gmssl·kona
花月C14 小时前
LangGraph 状态机与 ReAct Agent
python·agent·react·langgragh
烤代码的吐司君14 小时前
面向对象编程(OOP)在 Python 中的实现——类、继承与特殊方法
开发语言·python
小龙报14 小时前
【优选算法】双指针专项:1.移动零 2. 复写零 3.快乐数
java·c语言·数据结构·c++·python·算法·面试
IT策士14 小时前
Django 从 0 到 1 打造完整电商平台:我的订单列表与订单详情
后端·python·django
AI行业学习14 小时前
CC-Switch Windows + macOS 下载安装配置全流程
java·开发语言·人工智能·python
曾阿伦14 小时前
requests HTTP 库解析
网络·网络协议·http