Условно отключить JSESSIONID в Spring-Boot-Web/Tomcat

avatar
Felix
9 августа 2021 в 03:12
783
1
3

Я пытаюсь условно отключить создание файла cookie JSESSIONID.

Я хочу создать этот файл cookie, только если в запросе присутствует определенный файл cookie.

Я использую spring-boot-starter-web 2.5.3 с spring-boot-starter-security.

Что я уже пробовал

Spring-Session -> Компонент CookieSerializer

Я попытался добавить spring-session и определить пользовательский DefaultCookieSerializer-Bean. Оказалось, что JSESSIONID на самом деле исходит не от весны, а от нижележащего Tomcat.

Использование javax.servlet.Filter, которое блокирует настройку заголовка Set-Cookie:

package xxxx.configuration;

import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import javax.servlet.*;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
import java.io.IOException;
import java.net.URLDecoder;
import java.nio.charset.StandardCharsets;

@Configuration
public class CookieConsentConfiguration {

    private static class CookieConsentFilter implements Filter {

        private final ObjectMapper objectMapper;

        private CookieConsentFilter(ObjectMapper objectMapper) {
            this.objectMapper = objectMapper;
        }

        @Override
        public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
            final HttpServletRequest req = (HttpServletRequest) request;

            if (hasCookieConsent(req)) {
                chain.doFilter(request, response);
            } else {
                chain.doFilter(request, new NoCookiesResponseWrapper((HttpServletResponse) response));
            }
        }

        private boolean hasCookieConsent(HttpServletRequest request) {
            try {
                final Cookie[] cookies = request.getCookies();
                if (cookies != null) {
                    for (Cookie cookie : cookies) {
                        if (cookie.getName().equals("cookie_consent_level")) {
                            final String value = cookie.getValue();
                            final JsonNode json = this.objectMapper.readTree(URLDecoder.decode(value, StandardCharsets.US_ASCII));

                            return json.hasNonNull("strictly-necessary") && json.get("strictly-necessary").booleanValue();
                        } else if (cookie.getName().equals("cookie_consent_user_accepted")) {
                            return Boolean.parseBoolean(cookie.getValue());
                        }
                    }
                }
            } catch (Exception e) {
                return false;
            }

            return false;
        }
    }

    private static class NoCookiesResponseWrapper extends HttpServletResponseWrapper {

        public NoCookiesResponseWrapper(HttpServletResponse response) {
            super(response);
        }

        @Override
        public void addCookie(Cookie cookie) {
            // not allowed
        }

        @Override
        public void setHeader(String name, String value) {
            if (!name.equalsIgnoreCase("set-cookie")) {
                super.setHeader(name, value);
            }
        }

        @Override
        public void addHeader(String name, String value) {
            if (!name.equalsIgnoreCase("set-cookie")) {
                super.addHeader(name, value);
            }
        }
    }

    @Bean
    public Filter cookieConsentFilter(ObjectMapper objectMapper) {
        return new CookieConsentFilter(objectMapper);
    }
}

Но и это не сработало. JSESSIONID все еще создается.

Есть ли способ отключить создание этого (или любого) файла cookie, если некоторые условия не выполняются?

Источник
chrylis -cautiouslyoptimistic-
9 августа 2021 в 03:18
1

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

Felix
9 августа 2021 в 03:31
0

Я знаю, и у меня есть логин. А вот Session-Cookie создается сразу при первом посещении страницы, вообще без попытки авторизации. Кроме того, я установил для SessionCreationPolicy значение IF_REQUIRED. Запоминаются ли значения CSRF на стороне сервера при использовании сеанса? Может ли это быть причиной создания сеанса?

Piotr P. Karwasz
10 августа 2021 в 19:58
0

По умолчанию используется LazyCsrfTokenRepository (см. этот вопрос), поэтому сеанс не создается, если вы используете токен. Можете ли вы привести минимальный пример страницы, которая вызывает проблемы?

Felix
10 августа 2021 в 23:33
0

Да, это была проблема. Я использовал токен csrf в каждом из моих шаблонов тимелеафа. Это заставило сеанс быть созданным

Piotr P. Karwasz
11 августа 2021 в 09:57
0

@Felix: можешь написать ответ на свой вопрос?

Ответы (1)

avatar
Felix
13 августа 2021 в 14:56
1
Отказ от ответственности: этот ответ не дает прямого ответа на вопрос, как прекратить создание JSESSIONID на основе условия. Я не приму этот ответ по этой причине.

GDPR

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

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

Для получения дополнительной информации см.: https://gdpr.eu/cookies/


Технические вопросы

Общие

Прежде всего, мы должны понять, при каких обстоятельствах создается JSESSIONID.

Он создается всякий раз, когда какая-то часть кода пытается получить сеанс от сервера приложений. Это означает, что если мы сможем структурировать наш код таким образом, чтобы он не пытался получить сеанс от сервера приложений, никакие JSESSIONID-Cookie не будут отправлены клиенту.

Но есть несколько подводных камней. Для некоторых вещей сеанс является самым необходимым файлом cookie из всех:

.
  • Логин: Логин (как и вход человека-пользователя на веб-сайте), строго требует сеанса
  • CSRF-токены: серверу нужно что-то, откуда можно получить эти токены. Это сеанс
  • ... больше

Пример с Spring-Boot

Я использую spring-boot-starter-web в версии 2.5.3. Я хочу, чтобы сеанс создавался только в том случае, если он действительно необходим, по одной из причин, упомянутых выше: пользователи пытаются войти в систему или пользователи получают доступ к сайту, который требует создания токенов CSRF по соображениям безопасности.

Первый соответствующий конфиг можно найти в моем WebSecurityConfigurerAdapter: Установка SessionCreationPolicy на IF_REQUIRED.

    @Configuration
    public static class GlobalWebSecurityConfigurerAdapter extends WebSecurityConfigurerAdapter {

        @Override
        public void configure(WebSecurity web) {
            web.ignoring().antMatchers("/webjars/**", "/css/**", "/js/**");
        }

        @Override
        protected void configure(HttpSecurity http) throws Exception {
            http
                    .csrf()
                        .and()
                    .headers()
                        .frameOptions()
                        .deny()
                        .and()
                    .sessionManagement()// these two lines
                        .sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
                        .and()
                    .oauth2Login()
                        .and()
                    .logout()
                        .logoutSuccessUrl("/");
        }
    }

Вторым важным моментом в моем случае было встраивание CSRF-токенов только в том случае, если они действительно необходимы для страницы.

У меня было несколько шаблонов тимелеафа, из которых я всегда использовал один и тот же раздел <head>. В каждом из моих шаблонов раздел <head> содержал следующие строки:

    <meta name="_csrf" th:content="${_csrf.token}"/>
    <meta name="_csrf_header" th:content="${_csrf.headerName}"/>

Хотя это по-прежнему требуется для страниц, которые выполняют POST, PUT, ... - запросы к моему серверу, это не требуется для большинства моих страниц.

Я удалил использование CSRF-токена в каждом шаблоне тимелеафа, где он не требовался.

Теперь сеанс устанавливается (и создается JSESSIONID) с сервера приложений только после того, как пользователь попытается получить доступ к странице, содержащей CSRF-токен

.