cookie作為web瀏覽器的重要組成部分,經常被用於增加使用者的體驗,比如:記住登入名,購物車、跨域共享資料等,但同時也帶來了如CSRF攻擊的安全問題。自Chrome 80開始,谷歌對使用者實施了新的cookie政策,該政策添加了對Samesite的IETF標準的支援,並且預設將cookie的samesite級別設定為lax,這種的策略阻止了開發者對第三方cookie操作,對很多涉及到跨域的系統造成了巨大的影響。
什麼是cookie的samesite屬性?samesite是從chrome 51開始,cookie新增的屬性,主要是用於防止csrf攻擊和使用者追蹤的,主要的屬性如下:(參考文件: Adobe Target和谷歌的SameSite Cookie政策 )
samesite屬性
透過屬性描述,可以瞭解到,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@Configurationpublic 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@Componentpublic 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情況下