26

是否可以在 Spring Security中设置Same-site Cookie标志?

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

4

10 回答 10

19

新的 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;
于 2020-03-26T03:17:39.187 回答
9

在您的身份验证成功处理程序中,您可以以这种方式提及,而不是过滤器。

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

在其中一个答案中提到了这一点。实现后找不到链接。

于 2019-11-22T14:54:58.183 回答
6

这里所有可能的解决方案对我来说都失败了。每次我尝试过滤器或拦截器时,尚未添加 Set-Cookie 标头。我能够完成这项工作的唯一方法是添加 Spring Session 并将这个 bean 添加到我的一个@Configuration文件中:

@Bean
public CookieSerializer cookieSerializer() {
    DefaultCookieSerializer serializer = new DefaultCookieSerializer();
    serializer.setSameSite("none");
    return serializer;
}

无论如何,希望这可以帮助我遇到同样情况的其他人。

于 2020-04-15T00:51:40.150 回答
6

如果您可以获得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"/>
于 2017-04-06T08:46:20.407 回答
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>
于 2020-03-25T00:19:05.617 回答
3

在 SpringBoot 中使用拦截器。

我正在寻找与您一样添加 SameSite 的解决方案,我只想将属性添加到现有的“Set-Cookie”,而不是创建新的“Set-Cookie”。我尝试了几种方法来满足这个要求,包括:

  1. 正如@unwichtich 所说,添加自定义过滤器,
  2. 还有更多我覆盖了basicAuthenticationFilter。它确实添加了 SameSite 属性。虽然 Spring 添加“Set-Cookie”的时机很难把握。我认为在 onAuthenticationSuccess() 方法中,响应必须具有此标头,但事实并非如此。我不确定这是否是我的自定义 basicAuthenticationFilter 订单的错。
  3. 使用 cookieSerializer,但是 spring-session 版本出现了问题。似乎只有最新版本支持它,但我仍然无法弄清楚应该将版本号添加到依赖项列表中。
    不幸的是,上面没有一个可以像预期的那样添加相同的站点。

最后发现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

于 2020-02-10T10:33:21.787 回答
1

我已经测试了这个解决方案,spring-webmvcspring-security我认为它也应该适用于spring-boot.

使用来自spring-session-coreSessionRepositoryFilter的bean

HttpSession您可以使用spring 扩展默认 javaSession并用自定义替换JSESSIONIDcookie,如下所示:

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

可以使用以下方法设置额外的spring cookie 标志:SessionDefaultCookieSerializer

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

于 2020-09-29T22:54:37.550 回答
1

您可以自己添加 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());
于 2021-06-17T13:28:20.780 回答
1

对于 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;
    }
}
于 2021-03-25T21:41:09.357 回答
0

显然,使用 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
于 2022-02-16T16:45:17.603 回答