6

我已经使用 nopcommerce 开发了一个 mvc 5 应用程序,我使用 facebook 登录使用外部回调它正在工作,但现在它不工作,我找不到实际问题。并使用下面的代码

this.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri());

它返回给我总是 null 并且身份验证状态失败我在网上搜索并做所有事情并按照这些步骤操作,但我仍然无法使用 facebook 登录。

我的代码在 FacebookProviderAuthorizer.cs 中是这样的

private AuthorizeState VerifyAuthentication(string returnUrl)
{
   var authResult = DotNetOpenAuth.AspNet.Clients.FacebookApplication.VerifyAuthentication(_httpContext, GenerateLocalCallbackUri());

   if (authResult.IsSuccessful)
   {
   }
}

然后编写回调方法

private Uri GenerateLocalCallbackUri()
{
    string url = string.Format("{0}plugins/externalauthFacebook/logincallback/", _webHelper.GetStoreLocation());
    return new Uri(url);            
}

然后生成服务登录url

private Uri GenerateServiceLoginUrl()
{
   //code copied from DotNetOpenAuth.AspNet.Clients.FacebookClient file
   var builder = new UriBuilder("https://www.facebook.com/dialog/oauth");
   var args = new Dictionary<string, string>();
   args.Add("client_id", _facebookExternalAuthSettings.ClientKeyIdentifier);
   args.Add("redirect_uri", GenerateLocalCallbackUri().AbsoluteUri);
   args.Add("response_type", "token");
   args.Add("scope", "email");
   AppendQueryArgs(builder, args);
   return builder.Uri;
}
4

6 回答 6

7

我们在 2017 年 3 月 27 日星期一遇到了同样的问题,当时 Facebook 停止了对其 Graph API v2.2 的支持。

我们还使用最初通过 Nuget 安装的 DotNetOpenAuth。源代码可在以下链接中找到:

https://github.com/DotNetOpenAuth/DotNetOpenAuth

具体来说,我们发现我们的代码使用了 4.3 分支,其中包含 DotNetOpenAuth.AspNet.DLL 的源代码。在检查源代码后,我们发现问题出在 DotNetOpenAuth.AspNet\Clients\OAuth2\FacebookClient.cs 中的这段代码,它位于 QueryAccessToken 方法中:

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"];
}

具体来说,问题是 ParseQueryString 调用。从 API v2.3 开始,数据不再作为 HTML 查询字符串返回,而是以标准 JSON 格式返回。

为了解决这个问题,我们创建了我们自己的继承 OAuth2Client 的自定义类,并从 FacebookClient.cs 导入了大部分相同的代码。然后,我们将上面的代码片段替换为解析 JSON 响应以提取 access_token 并返回它的代码。您可以在 GetUserData 方法中的同一 FacebookClient 类中看到如何执行此操作的示例:

FacebookGraphData graphData;
var request =
    WebRequest.Create(
        "https://graph.facebook.com/me?access_token=" +
             MessagingUtilities.EscapeUriDataStringRfc3986(accessToken));
using (var response = request.GetResponse()) {
     using (var responseStream = response.GetResponseStream()) {
        graphData = JsonHelper.Deserialize<FacebookGraphData>(responseStream);
     }
}

唯一的其他变化是注册我们的自定义类来代替 FacebookClient 类,以便 OAuth 回调使用它来处理来自 Facebook 的 API 的帖子。一旦我们这样做了,一切都会再次顺利进行。

于 2017-03-29T22:19:21.860 回答
3

我使用了@Vishal 共享的代码并得到了相同的工作。

我们必须关注的主要事情是重写 QueryAccessToken 方法以使用 json 响应。

protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
    {
        var uri = BuildUri(TokenEndpoint, new NameValueCollection
            {
                { "code", authorizationCode },
                { "client_id", _appId },
                { "client_secret", _appSecret },
                { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
            });

        var webRequest = (HttpWebRequest)WebRequest.Create(uri);
        string accessToken = null;            
        HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

        // handle response from FB 
        // this will not be a url with params like the first request to get the 'code'
        Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);

        using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
        {
            var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
            var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

            Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
            accessToken = desirializedJsonObject["access_token"].ToString();
        }
        return accessToken;
    }

实现这一点的步骤: 第 1 步。您要做的是添加一个名为 FacebookClientOverride.cs 的文件(除了 FacebookClient.cs)

这是整个文件的代码片段。

 using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;

public class FacebookClient : 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 user info endpoint.
    /// </summary>
    private const string UserInfoEndpoint = "https://graph.facebook.com/me";

    /// <summary>
    /// The app id.
    /// </summary>
    private readonly string _appId;

    /// <summary>
    /// The app secret.
    /// </summary>
    private readonly string _appSecret;

    /// <summary>
    /// The requested scopes.
    /// </summary>
    private readonly string[] _requestedScopes;

    #endregion

    /// <summary>
    /// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
    /// </summary>
    /// <param name="appId">The Facebook App Id</param>
    /// <param name="appSecret">The Facebook App Secret</param>
    public FacebookClient(string appId, string appSecret)
        : this(appId, appSecret, new[] { "email" }) { }

    /// <summary>
    /// Creates a new Facebook OAuth2 client.
    /// </summary>
    /// <param name="appId">The Facebook App Id</param>
    /// <param name="appSecret">The Facebook App Secret</param>
    /// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
    public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
        : base("facebook")
    {
        if (string.IsNullOrWhiteSpace(appId))
            throw new ArgumentNullException("appId");

        if (string.IsNullOrWhiteSpace(appSecret))
            throw new ArgumentNullException("appSecret");

        if (requestedScopes == null)
            throw new ArgumentNullException("requestedScopes");

        if (requestedScopes.Length == 0)
            throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");

        _appId = appId;
        _appSecret = appSecret;
        _requestedScopes = requestedScopes;
    }

    protected override Uri GetServiceLoginUrl(Uri returnUrl)
    {
        var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);

        return BuildUri(AuthorizationEndpoint, new NameValueCollection
                {
                    { "client_id", _appId },
                    { "scope", string.Join(" ", _requestedScopes) },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                    { "state", state },
                });
    }

    protected override IDictionary<string, string> GetUserData(string accessToken)
    {
        var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });

        var webRequest = (HttpWebRequest)WebRequest.Create(uri);

        using (var webResponse = webRequest.GetResponse())
        using (var stream = webResponse.GetResponseStream())
        {
            if (stream == null)
                return null;

            using (var textReader = new StreamReader(stream))
            {
                var json = textReader.ReadToEnd();
                var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());

                data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));

                return data;
            }
        }
    }

    protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
    {
        var uri = BuildUri(TokenEndpoint, new NameValueCollection
                {
                    { "code", authorizationCode },
                    { "client_id", _appId },
                    { "client_secret", _appSecret },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                });

        var webRequest = (HttpWebRequest)WebRequest.Create(uri);
        string accessToken = null;
        HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

        // handle response from FB 
        // this will not be a url with params like the first request to get the 'code'
        Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);

        using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
        {
            var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
            var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

            Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
            accessToken = desirializedJsonObject["access_token"].ToString();
        }
        return accessToken;
    }

    private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
    {
        var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
        var qs = String.Join("&", keyValuePairs);

        var builder = new UriBuilder(baseUri) { Query = qs };
        return builder.Uri;
    }

    /// <summary>
    /// Facebook works best when 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.
    /// </summary>
    public static void RewriteRequest()
    {
        var ctx = HttpContext.Current;

        var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
        if (stateString == null || !stateString.Contains("__provider__=facebook"))
            return;

        var q = HttpUtility.ParseQueryString(stateString);
        q.Add(ctx.Request.QueryString);
        q.Remove("state");

        ctx.RewritePath(ctx.Request.Path + "?" + q);
    }
}

步骤 2. 添加一个对 System.Web.Extensions 的引用

第 3 步。在 FacebookProviderAuthorizer.cs(Nopcommerce 项目)中查找 FacebookClient 属性 private FacebookClient _facebookApplication;

这应该是指您刚刚添加的新文件。

步骤 4. 现在在文件 FacebookProviderAuthorizer.cs 中名为 VerifyAuthentication 的方法中放置一个断点。

authResult.IsSuccessful 现在必须为真,因为它成功解析了令牌。

谢谢大家。如果解决方案对您有用,请点赞。

于 2017-04-09T04:32:48.900 回答
2

在史蒂夫的帖子的基础上,我创建了一个“FriendlyFacebookClient”来代替 FacebookClient,复制了一些内部方法,并将 QueryAccessToken 替换为以下内容:

  protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {
            UriBuilder builder = new UriBuilder("https://graph.facebook.com/oauth/access_token");
            AppendQueryArgs(builder, (IEnumerable<KeyValuePair<string, string>>)new Dictionary<string, string>()
              {
                { "client_id", this.appId},
                { "redirect_uri", FriendlyFacebookClient.NormalizeHexEncoding(returnUrl.AbsoluteUri)},
                { "client_secret",  this.appSecret },
                { "code",  authorizationCode },
                { "scope", "email" }
              });

            using (WebClient webClient = new WebClient())
            {
                var response = webClient.DownloadString(builder.Uri);
                var data = JsonConvert.DeserializeObject<Dictionary<string, string>>(response);
                return data["access_token"];
            }
        }
于 2017-03-30T18:21:34.937 回答
2

根据以前的答案,我得到了解决方案。在MVC4每个人写下他们的AppIDSecurityCode。由于 facebook GRAPH API 的更改,这些先前的链接已损坏。因此,每个人都需要改变RegisterFacebookClient班级。但是这个类是.Net库中的一个密封类,所以任何人都不能扩展或覆盖它。因此,我们需要使用一个wrapper类。我写这个答案是因为之前所有的答案供应商都错过了一件事,为此我受了很多苦。因为他们没有在AuthConfigClass里面提到class warping system的系统。所以解决这个问题需要太多的时间。因此,我将按如下步骤逐步进行。让我们考虑一下我的 Wrapper 课程,FacebookClientV2Dot3因此我的课程将是

using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Web;
using DotNetOpenAuth.AspNet.Clients;
using Newtonsoft.Json;

public class FacebookClientV2Dot3 : 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 user info endpoint.
    /// </summary>
    private const string UserInfoEndpoint = "https://graph.facebook.com/me";

    /// <summary>
    /// The app id.
    /// </summary>
    private readonly string _appId;

    /// <summary>
    /// The app secret.
    /// </summary>
    private readonly string _appSecret;

    /// <summary>
    /// The requested scopes.
    /// </summary>
    private readonly string[] _requestedScopes;

    #endregion

    /// <summary>
    /// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
    /// </summary>
    /// <param name="appId">The Facebook App Id</param>
    /// <param name="appSecret">The Facebook App Secret</param>
    public FacebookClient(string appId, string appSecret)
        : this(appId, appSecret, new[] { "email" }) { }

    /// <summary>
    /// Creates a new Facebook OAuth2 client.
    /// </summary>
    /// <param name="appId">The Facebook App Id</param>
    /// <param name="appSecret">The Facebook App Secret</param>
    /// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
    public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
        : base("facebook")
    {
        if (string.IsNullOrWhiteSpace(appId))
            throw new ArgumentNullException("appId");

        if (string.IsNullOrWhiteSpace(appSecret))
            throw new ArgumentNullException("appSecret");

        if (requestedScopes == null)
            throw new ArgumentNullException("requestedScopes");

        if (requestedScopes.Length == 0)
            throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");

        _appId = appId;
        _appSecret = appSecret;
        _requestedScopes = requestedScopes;
    }

    protected override Uri GetServiceLoginUrl(Uri returnUrl)
    {
        var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);

        return BuildUri(AuthorizationEndpoint, new NameValueCollection
                {
                    { "client_id", _appId },
                    { "scope", string.Join(" ", _requestedScopes) },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                    { "state", state },
                });
    }

    protected override IDictionary<string, string> GetUserData(string accessToken)
    {
        var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });

        var webRequest = (HttpWebRequest)WebRequest.Create(uri);

        using (var webResponse = webRequest.GetResponse())
        using (var stream = webResponse.GetResponseStream())
        {
            if (stream == null)
                return null;

            using (var textReader = new StreamReader(stream))
            {
                var json = textReader.ReadToEnd();
                var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());

                data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));

                return data;
            }
        }
    }

    protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
    {
        var uri = BuildUri(TokenEndpoint, new NameValueCollection
                {
                    { "code", authorizationCode },
                    { "client_id", _appId },
                    { "client_secret", _appSecret },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                });

        var webRequest = (HttpWebRequest)WebRequest.Create(uri);
        string accessToken = null;
        HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

        // handle response from FB 
        // this will not be a url with params like the first request to get the 'code'
        Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);

        using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
        {
            var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
            var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
            var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

            Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
            accessToken = desirializedJsonObject["access_token"].ToString();
        }
        return accessToken;
    }

    private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
    {
        var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
        var qs = String.Join("&", keyValuePairs);

        var builder = new UriBuilder(baseUri) { Query = qs };
        return builder.Uri;
    }

    /// <summary>
    /// Facebook works best when 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.
    /// </summary>
    public static void RewriteRequest()
    {
        var ctx = HttpContext.Current;

        var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
        if (stateString == null || !stateString.Contains("__provider__=facebook"))
            return;

        var q = HttpUtility.ParseQueryString(stateString);
        q.Add(ctx.Request.QueryString);
        q.Remove("state");

        ctx.RewritePath(ctx.Request.Path + "?" + q);
    }
}

看这里,我已经用较新版本的链接替换了所有 API 链接。

现在你需要修改你的

授权配置

只需使用包装类而不是RegisterFacebookClient. 完全阻止这部分代码。并添加这个...

OAuthWebSecurity.RegisterClient(new FacebookClientV2Dot3("AppID", "HassedPassword"));

然后一切顺利。您的 facebook 登录将恢复到以前的状态。

但是,您可能会面临关于这个新 API 而不是以前的 API 的新问题,问题是IP Whitelisting. 喜欢这个形象。希望你只需要这个。快乐编码。

于 2017-05-05T06:41:31.053 回答
1

正如@SteveTerry 所建议的,我们需要在 FacebookClient 类中更新 QueryAccessToken 函数。不幸的是,“FacebookClient”是密封类,所以我们不能继承和覆盖。因此,无论您选择哪种方式,都取决于您。以下是最终结果的样子:

这个函数的旧代码是:

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.AppendQueryArgs(
    new Dictionary<string, string> {
        { "client_id", this.appId },
        { "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) },
        { "client_secret", this.appSecret },
        { "code", authorizationCode },
        { "scope", "email" },
    });

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"];
}

}

为了支持新版本的 fb api,它应该是这样的:

/// <summary>
/// Contains access_token of a Facebook user.
/// </summary>
[DataContract]
[EditorBrowsable(EditorBrowsableState.Never)]
[SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Facebook", Justification = "Brand name")]
public class FacebookAccessTokenData
{
    #region Public Properties

    /// <summary>
    /// 
    /// </summary>
    [DataMember(Name = "access_token")]
    public string AccessToken { get; set; }

    /// <summary>
    /// 
    /// </summary>
    [DataMember(Name = "token_type")]
    public string TokenType { get; set; }

    /// <summary>
    /// 
    /// </summary>
    [DataMember(Name = "expires_in")]
    public string ExpiresIn { get; set; }

    #endregion
}


/// <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.AppendQueryArgs(
        new Dictionary<string, string> {
            { "client_id", this.appId },
            { "redirect_uri", NormalizeHexEncoding(returnUrl.AbsoluteUri) },
            { "client_secret", this.appSecret },
            { "code", authorizationCode },
            { "scope", "email" },
        });

    FacebookAccessTokenData graphData;
    var request = WebRequest.Create(builder.Uri);
    using (var response = request.GetResponse())
    {
        using (var responseStream = response.GetResponseStream())
        {
            graphData = JsonHelper.Deserialize<FacebookAccessTokenData>(responseStream);
        }
    }

    return graphData.AccessToken;
}
于 2017-03-31T10:00:57.467 回答
1

我解决了我的问题,我将做与@Adam 在他的回答中描述的相同的事情,根据@Adam、@SteveTerry 和@Adeem 的回答,我更改了我的代码并创建了具有不同名称的自定义 FacebookClient 类。并替换为 nopCommerce 中 FacebookClient 的原始参考。

public class FacebookOAuth2Client : 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 user info endpoint.
        /// </summary>
        private const string UserInfoEndpoint = "https://graph.facebook.com/me";

        /// <summary>
        /// The app id.
        /// </summary>
        private readonly string _appId;

        /// <summary>
        /// The app secret.
        /// </summary>
        private readonly string _appSecret;

        /// <summary>
        /// The requested scopes.
        /// </summary>
        private readonly string[] _requestedScopes;

        #endregion

        /// <summary>
        /// Creates a new Facebook OAuth2 client, requesting the default "email" scope.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        public FacebookClient(string appId, string appSecret)
            : this(appId, appSecret, new[] { "email" }) { }

        /// <summary>
        /// Creates a new Facebook OAuth2 client.
        /// </summary>
        /// <param name="appId">The Facebook App Id</param>
        /// <param name="appSecret">The Facebook App Secret</param>
        /// <param name="requestedScopes">One or more requested scopes, passed without the base URI.</param>
        public FacebookClient(string appId, string appSecret, params string[] requestedScopes)
            : base("facebook")
        {
            if (string.IsNullOrWhiteSpace(appId))
                throw new ArgumentNullException("appId");

            if (string.IsNullOrWhiteSpace(appSecret))
                throw new ArgumentNullException("appSecret");

            if (requestedScopes == null)
                throw new ArgumentNullException("requestedScopes");

            if (requestedScopes.Length == 0)
                throw new ArgumentException("One or more scopes must be requested.", "requestedScopes");

            _appId = appId;
            _appSecret = appSecret;
            _requestedScopes = requestedScopes;
        }

        protected override Uri GetServiceLoginUrl(Uri returnUrl)
        {
            var state = string.IsNullOrEmpty(returnUrl.Query) ? string.Empty : returnUrl.Query.Substring(1);

            return BuildUri(AuthorizationEndpoint, new NameValueCollection
                {
                    { "client_id", _appId },
                    { "scope", string.Join(" ", _requestedScopes) },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                    { "state", state },
                });
        }

        protected override IDictionary<string, string> GetUserData(string accessToken)
        {
            var uri = BuildUri(UserInfoEndpoint, new NameValueCollection { { "access_token", accessToken } });

            var webRequest = (HttpWebRequest)WebRequest.Create(uri);

            using (var webResponse = webRequest.GetResponse())
            using (var stream = webResponse.GetResponseStream())
            {
                if (stream == null)
                    return null;

                using (var textReader = new StreamReader(stream))
                {
                    var json = textReader.ReadToEnd();
                    var extraData = JsonConvert.DeserializeObject<Dictionary<string, object>>(json);
                    var data = extraData.ToDictionary(x => x.Key, x => x.Value.ToString());

                    data.Add("picture", string.Format("https://graph.facebook.com/{0}/picture", data["id"]));

                    return data;
                }
            }
        }

        protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
        {
            var uri = BuildUri(TokenEndpoint, new NameValueCollection
                {
                    { "code", authorizationCode },
                    { "client_id", _appId },
                    { "client_secret", _appSecret },
                    { "redirect_uri", returnUrl.GetLeftPart(UriPartial.Path) },
                });

            var webRequest = (HttpWebRequest)WebRequest.Create(uri);
            string accessToken = null;            
            HttpWebResponse response = (HttpWebResponse)webRequest.GetResponse();

            // handle response from FB 
            // this will not be a url with params like the first request to get the 'code'
            Encoding rEncoding = Encoding.GetEncoding(response.CharacterSet);

            using (StreamReader sr = new StreamReader(response.GetResponseStream(), rEncoding))
            {
                var serializer = new System.Web.Script.Serialization.JavaScriptSerializer();
                var jsonObject = serializer.DeserializeObject(sr.ReadToEnd());
                var jConvert = JsonConvert.DeserializeObject(JsonConvert.SerializeObject(jsonObject));

                Dictionary<string, object> desirializedJsonObject = JsonConvert.DeserializeObject<Dictionary<string, object>>(jConvert.ToString());
                accessToken = desirializedJsonObject["access_token"].ToString();
            }
            return accessToken;
        }

        private static Uri BuildUri(string baseUri, NameValueCollection queryParameters)
        {
            var keyValuePairs = queryParameters.AllKeys.Select(k => HttpUtility.UrlEncode(k) + "=" + HttpUtility.UrlEncode(queryParameters[k]));
            var qs = String.Join("&", keyValuePairs);

            var builder = new UriBuilder(baseUri) { Query = qs };
            return builder.Uri;
        }

        /// <summary>
        /// Facebook works best when 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.
        /// </summary>
        public static void RewriteRequest()
        {
            var ctx = HttpContext.Current;

            var stateString = HttpUtility.UrlDecode(ctx.Request.QueryString["state"]);
            if (stateString == null || !stateString.Contains("__provider__=facebook"))
                return;

            var q = HttpUtility.ParseQueryString(stateString);
            q.Add(ctx.Request.QueryString);
            q.Remove("state");

            ctx.RewritePath(ctx.Request.Path + "?" + q);
        }
    }

在这段代码中,我没有引用 JsonHelper,所以我使用简单的 jsonConvert。再次感谢@Adam、@SteveTerry 和@Adeem 的帮助。

于 2017-04-03T10:29:07.537 回答