【问题标题】:How to provide temporary download url in Flask?如何在 Flask 中提供临时下载 url?
【发布时间】:2015-09-04 05:01:10
【问题描述】:

目前,我的index.html 文件包含

<a href="static/file.ext">Download</a>

我想更改此设置,以使下载 url 仅在特定时间内有效。例如,我将如何将其更改为

<a href="get_file?file=file.ext&token=TEMPORARYTOKEN">Download</a>

在我的 Flask 文件中,我可以拥有

@app.route('/get_file')
def get_file():
    filename = request.args.get('file')
    token = request.args.get('token')
    if token is valid: # what can be done here
        return send_from_directory('static', filename)

如何生成和处理令牌?还是我完全错了?

【问题讨论】:

  • 虽然我认为您的问题是正确的,但我想讨论与此相关的一件事。仅仅因为这个限制,我们将强制框架(django/flask/RoR)将文件流回,而如果有一个解决方案可以让 nginx/apache 为它提供服务,我会喜欢它。
  • 有没有更好的方法来为使用 nginx 或 apache 的下载提供临时身份验证?它将如何与程序的其余部分进行通信?
  • 你用的是什么框架?
  • 框架是指Flask 之类的东西,还是其他意思?目前,我只是使用Flask的内置run()方法在本地运行服务器,但对于生产我可能会部署到第三方服务器,如Heroku。
  • @darkryder send_from_directorysend_file 来自 flask 支持 X-Sendfile 标头,它将文件的实际发送委托给 Web 服务器(通常是带有 flask 的 nginx)。

标签: python authentication download flask session-timeout


【解决方案1】:

最好的方法是使用itsdangerous 包。您可以生成一个在任何时间都可以使用的 URL。此外,您可以对 URL 中的任何信息进行秘密编码。这样做的好处是不需要将时间戳处理或存储到数据库中

生成一个持续 30 分钟的 URL 并在令牌中编码用户 ID

from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

s = Serializer('WEBSITE_SECRET_KEY', 60*30) # 60 secs by 30 mins
token = s.dumps({'user_id': currentuser.id}).decode('utf-8') # encode user id 

使用以下内容生成你想要的网址

url_for('get_file', token=token)

验证 URL

@app.route('/get_file/<token>')
def get_file(token):
    s = Serializer('WEBSITE_SECRET_KEY')
    try:
        user_id = s.loads(token)['user_id']
    except:
        return None
    user = User.query.get(user_id)

    if not user:
        flash('This is an invalid or expired URL, please generate a new one!', 'warning')
        return redirect(url_for('another_route'))

    return send_from_directory('static', filename, as_attachment=True)

【讨论】:

  • 像魅力一样工作!
【解决方案2】:

有几种方法可以做到这一点。

  1. 为您的令牌生成一个 UUID 并将其与所需的到期日期时间一起存储在数据库表中。然后,当有人使用令牌调用 URL 时,您只需对照数据库检查它的有效性和过期时间。

  2. 如果您不想使用数据库来存储令牌,您可以使用 GPG 加密包含到期日期时间的字符串,并将生成的加密字符串用作您的令牌。这意味着您的令牌将比 UUID 长很多,但您可以避免使用 db。

我建议使用 UUID 和 db 表。

【讨论】:

  • 而不是用 GPG 重新发明轮子,对于选项 2,应该使用 itsdangerous 包,它已经是一个烧瓶依赖项,并且有专门用于此目的的方法(URL 签名)。安全性很难做到正确,人们应该始终更喜欢使用经过验证和审核的库,它经受住了时间的考验,而不是提出自己的加密/签名机制。
【解决方案3】:

也许你应该使用 hmac。

生成链接

import hashlib
import hmac
import time
secret = "anything you like"  # such as generate from os.urandom(length)
def generate(filename):
    current_time = str(int(time.time()))
    token = hmac.new(secret, current_time, hashlib.sha256).hexdigest()
    return "get_file?file=%(filename)s&time=%(current_time)s&token=%(token)s" % {
        "filename": filename,
        "current_time": current_time,
        "token": token
    }

验证链接

import hashlib
import hmac
import time
secret = "anything you like"  # same as in generate function
def verify(time_in_link, token_in_link):
    time_limit = 15 * 60  # maximum time in sec(such as: 15(mins) * 60 (convert them to sec)`enter code here`) that you want them to start download after the link has been generated.
    if (time.time() - int(time_in_link)) > time_limit:  #timeout, return False
        return False
    if hmac.new(secret, str(time_in_link), hashlib.sha256).hexdigest() == token_in_link:  # Check the token is available or not
        return True
    else:
        return False

【讨论】:

  • itsdangerous 包已经这样做了,自动为您处理所有 HMAC 和时间计算,并且已经是烧瓶依赖项。安全性很难做到正确,人们应该始终更喜欢使用经过验证和审核的库,它经受住了时间的考验,而不是提出自己的加密/签名机制。
【解决方案4】:

我在淋浴时想出了一些东西:

from flask import Flask
import datetime as dt
import hashlib

app = Flask(__name__)

time_lock = dt.datetime.now().strftime("%m/%d/%Y")

@app.route('/')
def home():
    security_code = hashlib.sha256(time_lock.encode())
    return f"<a href='/test/{security_code.hexdigest()}'>Click Here</a>"


@app.route('/test/<security_code>')
def test(security_code):
    master_code = hashlib.sha256(time_lock.encode())
    if security_code == master_code.digest():
        return "Access granted!"

这样做是使用当前日期的哈希作为临时 URL。显然存在安全漏洞,但它确实是一个疯狂的想法,并且可以建立在更安全的基础上。它与选择的答案非常相似,但不使用持久存储。这适用于保护链接不被添加书签?

【讨论】:

    【解决方案5】:

    另一种方法是将数据库中的主键编码为 base64。这通常是整个网络上的短网址服务获取短网址的方式。 db 确保唯一性,并且由于它是 base64,因此在 url 变长之前需要相当多的文件。

    然后在每个请求中检查您的数据库以查看链接是否仍然有效。如果不是,你对请求做一些有意义的事情。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-11-02
      • 1970-01-01
      • 1970-01-01
      • 2017-03-13
      • 2017-10-22
      • 1970-01-01
      相关资源
      最近更新 更多