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

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

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

这个文件已被修改,以避免这种技术: http : //code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=20

从文章:

从devise的angular度来看,threadlocals本质上是全局variables,受到全局variables通常所需的可移植性和可预测性的所有常见问题的影响。

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

我明白为什么全局variables可能是坏的,但在这种情况下,我在自己的服务器上运行我自己的代码,所以我不能看到两个全局variables构成什么危险。

有人可以解释涉及的安全问题吗? 我问过许多人,如果他们读了这篇文章,知道我在使用线程本地程序,他们将如何破解我的应用程序,但没有人能够告诉我。 我开始怀疑,这是一个热衷于明确地通过对象的纯粹主义分子所持有的观点。

我完全不同意。 TLS非常有用。 它应该小心使用,就像应该谨慎使用全局variables一样。 但说它不应该被使用,就像说全球化不应该被使用一样荒谬。

例如,我将当前活动的请求存储在TLS中。 这使得它可以从我的日志logging类访问,而不必通过每个接口传递请求 – 包括很多根本不关心Django的接口。 它使我可以从代码中的任何地方创build日志条目; logging器输出到数据库表,并且如果一个请求在一个日志发生时被激活,它就logging诸如活动用户和被请求的东西。

如果你不希望一个线程能够修改另一个线程的TLS数据,那么设置你的TLS来禁止这个,这可能需要使用本地的TLS类。 尽pipe如此,我没有发现这个观点令人信服。 如果攻击者可以执行任意的Python代码作为你的后端,那么你的系统已经遭到了严重的破坏 – 例如,他可以修补任何以后以不同的用户身份运行的任何东西。

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

尽pipe你可以混淆来自不同用户的数据,但是应该避免线程本地人,因为他们隐藏了依赖关系。 如果您将parameter passing给您所看到的方法,并知道您传递的是什么。 但是本地线程就像是后台的隐藏通道,您可能会想,某种方法在某些情况下无法正常工作。

有些情况下,线程本地人是一个不错的select,但他们应该使用很less,小心!

一个关于如何创build与最新的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)