Spring Security 中会话 cookie 的同站点标志
Posted
技术标签:
【中文标题】Spring Security 中会话 cookie 的同站点标志【英文标题】:Same-Site flag for session cookie in Spring Security 【发布时间】:2017-08-17 07:48:00 【问题描述】:是否可以在 Spring Security 中设置Same-site Cookie 标志?
如果没有,请问是否有增加支持的路线图?一些浏览器(例如 Chrome)已经支持。
【问题讨论】:
检查这个使用GenericFilterBean / 临时重定向请求来解决同类问题***.com/questions/63939078/… 这对我有用。 vaadin.com/forum/thread/18124830/18509113 使用 Spring Boot 2.4.1 为我工作的解决方案***.com/a/64558083/4423695 【参考方案1】:New Tomcat version 通过TomcatContextCustomizer
支持 SameSite cookie。所以你应该只自定义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
!
请参阅 SameSite Cookie in Spring Boot 和 SameSite cookie recipes。
对于 nginx 代理,可以在 nginx 配置中轻松解决:
if ($scheme = http)
return 301 https://$http_host$request_uri;
proxy_cookie_path / "/; secure; SameSite=None";
来自@madbreaks 的更新:
proxy_cookie_flagsiso proxy_cookie_path
proxy_cookie_flags ~ secure samesite=none;
【讨论】:
这也有效。 vaadin.com/forum/thread/18124830/18509113 我相信 proxy_cookie_flags 是更好的选择:nginx.org/en/docs/http/… 第一个链接失效了。 @jub0bs 再试一次,我的就可以了 @GrigoryKislin 对不起,第二个链接:techhub.erimy.net/archives/0-a-2-aca-650721626055-a-3【参考方案2】:您可以在身份验证成功处理程序中以这种方式提及,而不是过滤器。
@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"));
在其中一个答案中提到过。实现后找不到链接。
【讨论】:
【参考方案3】:如果您可以获得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);
像这样将此过滤器添加到您的 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"/>
【讨论】:
如果你想对所有 cookie 应用这个,你可以这样做:String cookie = resp.getHeader("Set-Cookie");if (cookie != null) resp.setHeader("Set-Cookie", cookie + "; HttpOnly; SameSite=strict");
【参考方案4】:
这里所有可能的解决方案对我来说都失败了。每次尝试过滤器或拦截器时,尚未添加 Set-Cookie 标头。我能够完成这项工作的唯一方法是添加 Spring Session 并将这个 bean 添加到我的 @Configuration
文件之一中:
@Bean
public CookieSerializer cookieSerializer()
DefaultCookieSerializer serializer = new DefaultCookieSerializer();
serializer.setSameSite("none");
return serializer;
无论如何,希望这可以帮助我遇到同样情况的其他人。
【讨论】:
【参考方案5】:这是不可能的。 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>
【讨论】:
【参考方案6】:在 SpringBoot 中使用拦截器。
我正在寻找添加 SameSite 的解决方案,我只想将属性添加到现有的“Set-Cookie”,而不是创建新的“Set-Cookie”。 我尝试了几种方法来满足这个要求,包括:
-
如@unwichtich 所说,添加自定义过滤器,
以及更多我覆盖了 basicAuthenticationFilter。它确实添加了 SameSite 属性。虽然 Spring 添加“Set-Cookie”的时机很难把握。我认为在 onAuthenticationSuccess() 方法中,响应必须有这个头,但它没有。我不确定这是否是我的自定义 basicAuthenticationFilter 顺序的错。
使用cookieSerializer,但spring-session版本出现问题。似乎只有最新版本支持它,但我仍然无法弄清楚应该将版本号添加到依赖列表中。
不幸的是,上面没有一个可以像预期的那样很好地添加相同的站点。
最后,我发现spring中的interceptor可以帮我实现。 我花了一个星期才拿到它。如果有人遇到同样的问题,希望这可以帮助您。
@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)时,它无法添加samesite,而当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://***.com/a/57622508/4033979
【讨论】:
【参考方案7】:对于 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;
【讨论】:
【参考方案8】:您可以通过使用 ResponseCookie 自己添加 cookie 并将其添加到您的 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());
【讨论】:
【参考方案9】:我已经在没有spring-security
的情况下为spring-webmvc
测试了这个解决方案,但我认为它也应该适用于spring-boot
。
使用来自spring-session-core 的SessionRepositoryFilter
bean
您可以使用 spring Session
扩展默认 java HttpSession
并将 JSESSIONID
cookie 替换为自定义 cookie,如下所示:
Set-Cookie: JSESSIONID=NWU4NzY4NWUtMDY3MC00Y2M1LTg1YmMtNmE1ZWJmODcxNzRj; Path=/; Secure; HttpOnly; SameSite=None
额外的spring 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 - 我在添加新会话之前添加了过期会话的清除。我认为这对于小型应用程序来说可能已经足够了。
【讨论】:
以上是关于Spring Security 中会话 cookie 的同站点标志的主要内容,如果未能解决你的问题,请参考以下文章
Spring-security:带有 COOKIE 的会话配置不起作用
带有会话 Cookie 的 Spring Security RememberMe 服务