Google Chrome Samesite cookie 新策略带来跨域问题解决

cookie作为web浏览器的重要组成部分,经常被用于增加用户的体验,比如:记住登录名,购物车、跨域共享数据等,但同时也带来了如CSRF攻击的安全问题。自Chrome 80开始,谷歌对用户实施了新的cookie政策,该政策添加了对Samesite的IETF标准的支持,并且默认将cookie的samesite级别设置为lax,这种的策略阻止了开发者对第三方cookie操作,对很多涉及到跨域的系统造成了巨大的影响。

什么是cookie的samesite属性?

samesite是从chrome 51开始,cookie新增的属性,主要是用于防止csrf攻击和用户追踪的,主要的属性如下:(参考文档: Adobe Target和谷歌的SameSite Cookie政策

描述
Strict只有在访问最初设置的域时,才可访问具有此设置的 Cookie。换言之,Strict 会完全阻止跨站点使用 Cookie。这一选择最适合需要高安全性的应用程序,如银行。
LaxCookie with this setting are sent only on same-site requests or top-level navigation with non-idempotent HTTP requests, like HTTP GET . 因此,如果第三方可以使用Cookie,但增加了安全优势,保护用户免受CSRF攻击的侵害,则使用此选项。
None使用此设置的Cookie将像Cookie现在的工作方式一样工作。

通过属性描述,可以了解到,samesite属性主要是用于控制跨站cookie的操作,默认情况下,属性为lax,在该属性下只有少数方式可以使用跨域cookie,这对很多现有的跨域系统带来巨大的困扰,需要通过将cookie的samesite设置为none才能让新的浏览器下的cookie像在旧浏览器下的cookie一样工作。同时,改配置必须与 Secure并行,也就是cookie必须通过https传输才能生效(该配置可以通过chrome配置去掉,服务端可以通过非https方式实现samesite=none的配置)。

如何解决(Java)

解决方案1:通过response写入(非HTTPS请求可以生效)

public class CookieUtil {
    public static void setDefaultCookie(HttpServletResponse response, String name, String value, int maxAge) {
        try {
            value = URLEncoder.encode(value, "UTF-8");
        } catch (UnsupportedEncodingException e) {
        }
        String cookie = newCookie(name, value, "test.com", maxAge,"/","None");
        response.addHeader("Set-Cookie",cookie);
//        response.setHeader("Set-Cookie",cookie);//只会写入一次
    }
    /**
     * 获取cookies 配置
     * dd=dd; Max-Age=518400; Domain=test.com; Path=/; SameSite=None
     * @param name
     * @param value
     * @param domain
     * @param maxAge
     * @param path
     * @return
     */
    private static String newCookie(String name, String value, String domain, int maxAge,String path,String sameSite ){
        String strTemp="${COOKIE_KEY}=${COOKIE_VALUE}; Max-Age=${MAXAGE}; Domain=${DOMAIN}; Path=${PATH}; SameSite=${SAMESITE};Secure";
        String cookieStr=strTemp.replace("${COOKIE_KEY}",name).
                replace("${COOKIE_VALUE}",value).
                replace("${MAXAGE}",maxAge+"").
                replace("${DOMAIN}",domain).
                replace("${PATH}",path).
                replace("${SAMESITE}",sameSite);
        return cookieStr;
    }
}

注意: setHeader与addHeader的区别

通过response设置header的时候,可以通过setHeader与addHeader两种方式进行设置,但是需要注意的是addHeader允许重复设置,而setHeader是不允许重复设置的,由于配置cookies的key为Set-Cookie,如果使用setHeader的方式进行设置,只会有一个cookie生效,所以在此需要用addHeader的方式进行设置。

setHeader源码
addHeader源码

解决方案2:基于springboot,修改tomcat配置 (参考Same-Site flag for session cookie in Spring Security

经测试,该方案需要在springboot2.2.3版本以上,springboot内置版本为9.0.30才支持(更早版本未全部测试,2.1.*暂不支持)

@Component
@Configuration
public class WebConfiguration implements WebMvcConfigurer {
    @Bean
    public TomcatContextCustomizer sameSiteCookiesConfig() {
       return new TomcatContextCustomizer() {
            @Override
            public void customize(Context context) {
                final Rfc6265CookieProcessor cookieProcessor = new Rfc6265CookieProcessor();
                cookieProcessor.setSameSiteCookies(SameSiteCookies.NONE.getValue());
                context.setCookieProcessor(cookieProcessor);
            }
        };
    }
}
        Cookie cookie=new Cookie("test","test");
        cookie.setDomain("test.com");
        cookie.setPath("/");
//      cookie.setSecure(true);//添加后,http无法添加cookie
        response.addCookie(cookie);

该配置是通过重写定制化tomcat配置的方式,使所有的cookie都上samesite=None的配置。代码虽然在springboot2.1.6版本就可以用,但是并未生效。

网络方案(测试不一定通)

1、 通过filter重设cookie

网传解决方案

该方案在看起来没毛病,可是真实操作的时候,却有致命的问题,无论addSameSiteCookieAttribute方法中的for循环操作了几次,addHeader都不会改变header,cookie也不会写回客户端,这是因为,在tomcat下response在addHeader的时候,会判断response是否提交,若已经提交,addHeader将被直接跳过(Filter 中 addCookie 不生效问题)。

上图中addSameSiteCookieAttribute发生在doFilter之后,此时response的提交状态为已提交,所以将不会再往header里面添加内容,故功能无法实现。若将addSameSiteCookieAttribute提前,由于filter比controller早执行,无法拿到controller层的cookie,亦无法达到效果。

response的isCommitted是什么时候提交的?

springMvc的RequestMappingHandlerAdapter进行请求处理后 ,会进行一次prepareResponse的操作,该操作会将response设置为已提交。

2、 使用AOP方式重设cookie

@Aspect
@Component
public class CookiesAspect {
    @After("execution(public * com.example.demo.controller.*Controller.*(..))")
    public void handlerSameSite(JoinPoint joinPoint) throws Throwable {
        addSameSite(joinPoint);
    }
    private void addSameSite(JoinPoint joinPoint) {
        RequestAttributes ra = RequestContextHolder.getRequestAttributes();
        ServletRequestAttributes sra = (ServletRequestAttributes) ra;
        HttpServletResponse response = sra.getResponse();
        Collection<String> headers = response.getHeaders(HttpHeaders.SET_COOKIE);
        for (String header : headers) {
            response.addHeader(HttpHeaders.SET_COOKIE, String.format("  %s;%s", header, "Samesite=None;Secure"));
        }
    }
}

此段代码亲测可行,可是存在一个不可靠的因数,因为addHeader是往header中添加cookie,不会删除原有的cookie,这就会导致返回报文中,有对同一个cookie设置的情况。该情况在不通浏览器下体现不一样。对于这种模棱两可的因数,不用为妙。

请求监控

chrome80下的情况,如下:

chrome80下

360浏览器的情况如下:

360情况下

Leave a Comment

邮箱地址不会被公开。 必填项已用*标注