Spring Security中的Same-Site cookie

时间:2017-03-24 11:28:23

标签: security cookies spring-security csrf-protection jsessionid

是否可以在Spring Security中设置Same-site Cookie标志? 请参阅:https://tools.ietf.org/html/draft-west-first-party-cookies-07 如果没有,请在路线图上添加支持吗?某些浏览器(即Chrome)已经支持。 T.H。

9 个答案:

答案 0 :(得分:7)

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 BootSameSite cookie recipes


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

if ($scheme = http) {
    return 301 https://$http_host$request_uri;
}

proxy_cookie_path / "/; secure; SameSite=None";

答案 1 :(得分:5)

不可能。 Spring Session中支持此功能:https://spring.io/blog/2018/10/31/spring-session-bean-ga-released

我想出了一种类似于罗恩(Ron)的解决方案。但是有一件重要的事情要注意:

用于跨站点使用的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>

答案 2 :(得分:4)

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

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

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

答案 3 :(得分:3)

在SpringBoot中使用拦截器。

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

  1. 按照@unwichtich的说明添加自定义过滤器,
  2. 还有更多我取代了basicAuthenticationFilter。它确实添加了SameSite属性。当Spring添加“ Set-Cookie”的时机很难把握。我认为在onAuthenticationSuccess()方法中,响应必须具有此标头,但事实并非如此。我不确定这是否是我的自定义basicAuthenticationFilter顺序的错误。
  3. 使用cookieSerializer,但是春季会话版本出现了问题。似乎只有最新版本支持它,但我仍然不知道应该将版本号添加到依赖项列表中。
    不幸的是,以上所有方法都无法按预期添加相同的站点。

最后,我发现春季的拦截器可以帮助我做到这一点。 我花了一个星期才得到它。希望如果有人遇到同样的问题,可以为您提供帮助。

@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)时,它无法添加相同的站点,而在SSO时它使我的应用程序失败。最终,我必须使用Tomcat cookie,因为我没有在我的springboot应用程序中嵌入tomcat。我添加

<Context>
    <CookieProcessor sameSiteCookies="none" />
</Context>
在我的应用程序/ META-INF下的context.xml中

。它将在每个响应的set-cookie标头中添加SameSite属性。请注意,从Tomcat 9.0.21和8.5.42开始,此行为是可能的。根据{{​​3}}

答案 4 :(得分:1)

如果您可以获得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"/>

答案 5 :(得分:1)

我已经针对没有spring-webmvc的{​​{1}}测试了该解决方案-请参阅simple feedback form,但我认为它也适用于spring-security


使用SessionRepositoryFilter中的spring-session-core bean

您可以使用 spring HttpSession扩展默认的Java Session,并用自定义的cookie替换spring-boot cookie,如下所示:

JSESSIONID

其他 spring Session cookie标志可以使用DefaultCookieSerializer设置:

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

我扩展了MapSessionRepository的实现,因为它不支持触发SessionDeletedEvent或SessionExpiredEvent -我在添加新会话之前添加了清除过期会话的功能。我认为这对于一个小型应用程序就足够了。

答案 6 :(得分:0)

您可以在身份验证成功处理程序中代替过滤器。


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

答案之一中提到了它。实现链接后找不到链接。

答案 7 :(得分:0)

对于 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 :(得分:0)

您可以通过使用 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());