Spring Безопасность - одновременный запрос при выходе из системы

Мы используем Spring Безопасность в нашем веб-приложении. Большинство страниц защищены, то есть пользователь должен войти в систему, чтобы получить доступ к этим страницам. Обычно он работает нормально. Однако при выходе из системы мы сталкиваемся с нежелательным поведением.

Предположим, что пользователь вошел в систему и отправил запрос на сервер для загрузки некоторой (защищенной) страницы. Прежде чем этот запрос будет выполнен, один и тот же пользователь отправит запрос на выход (т.е. Запрос с servlet_path "/j_spring_security_logout" ). Запрос на выход обычно выполняется очень быстро, и его можно завершить раньше предыдущего запроса. Конечно, запрос на выход очищает контекст безопасности. Следовательно, предыдущий запрос теряет контекст безопасности в середине своей жизни, и это обычно вызывает исключение.

Фактически, пользователю не нужно начинать первый запрос "вручную". Этот сценарий может произойти на странице с автоматическим обновлением, т.е. Пользователь нажимает ссылку на выход только через секунду после того, как обновление было отправлено автоматически.

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

Есть ли способ настроить Spring Безопасность, чтобы этого избежать? (что-то вроде "отложить очистку контекста безопасности при наличии других одновременных запросов из того же сеанса" или "прочитать контекст безопасности только один раз во время одного запроса и кешировать его для дальнейшего использования" )

Спасибо.

Ответ 1

Итак, это все (неудивительно) по дизайну. Эти spring документы безопасности дают хорошее объяснение того, что происходит - цитирование:

В приложении, которое получает одновременные запросы за один сеанс, один и тот же экземпляр SecurityContext будет разделяться между потоками. Несмотря на то, что используется ThreadLocal, это тот же самый экземпляр, который извлекается из HttpSession для каждого потока. Это имеет последствия, если вы хотите временно изменить контекст, в котором работает поток. Если вы просто используете SecurityContextHolder.getContext() и вызываете setAuthentication(anAuthentication) в возвращаемом объекте контекста, то объект Authentication будет изменяться во всех параллельных потоках, которые имеют один и тот же экземпляр SecurityContext. Вы можете настроить поведение SecurityContextPersistenceFilter, чтобы создать абсолютно новый SecurityContext для каждого запроса, не позволяя изменениям в одном потоке влиять на другой. В качестве альтернативы вы можете создать новый экземпляр только в том месте, где вы временно меняете контекст. Метод SecurityContextHolder.createEmptyContext() всегда возвращает новый экземпляр контекста.

В приведенном выше цитате сказано:

... вы можете настроить поведение SpringContextPersistenceFilter...

К сожалению, документы не предоставляют никакой информации о том, как это сделать, или о том, как можно было бы приблизиться. Этот вопрос SO) задает именно это (по сути, является дистиллированной версией этого вопроса), но он не получил большого внимания.

Там также этот SO ответ, который обеспечивает немного большую глубину во внутренней работе HttpSessionSecurityContextRepository, которая, вероятно, будет частью, которая должна быть повторена -писано/обновлено, чтобы решить эту проблему.

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

Update

Корень проблемы, с которой я столкнулся, был связан с чтением атрибута идентификатора пользователя из HttpSession (после того, как он был очищен параллельным запросом "выход из системы" ). Вместо реализации моего собственного SpringContextRepository я решил создать простой фильтр, который сохраняет текущий Authentication в запрос, а затем работает оттуда.

Здесь основной фильтр:

public class SaveAuthToRequestFilter extends OncePerRequestFilter {

    public static final String REQUEST_ATTR = SaveAuthToRequestFilter.class.getCanonicalName();

    @Override
    protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain)
            throws ServletException, IOException {

        final SecurityContext context = SecurityContextHolder.getContext();
        if (context != null) {
            request.setAttribute(REQUEST_ATTR, context.getAuthentication());
        }

        filterChain.doFilter(request, response);
    }

}

Что нужно добавить после SecurityContextPersistenceFilter, добавив следующее к вашему методу WebSecurityConfigurerAdapter configure(final HttpSecurity http).

http.addFilterAfter(new SaveAuthToRequestFilter(), SecurityContextPersistenceFilter.class)

После этого вы можете прочитать "текущий" (по потоку/запросу) Authentication из HttpServletRequest (@Autowired или ввести в свои методы контроллера) и работать оттуда. Я думаю, что это решение по-прежнему оставляет желать лучшего, но это самый легкий вариант, о котором я мог думать. Благодаря @chimmi и @sura2k для вдохновения.

Ответ 2

Отказ от ответственности: поведение, о котором идет речь, было реализовано целенаправленно. И если кто-то решит отключить его, они должны прочитать SEC-2025, который описывает проблему, которую вы получаете взамен.


Если я правильно понимаю, проблема заключается в том, что logout очищает данные SecurityContext authentication, тем самым делая

SecurityContextHolder.getContext().getAuthentication()

return null. Но logout не очищает сам контекст, если ваш первый запрос удалось захватить его до выхода из системы, он остался в ThreadLocal для первого запроса.

Итак, все, что нам нужно, - это не очистить authentication, и получается (потому что Spring является удивительным), что SecurityContextLogoutHandler, который несет ответственность за это, имеет свойство:

private boolean clearAuthentication = true;

который делает именно то, что нам нужно, Javadoc:

Если значение true, удаляет аутентификацию из SecurityContext для предотвращения проблем с одновременными запросами.

Ответ 3

Ваше требование было сообщено как ошибка (а не запрос функции) в JIRA SEC-2025. И они исправили его в Spring 3.2, так что вы ожидаете, что здесь произойдет/решит неявно предотвратит его дизайн.

Поведение по умолчанию состоит в том, чтобы сохранить SecurityContext в HttpSession и это единственная реализация, обеспечивающая безопасность afaik Spring на данный момент.

Даже SecurityContext есть ThreadLocal, он имеет то же значение, что и в HttpSession. Поэтому, когда SecurityContext очищается, он удаляется из HttpSession, поэтому будет недоступен из всего сеанса пользователя.

Что вам нужно сделать, сохраните SecurityContext дополнительно в HttpServletRequest (или что-то связанное с HTTP-запросом), отличное от HttpSession, и прочитайте его обратно из HttpSession, а если он не найден, прочитайте его от HttpServletRequest. Обязательно сохраните глубокую копию SecurityContext в HttpServletRequest. Когда вы выйдете из системы, очистите SecurityContext только от HttpSession, который в настоящее время происходит. В этом случае любые выполняемые потоки (связанные с HTTP-запросами) будут иметь доступ к SecurityContext через HttpServletRequest (если он не найден в HttpSession), который сейчас происходит с вами), даже если пользователь вышел из системы, Следующие новые HTTP-запросы будут нуждаться в аутентификации, потому что новые запросы не имеют SecurityContext в HttpSession (или HttpServletRequest).

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

Для этого вам необходимо прочитать и понять следующие реализации Spring.

И вам может потребоваться заменить выше 2 классов, предоставив свои собственные реализации или переопределив необходимые реализации. (И могут быть и другие)

См:

.

Я думаю, что Spring в документах безопасности явно говорится о том, чтобы не иметь дело с HttpSession. Вот почему существует SecurityContext.

IMO просто придерживайтесь Spring реализаций безопасности, когда нет рекомендаций от Spring инженеров по безопасности, чтобы что-то делать самостоятельно. То, что я нахожу здесь, может не исправить, и убедитесь, что нет дыр в безопасности, и он не должен нарушать другие варианты использования, когда вы делаете некоторые не рекомендованные изменения только для того, чтобы покрыть угловой случай. Я никогда не сделаю этого, если бы это случилось со мной, потому что дизайн, который Spring эксперты по безопасности решили пойти, рассмотрев много многих фактов безопасности, о которых я понятия не имею./p >