Почему использование локальных потоков в Django плохо? - PullRequest
46 голосов
/ 12 июля 2010

Я использую локальные потоки для хранения текущего пользователя и объектов запроса. Таким образом, я могу легко получить доступ к запросу из любой точки программы (например, к динамическим формам) без необходимости их передачи.

Чтобы реализовать локальное хранилище потоков в промежуточном программном обеспечении, я следовал учебному руководству на сайте Django: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=18

Этот документ с тех пор был изменен, чтобы предложить избегать этой техники: http://code.djangoproject.com/wiki/CookBookThreadlocalsAndUser?version=20

Из статьи:

С точки зрения проектирования локальные потоки являются по существу глобальными переменными и подвержены всем обычным проблемам переносимости и предсказуемости, которые обычно влекут за собой глобальные переменные.

Что еще более важно, с точки зрения безопасности, локальные потоки представляют огромный риск. Предоставляя хранилище данных, которое отображает состояние других потоков, вы предоставляете возможность одному потоку на вашем веб-сервере потенциально изменять состояние другого потока в системе. Если локальные данные потока содержат описания пользователей или другие данные, связанные с аутентификацией, эти данные могут быть использованы в качестве основы для атаки, которая предоставляет доступ неавторизованному пользователю или предоставляет личные данные пользователя. Несмотря на то, что можно создать локальную систему с защитой от такого рода атак, гораздо проще быть защитным и создать систему, которая не подвержена такой уязвимости.

Я понимаю, почему глобальные переменные могут быть плохими, но в этом случае я запускаю свой собственный код на своем собственном сервере, поэтому не могу понять, какую опасность представляют две глобальные переменные.

Может кто-нибудь объяснить проблему безопасности? Я спросил многих людей, как они взломали бы мое приложение, если бы они прочитали эту статью и знали, что я использую локальные потоки, но никто не смог мне сказать. Я начинаю подозревать, что такого мнения придерживаются пуристы, разделяющие волосы, которые любят явно передавать объекты.

Ответы [ 3 ]

46 голосов
/ 12 июля 2010

Я совершенно не согласен. TLS чрезвычайно полезен. Его следует использовать с осторожностью, так же как и с глобальными. но говорить, что его вообще не следует использовать, так же смешно, как и говорить, что глобальные символы никогда не следует использовать.

Например, я храню текущий активный запрос в TLS. Это делает его доступным из моего класса логов, без необходимости передавать запрос через каждый отдельный интерфейс - включая многие, которые вообще не заботятся о Django. Это позволяет мне делать записи в любом месте кода; регистратор выводит данные в таблицу базы данных, и если при создании журнала запрос оказывается активным, он регистрирует такие вещи, как активный пользователь и то, что запрашивается.

Если вы не хотите, чтобы один поток имел возможность изменять данные TLS другого потока, то настройте свой TLS, чтобы запретить это, что, вероятно, требует использования собственного класса TLS. Я не считаю этот аргумент убедительным; если злоумышленник может выполнить произвольный код Python в качестве бэкэнда, ваша система уже смертельно скомпрометирована - он может обезопасить все, что будет позже запущено другим пользователем, например.

Очевидно, вы захотите очистить любой TLS в конце запроса; в Django это означает очистку его в process_response и process_exception в классе промежуточного программного обеспечения.

12 голосов
/ 04 сентября 2016

Краткий пример создания промежуточного программного обеспечения TLS, совместимого с последней версией Django 1.10:

# 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)
12 голосов
/ 12 июля 2010

Несмотря на то, что вы можете смешивать данные от разных пользователей, локальных потоков следует избегать, поскольку они скрывают зависимость.Если вы передаете аргументы методу, который вы видите, и знаете, что вы передаете.Но локальный поток - это что-то вроде скрытого канала в фоновом режиме, и вы можете удивиться, что в некоторых случаях метод работает неправильно.

В некоторых случаях локальные потоки являются хорошим выбором, носледует использовать редко и осторожно!

...