为什么在Django中使用线程本地不好?

时间:2010-07-12 09:21:49

标签: python django thread-local

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

为了在中间件中实现线程本地存储,我遵循了Django站点上的教程: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=18

此文档已经过修改,建议避免使用此技术: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=20

来自文章:

  

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

     

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

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

有人可以解释涉及的安全问题吗?我问过很多人,如果他们阅读这篇文章并知道我正在使用线程本地人,他们会如何破解我的应用程序,但没有人能告诉我。我开始怀疑这是一个由分裂的纯粹主义者所持有的观点,他们喜欢明确传递物品。

4 个答案:

答案 0 :(得分:46)

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

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

如果您不希望一个线程具有修改另一个线程的TLS数据的能力,那么将TLS设置为禁止此操作,这可能需要使用本机TLS类。不过,我认为这个论点并不令人信服;如果攻击者可以执行任意Python代码作为你的后端,那么你的系统已经受到了致命的攻击 - 例如,他可能会修补任何东西,以便稍后作为不同的用户运行。

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

答案 1 :(得分:12)

尽管您可以混合来自不同用户的数据,但应避免使用线程本地因为它们隐藏了依赖关系。如果您将参数传递给方法,您会看到并知道您传递的是什么。但是本地的线程就像背景中的隐藏通道,您可能想知道,某些方法在某些情况下无法正常工作。

在某些情况下,线程本地人是一个不错的选择,但应该很少使用它们!

答案 2 :(得分:11)

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

答案 3 :(得分:0)

这个问题确实很老,但是我刚刚看到有人提到这个问题,所以我只想指出这个问题先引述stopped recommending threadlocal storage in 2010,然后引述was deleted altogether by 2012的维基页面。