OAuthWebSecurity与Facebook未按预期使用电子邮件权限
使用新的OAuthWebSecurity进行Facebook身份validation,我在我的Facebook应用程序中添加了电子邮件权限。 现在,正如我可以阅读的那样,我需要定义一个范围,以便能够在结果中实际获取电子邮件。 到目前为止没有范围我没有收到用户的电子邮件,我不知道为什么因为我看不到在哪里定义“范围”。
它只是ASP.NET MVC 4默认身份validation控制器外部登录的一小部分。
首先,extraData参数不会传递给facebook。 它仅供内部使用。 请参阅以下链接,了解如何在您的网站上使用此数据:
现在,到肉:
除了OAuthWebSecurity
RegisterFacebookClient
, RegisterYahooClient
等方法OAuthWebSecurity
,还有一个generics方法RegisterClient
。 这是我们将用于此解决方案的方法。
这个想法来自以下网站提供的代码: http : //mvc4beginner.com/Sample-Code/Facebook-Twitter/MVC-4-oAuth-Facebook-Login-EMail-Problem-Solved.html
但是,我们不会使用解决方案提供的hacky方法。 相反,我们将创建一个名为FacebookScopedClient
的新类,它将实现IAuthenticationClient
。 然后我们将使用以下命令注册该类:
OAuthWebSecurity.RegisterClient(new FacebookScopedClient("your_app_id", "your_app_secret"), "Facebook", null);
在AuthConfig.cs中
该类的代码是:
using System.Collections.Generic; using System.IO; using System.Linq; using System.Net; using System.Text; using System.Text.RegularExpressions; using System.Web; public class FacebookScopedClient : IAuthenticationClient { private string appId; private string appSecret; private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id="; public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?"; public const string graphApiMe = "https://graph.facebook.com/me?"; private static string GetHTML(string URL) { string connectionString = URL; try { System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString); myRequest.Credentials = CredentialCache.DefaultCredentials; //// Get the response WebResponse webResponse = myRequest.GetResponse(); Stream respStream = webResponse.GetResponseStream(); //// StreamReader ioStream = new StreamReader(respStream); string pageContent = ioStream.ReadToEnd(); //// Close streams ioStream.Close(); respStream.Close(); return pageContent; } catch (Exception) { } return null; } private IDictionary GetUserData(string accessCode, string redirectURI) { string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode); if (token == null || token == "") { return null; } string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + token.Substring("access_token=", "&")); // this dictionary must contains Dictionary userData = JsonConvert.DeserializeObject>(data); return userData; } public FacebookScopedClient(string appId, string appSecret) { this.appId = appId; this.appSecret = appSecret; } public string ProviderName { get { return "Facebook"; } } public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl) { string url = baseUrl + appId + "&redirect_uri=" + HttpUtility.UrlEncode(returnUrl.ToString()) + "&scope=email"; context.Response.Redirect(url); } public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context) { string code = context.Request.QueryString["code"]; string rawUrl = context.Request.Url.OriginalString; //From this we need to remove code portion rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", ""); IDictionary userData = GetUserData(code, rawUrl); if (userData == null) return new AuthenticationResult(false, ProviderName, null, null, null); string id = userData["id"]; string username = userData["email"]; userData.Remove("id"); userData.Remove("email"); AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData); return result; } }
现在在
public ActionResult ExternalLoginCallback(string returnUrl)
在AccountController
方法, result.ExtraData
应该有电子邮件。
编辑:我在这篇文章中遗漏了一些代码。 我在下面添加它:
public static class String { public static string Substring(this string str, string StartString, string EndString) { if (str.Contains(StartString)) { int iStart = str.IndexOf(StartString) + StartString.Length; int iEnd = str.IndexOf(EndString, iStart); return str.Substring(iStart, (iEnd - iStart)); } return null; } }
干杯!
更新MVC4 Internet项目中的NuGet包。
DotNetOpenAuthCore。 它会自动更新所有依赖项。
现在result.UserName将包含电子邮件地址而不是您的姓名。
[AllowAnonymous] public ActionResult ExternalLoginCallback(string returnUrl) { AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl })); if (!result.IsSuccessful) { return RedirectToAction("ExternalLoginFailure"); } if (OAuthWebSecurity.Login(result.Provider, result.ProviderUserId, createPersistentCookie: false)) { return RedirectToLocal(returnUrl); } if (User.Identity.IsAuthenticated) { // If the current user is logged in add the new account OAuthWebSecurity.CreateOrUpdateAccount(result.Provider, result.ProviderUserId, User.Identity.Name); return RedirectToLocal(returnUrl); } else { // User is new, ask for their desired membership name string loginData = OAuthWebSecurity.SerializeProviderUserId(result.Provider, result.ProviderUserId); ViewBag.ProviderDisplayName = OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName; ViewBag.ReturnUrl = returnUrl; return View("ExternalLoginConfirmation", new RegisterExternalLoginModel { UserName = result.UserName, ExternalLoginData = loginData }); } }
这是为什么?
提交修复: https : //github.com/AArnott/dotnetopenid/commit/a9d2443ee1a35f13c528cce35b5096abae7128f4
我使用了Varun的答案 ,但我不得不进行一些小修改才能让我的应用程序在AppHarbor上托管 。
AppHarbor必须使用url中的端口号做一些时髦的事情来处理负载平衡。 你可以在这里阅读更多相关内容。 简而言之,在AppHarbor上托管时获取当前请求的AbsoluteUri可能会返回一个端口号不是80的uri。这会导致Facebook身份validation出现问题,因为他们希望您的返回URL是您在创建应用时指定的那个。
问题来自string rawUrl = context.Request.Url.OriginalString;
在VerifyAuthentication()
。 如果您使用此代码, rawUrl
可能包含80以外的某些端口号,导致Facebook身份validation失败。 相反,用。替换该行
string rawUrl = GetRawUrl(context.Request.Url);
并将GetRawUrl()
函数添加到类中:
public static string GetRawUrl(Uri url) { var port = url.Port; if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor) port = 80; return new UriBuilder(url) { Port = port }.Uri.AbsoluteUri; }
您需要使用自己的逻辑替换if (SettingsHelper.GetHostingService() == HostingServices.AppHarbor)
,以确定您的应用程序是否在AppHarbor上运行。
我为这个问题编写了自己的解决方案。 我扩展了OAuth2Client以利用它的工作,并使用facebook范围和其他function来检索其他用户数据。 我在这里发布了自己的解决方案,希望能帮到别人!
由于FB强制要求“使用严格模式重定向URI”,因此需要RewriteRequest(对于Google Oauth)。 在OAuthWebSecurity.VerifyAuthentication之前在回调处理中添加以下调用。
FacebookScopedClient.RewriteRequest();
FacebookScopedClient类
using System; using System.Collections.Generic; using System.Text; using DotNetOpenAuth.AspNet; using System.Web; using System.Net; using System.IO; using System.Text.RegularExpressions; using Newtonsoft.Json; namespace UserAccounts.WebApi.ExternalLogin { // Thnks to Har Kaur https://www.c-sharpcorner.com/blogs/facebook-integration-by-using-oauth and https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs public class FacebookScopedClient : IAuthenticationClient { private string appId; private string appSecret; private static string providerName = "Facebook"; private const string baseUrl = "https://www.facebook.com/dialog/oauth?client_id="; public const string graphApiToken = "https://graph.facebook.com/oauth/access_token?"; public const string graphApiMe = "https://graph.facebook.com/me?"; private static string GetHTML(string URL) { string connectionString = URL; try { System.Net.HttpWebRequest myRequest = (HttpWebRequest)WebRequest.Create(connectionString); myRequest.Credentials = CredentialCache.DefaultCredentials; //// Get the response WebResponse webResponse = myRequest.GetResponse(); Stream respStream = webResponse.GetResponseStream(); //// StreamReader ioStream = new StreamReader(respStream); string pageContent = ioStream.ReadToEnd(); //// Close streams ioStream.Close(); respStream.Close(); return pageContent; } catch (WebException ex) { StreamReader reader = new StreamReader(ex.Response.GetResponseStream()); string line; StringBuilder result = new StringBuilder(); while ((line = reader.ReadLine()) != null) { result.Append(line); } } catch (Exception) { } return null; } private IDictionary GetUserData(string accessCode, string redirectURI) { string value = ""; string token = GetHTML(graphApiToken + "client_id=" + appId + "&redirect_uri=" + HttpUtility.UrlEncode(redirectURI) + "&client_secret=" + appSecret + "&code=" + accessCode); if (token == null || token == "") { return null; } if (token != null || token != "") { if (token.IndexOf("access_token") > -1) { string[] arrtoken = token.Replace("\''", "").Split(':'); string[] arrval = arrtoken[1].ToString().Split(','); value = arrval[0].ToString().Replace("\"", ""); } } string data = GetHTML(graphApiMe + "fields=id,name,email,gender,link&access_token=" + value); // this dictionary must contains Dictionary userData = JsonConvert.DeserializeObject>(data); return userData; } public FacebookScopedClient(string appId, string appSecret) { this.appId = appId; this.appSecret = appSecret; } public string ProviderName { get { return providerName; } } public void RequestAuthentication(System.Web.HttpContextBase context, Uri returnUrl) { var uriBuilder = new UriBuilder(returnUrl); uriBuilder.Query = ""; var newUri = uriBuilder.Uri; string returnUrlQuery = HttpUtility.UrlEncode(returnUrl.Query); string url = baseUrl + appId + "&scope=email" + "&state=" + returnUrlQuery + "&redirect_uri=" + HttpUtility.UrlEncode(newUri.ToString()); context.Response.Redirect(url); } public AuthenticationResult VerifyAuthentication(System.Web.HttpContextBase context) { string code = context.Request.QueryString["code"]; string rawUrl = context.Request.Url.OriginalString; //From this we need to remove code portion rawUrl = Regex.Replace(rawUrl, "&code=[^&]*", ""); var uriBuilder = new UriBuilder(rawUrl); uriBuilder.Query = ""; var newUri = uriBuilder.Uri; IDictionary userData = GetUserData(code, newUri.ToString()); if (userData == null) return new AuthenticationResult(false, ProviderName, null, null, null); string id = userData["id"]; string username = userData["email"]; userData.Remove("id"); userData.Remove("email"); AuthenticationResult result = new AuthenticationResult(true, ProviderName, id, username, userData); return result; } /// /// Facebook requires that all return data be packed into a "state" parameter. /// This should be called before verifying the request, so that the url is rewritten to support this. /// Thnks to Matt Johnson mj1856 https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2/blob/master/DotNetOpenAuth.GoogleOAuth2/GoogleOAuth2Client.cs /// /// public static void RewriteRequest() { var ctx = HttpContext.Current; var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]); if (stateString == null || !stateString.Contains("__provider__=" + providerName)) return; var q = HttpUtility.ParseQueryString(stateString); q.Add(ctx.Request.QueryString); q.Remove("state"); ctx.RewritePath(ctx.Request.Path + "?" + q); } } }
我在这里面临同样的问题。 我发现将“scope”参数传递给facebook的唯一方法是编写我自己的OAuth客户端。
为此,您必须扩展并实现DotNetOpenAuth.AspNet.Clients.OAuth2Client的抽象方法。
在GetServiceLoginUrl方法上,您可以将scope参数添加到url。 因此,当您调用OAuthWebSecurity.VerifyAuthentication()方法时,AuthenticationResult.UserName会提供用户的电子邮件。
这里有一个例子。
祝好运。
它可以做到……像这样:
var fb = new Dictionary(); fb.Add("scope", "email,publish_actions"); OAuthWebSecurity.RegisterFacebookClient( appId: ConfigurationManager.AppSettings["FacebookAppId"], appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"], displayName: "FaceBook", extraData: fb);
- 使用ASP.Net MVC RouteConfig的AngularJS Ui-Router。 它是如何工作的?
- 如何处理System.Data.Entity.Validation.DbEntityValidationException?
- 将自定义HTML Helper添加到MVC项目
- HTML.EditorFor有3个小数位
- MVC 4 – 如何将模型数据传递到局部视图?
- ASP.NET MVC 4捆绑 – DEBUG模式下的单个文件URL
- 无法从’System.Data.Objects.ObjectParameter’转换为’System.Data.Entity.Core.Objects.ObjectParameter’
- 需要了解如何获取/显示用户角色
- 充分利用具有n(3)层架构的MVC Owin身份