Файл cookie того же сайта в Spring Security

Можно ли установить флаг Cookie того же сайта в Spring Security? Смотрите: https://tools.ietf.org/html/draft-west-first-party-cookies-07 А если нет, то есть ли план по добавлению поддержки, пожалуйста? Уже есть поддержка в некоторых браузерах (например, Chrome). TH

10 ответов

Новая версия Tomcat поддерживает файлы cookie SameSite через TomcatContextCustomizer. Таким образом, вы должны настраивать Tomcat CookieProcessor, например, для Spring Boot:

@Configuration
public class MvcConfiguration implements WebMvcConfigurer {

  @Bean
  public TomcatContextCustomizer sameSiteCookiesConfig() {
    return context -> {
        final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
        cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
        context.setCookieProcessor(cookieProcessor);
    };
  }

За SameSiteCookies.NONE знайте, чем файлы cookie Secure (Используется SSL), иначе они не могут быть применены.

По умолчанию, поскольку файлы cookie Chrome 80 считаются SameSite=Lax!

Смотрите SameSite Cookie в рецептах Spring Boot и SameSite cookie


ОБНОВЛЕНИЕ: для прокси nginx это можно легко решить в конфигурации nginx:

    if ($scheme = http) {
        return 301 https://$http_host$request_uri;
    }

    proxy_cookie_path / "/; secure; SameSite=None";

Вместо фильтра в обработчике успешной аутентификации вы можете указать таким образом.


    @Override
        public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response,
                Authentication authentication) throws IOException {
            response.setStatus(HttpServletResponse.SC_OK);
            clearAuthenticationAttributes(request);
            addSameSiteCookieAttribute(response);
            handle(request, response);
        }

        private void addSameSiteCookieAttribute(HttpServletResponse response) {
            Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
            boolean firstHeader = true;
            for (String header : headers) { // there can be multiple Set-Cookie attributes
                if (firstHeader) {
                    response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
                    firstHeader = false;
                    continue;
                }
                response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s", header, "SameSite=Strict"));
            }
        }

Об этом упоминалось в одном из ответов. Не могу найти ссылку после того, как реализовал ее.

Все возможные решения здесь не помогли мне. Каждый раз, когда я пробовал использовать фильтр или перехватчик, заголовок Set-Cookie еще не добавлялся. Единственный способ, которым я смог выполнить эту работу, - это добавить Spring Session и добавить этот bean-компонент в один из моих файлов @Configuration:

@Bean
public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setSameSite("none");
    return serializer;
}

В любом случае надеюсь, что это поможет кому-то еще в моей ситуации.

Вы всегда можете установить значения cookie самостоятельно в мире Java, если вы можете получить экземпляр HttpServletResponse,

Тогда вы можете сделать:

response.setHeader("Set-Cookie", "key=value; HttpOnly; SameSite=strict")

В Spring-Security вы можете легко сделать это с помощью фильтра, вот пример:

public class CustomFilter extends GenericFilterBean {

    @Override
    public void doFilter(ServletRequest request,  ServletResponse response, FilterChain chain) throws IOException, ServletException {

        HttpServletResponse resp = (HttpServletResponse)response;

        resp.setHeader("Set-Cookie", "locale=de; HttpOnly; SameSite=strict");

        chain.doFilter(request, response);
    }
}

Добавьте этот фильтр в ваш SecurityConfig следующим образом:

http.addFilterAfter(new CustomFilter(), BasicAuthenticationFilter.class)

Или через XML:

    <http>
        <custom-filter after="BASIC_AUTH_FILTER" ref="myFilter" />
    </http>

<beans:bean id="myFilter" class="org.bla.CustomFilter"/>

Это невозможно. В Spring Session есть поддержка этой функции: https://spring.io/blog/2018/10/31/spring-session-bean-ga-released.

Я придумал решение, похожее на решение Рона. Но следует отметить одну важную вещь:

Файлы cookie для межсайтового использования должны указывать SameSite=None; Secure чтобы включить включение в сторонний контекст.

Поэтому я включил атрибут Secure в заголовок. Кроме того, вам не нужно переопределять все три метода, если вы их не используете. Это требуется только тогда, когда вы внедряетеHandlerInterceptor.

import org.apache.commons.lang.StringUtils;

public class CookiesInterceptor extends HandlerInterceptorAdapter {

final String sameSiteAttribute = "; SameSite=None";
final String secureAttribute = "; Secure";

@Override
public void postHandle(HttpServletRequest request, HttpServletResponse response, Object handler, ModelAndView modelAndView) throws Exception {

    addEtagHeader(request, response);

    Collection<String> setCookieHeaders = response.getHeaders(HttpHeaders.SET_COOKIE);

    if (setCookieHeaders == null || setCookieHeaders.isEmpty())
        return;

    setCookieHeaders
        .stream()
        .filter(StringUtils::isNotBlank)
        .map(header -> {
            if (header.toLowerCase().contains("samesite")) {
                return header;
            } else {
                return header.concat(sameSiteAttribute);
            }
        })
        .map(header -> {
            if (header.toLowerCase().contains("secure")) {
                return header;
            } else {
                return header.concat(secureAttribute);
            }
        })
        .forEach(finalHeader -> response.setHeader(HttpHeaders.SET_COOKIE, finalHeader));
}

Я использовал xml в своем проекте, поэтому мне пришлось добавить это в свой файл конфигурации:

<mvc:interceptors>
    <bean class="com.zoetis.widgetserver.mvc.CookiesInterceptor"/>
</mvc:interceptors>

Использование перехватчика в SpringBoot.

Я ищу решение для добавления SameSite, как и вы, и я хочу только добавить атрибут к существующему "Set-Cookie" вместо создания нового "Set-Cookie". Я пробовал несколько способов удовлетворить это требование, в том числе:
1. добавив настраиваемый фильтр, как сказал
@unwichtich, 2. и многое другое я переопределил basicAuthenticationFilter. Он добавляет атрибут SameSite. Трудно уловить время, когда Spring добавит "Set-Cookie". Я думал, что в методе onAuthenticationSuccess() ответ должен иметь этот заголовок, но его нет. Я не уверен, что это вина моего пользовательского заказа basicAuthenticationFilter.
3. с помощью cookieSerializer, но в версии с весенним сеансом возникают проблемы. Кажется, это поддерживается только последней версией, но я до сих пор не могу понять, какой номер версии следует добавить в список зависимостей.
К сожалению, ни один из перечисленных выше не может добавить тот же сайт так, как ожидалось.

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

@Component
public class CookieServiceInterceptor extends HandlerInterceptorAdapter {

@Override
public boolean preHandle(
   HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {

   return true;
}

@Override
public void postHandle(
   HttpServletRequest request, HttpServletResponse response, Object handler, 
   ModelAndView modelAndView) throws Exception {
    //check whether it has "set-cookie" in the response, if it has, then add "SameSite" attribute
    //it should be found in the response of the first successful login
    Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
    boolean firstHeader = true;
    for (String header : headers) { // there can be multiple Set-Cookie attributes
        if (firstHeader) {
            response.setHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s",  header, "SameSite=strict"));
            firstHeader = false;
            continue;
        }
        response.addHeader(HttpHeaders.SET_COOKIE, String.format("%s; %s",  header, "SameSite=strict"));
    }
}
@Override
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, 
   Object handler, Exception exception) throws Exception {}
}

и вам также необходимо заставить этот перехватчик работать в вашем приложении, что означает, что вы должны добавить bean-компонент, как показано ниже:

   @Autowired
   CookieServiceInterceptor cookieServiceInterceptor;

   @Bean
  public MappedInterceptor myInterceptor()
  {
     return new MappedInterceptor(null, cookieServiceInterceptor);
  }

Я тестировал это решение на spring-webmvc без spring-security- видите простую форму обратной связи, но я думаю, что она также должна работать для spring-boot.


Используя SessionRepositoryFilter bean из Spring-session-core

Вы можете расширить Java по умолчанию HttpSession с пружиной Session и заменить JSESSIONID cookie на собственный, например:

       Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None

Дополнительная пружина Session флаги cookie можно установить с помощью DefaultCookieSerializer:

       @Configuration
@EnableSpringHttpSession
public class WebAppConfig implements WebApplicationInitializer {
    @Override
    public void onStartup(ServletContext servletContext) {
        servletContext
                .addFilter("sessionRepositoryFilter", DelegatingFilterProxy.class)
                .addMappingForUrlPatterns(null, false, "/*");
    }

    @Bean
    public MapSessionRepository sessionRepository() {
        final Map<String, Session> sessions = new ConcurrentHashMap<>();
        MapSessionRepository sessionRepository =
                new MapSessionRepository(sessions) {
                    @Override
                    public void save(MapSession session) {
                        sessions.entrySet().stream()
                                .filter(entry -> entry.getValue().isExpired())
                                .forEach(entry -> sessions.remove(entry.getKey()));
                        super.save(session);
                    }
                };
        sessionRepository.setDefaultMaxInactiveInterval(60*5);
        return sessionRepository;
    }

    @Bean
    public SessionRepositoryFilter<?> sessionRepositoryFilter(MapSessionRepository sessionRepository) {
        SessionRepositoryFilter<?> sessionRepositoryFilter =
                new SessionRepositoryFilter<>(sessionRepository);

        DefaultCookieSerializer cookieSerializer = new DefaultCookieSerializer();
        cookieSerializer.setCookieName("JSESSIONID");
        cookieSerializer.setSameSite("None");
        cookieSerializer.setUseSecureCookie(true);

        CookieHttpSessionIdResolver cookieHttpSessionIdResolver =
                new CookieHttpSessionIdResolver();
        cookieHttpSessionIdResolver.setCookieSerializer(cookieSerializer);

        sessionRepositoryFilter.setHttpSessionIdResolver(cookieHttpSessionIdResolver);

        return sessionRepositoryFilter;
    }
}

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

Вы можете добавить cookie самостоятельно, используя ResponseCookie и добавив его в свой HttpServletResponse.

      ResponseCookie cookie = ResponseCookie.from("cookiename", "cookieValue")
            .maxAge(3600) // one hour
            .domain("test.com")
            .sameSite("None")
            .secure(true)
            .path("/")
            .build();
 response.addHeader(HttpHeaders.SET_COOKIE, cookie.toString());

Для Spring Webflux (реактивная среда) это сработало для меня:

      @Configuration
@EnableSpringWebSession
public class SessionModule {

  @Bean
  public ReactiveSessionRepository<MapSession> reactiveSessionRepository() {
    return new ReactiveMapSessionRepository(new ConcurrentHashMap<>());
  }

  @Bean
  public WebSessionIdResolver webSessionIdResolver() {
    CookieWebSessionIdResolver resolver = new CookieWebSessionIdResolver();
    resolver.setCookieName("SESSION");
    resolver.addCookieInitializer((builder) -> {
      builder.path("/")
          .httpOnly(true)
          .secure(true)
          .sameSite("None; Secure");
    });
    return resolver;
  }
}

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

      @Configuration
public static class WebConfig implements WebMvcConfigurer {
    @Bean
    public CookieSameSiteSupplier cookieSameSiteSupplier(){
        return CookieSameSiteSupplier.ofNone();
    }
}

Или... еще проще, spring boot начиная с 2.6.0 поддерживает настройку в application.properties.

Документация Spring о файлах cookie SameSite

      server.servlet.session.cookie.same-site = none
Другие вопросы по тегам