Почему не-потокобезопасный «сеанс» используется в фильтрах Catalina без синхронизации? - PullRequest
0 голосов
/ 05 октября 2018

В этой записи на stackoverflow установлено, что HttpSession не является поточно-ориентированным.В частности,

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

Однако, глядя на реализацию Catalina'sCsrfPreventionFilter , синхронизация не применяется:

HttpSession session = req.getSession(false);

@SuppressWarnings("unchecked")
LruCache<String> nonceCache = (session == null) ? null
        : (LruCache<String>) session.getAttribute(
                Constants.CSRF_NONCE_SESSION_ATTR_NAME);


...

if (nonceCache == null) {
    nonceCache = new LruCache<String>(nonceCacheSize);
    if (session == null) {
        session = req.getSession(true);
    }
    session.setAttribute(
            Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
}

Не могли бы вы объяснить, является ли такой доступ безопасным и почему?


Обновление: В ответ на Andrew ответ ниже: Пожалуйста, проверьте следующий код.В функции process() из ThreadDemo, хотя req является локальной переменной, она не является поточно-ориентированной, поскольку содержит ссылку на общий объект session.

import java.util.HashMap;
import java.util.Map;

public class Main {

    public static void main(String[] args) {
        Session session = new Session();
        int len = 10;

        ThreadDemo[] t = new ThreadDemo[len];

        for (int i = 0; i < len; i++) {
            t[i] = new ThreadDemo(session);
            t[i].start();
        }
    }
}

class ThreadDemo extends Thread {
    private final Session session;

    ThreadDemo(Session session) {
        this.session = session;
    }

    @Override
    public void run() {
        Request request = new HttpRequest(session);
        process(request);
    }

    private void process(Request request) {
        HttpRequest req = (HttpRequest) request;
        Session session = req.getSession();

        if (session.getAttribute("TEST") == null)
            session.putAttribute("TEST", new Object());
        else
            session.clearAttributes();

        System.out.println(session.getAttribute("TEST") == null);
    }
}

class Session {
    private final Map<String, Object> session;

    Session() {
        session = new HashMap<>();
    }

    Object getAttribute(String attr) {
        return session.get(attr);
    }

    void putAttribute(String attr, Object o) {
        session.put(attr, o);
    }

    void clearAttributes() {
        session.clear();
    }
}

class Request {
    private final Session session;

    Request(Session session) {
        this.session = session;
    }

    Session getSession() {
        return session;
    }
}

class HttpRequest extends Request {
    HttpRequest(Session session) {
        super(session);
    }
}

1 Ответ

0 голосов
/ 05 октября 2018

Из источника

HttpSession session = req.getSession(false);  // line 196

req является локальным, поэтому потокобезопасным, и если параллельные запросы получены, где вышеупомянутый session (уникальный для каждого потока) приводит кв null тогда вполне разумно предположить, что параллельные запросы представляют разных пользователей (или, по крайней мере, несвязанные запросы от одного пользователя).

Позже устанавливается новый сеанс (уникальный для каждого потока):

session = req.getSession(true);   // line 217

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

Таким образом, можно безопасно создать атрибут сеанса для только что созданного сеанса:

session.setAttribute(Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);  // line 219

Затем ответ отправляется обратно клиенту с идентификатором сеанса.,Следующий запрос включает идентификатор сеанса, поэтому

session = req.getSession(true);   // line 217

не будет нулевым.И точно так же, nonceCache не будет нулевым, поэтому весь этот блок

if (nonceCache == null) {
    nonceCache = new LruCache<String>(nonceCacheSize);
    if (session == null) {
        session = req.getSession(true);
    }
    session.setAttribute(
        Constants.CSRF_NONCE_SESSION_ATTR_NAME, nonceCache);
}

будет пропущен во время последующих запросов, связанных с существующим сеансом.

Доступ к элементам кэша синхронизированво вложенных LruCache строках классов 358 и 364.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...