56

我正在使用线程本地来存储当前用户和请求对象。通过这种方式,我可以轻松地从程序中的任何位置(例如动态表单)访问请求,而无需传递它们。

为了在中间件中实现线程本地存储,我遵循了 Django 网站上的教程: https ://web.archive.org/web/20091128195932/http://code.djangoproject.com:80/wiki/CookBookThreadlocalsAndUser

此文档已被修改以建议避免使用此技术: https ://web.archive.org/web/20110504132459/http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser

来自文章:

从设计的角度来看,threadlocals 本质上是全局变量,并且受到全局变量通常带来的所有常见的可移植性和可预测性问题的影响。

更重要的是,从安全的角度来看,threadlocals 带来了巨大的风险。通过提供暴露其他线程状态的数据存储,您可以为您的 Web 服务器中的一个线程提供一种可能修改系统中另一个线程状态的方法。如果 threadlocal 数据包含用户描述或其他与身份验证相关的数据,则该数据可用作攻击的基础,从而授予未经授权的用户访问权限,或公开用户的私人详细信息。虽然可以构建一个不受此类攻击的线程本地系统,但防御性并构建一个从一开始就不受任何此类漏洞影响的系统要容易得多。

我理解为什么全局变量可能不好,但在这种情况下,我在自己的服务器上运行自己的代码,所以我看不到两个全局变量会带来什么危险。

有人可以解释所涉及的安全问题吗?我问过很多人,如果他们阅读了这篇文章并且知道我正在使用线程本地,他们将如何破解我的应用程序,但没有人能够告诉我。我开始怀疑这是喜欢明确传递对象的令人毛骨悚然的纯粹主义者的观点。

4

4 回答 4

54

我完全不同意。TLS 非常有用。它应该小心使用,就像全局变量应该小心使用一样;但是说根本不应该使用它就像说永远不应该使用全局变量一样荒谬。

例如,我将当前活动的请求存储在 TLS 中。这使得它可以从我的日志类访问,而不必通过每个接口传递请求——包括许多根本不关心 Django 的接口。它让我可以从代码中的任何地方创建日志条目;记录器输出到数据库表,如果在生成日志时请求恰好处于活动状态,它会记录活动用户和所请求的内容等内容。

如果您不希望一个线程具有修改另一个线程的 TLS 数据的能力,则将您的 TLS 设置为禁止此操作,这可能需要使用本机 TLS 类。不过,我觉得这个论点没有说服力。如果攻击者可以执行任意 Python 代码作为您的后端,那么您的系统已经受到了致命的威胁——例如,他可以对任何东西进行修补,以便以后以不同的用户身份运行。

显然,您需要在请求结束时清除所有 TLS;在 Django 中,这意味着在中间件类的 process_response 和 process_exception 中清除它。

于 2010-07-12T10:06:27.397 回答
14

尽管您可能会混淆来自不同用户的数据,但应避免使用线程本地变量,因为它们隐藏了依赖关系。如果您将参数传递给您看到并知道您传递的方法的方法。但是本地线程类似于后台的隐藏通道,您可能想知道,在某些情况下某个方法无法正常工作。

在某些情况下,线程局部变量是一个不错的选择,但应谨慎使用它们!

于 2010-07-12T09:36:09.347 回答
13

关于如何创建与最新 Django 1.10 兼容的 TLS 中间件的快速示例:

# coding: utf-8
# Copyright (c) Alexandre Syenchuk (alexpirine), 2016

try:
    from threading import local
except ImportError:
    from django.utils._threading_local import local

_thread_locals = local()

def get_current_request():
    return getattr(_thread_locals, 'request', None)

def get_current_user():
    request = get_current_request()
    if request:
        return getattr(request, 'user', None)

class ThreadLocalMiddleware(object):
    def __init__(self, get_response):
        self.get_response = get_response

    def __call__(self, request):
        _thread_locals.request = request
        return self.get_response(request)
于 2016-09-04T05:22:57.057 回答
3

这个问题真的很老了,但是我只是看到有人提到它,所以我只想注意这个问题引用的wiki页面在2010年停止推荐threadlocal storage,然后到2012年完全删除

于 2019-10-06T20:01:57.413 回答