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

Flask后端向React前端安全传输PDF文件问题求助

解决方案

一、修复send_from_directory返回null的问题

你的代码核心问题是send_from_directory缺少必填的directory和filename参数,加上以下调整即可解决:

  1. 补全参数并配置绝对路径
    用绝对路径配置PDF存储目录,避免因运行环境导致的路径错误,同时限制目录遍历攻击:
from flask import Flask, send_from_directory, request
import os

app = Flask(__name__)
# 配置PDF存储绝对路径,建议放在非静态目录下
app.config['PDF_FOLDER'] = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'private_pdfs')

@app.route('/get-pdf/<filename>')
def function_to_get_file(filename):
    try:
        # 校验请求文件是否在指定目录内,防止目录遍历
        target_path = os.path.abspath(os.path.join(app.config['PDF_FOLDER'], filename))
        if not target_path.startswith(app.config['PDF_FOLDER']):
            return "无权访问该文件", 403
        # as_attachment设为False,让浏览器在新标签页打开PDF
        return send_from_directory(
            app.config['PDF_FOLDER'], 
            filename, 
            as_attachment=False, 
            mimetype='application/pdf'
        )
    except Exception as e:
        return str(e), 500
  1. 排查常见坑点
  • 确认目录权限:确保Flask进程有读取PDF目录的权限
  • 文件名编码:如果文件名含中文/特殊字符,前后端统一用UTF-8编码,或直接使用英文文件名
  • 路径拼接:始终用os.path.join拼接路径,避免手动写斜杠导致的跨系统问题

二、实现半锁定文档访问(仅应用用户可访问相关文档)

因为无需登录,推荐两种适配场景的方案:

方案1:请求溯源验证(适合非敏感文档)

校验请求的Referer头,确保请求来自你的React前端:

@app.route('/get-pdf/<filename>')
def function_to_get_file(filename):
    # 验证请求来源为前端域名
    referer = request.headers.get('Referer')
    allowed_origins = ['http://localhost:3000', 'https://你的线上前端域名']
    if not referer or not any(referer.startswith(origin) for origin in allowed_origins):
        return "无权访问", 403
    
    # 后续文件返回逻辑同前
    try:
        target_path = os.path.abspath(os.path.join(app.config['PDF_FOLDER'], filename))
        if not target_path.startswith(app.config['PDF_FOLDER']):
            return "无权访问该文件", 403
        return send_from_directory(app.config['PDF_FOLDER'], filename, as_attachment=False, mimetype='application/pdf')
    except Exception as e:
        return str(e), 500

注意:Referer头可被伪造,适合对安全性要求不高的场景。

方案2:一次性临时令牌(适合敏感文档)

前端先获取对应PDF的临时令牌,再用令牌请求文件,实现严格的访问控制:

  1. 后端令牌生成接口
import uuid
from datetime import datetime, timedelta

# 生产环境建议用Redis存储令牌,避免内存泄漏
token_store = {}

@app.route('/get-pdf-token/<filename>')
def get_pdf_token(filename):
    # 这里可添加业务逻辑:验证用户是否有权限访问该PDF(比如关联之前的请求记录)
    token = str(uuid.uuid4())
    # 令牌有效期设为5分钟
    token_store[token] = {
        'filename': filename,
        'expire': datetime.now() + timedelta(minutes=5)
    }
    return {'token': token}
  1. 后端文件接口验证令牌
@app.route('/get-pdf')
def get_pdf_with_token():
    token = request.args.get('token')
    if not token or token not in token_store:
        return "无效令牌", 403
    
    token_data = token_store[token]
    # 检查令牌是否过期
    if datetime.now() > token_data['expire']:
        del token_store[token]
        return "令牌已过期", 403
    
    filename = token_data['filename']
    # 校验文件路径
    target_path = os.path.abspath(os.path.join(app.config['PDF_FOLDER'], filename))
    if not target_path.startswith(app.config['PDF_FOLDER']):
        return "无权访问该文件", 403
    
    # 一次性令牌,返回后删除
    del token_store[token]
    return send_from_directory(app.config['PDF_FOLDER'], filename, as_attachment=False, mimetype='application/pdf')
  1. 前端调用示例
// 溯源方案直接生成链接
<a href={`http://localhost:5000/get-pdf/${pdfFilename}`} target="_blank" rel="noopener noreferrer">查看PDF</a>

// 令牌方案先获取令牌再打开
const handleViewPdf = async (filename) => {
  const res = await fetch(`http://localhost:5000/get-pdf-token/${filename}`);
  const data = await res.json();
  window.open(`http://localhost:5000/get-pdf?token=${data.token}`, '_blank');
};

// 组件中使用
<button onClick={() => handleViewPdf('report.pdf')}>查看PDF</button>

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 13:47:43