间歇性的ASP.NET oAuth问题与Google有关,AuthenticationManager.GetExternalIdentityAsync返回null。

23

我正在尝试修复使用Google作为外部登录提供者时出现的间歇性问题。

当试图登录时,用户被重定向回登录页面而不是被验证。

问题出现在这一行(下面链接的第55行),GetExternalIdentityAsync返回null。

var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

完整的代码如下:

[Authorize]
public abstract class GoogleAccountController<TUser> : Controller where TUser : Microsoft.AspNet.Identity.IUser
{
    public IAuthenticationManager AuthenticationManager
    {
        get
        {
            return HttpContext.GetOwinContext().Authentication;
        }
    }

    public abstract UserManager<TUser> UserManager { get; set; }

    [AllowAnonymous]
    [HttpGet]
    [Route("login")]
    public ActionResult Login(string returnUrl)
    {
        ViewData.Model = new LoginModel()
        {
            Message = TempData["message"] as string,
            Providers = HttpContext.GetOwinContext().Authentication.GetExternalAuthenticationTypes(),
            ReturnUrl = returnUrl
        };

        return View();
    }

    [AllowAnonymous]
    [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("login")]
    public ActionResult Login(string provider, string returnUrl)
    {
        return new ChallengeResult(provider, Url.Action("Callback", "Account", new { ReturnUrl = returnUrl }));
    }

    [AllowAnonymous]
    [Route("authenticate")]
    public async Task<ActionResult> Callback(string returnUrl)
    {
        var externalIdentity = await AuthenticationManager.GetExternalIdentityAsync(DefaultAuthenticationTypes.ExternalCookie);

        if (externalIdentity == null)
        {
            return RedirectToAction("Login", new { ReturnUrl = returnUrl });
        }

        var emailAddress = externalIdentity.FindFirstValue(ClaimTypes.Email);
        var user = await UserManager.FindByNameAsync(emailAddress);

        if (user != null)
        {
            await SignInAsync(user, false);

            return RedirectToLocal(returnUrl);
        }
        else
        {
            TempData.Add("message", string.Format("The account {0} is not approved.", emailAddress));

            return RedirectToAction("Login", new { ReturnUrl = returnUrl });
        }
    }

    [HttpPost]
    [ValidateAntiForgeryToken]
    [Route("logout")]
    public ActionResult Logout(string returnUrl)
    {
        AuthenticationManager.SignOut();

        return RedirectToLocal(returnUrl);
    }

    private async Task SignInAsync(TUser user, bool isPersistent)
    {
        AuthenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);

        var identity = await UserManager.CreateIdentityAsync(user, DefaultAuthenticationTypes.ApplicationCookie);
        var authenticationProperties = new AuthenticationProperties()
        {
            IsPersistent = isPersistent
        };

        AuthenticationManager.SignIn(authenticationProperties, identity);
    }

    private ActionResult RedirectToLocal(string returnUrl)
    {
        if (Url.IsLocalUrl(returnUrl))
        {
            return Redirect(returnUrl);
        }
        else
        {
            return RedirectToAction("Index", "Home");
        }
    }

    protected override void Dispose(bool disposing)
    {
        if (disposing && UserManager != null)
        {
            UserManager.Dispose();
            UserManager = null;
        }

        base.Dispose(disposing);
    }
}

这里也有相关代码

这是一个间歇性的问题,重新部署应用程序通常会暂时解决此问题。

在 Fiddler 中查看,我可以看到在 authenticate 方法之前调用了 sign-google,并且它找不到 cookie。

Fiddler 截图

该应用程序使用以下代码初始化 Google 登录。

app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
        LoginPath = new PathString("/login")
    });
app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
app.UseGoogleAuthentication();

我已经在web.config中将验证模式设置为“non”,并且删除了表单验证模块。

<system.web>
    <authentication mode="None" />
</system.web>    
<system.webServer>
    <validation validateIntegratedModeConfiguration="false" />    
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="FormsAuthenticationModule" />
    </modules>
</system.webServer>

这些网站托管在Azure上,有些运行在一个实例上,有些运行在两个实例上。

它们有自定义域名,但仍然无法在自定义域名和azurewebsites域名以及http / https上运行。

有人能帮忙解决这个问题吗?

更新

Microsoft.Owin.Security.Google的3.0版本昨晚发布。我们将切换到这个版本,看看是否可以解决这个问题。

https://www.nuget.org/packages/Microsoft.Owin.Security.Google


我刚遇到了一个类似的间歇性oAuth问题,原因是我连接的API网络上的负载均衡器。这可能会给你提供其他需要调查的方向(超出你的代码范畴)。 - brendo234
7个回答

3

我曾经遇到了一个无法解释的问题,但通过比较两个项目,我最终解决了它。其中一个测试项目每次都能正常运行,而另一个项目则不行。我发现它们有着完全相同的代码,但是使用了不同版本的dll。

Nuget引用包是问题的症结所在。

确保你拥有最新的包,并且检查你的web.config中的runtime部分。


当我更新了所有与Owin相关的包和Microsoft.Owin,并添加了以下内容后:

<assemblyBinding>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security.Cookies" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
  <dependentAssembly>
    <assemblyIdentity name="Microsoft.Owin.Security.OAuth" publicKeyToken="31bf3856ad364e35" culture="neutral" />
    <bindingRedirect oldVersion="0.0.0.0-3.0.1.0" newVersion="3.0.1.0" />
  </dependentAssembly>
</assemblyBinding>

...它又起作用了! 根据你使用的软件包可能会有所不同,但这就是对我起作用的方法。


2

2

微软的Owin实现中存在一个bug,它是在System.Web上运行时使用的。这可能是我们中99%的人使用的方式,如果我们正在使用基于新的Owin的认证处理与ASP.NET MVC5。

这个bug会导致由Owin设置的Cookie在某些情况下神秘地消失。

app.UseGoogleAuthentication(...)之前放置这个nuget:https://github.com/KentorIT/owin-cookie-saver


1

我认为你不应该使用 app.UseGoogleAuthentication();,因为这会尝试使用已经被弃用的 OpenID 2.0。
相反,你应该使用 OAuth 2.0 for Login (OpenID Connect)
所以:

  1. 在 Google Developers Console 中注册你的应用程序
  2. 使其能够访问 Google+ API(即使你不打算直接使用 Google+ - 它现在作为身份验证手段使用)
  3. 通过这种方式启用 ASP.NET Identity 的 Google 身份验证
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions()  
{  
    ClientId = "YOUR_CLIENT_ID",  
    ClientSecret = "YOUR_CLIENT_SECRET",  
});

我们确实尝试过这样做,因为在较新版本的Microsoft.Owin.Security.Google中现在需要这样做,但是我们仍然遇到了相同的问题。 - Tom

1

Tom,我正在使用REST API在我的asp.net应用程序中使用google-oauth。它工作正常,我没有遇到任何连接问题。

我执行以下步骤:

1.我在google开发者控制台中创建了一个项目,其中我创建了“Web应用程序的客户端ID”设置,其中包含以下参数。

a)客户端ID =>将自动生成 b)电子邮件地址=>将自动生成 c)客户端密钥=>将自动生成 d)重定向URI =>需要指定用于处理身份验证过程的网页的URL。在此页面上,我们可以进行身份验证并获取用户的基本信息。

my url: "http://localhost:1822/WebForm1.aspx/code"

我的使用情况:

  1. 我创建了一个示例项目,其中包含“Webpage1.aspx”和“Webpage2.aspx”。

我将“Webpage2.aspx”设置为启动页面,并在其中形成开放认证 URL,并重定向到 Google 登录页面。

Google Open Auth url Formation

登录后,它将重定向到“Webpage1.aspx”,并附带访问代码。通过将此访问代码传递到“https://accounts.google.com/o/oauth2/token”网址,我获取访问令牌以及令牌类型和令牌过期时间。之后,通过将此访问传递到“https://www.googleapis.com/oauth2/v2/userinfo”网址,我获取用户基本信息,例如“电子邮件、姓名、性别、照片等)

示例代码

    public class GoogleAuthorizationData
    {
        public string access_token { get; set; }
        public int expires_in { get; set; }
        public string token_type { get; set; }

    }

  public class GoogleUserInfo
    {
        public string name { get; set; }
        public string family_name { get; set; }
        public string gender { get; set; }
        public string email { get; set; }
        public string given_name { get; set; }
        public string picture { get; set; }
        public string link { get; set; }
        public string id { get; set; }

    }

  Webpage1.aspx
  ============
 protected void Page_Load(object sender, EventArgs e)
        {
            string code = Request.QueryString["code"].ToString();
            string scope = Request.QueryString["scope"].ToString();
            string url = "https://accounts.google.com/o/oauth2/token";
            string postString = "code=" + code + "&client_id=" + ConfigurationManager.AppSettings["GoogleClientID"].ToString() + "&client_secret=" + ConfigurationManager.AppSettings["GoogleSecretKey"].ToString() + "&redirect_uri=" + ConfigurationManager.AppSettings["ResponseUrl"].ToString() + "&grant_type=authorization_code";

            HttpWebRequest request = (HttpWebRequest)WebRequest.Create(url.ToString());
            request.Method = "POST";
            request.ContentType = "application/x-www-form-urlencoded";

            UTF8Encoding utfenc = new UTF8Encoding();
            byte[] bytes = utfenc.GetBytes(postString);
            Stream os = null;
            try
            {
                request.ContentLength = bytes.Length;
                os = request.GetRequestStream();
                os.Write(bytes, 0, bytes.Length);
            }
            catch
            { }

            try
            {
                HttpWebResponse webResponse = (HttpWebResponse)request.GetResponse();
                Stream responseStream = webResponse.GetResponseStream();
                StreamReader responseStreamReader = new StreamReader(responseStream);
                var result = responseStreamReader.ReadToEnd();//
                var json = new JavaScriptSerializer();

                GoogleAuthorizationData authData = json.Deserialize<GoogleAuthorizationData>(result);

                HttpWebRequest request1 = (HttpWebRequest)WebRequest.Create("https://www.googleapis.com/oauth2/v2/userinfo");
                request1.Method = "GET";
                request1.ContentLength = 0;
                request1.Headers.Add("Authorization", string.Format("{0} {1}", authData.token_type, authData.access_token));
                HttpWebResponse webResponse1 = (HttpWebResponse)request1.GetResponse();
                Stream responseStream1 = webResponse1.GetResponseStream();
                StreamReader responseStreamReader1 = new StreamReader(responseStream1);
                GoogleUserInfo userinfo = json.Deserialize<GoogleUserInfo>(responseStreamReader1.ReadToEnd());
               Response.Write(userinfo.email);

            }
            catch (Exception eX)
            {
                throw eX;
            }





        }

1
我有同样的问题。我正在使用Visual Studio 2013,网站托管在Azure上。社交登录一直没有问题,但突然停止工作,LinkLoginCallback收到了loginInfo的null值。我重新发布项目,没有任何代码更改或重建,然后loginInfo接收到了正确的数据,所有功能都正常工作。这没有道理,但就是这样。

0
确保启用第三方Cookie。我发现,如果您在尝试使用应用程序“注册”用户时未登录Google,则会重定向到登录页面,因为它正在寻找不存在的Cookie,但仍然能够通过外部提供商完成所需操作。下次尝试“注册”时,由于已经完成了部分过程,不再需要查找外部Cookie,因此第二次尝试将成功。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接