Python 为什么在Django中使用线程局部变量不好?

Python 为什么在Django中使用线程局部变量不好?,python,django,thread-local,Python,Django,Thread Local,我使用线程局部变量来存储当前用户和请求对象。这样,我就可以从程序中的任何位置(例如动态表单)轻松访问请求,而无需传递它们 为了在中间件中实现线程本地存储,我遵循了Django站点上的教程: 此后,对本文件进行了修改,建议避免使用这种技术: 从文章中: 从设计的角度来看,线程局部变量本质上是全局变量,并且受制于全局变量通常涉及的所有可移植性和可预测性问题 更重要的是,从安全的角度来看,本地人构成了巨大的风险。通过提供公开其他线程状态的数据存储,您可以为web服务器中的一个线程提供一种可能修改系

我使用线程局部变量来存储当前用户和请求对象。这样,我就可以从程序中的任何位置(例如动态表单)轻松访问请求,而无需传递它们

为了在中间件中实现线程本地存储,我遵循了Django站点上的教程:

此后,对本文件进行了修改,建议避免使用这种技术:

从文章中:

从设计的角度来看,线程局部变量本质上是全局变量,并且受制于全局变量通常涉及的所有可移植性和可预测性问题

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

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


有人能解释一下涉及的安全问题吗?我问过很多人,如果他们读了这篇文章并且知道我使用的是线程局部变量,他们会如何破解我的应用程序,但是没有人告诉我。我开始怀疑这是喜欢显式传递对象的吹毛求疵的纯粹主义者持有的观点。

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


在某些情况下,线程局部变量是一个不错的选择,但它们应该很少使用,而且要小心

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

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

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


显然,您将希望在请求结束时清除任何TLS;在Django中,这意味着在中间件类的process\u response和process\u exception中清除它。

关于如何创建与最新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)

这个问题真的很老了,但我刚刚看到有人提到它,所以我只想注意到这个问题引用的wiki页面,然后。

谢谢您的确认。我现在感觉不那么疯狂了。如果攻击者可以读取threadlocal数据,那么他无论如何都必须能够SSH到我的机器中。不一定是SSH,但至少要对Python后端进行某种控制。不管怎么说,整个争论似乎都很做作。我很好奇你所说的“在请求结束时清除任何TLS”到底是什么意思。如何清除它?删除
local
对象本身,还是仅删除存储会话的
local
对象上的属性?不过,老实说,我甚至不确定这些数据之间是否存在相关的差异,只是删除数据。如果删除该对象,则会清除整个数据存储,其他线程可能仍在使用该存储。一个巨大的警告-我发现如何导入模块(绝对与相对)会影响模块是否会再次加载(使用其自己的一组全局变量)或者没有-导致非常微妙的错误,特别是在全局变量和threading.local周围!例如,
from path.to.package import global_event
将导致不同的
from.package import global_event
实例,但这不是问题,因为我知道线程局部变量在我调用get_current_user()的任何地方都会使用。我的线程局部变量只有两个。顺便问一下,你有原始的例子吗?它现在被删除了,我想使用它…这个片段与被删除的页面非常相似:GlobalRequest中间件:当使用UWSGI时,它不起作用
local()
是否共享请进一步解释@javierbuski?您的意思是使用UWSGI时跨线程共享
local()
?你能解释为什么会这样吗?因为UWSGI的工作方式,它在所有线程之间共享一切以加快速度。-测试非常简单:pip安装uwsgi并自己测试——顺便说一句,我必须创建一个中间件,在请求开始时创建缓存,在请求结束时清除缓存。。只是为了确保——否则我在内部运行时会遇到各种各样的错误