代码之家  ›  专栏  ›  技术社区  ›  hekevintran

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

  •  43
  • hekevintran  · 技术社区  · 15 年前

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

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

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

    从文章中:

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

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

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

    有人能解释所涉及的安全问题吗?我问过很多人,如果他们阅读本文并知道我使用的是线程局部变量,他们会如何破解我的应用程序,但是没有人能够告诉我。我开始怀疑,这是一个观点持有分裂的纯粹主义者谁爱传递明确的对象。

    3 回复  |  直到 9 年前
        1
  •  44
  •   Glenn Maynard    15 年前

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

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

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

    显然,您需要在请求结束时清除任何TLS;在Django中,这意味着在进程响应中清除它,在中间件类中处理异常。

        2
  •  12
  •   deamon    15 年前

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

    在某些情况下,线程局部变量是一个很好的选择,但是它们应该很少和小心地使用!

        3
  •  10
  •   alexpirine    9 年前

    有关如何创建与最新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)
    
    推荐文章