Файл 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