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 服务

Spring Security 返回 403 而不是 401 并创建无效的 Redis 会话 cookie

使用没有 Cookie 的 Spring Security

Spring Security 会话 JSESSIONID

Spring Security - 需要添加自定义cookies