Spring 安全性中会话 cookie 的同一站点标志



是否可以在Spring Security中设置同站点Cookie标志?

如果没有,请问是否在路线图上添加支持?某些浏览器(即Chrome)已经支持。

新的Tomcat版本通过TomcatContextCustomizer支持SameSite cookie。所以你应该只自定义tomcat饼干处理器,例如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.NONEcookie 也是Secure的(使用 SSL),否则无法应用它们。

默认情况下,由于Chrome 80 cookie被视为SameSite=Lax

请参阅 Spring Boot 中的 SameSite Cookie 和 SameSite cookie recipes。


对于nginx代理,可以在nginx配置中轻松解决:

if ($scheme = http) {
return 301 https://$http_host$request_uri;
}
proxy_cookie_path / "/; secure; SameSite=None";

来自@madbreaks的更新: proxy_cookie_flags ISOproxy_cookie_path

proxy_cookie_flags ~ 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;
// there can be multiple Set-Cookie attributes
for (String header : headers) {
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;
}

无论如何,希望这对我同样情况的其他人有所帮助。

如果你能得到HttpServletResponse的实例,你总是可以在 Java 世界中自己设置 cookie 值。

然后你可以做:

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);
}
}

将此过滤器添加到您的安全配置中,如下所示:

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"/>

这是不可能的。春季会议支持此功能:https://spring.io/blog/2018/10/31/spring-session-bean-ga-released

我想出了一个类似于罗恩的解决方案。但有一件重要的事情需要注意:

用于跨站点使用的 Cookie 必须指定SameSite=None; 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. 以及更多我覆盖了基本身份验证过滤器。它确实添加了 SameSite 属性。而春天添加"Set-Cookie"的时机很难赶上。我认为在 onAuthenticationSuccess() 方法中,响应必须具有此标头,但它没有。我不确定这是否是我的自定义 basicAuthenticationFilter 的顺序的错误。
  3. 使用cookieSerializer,但spring会话版本遇到了问题。似乎只有最新版本支持它,但我仍然无法弄清楚版本号应该添加到依赖项列表中.
    不幸的是,上面都没有像预期的那样添加相同的站点。

最后,我发现春天的拦截器可以帮助我制作它。 我花了一周时间才得到它。如果有人遇到同样的问题,希望这可以对您有所帮助。

@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);
}

此拦截器有一个缺陷,当请求被重定向(例如返回 302)或失败(例如返回 401)时,它无法添加 samesite,而在 SSO 时它会使我的应用程序失败。最终,我不得不使用 Tomcat cookie,因为我没有在我的 springboot 应用程序中嵌入 tomcat。我加

<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>

在上下文中.xml在我的应用的/META-INF 下。它将在每个响应的设置cookie标头中添加SameSite属性。请注意,从 Tomcat 9.0.21 和 8.5.42 开始,此行为是可能的。根据 https://stackoverflow.com/a/57622508/4033979

对于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;
}
}

您可以通过使用 ResponseCookie 并将其添加到您的 HttpServletResponse 中来自行添加 cookie。

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-security的情况下测试了这个解决方案spring-webmvc,但我认为它也应该适用于spring-boot

使用弹簧会话核心中的SessionRepositoryFilterbean

您可以使用SpringSession扩展默认的 javaHttpSessionJSESSIONID并将 cookie 替换为自定义cookie,如下所示:

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

可以使用以下DefaultCookieSerializer方法设置其他弹簧Sessioncookie 标志:

@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或SessionExpireEvent- 我在添加新会话之前添加了过期会话的清除。我认为这对于小型应用程序来说可能就足够了。

显然,使用弹簧靴,您可以编写此内容并被拾取。

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

或。。。更简单的是,从 2.6.0 开始的 Spring 引导支持在 application.properties 中设置它。

关于 SameSite Cookie 的春季文档

server.servlet.session.cookie.same-site = none

最新更新