Почему использование локальных потоков в Django плохо?

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

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

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

Из статьи:

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

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

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

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

3 ответа

Решение

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

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

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

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

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

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

Краткий пример того, как создать промежуточное программное обеспечение 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)

Этот вопрос действительно старый, но я только что видел, как кто-то ссылался на него, поэтому я просто хочу отметить, что вики-страница, на которую ссылается этот вопрос, перестала рекомендовать threadlocal storage в 2010 году, а затем была полностью удалена к 2012 году.

Другие вопросы по тегам