Facebook的OAuthWebSecurity未按预期使用电子邮件权限



使用新的OAuthWebSecurity与Facebook进行身份验证,我在Facebook应用程序上添加了电子邮件权限。现在,正如我所读到的,我需要定义一个范围,以便能够在结果中实际获得电子邮件。到目前为止,如果没有范围,我还没有收到用户的电子邮件,也不确定为什么,因为我不知道在哪里定义"范围"。

这只是ASP.NET MVC 4默认身份验证控制器外部登录的翻版。

首先,extraData参数不会传递给facebook。仅供内部使用。请参阅以下链接,了解如何在您的网站上使用这些数据:

http://blogs.msdn.com/b/pranav_rastogi/archive/2012/08/24/customizing-the-login-ui-when-using-oauth-openid.aspx

现在,到肉:

除了OAuthWebSecurity中的方法RegisterFacebookClientRegisterYahooClient等之外,还有一种通用方法RegisterClient。这是我们将用于此解决方案的方法。

这个想法源于提供的代码:http://mvc4beginner.com/Sample-Code/Facebook-Twitter/MVC-4-oAuth-Facebook-Login-EMail-Problem-Solved.html

然而,我们不会使用该解决方案提供的黑客方法。相反,我们将创建一个名为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<string, string> 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<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(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<string, string> 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)

方法中,AccountControllerresult.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。它将自动更新所有依赖项。

现在结果。用户名将包含电子邮件地址而不是您的姓名。

[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/blob/a9d2443ee1a35f13c528cce35b5096abae7128f4/src/DotNetOpenAuth.AspNet/Clients/OAuth2/FacebookClient.cs已在最新的NuGet软件包中更新。

带有修复程序的提交:https://github.com/AArnott/dotnetopenid/commit/a9d2443ee1a35f13c528cce35b5096abae7128f4

我使用了Varun的答案,但我不得不做一个小的修改,以使其适用于我的应用程序,该应用程序托管在AppHarbor上。

AppHarbor必须使用url中的端口号来处理负载平衡。你可以在这里读到更多关于它的信息。简而言之,在AppHarbor上托管时获取当前请求的AbsoluteUri可能会返回端口号不是80的uri。这会导致Facebook身份验证出现问题,因为他们希望您的返回url是您在创建应用程序时指定的url。

问题出现在VerifyAuthentication()中的string rawUrl = context.Request.Url.OriginalString;。如果使用此代码,rawUrl可能包含80以外的某些端口号,从而导致Facebook身份验证失败。相反,用替换该行

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范围和其他功能来检索额外的用户数据。我在这里发布了我自己的解决方案,我希望它能帮助到别人!

由于FB强制要求"使用严格模式重定向URI",因此需要重写请求(对于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<string, string> 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<string, string> userData = JsonConvert.DeserializeObject<Dictionary<string, string>>(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<string, string> 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;
}
/// <summary>
/// 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
/// </summary>
/// 
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.Auth2Client.的抽象方法

在GetServiceLoginUrl方法中,可以将scope参数添加到url中。因此,当您调用OAuthWebSecurity.VerifyAuthentication()方法时,AuthenticationResult.UserName会提供用户的电子邮件。

这里有一个例子。

祝你好运。

它可以。。。像这样:

var fb = new Dictionary<string, object>();
fb.Add("scope", "email,publish_actions");
OAuthWebSecurity.RegisterFacebookClient(
appId: ConfigurationManager.AppSettings["FacebookAppId"],
appSecret: ConfigurationManager.AppSettings["FacebookAppSecret"],
displayName: "FaceBook",
extraData: fb);

最新更新