Chrome80调整SameSite策略对IdentityServer4的影响以及处理方案(翻译)
首先,好消息是goole将于2020年2月份发布chrome 80版本。本次发布将推进google的“渐进改良cookie”策略,打造一个更为安全和保障用户隐私的网络环境。
坏消息是,本次更新可能导致浏览器无法向服务端发送cookie。如果你有多个不同域名的应用,部分用户很有可能出现会话时常被打断的情况。还有部分用户可能无法正常登出系统。
本篇博客将处理第一个问题(无法发送cookie到服务端)。至于第二个问题(cookie无法被删除),请参考另一篇。
首先,samesite是什么
互联网是十分开放的平台:cookie诞生于二十多年前,于2011年修订(rfc 6265)。当时跨站访问攻击(csrf)没有现在这么猖獗,侵犯用户隐私的行为也不像现在这样泛滥。
简而言之,cookie标准规范规定,如果某域名下设置了cookie,不管你是直接跳转到该域名,或是加载了该域名的某些资源(例如图片),或是向该域名发送post请求,亦或将其嵌入iframe,浏览器访问该域名的每次请求,都将带上这个cookie。
对于iframe嵌入的场景,你可能不希望浏览器将用户会话cookie自动发送到服务器,因为这样任何其他网站都可以在用户不知情的情况下,用他的会话上下文,跟你的服务器发送请求。
为了避免这种情况,samesite cookie于2016年起草。对于发送cookie我们有了更多的控制权:现在可以明确指定每个cookie是否被发送。规范引入了同站/跨站cookie的概念,如果浏览器访问a域名,请求服务端也是a域名,这些cookie就叫同站cookies(same-site cookies),如果浏览器访问a域名,请求服务端是b域名,这些cookie就叫跨站cookies(cross-site cookies)。
为了向后兼容,same-site的默认设置并未改变之前的行为。你必须手动指定samesite=lax或者samesite=strict,来能使用这项特性加固安全。所有的.net框架和常见的浏览器都已支持这一特性。设置为lax,大多数情况不允许发送第三方cookie,导航到目标网址的get请求除外。设置为strict,完全禁止第三方cookie,除非你之前访问过该域名而cookie已经存在于浏览器。
悲哀的是,这项新特性的采用率低的可怜(基于chrome2019年3月份显示,在所有的cookie中,只有0.1%使用了samesite标志)。
google决定推进这项特性的使用。他们决定修改世界上最多人使用的浏览器——chrome的默认设置:如果想保持之前处理cookie的方式,chrome 80要求显示指定samesite=none。如果像以前一样忽略samesite属性,chrome将视作samesite=lax。
请注意:samesite=none只有在cookie同时被标记为secure并且使用https连接时才会生效。
更新:如果你想知道关于samesite cookies的更多背景知识,请扩展阅读这篇。
这会影响我吗?什么影响?
如果你有一个单页应用(spa),使用另一域名的认证服务(比如identityserver4)进行身份认证,并且使用了所谓的静默令牌刷新的话,你将受影响。
译者注:使用refresh_token刷新access_token,用户无感知
登录到认证服务的时候,它会为当前用户设置会话cookie,这个cookie属于认证服务域名。认证流程结束之后,另一域名会收到认证服务颁发的access token,有效期通常不会太长。当access token过期之后,应用无法访问api,用户需要频繁的登录,体验十分差。
为了避免这一情况,我们可以使用refresh_token实现静默刷新。应用创建一个用户不可见的iframe,在iframe中进行新的认证流程。iframe中加载了认证服务站点,当浏览器发送会话cookie的时候,认证服务识别出当前用户然后颁发新的token。
spa网站使用iframe嵌入了认证服务站点的内容,这就是一个跨站请求,只有将iframe中属于认证服务站点的cookie设置为samesite=none,chrome 80才会将iframe中的cookie发送到认证服务。否则,token静默刷新将无法正常运行。
可能还会导致一些其他的问题:如果应用中嵌入了其他域名的资源,比如视频自动播放设置,它们需要cookie才能正常运行。某些依赖cookie认证来访问第三方api的应用也会出现问题。
注意:很显然你只能修改自己服务的cookie设置。如果使用了其他域名的资源,这些不在你的控制范围之内,你需要联系第三方修改他们的cookie设置。
好的,我会修改代码将samesite设置为none的,这样就万事大吉了,是吗?
很不幸,并不是:safari存在一个"bug"。这个bug导致safari不会将none识别为samesite的合法值。当safari遇到非法参数值的时候,它会将其视作samesite=strict,不会向认证服务发送会话cookie。ios13和macos 10.15 catalina系统上的safari 13已修复此bug,macos 10.14 mojave和ios 12将不会修复,而这几个版本依旧存在大量用户群。
现在我们进退两难:要么忽略此次更新,chrome用户无法使用静默刷新,要么设置samesite=none,那么无法更新到最新系统的iphone,ipad和mac用户的应用将出现异常。
有没有方法明确知道自己受影响?
幸运的是,你可以。如果你已经设置了samesite=none,应该注意到应用在ios 12,macos 10.4的safari上运行异常。如果还没有设置的话,确保要在上面版本系统的safari上做一下测试。
如果还没有设置的话,可以打开chrome的开发者工具。可以看到这些警告:
a cookie associated with a cross-site resource at {cookie domain} was set without the `samesite` attribute. a future release of chrome will only deliver cookies with cross-site requests if they are set with `samesite=none` and `secure`. you can review cookies in developer tools under application>storage>cookies and see more details at https://www.chromestatus.com/feature/5088147346030592 and https://www.chromestatus.com/feature/5633521622188032.
如果设置了samesite=none但是没有secure标识,将看到如下警告:
a cookie associated with a resource at {cookie domain} was set with `samesite=none` but without `secure`. a future release of chrome will only deliver cookies marked `samesite=none` if they are also marked `secure`. you can review cookies in developer tools under application>storage>cookies and see more details at https://www.chromestatus.com/feature/5633521622188032.
怎样才能修复这个问题?我需要chrome和safari都能正常运行。
我和我的同事boris wilhelms做了一些研究和验证,找到了一个解决方案。微软的barry dorrans写了一篇很不错的可以参考。这个解决方案并不是完美之策,它需要在服务端嗅探浏览器类型,但是它很简单,在过去几周,我们已经用这个方案修复了数个项目。
首先我们需要确保需要通过跨站请求发送的cookie - 比如会话cookie - 被设置为samesite=none并且标识为secure。我们需要在项目中找到cookie选项配置代码,然后做出调整。这样chrome的问题修复了,然后safari会出现问题。
然后我们需要将下面的类和代码段加到项目中。这段代码在asp.net core应用中配置了一个cookie策略。这个策略会检查cookie是否应该被设置位samesite=none。
请注意:这个解决方案是.net core使用的。至于.net framework项目,请查看barry dorran的这篇。
将这个类加到项目中
using microsoft.aspnetcore.builder; using microsoft.aspnetcore.http; namespace microsoft.extensions.dependencyinjection { public static class samesitecookiesservicecollectionextensions { /// <summary> /// -1 defines the unspecified value, which tells aspnet core to not /// send the samesite attribute. with aspnet core 3.1 the /// <seealso cref="samesitemode" /> enum will have a definition for /// unspecified. /// </summary> private const samesitemode unspecified = (samesitemode) (-1); /// <summary> /// configures a cookie policy to properly set the samesite attribute /// for browsers that handle unknown values as strict. ensure that you /// add the <seealso cref="microsoft.aspnetcore.cookiepolicy.cookiepolicymiddleware" /> /// into the pipeline before sending any cookies! /// </summary> /// <remarks> /// minimum aspnet core version required for this code: /// - 2.1.14 /// - 2.2.8 /// - 3.0.1 /// - 3.1.0-preview1 /// starting with version 80 of chrome (to be released in february 2020) /// cookies with no samesite attribute are treated as samesite=lax. /// in order to always get the cookies send they need to be set to /// samesite=none. but since the current standard only defines lax and /// strict as valid values there are some browsers that treat invalid /// values as samesite=strict. we therefore need to check the browser /// and either send samesite=none or prevent the sending of samesite=none. /// relevant links: /// - https://tools.ietf.org/html/draft-west-first-party-cookies-07#section-4.1 /// - https://tools.ietf.org/html/draft-west-cookie-incrementalism-00 /// - https://www.chromium.org/updates/same-site /// - https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ /// - https://bugs.webkit.org/show_bug.cgi?id=198181 /// </remarks> /// <param name="services">the service collection to register <see cref="cookiepolicyoptions" /> into.</param> /// <returns>the modified <see cref="iservicecollection" />.</returns> public static iservicecollection configurenonbreakingsamesitecookies(this iservicecollection services) { services.configure<cookiepolicyoptions>(options => { options.minimumsamesitepolicy = unspecified; options.onappendcookie = cookiecontext => checksamesite(cookiecontext.context, cookiecontext.cookieoptions); options.ondeletecookie = cookiecontext => checksamesite(cookiecontext.context, cookiecontext.cookieoptions); }); return services; } private static void checksamesite(httpcontext httpcontext, cookieoptions options) { if (options.samesite == samesitemode.none) { var useragent = httpcontext.request.headers["user-agent"].tostring(); if (disallowssamesitenone(useragent)) { options.samesite = unspecified; } } } /// <summary> /// checks if the useragent is known to interpret an unknown value as strict. /// for those the <see cref="cookieoptions.samesite" /> property should be /// set to <see cref="unspecified" />. /// </summary> /// <remarks> /// this code is taken from microsoft: /// https://devblogs.microsoft.com/aspnet/upcoming-samesite-cookie-changes-in-asp-net-and-asp-net-core/ /// </remarks> /// <param name="useragent">the user agent string to check.</param> /// <returns>whether the specified user agent (browser) accepts samesite=none or not.</returns> private static bool disallowssamesitenone(string useragent) { // cover all ios based browsers here. this includes: // - safari on ios 12 for iphone, ipod touch, ipad // - wkwebview on ios 12 for iphone, ipod touch, ipad // - chrome on ios 12 for iphone, ipod touch, ipad // all of which are broken by samesite=none, because they use the // ios networking stack. // notes from thinktecture: // regarding https://caniuse.com/#search=samesite ios versions lower // than 12 are not supporting samesite at all. starting with version 13 // unknown values are not treated as strict anymore. therefore we only // need to check version 12. if (useragent.contains("cpu iphone os 12") || useragent.contains("ipad; cpu os 12")) { return true; } // cover mac os x based browsers that use the mac os networking stack. // this includes: // - safari on mac os x. // this does not include: // - chrome on mac os x // because they do not use the mac os networking stack. // notes from thinktecture: // regarding https://caniuse.com/#search=samesite macos x versions lower // than 10.14 are not supporting samesite at all. starting with version // 10.15 unknown values are not treated as strict anymore. therefore we // only need to check version 10.14. if (useragent.contains("safari") && useragent.contains("macintosh; intel mac os x 10_14") && useragent.contains("version/")) { return true; } // cover chrome 50-69, because some versions are broken by samesite=none // and none in this range require it. // note: this covers some pre-chromium edge versions, // but pre-chromium edge does not require samesite=none. // notes from thinktecture: // we can not validate this assumption, but we trust microsofts // evaluation. and overall not sending a samesite value equals to the same // behavior as samesite=none for these old versions anyways. if (useragent.contains("chrome/5") || useragent.contains("chrome/6")) { return true; } return false; } } }
配置并启用cookie策略
在starup中加入下面的代码,使用cookie策略
public void configureservices(iservicecollection services) { // add this services.configurenonbreakingsamesitecookies(); } public void configure(iapplicationbuilder app) { // add this before any other middleware that might write cookies app.usecookiepolicy(); // this will write cookies, so make sure it's after the cookie policy app.useauthentication(); }
ok,完事了吗?
还需要做全面的测试,特别是chrome79,以及受影响的safari版本。
检查一下你的静默token刷新,还有需要cookie的跨站请求,是否正常工作。
这些都没问题就完事了。
可以等identityserver4修复这个问题吗?
不太可能。并不是identityserver在管理这些cookie。identityserver依赖于asp.net core框架内置的认证系统,它们在管理会话cookie。然而微软表示它们不能使用在asp.net core直接嗅探浏览器版本的方案。所以基本上短期内只能靠自己了。
总结
chrome于2020年2月发布的新版本修改了cookie的默认行为。新版本需要samesite明确设置为none,同时有secure标识,才会将该cookie发送到跨站请求。如果你这么做的话,很多版本的safari会出现问题。
为了确保应用在所有浏览器运行正常,我们将所有受影响的cookie设置为secure,samesite=none,然后新增一个cookie策略,根据浏览器版本动态处理samesite设置。
译者注
文中提到的方案需要设置samesitemode=-1,这个新增加的枚举,需要更新微软相补丁包,.net core2.1由于是长期维护版本微软提供了补丁包,.net core 3.x也已经支持。如果是2.2或者其他不再维护的版本,可能需要升级到3.x。详情见下面的博客。
原文地址: