是否可以在 Spring Security中设置Same-site Cookie标志?
如果没有,请问是否在添加支持的路线图上?一些浏览器(即Chrome)已经支持。
是否可以在 Spring Security中设置Same-site Cookie标志?
如果没有,请问是否在添加支持的路线图上?一些浏览器(即Chrome)已经支持。
新的 Tomcat 版本通过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),否则无法应用。
默认情况下,Chrome 80 cookie 被视为SameSite=Lax
!
请参阅Spring Boot 中的SameSite Cookie和SameSite cookie 食谱。
对于 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
.
然后你可以这样做:
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
我想出了一个类似于 Ron 的解决方案。但是有一件重要的事情需要注意:
跨站点使用的 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”。我尝试了几种方法来满足这个要求,包括:
最后发现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);
}
这个拦截器有一个缺陷,当请求被重定向(ex.return 302)或失败(ex.return 401)时,它无法添加相同的站点,而当 SSO 时它使我的应用程序失败。最终,我不得不使用 Tomcat cookie,因为我没有在我的 springboot 应用程序中嵌入 tomcat。我加
<Context>
<CookieProcessor sameSiteCookies="none" />
</Context>
在我的应用程序的 /META-INF 下的 context.xml 中。它将在每个响应的 set-cookie 标头中添加 SameSite 属性。请注意,从 Tomcat 9.0.21 和 8.5.42 开始,这种行为是可能的。根据https://stackoverflow.com/a/57622508/4033979
我已经测试了这个解决方案,spring-webmvc
但spring-security
我认为它也应该适用于spring-boot
.
SessionRepositoryFilter
的beanHttpSession
您可以使用spring 扩展默认 javaSession
并用自定义替换JSESSIONID
cookie,如下所示:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
可以使用以下方法设置额外的spring cookie 标志:Session
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;
}
}
显然,使用 spring boot 你可以写这个并且它被拾取。
@Configuration
public static class WebConfig implements WebMvcConfigurer {
@Bean
public CookieSameSiteSupplier cookieSameSiteSupplier(){
return CookieSameSiteSupplier.ofNone();
}
}
或者...更简单,spring boot 从 2.6.0 开始支持在 application.properties 中设置。
关于 SameSite Cookie 的 Spring 文档
server.servlet.session.cookie.same-site = none