C# Can';t在Web API中使用外部身份验证提供程序创建新用户(Visual Studio 2013更新2)

C# Can';t在Web API中使用外部身份验证提供程序创建新用户(Visual Studio 2013更新2),c#,asp.net,facebook,authentication,asp.net-web-api,C#,Asp.net,Facebook,Authentication,Asp.net Web Api,问题: 我正在尝试使用默认的WebAPI模板项目进行外部身份验证。我使用以下说明添加了对外部身份验证服务(FB/Google/Microsoft)的支持: 作为记录,我能够使用默认SPA模板项目进行外部身份验证。 此外,新的本地用户创建工作正常 当我尝试使用我的客户端应用程序(基于WPF)使用外部提供商(如FB)注册用户时,问题就出现了 为了记录在案,我使用这两篇文章作为起点:在Stack Overflow中使用thread#21065648。 他们真的帮助我理解了整个逻辑 以下是我所做步骤的简

问题:

我正在尝试使用默认的WebAPI模板项目进行外部身份验证。我使用以下说明添加了对外部身份验证服务(FB/Google/Microsoft)的支持:

作为记录,我能够使用默认SPA模板项目进行外部身份验证。 此外,新的本地用户创建工作正常

当我尝试使用我的客户端应用程序(基于WPF)使用外部提供商(如FB)注册用户时,问题就出现了

为了记录在案,我使用这两篇文章作为起点:在Stack Overflow中使用thread#21065648。 他们真的帮助我理解了整个逻辑

以下是我所做步骤的简要概述: 两个窗口,一个用于外部身份验证提供程序,一个用于嵌入式WebBrowser 流程: 2.1. 用户打开一个应用程序,主窗口出现 2.2. 用户单击按钮以获取所有受支持的外部身份验证提供程序的列表, 下面是代码中发生的情况:

            var client = new HttpClient();

            client.BaseAddress = new Uri(baseAddress);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            HttpResponseMessage loginResponse = await client.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
            if (loginResponse.IsSuccessStatusCode)
            {
            var externalLoginProviders = await loginResponse.Content.ReadAsAsync<IEnumerable<AuthenticationProvider>>();

            // cleaning resources
            client.Dispose();
            loginResponse.Dispose();

            // obtained data is sent to UI
然后解析该地址,并以结构化形式保存地址参数(令牌等),以供以后使用

之后,我转到api/UserInfo以了解用户是否已使用此外部身份验证提供程序登录:

            var client = new HttpClient();

            client.BaseAddress = new Uri(baseAddress);
            client.DefaultRequestHeaders.Accept.Clear();
            client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));

            // setting Bearer Token obtained from Auth provider
            client.SetBearerToken(result.AccessToken);

            // calling /api/Account/UserInfo
            var userInfoResponse = await client.GetAsync("api/Account/UserInfo");
            var userInfoMessage = userInfoResponse.Content.ReadAsStringAsync().Result;

            var userInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(userInfoMessage);

            // cleaning resources
            client.Dispose();
            userInfoResponse.Dispose();

            if (userInfo.hasRegistered == true)
            {
                // going to login
            }   
带来null,因此系统无法使用提供的承载令牌正确地进行身份验证

我不明白为什么。看起来框架中有些东西坏了,或者我做错了什么

解决方案:

感谢@berhir,以下是解决方案:

  • 在MainWindow.xaml.cs中,定义cookieContainer

            CookieContainer cookieContainer;
    
  • 在MainWindow_加载的事件处理程序中,实例化它:

            // we create new cookie container
            cookieContainer = new CookieContainer();
    
  • 一旦用户要求应用程序显示所有外部登录提供商,使用@berhir建议的代码实例化HttpClient:

            using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
            using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
            {
                // send request
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                HttpResponseMessage loginResponse = await client.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
    
  • 一旦获得外部身份验证提供程序列表,下一步就是显示第二个窗口,其中包含嵌入式WebBrowser。在这里,您必须为cookie声明两个WinAPI调用:

            [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern bool InternetSetCookie(string lpszUrlName, string lbszCookieName, string lpszCookieData);
    
            [DllImport("wininet.dll", SetLastError = true)]
            public static extern bool InternetGetCookieEx(string url, string cookieName, StringBuilder cookieData, ref int size, Int32 dwFlags, IntPtr lpReserved); 
    
            // and
    
            private const Int32 InternetCookieHttponly = 0x2000;
    
            /// <summary>
            /// Gets the URI cookie container.
            /// </summary>
            /// <param name="uri">The URI.</param>
            /// <returns></returns>
            public static CookieContainer GetUriCookieContainer(Uri uri)
            {
                CookieContainer cookies = null;
                // Determine the size of the cookie
                int datasize = 8192 * 16;
                StringBuilder cookieData = new StringBuilder(datasize);
                if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
                {
                    if (datasize < 0)
                    return null;
                    // Allocate stringbuilder large enough to hold the cookie
                    cookieData = new StringBuilder(datasize);
                    if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
                        return null;
                }
                if (cookieData.Length > 0)
                {
                    cookies = new CookieContainer();
                    cookies.SetCookies(uri, cookieData.ToString().Replace(';', ','));
                }
                return cookies;
            }
    
  • 在那里,我们发出两个新请求,分别是api/Account/UserInfo和api/Account/RegisterExternal:

    this.cookieContainer=result.cookieContainer;//其中result是AuthResult,其中包含从WebBrowser会话获得的cookie
    使用(var handler=new-HttpClientHandler(){CookieContainer=CookieContainer})
    使用(var client=newhttpclient(handler){BaseAddress=BaseAddress})
    {
    //发送请求
    client.DefaultRequestHeaders.Accept.Clear();
    client.DefaultRequestHeaders.Accept.Add(新的MediaTypeWithQualityHeaderValue(“应用程序/json”);
    //设置从身份验证提供程序获取的承载令牌
    client.SetBearerToken(result.AccessToken);
    //调用/api/Account/UserInfo
    var userInfoResponse=await client.GetAsync(“api/Account/UserInfo”);
    var userInfo=Newtonsoft.Json.JsonConvert.DeserializeObject(userInfoMessage);
    if(userInfo.hasRegistered==false)
    {
    var data=newdictionary();
    data.Add(“Email”,this.externalUserEmailTextBox.Text);
    var registerexternalur=新Uri(string.Concat(baseAddress,@“api/Account/RegisterExternal”);
    var内容=新的FormUrlEncodedContent(数据);
    var response=client.PostAsync(registerexternalur.ToString(),content).Result;
    //获取内容
    var responseContent=response.Content.ReadAsStringAsync().Result;
    if(response!=null&&response.issucessStatusCode)
    {
    MessageBox.Show(“新用户注册,带有“+result.ProviderName+”帐户”);
    }
    

  • 好了,我们开始吧。从第一次HttpClient请求到我们使用api/account/registerExternal注册新用户的最后一刻,整个生命周期都在使用cookie。

    您似乎忘记了处理cookie。最简单的方法是对所有请求使用相同的HttpClient实例,或者您可以使用相同的CookieContainer。

    var cookieContainer = new CookieContainer();
    using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
    using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
    {
        // send request
    }
    

    Bernd,这太神奇了!我做了以下工作:1.在MainWindow.xaml.cs中定义CookieContainer,并在MainWindow_加载的事件中实例化它2.一旦用户单击“显示外部提供者”按钮,该CookieContainer将根据代码3分配给新创建的HttpClient。一旦用户选择登录提供者并单击“登录”,调用InternetSetCookie以将所有现有Cookie添加到WebBrowser 4。经过身份验证后,InternetGetCookieEx用于从WebBrowser 5获取所有Cookie。这些Cookie用于调用api/Account/UserInfo和api/Account/RegisterExternal
            using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
            using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
            {
                // send request
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                HttpResponseMessage loginResponse = await client.GetAsync("api/Account/ExternalLogins?returnUrl=%2F&generateState=true");
    
            [DllImport("wininet.dll", CharSet = CharSet.Auto, SetLastError = true)]
            public static extern bool InternetSetCookie(string lpszUrlName, string lbszCookieName, string lpszCookieData);
    
            [DllImport("wininet.dll", SetLastError = true)]
            public static extern bool InternetGetCookieEx(string url, string cookieName, StringBuilder cookieData, ref int size, Int32 dwFlags, IntPtr lpReserved); 
    
            // and
    
            private const Int32 InternetCookieHttponly = 0x2000;
    
            /// <summary>
            /// Gets the URI cookie container.
            /// </summary>
            /// <param name="uri">The URI.</param>
            /// <returns></returns>
            public static CookieContainer GetUriCookieContainer(Uri uri)
            {
                CookieContainer cookies = null;
                // Determine the size of the cookie
                int datasize = 8192 * 16;
                StringBuilder cookieData = new StringBuilder(datasize);
                if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
                {
                    if (datasize < 0)
                    return null;
                    // Allocate stringbuilder large enough to hold the cookie
                    cookieData = new StringBuilder(datasize);
                    if (!InternetGetCookieEx(uri.ToString(), null, cookieData, ref datasize, InternetCookieHttponly, IntPtr.Zero))
                        return null;
                }
                if (cookieData.Length > 0)
                {
                    cookies = new CookieContainer();
                    cookies.SetCookies(uri, cookieData.ToString().Replace(';', ','));
                }
                return cookies;
            }
    
        // set cookies
        var cookies = cookieContainer.GetCookies(baseAddress).OfType<Cookie>().ToList();
        foreach (var cookie in cookies)
        {
            InternetSetCookie(startUrl, cookie.Name, cookie.Value); 
        } 
    
            if (this.callback != null)
            {
                var cookies = GetUriCookieContainer(e.Uri);
    
                this.callback(new AuthResult(e.Uri, this.providerName, cookies));
            }
    
            this.cookieContainer = result.CookieContainer; // where result is AuthResult containing the cookies obtained from WebBrowser's session
    
            using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
            using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
            {
                // send request
                client.DefaultRequestHeaders.Accept.Clear();
                client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
    
                // setting Bearer Token obtained from Auth provider
                client.SetBearerToken(result.AccessToken);
    
                // calling /api/Account/UserInfo
                var userInfoResponse = await client.GetAsync("api/Account/UserInfo");
    
                var userInfo = Newtonsoft.Json.JsonConvert.DeserializeObject<UserInfo>(userInfoMessage);
    
            if (userInfo.hasRegistered == false)
            {
                var data = new Dictionary<string, string>();
                data.Add("Email", this.externalUserEmailTextBox.Text);
    
                var registerExternalUrl = new Uri(string.Concat(baseAddress, @"api/Account/RegisterExternal"));
    
                var content = new FormUrlEncodedContent(data);
    
                var response = client.PostAsync(registerExternalUrl.ToString(), content).Result;
    
                // obtaining content
                var responseContent = response.Content.ReadAsStringAsync().Result;
    
                if (response != null && response.IsSuccessStatusCode)
                {
                    MessageBox.Show("New user registered, with " + result.ProviderName + " account");
                }
    
    var cookieContainer = new CookieContainer();
    using (var handler = new HttpClientHandler() { CookieContainer = cookieContainer })
    using (var client = new HttpClient(handler) { BaseAddress = baseAddress })
    {
        // send request
    }