提供的 Facebook OAuth 客户端不会让您获得超出默认信息的任何内容。要获得其他任何东西,您需要能够更改scope
参数的值,这是包含的客户端不允许的。因此,要解决这个问题并仍然使用 Internet 模板提供的其他样板代码,您需要实现一个遵循相同模式的自定义 OAuth 客户端。
由于整个 ASP.NET 源代码以及 OAuth 库 DotNetOpenAuth 都是开源的,因此您实际上可以查看 OAuth 库并确切了解 Facebook 提供程序是如何构建的。使用它,我能够想出这个:
using System;
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Net;
using System.Web;
using DotNetOpenAuth.Messaging;
using DotNetOpenAuth.AspNet;
using DotNetOpenAuth.AspNet.Clients;
using Validation;
using Newtonsoft.Json;
namespace OAuthProviders
{
/// <summary>
/// The facebook client.
/// </summary>
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Facebook", Justification = "Brand name")]
public sealed class FacebookScopedClient : OAuth2Client
{
#region Constants and Fields
/// <summary>
/// The authorization endpoint.
/// </summary>
private const string AuthorizationEndpoint = "https://www.facebook.com/dialog/oauth";
/// <summary>
/// The token endpoint.
/// </summary>
private const string TokenEndpoint = "https://graph.facebook.com/oauth/access_token";
/// <summary>
/// The _app id.
/// </summary>
private readonly string appId;
/// <summary>
/// The _app secret.
/// </summary>
private readonly string appSecret;
private readonly string scope;
#endregion
#region Constructors and Destructors
/// <summary>
/// Initializes a new instance of the <see cref="FacebookScopedClient"/> class.
/// </summary>
/// <param name="appId">
/// The app id.
/// </param>
/// <param name="appSecret">
/// The app secret.
/// </param>
/// <param name="scope">
/// The scope (requested Facebook permissions).
/// </param>
public FacebookScopedClient(string appId, string appSecret, string scope)
: base("facebook")
{
Requires.NotNullOrEmpty(appId, "appId");
Requires.NotNullOrEmpty(appSecret, "appSecret");
Requires.NotNullOrEmpty(scope, "scope");
this.appId = appId;
this.appSecret = appSecret;
this.scope = scope;
}
#endregion
#region Methods
/// <summary>
/// The get service login url.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <returns>An absolute URI.</returns>
protected override Uri GetServiceLoginUrl(Uri returnUrl)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(AuthorizationEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", returnUrl.AbsoluteUri);
builder.AppendQueryArgument("scope", this.scope);
return builder.Uri;
}
/// <summary>
/// The get user data.
/// </summary>
/// <param name="accessToken">
/// The access token.
/// </param>
/// <returns>A dictionary of profile data.</returns>
protected override IDictionary<string, string> GetUserData(string accessToken)
{
FacebookGraphData graphData;
var request =
WebRequest.Create("https://graph.facebook.com/me?access_token=" + UriDataStringRFC3986(accessToken));
using (var response = request.GetResponse())
{
using (var responseStream = response.GetResponseStream())
{
graphData = OAuthJsonHelper.Deserialize<FacebookGraphData>(responseStream);
}
}
// this dictionary must contains
var userData = new Dictionary<string, string>();
if (!string.IsNullOrEmpty(graphData.Id)) { userData.Add("id", graphData.Id); }
if (!string.IsNullOrEmpty(graphData.Email)) { userData.Add("username", graphData.Email); }
if (!string.IsNullOrEmpty(graphData.Name)) { userData.Add("name", graphData.Name); }
if (graphData.Link != null && !string.IsNullOrEmpty(graphData.Link.AbsoluteUri)) { userData.Add("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri); }
if (!string.IsNullOrEmpty(graphData.Gender)) { userData.Add("gender", graphData.Gender); }
if (!string.IsNullOrEmpty(graphData.Birthday)) { userData.Add("birthday", graphData.Birthday); }
return userData;
}
/// <summary>
/// Obtains an access token given an authorization code and callback URL.
/// </summary>
/// <param name="returnUrl">
/// The return url.
/// </param>
/// <param name="authorizationCode">
/// The authorization code.
/// </param>
/// <returns>
/// The access token.
/// </returns>
protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
{
// Note: Facebook doesn't like us to url-encode the redirect_uri value
var builder = new UriBuilder(TokenEndpoint);
builder.AppendQueryArgument("client_id", this.appId);
builder.AppendQueryArgument("redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri));
builder.AppendQueryArgument("client_secret", this.appSecret);
builder.AppendQueryArgument("code", authorizationCode);
builder.AppendQueryArgument("scope", this.scope);
using (WebClient client = new WebClient())
{
string data = client.DownloadString(builder.Uri);
if (string.IsNullOrEmpty(data))
{
return null;
}
var parsedQueryString = HttpUtility.ParseQueryString(data);
return parsedQueryString["access_token"];
}
}
/// <summary>
/// Converts any % encoded values in the URL to uppercase.
/// </summary>
/// <param name="url">The URL string to normalize</param>
/// <returns>The normalized url</returns>
/// <example>NormalizeHexEncoding("Login.aspx?ReturnUrl=%2fAccount%2fManage.aspx") returns "Login.aspx?ReturnUrl=%2FAccount%2FManage.aspx"</example>
/// <remarks>
/// There is an issue in Facebook whereby it will rejects the redirect_uri value if
/// the url contains lowercase % encoded values.
/// </remarks>
private static string NormalizeHexEncoding(string url)
{
var chars = url.ToCharArray();
for (int i = 0; i < chars.Length - 2; i++)
{
if (chars[i] == '%')
{
chars[i + 1] = char.ToUpperInvariant(chars[i + 1]);
chars[i + 2] = char.ToUpperInvariant(chars[i + 2]);
i += 2;
}
}
return new string(chars);
}
/// <summary>
/// The set of characters that are unreserved in RFC 2396 but are NOT unreserved in RFC 3986.
/// </summary>
private static readonly string[] UriRfc3986CharsToEscape = new[] { "!", "*", "'", "(", ")" };
internal static string UriDataStringRFC3986(string value)
{
// Start with RFC 2396 escaping by calling the .NET method to do the work.
// This MAY sometimes exhibit RFC 3986 behavior (according to the documentation).
// If it does, the escaping we do that follows it will be a no-op since the
// characters we search for to replace can't possibly exist in the string.
var escaped = new StringBuilder(Uri.EscapeDataString(value));
// Upgrade the escaping to RFC 3986, if necessary.
for (int i = 0; i < UriRfc3986CharsToEscape.Length; i++)
{
escaped.Replace(UriRfc3986CharsToEscape[i], Uri.HexEscape(UriRfc3986CharsToEscape[i][0]));
}
// Return the fully-RFC3986-escaped string.
return escaped.ToString();
}
#endregion
}
}
需要一些依赖库才能使其按原样工作,所有这些库都在 NuGet 上可用。您已经拥有 DotNetOpenAuth;http://nuget.org/packages/Validation/是另一个。这OAuthJsonHelper
是 DotNetOpenAuth 使用的内部类的副本 - 为了让这个提供程序工作,我必须在我自己的命名空间中重新实现它:
using System;
using System.IO;
using System.Runtime.Serialization.Json;
using Validation;
namespace OAuthProviders
{
/// <summary>
/// The JSON helper.
/// </summary>
internal static class OAuthJsonHelper
{
#region Public Methods and Operators
/// <summary>
/// The deserialize.
/// </summary>
/// <param name="stream">
/// The stream.
/// </param>
/// <typeparam name="T">The type of the value to deserialize.</typeparam>
/// <returns>
/// The deserialized value.
/// </returns>
public static T Deserialize<T>(Stream stream) where T : class
{
Requires.NotNull(stream, "stream");
var serializer = new DataContractJsonSerializer(typeof(T));
return (T)serializer.ReadObject(stream);
}
#endregion
}
}
这一切都是按原样提供的,不能保证它可以通过复制/粘贴来工作 - 由您决定如何将其集成到您的项目中。