2

在使用 MVC4 的 Internet 模板时希望获取电子邮件 ID 和 openid

这可用于使用 google 进行身份验证,但不适用于 facebook

想知道如何获取/请求 extraData 字典中的电子邮件 ID

查看http://aspnetwebstack.codeplex.com/的 AspNetWebStack 项目中的代码,它看起来像

 OAuthWebSecurity.RegisterFacebookClient()

在https://github.com/AArnott/dotnetopenid托管的 DotNetOpenAuth.AspNet.dll 中使用 FacebookClient

FacebookClient.GetUserData() 中的代码有

                    var userData = new NameValueCollection();
            userData.AddItemIfNotEmpty("id", graphData.Id);
            userData.AddItemIfNotEmpty("username", graphData.Email);
            userData.AddItemIfNotEmpty("name", graphData.Name);
            userData.AddItemIfNotEmpty("link", graphData.Link == null ? null : graphData.Link.AbsoluteUri);
            userData.AddItemIfNotEmpty("gender", graphData.Gender);
            userData.AddItemIfNotEmpty("birthday", graphData.Birthday);
            return userData;

它应该返回用户名中的电子邮件 ID,但它没有被返回

任何帮助表示赞赏

谢谢

4

1 回答 1

1

提供的 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
    }
}

这一切都是按原样提供,不能保证它可以通过复制/粘贴来工作 - 由您决定如何将其集成到您的项目中。

于 2013-04-11T07:52:26.317 回答