欢迎光临散文网 会员登陆 & 注册

.Net Core外部登录中的一个坑:Correlation failed

2020-05-11 15:31 作者:杨中科  | 我要投稿

    在开发youzack.com背单词模块的“外部登录”的时候,又遇到另外一个Bug,头疼了好久。今天我通过这篇文章把解决的过程分享出来。这个问题虽然是.Net Core开发中遇到的,但是用别的语言也会遇到这样的问题。

    解决完《解决反向代理后的.Net Core网站进行第三方登录的Bug》这篇文章提到的Bug之后,又遇到另一个问题,那么就是在微信中打开登录页面、拉起QQ登录,登录完成后,跳转回微信,报错“Correlation failed”,没有具体的异常堆栈信息,不知道是哪里抛出的异常。幸好.Net Core是开源的,所以到.Net Core的Microsoft.AspNetCore.Authentication.OAuth源代码中去搜寻,终于在OAuthHandler类中找到了“Correlation failed”,如下:

protected override async Task<HandleRequestResult> HandleRemoteAuthenticateAsync()

{

       var query = Request.Query;

       var state = query["state"];

       var properties = Options.StateDataFormat.Unprotect(state);

       if (properties == null)

       {

              return HandleRequestResult.Fail("The oauth state was missing or invalid.");

       }

       if (!ValidateCorrelationId(properties))

       {

              return HandleRequestResult.Fail("Correlation failed.", properties);

       }

       //...

}

       经过分析代码,得知OAuth跳转到第三方页面之前会在Cookie中写入类似于"AspNetCore.Correlation.QQ.23423424234242423=N"的值,从第三方页面跳转回来以后,第三方网站会把这个值以state参数的方式传递回来,我们的网站需要把本地cookie中的值和state值进行校验比对。这样就可以防范CSRF攻击。

       用上一篇文章介绍的方法,把从第三方页面跳转回来的Http请求的报文打印到日志中,比对在普通浏览器中拉起QQ登录和在微信中拉起QQ登录有什么不懂。我发现在微信中拉起QQ登录完成后返回的页面回调的Http请求中,是没有这个Cookie值的,但是在跳转之前的Http响应报文中,则是有这个Cookie值的。这证明了一点:在跳转到第三方登录页面之前Set-Cookie写入给浏览器的Cookie无效或者被丢失了。

       我之前是模模糊糊记得,在有一些浏览器中,在带重定向的Http响应中设置Cookie的Set-Cookie响应头,会被浏览器忽略。经过搜索,发现有人说有一些浏览器中,重定向到其他域名的Http响应中的Set-Cookie会被忽略。但是经过测试,我的微信中(不是所有人的微信都是这样,不清楚具体受影响的是哪些微信),即使是重定向到本域名下页面的Http响应中的Set-Cookie也会被忽略。

       解决方法有两种,第一种简单就是直接禁用ValidateCorrelationId检查。编写一个MyQQAuthenticationHandler类继承自QQAuthenticationHandler,然后重写ValidateCorrelationId方法,方法里直接返回true。

       然后在Starup里AddQQ的代码改为:

.AddOAuth<QQAuthenticationOptions, MyQQAuthenticationHandler>(QQAuthenticationDefaults.AuthenticationScheme

       , QQAuthenticationDefaults.DisplayName,...);

       这样做的缺点就是禁用了CSRF检查,系统的安全性会降低。

 

       第二种解决方法就是在本网站编写一个页面,这个页面中负责把Cookie写入浏览器,然后再跳转到QQ等第三方登录页面。修改生成重定向页面的代码,让重定向先跳转到这个我们页面。

       先编写一个Action方法:

public IActionResult RedirectToChallengeUrl(string url,string cookieName, string authenticationSchemeName)

{

       StringBuilder sb = new StringBuilder();

       sb.AppendLine("<script>");

       sb.AppendLine($"location.href='{url}';");

       sb.AppendLine("</script>");

       sb.AppendLine($"<a href='{url}'>Continue</a>");

 

       CookieOptions cookieOption = new CookieOptions();

       cookieOption.Expires = DateTimeOffset.UtcNow.AddMinutes(15);

       cookieOption.Path = "/";

       Response.Cookies.Append(cookieName, "N", cookieOption);

       return Content(sb.ToString(),"text/html");

}

       可以看到这里首先构造了一个页面的html,html中通过javascript代码进行了页面的跳转,同时为了防止万一某些浏览器不支持js跳转,还提供了一个让用户手动重定向的【Continue】链接。然后再写入Cookie的值。

       这里没有直接用Response.Redirect()而是构造一个通过js代码进行重定向的原因就是我的微信中“重定向到本域名下页面的Http响应中的Set-Cookie也会被忽略”。

       然后同样编写一个MyQQAuthenticationHandler,这次是重写BuildChallengeUrl,这是一个用来生成跳转到第三方登录页面的地址的方法,我们把它的url修改为先跳转到我们自己的RedirectToChallengeUrl地址:

protected override string BuildChallengeUrl(AuthenticationProperties properties, string redirectUri)

{

       string url = base.BuildChallengeUrl(properties, redirectUri);

       string redirectActionName = "RedirectToChallengeUrl";

       string xsrf = properties.Items[".xsrf"];

       var cookieName = Options.CorrelationCookie.Name + Scheme.Name + "." + xsrf;

       string redirectUrl = $"/Account/Home/{redirectActionName}?url="+Uri.EscapeDataString(url)

              + "&cookieName=" + Uri.EscapeDataString(cookieName) + "&AuthenticationSchemeName=" + Uri.EscapeDataString(this.Scheme.Name);

       return redirectUrl;

}

 

       然后用和第一种解决方法一样的方式注册MyQQAuthenticationHandler。这种方式的优点是保证了安全性。不过,曾经有用户反馈过,它的苹果手机上装了firefox浏览器,在firefox浏览器中访问我们的网站,点击QQ登录,拉起了QQ,但是QQ登录完成后,竟然在safari浏览器中打开了回调页面,跨两个浏览器当然就不可能访问设置的Cookie了,这是这种方案的唯一缺点。

       请根据自己的情况选用合适的方案吧。


.Net Core外部登录中的一个坑:Correlation failed的评论 (共 条)

分享到微博请遵守国家法律