【问题标题】:OAuth2 and DotNetOpenAuth - implementing Google custom clientOAuth2 和 DotNetOpenAuth - 实现 Google 自定义客户端
【发布时间】:2012-12-05 16:09:21
【问题描述】:

我在使用 DotNetOpenAuth 和 MVC4 为 google 实现自定义 OAuth2Client 时遇到问题。

我已经到了可以成功向谷歌端点发出授权请求的地步 https://accounts.google.com/o/oauth2/auth

Google 会询问用户是否允许我的应用程序访问他们的帐户。到目前为止一切都很好。当用户点击“确定”时,谷歌会按预期调用我的回调 URL。

问题是当我在 OAuthWebSecurity 类 (Microsoft.Web.WebPages.OAuth) 上调用 VerifyAuthentication 方法时

var authenticationResult = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

它总是返回一个带有IsSuccessful = falseProvider = "" 的AuthenticationResult

我已经查看了这方面的代码,OAuthWebSecurity 类尝试从

获取提供程序名称
Request.QueryString["__provider__"]

但 Google 不会在查询字符串中发回此信息。我实现的另一个提供程序(LinkedIn)正在发回提供程序名称,一切正常。

我不确定从这一点开始我能做什么,除了放弃 Microsoft.Web.WebPages.OAuth 类并在没有它们的情况下使用 DotNetOpenAuth,但我希望有人可以尝试另一种解决方案......

我进行了广泛的搜索,但似乎找不到任何帮助......我发现即使只是找到人们做同样事情的例子也很困难,这让我感到非常惊讶。

非常感谢任何帮助!

【问题讨论】:

  • 我从未使用过 MS 包装器,只是直接编写了 DotNetOpenAuth 并没有遇到太多问题,这样做并不复杂,而且他们有很多示例,您可以直接加入。
  • 您是否考虑向 google 提交关于缺少提供程序字符串的错误报告?
  • @PaulTyng - 谢谢,是的 - 这就是我最终所做的。
  • @weismat 好主意,但到目前为止我还没有找到我这样做的地方。稍后我会好好看看。
  • 清理和测试。修复了很多东西,它适用于新的OAuthWebSecurity 东西。享受! github.com/mj1856/DotNetOpenAuth.GoogleOAuth2

标签: asp.net-mvc-4 oauth-2.0 dotnetopenauth


【解决方案1】:

更新:正如马特约翰逊在下面提到的那样,他已经打包了一个解决方案,您可以从 GitHub 获得该解决方案:https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2

正如他所说: 用于 ASP.Net MVC 4 的 DNOA 和 OAuthWebSecurity 仅附带 Google 的 OpenId 提供程序。这是您可以使用的 OAuth2 客户端。

重要 - 如果您使用的是 ASP.Net MVC 5,则此包不适用。您应该改用 Microsoft.Owin.Security.Google。 (它还附带 VS 2013 中的 MVC 5 入门模板。)


我最终解决了这个问题,方法是在请求进入时捕获它,并自己检查它来自哪个提供商。 Google 允许您向 OAuth 请求发送一个名为“state”的参数,当他们进行回调时,他们只是将其直接传回给您,所以我使用它来传递 google 的提供程序名称,并在没有"__provider__"

类似这样的:

 public String GetProviderNameFromQueryString(NameValueCollection queryString)
    {
        var result = queryString["__provider__"];

        if (String.IsNullOrWhiteSpace(result))
        {
            result = queryString["state"];
        }

        return result;
    }

然后我为 Google 实现了一个自定义 OAuth2Client,我自己手动调用了 VerifyAuthentication 方法,绕过了 Microsoft 包装器。

 if (provider is GoogleCustomClient)
        {
            authenticationResult = ((GoogleCustomClient)provider).VerifyAuthentication(context, new Uri(String.Format("{0}/oauth/ExternalLoginCallback", context.Request.Url.GetLeftPart(UriPartial.Authority).ToString())));
        }
        else
        {
            authenticationResult = OAuthWebSecurity.VerifyAuthentication(returnUrl);
        } 

这使我可以使用 Microsoft 包装器为其他提供商保留我已经拥有的东西。

根据@1010100 1001010 的要求,这是我为 Google 定制的 OAuth2Client(注意:它需要一些整理!我还没有准备好整理代码。它确实有效):

public class GoogleCustomClient : OAuth2Client
{
    ILogger _logger;

    #region Constants and Fields

    /// <summary>
    /// The authorization endpoint.
    /// </summary>
    private const string AuthorizationEndpoint = "https://accounts.google.com/o/oauth2/auth";

    /// <summary>
    /// The token endpoint.
    /// </summary>
    private const string TokenEndpoint = "https://accounts.google.com/o/oauth2/token";

    /// <summary>
    /// The _app id.
    /// </summary>
    private readonly string _clientId;

    /// <summary>
    /// The _app secret.
    /// </summary>
    private readonly string _clientSecret;

    #endregion


    public GoogleCustomClient(string clientId, string clientSecret)
        : base("Google")
    {
        if (string.IsNullOrWhiteSpace(clientId)) throw new ArgumentNullException("clientId");
        if (string.IsNullOrWhiteSpace(clientSecret)) throw new ArgumentNullException("clientSecret");

        _logger = ObjectFactory.GetInstance<ILogger>();

        this._clientId = clientId;
        this._clientSecret = clientSecret;
    }

    protected override Uri GetServiceLoginUrl(Uri returnUrl)
    {
        StringBuilder serviceUrl = new StringBuilder();

        serviceUrl.AppendFormat("{0}?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.email+https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fuserinfo.profile", AuthorizationEndpoint);
        serviceUrl.Append("&state=google");
        serviceUrl.AppendFormat("&redirect_uri={0}", returnUrl.ToString());
        serviceUrl.Append("&response_type=code");
        serviceUrl.AppendFormat("&client_id={0}", _clientId);

        return new Uri(serviceUrl.ToString());

    }

    protected override IDictionary<string, string> GetUserData(string accessToken)
    {
        RestClient client = new RestClient("https://www.googleapis.com");
        var request = new RestRequest(String.Format("/oauth2/v1/userinfo?access_token={0}", accessToken), Method.GET);
        IDictionary<String, String> extraData = new Dictionary<String, String>();

        var response = client.Execute(request);
        if (null != response.ErrorException)
        {
            return null;
        }
        else
        {
            try
            {
                var json = JObject.Parse(response.Content);

                string firstName = (string)json["given_name"];
                string lastName = (string)json["family_name"];
                string emailAddress = (string)json["email"];
                string id = (string)json["id"];

                extraData = new Dictionary<String, String>
                {
                    {"accesstoken", accessToken}, 
                    {"name", String.Format("{0} {1}", firstName, lastName)},
                    {"firstname", firstName},
                    {"lastname", lastName},
                    {"email", emailAddress},
                    {"id", id}                                           
                };
            }
            catch(Exception ex)
            {
                _logger.Error("Error requesting OAuth user data from Google", ex);
                return null;
            }
            return extraData;
        }

    }

    protected override string QueryAccessToken(Uri returnUrl, string authorizationCode)
    {
        StringBuilder postData = new StringBuilder();
        postData.AppendFormat("client_id={0}", this._clientId);
        postData.AppendFormat("&redirect_uri={0}", HttpUtility.UrlEncode(returnUrl.ToString()));
        postData.AppendFormat("&client_secret={0}", this._clientSecret);
        postData.AppendFormat("&grant_type={0}", "authorization_code");
        postData.AppendFormat("&code={0}", authorizationCode);


        string response = "";
        string accessToken = "";

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

        webRequest.Method = "POST";
        webRequest.ContentType = "application/x-www-form-urlencoded";

        try
        {

            using (Stream s = webRequest.GetRequestStream())
            {
                using (StreamWriter sw = new StreamWriter(s))
                    sw.Write(postData.ToString());
            }

            using (WebResponse webResponse = webRequest.GetResponse())
            {
                using (StreamReader reader = new StreamReader(webResponse.GetResponseStream()))
                {
                    response = reader.ReadToEnd();
                }
            }

            var json = JObject.Parse(response);
            accessToken = (string)json["access_token"];
        }
        catch(Exception ex)
        {
            _logger.Error("Error requesting OAuth access token from Google", ex);
            return null;
        }

        return accessToken;

    }

    public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl)
    {

        string code = context.Request.QueryString["code"];
        if (string.IsNullOrEmpty(code))
        {
            return AuthenticationResult.Failed;
        }

        string accessToken = this.QueryAccessToken(returnPageUrl, code);
        if (accessToken == null)
        {
            return AuthenticationResult.Failed;
        }

        IDictionary<string, string> userData = this.GetUserData(accessToken);
        if (userData == null)
        {
            return AuthenticationResult.Failed;
        }

        string id = userData["id"];
        string name;

        // Some oAuth providers do not return value for the 'username' attribute. 
        // In that case, try the 'name' attribute. If it's still unavailable, fall back to 'id'
        if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name))
        {
            name = id;
        }

        // add the access token to the user data dictionary just in case page developers want to use it
        userData["accesstoken"] = accessToken;

        return new AuthenticationResult(
            isSuccessful: true, provider: this.ProviderName, providerUserId: id, userName: name, extraData: userData);
    }

【讨论】:

  • 顺便说一句 - 我发现很难找到自定义 OAuth2Client 的示例,如果有人想查看我在 Google 上的实现,请大声告诉我。
  • 在没有任何其他答案的情况下将此标记为正确,如果有更好的结果我会更新。
  • 我一直在玩,并试图让它很好地工作。你所拥有的工作正常,但我不喜欢额外检查看看回调是否适用于谷歌,然后手动检查谷歌 OAuth2 客户端。我一直在查看 DNOA 代码,但不明白为什么当您注册它的属性 (OAuthWebSecurity.RegisterClient(...)) 时它没有在自定义 Google 客户端上调用 VerifyAuthentication(),您有什么运气吗?
  • @Brendan - 不......我也想有一个更清洁的解决方案,但不得不继续前进并做其他事情。如果您有任何进展,请告诉我。
  • @Adam(以及所有) - 我让它工作,包括回调重写。 github.com/mj1856/DotNetOpenAuth.GoogleOAuth2
【解决方案2】:

您可以在回调网址的末尾添加 provider 查询参数。 例如https://mywebsite.com/Account/ExternalLoginCallback?provider=google

你会得到它,你不需要解决这个问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-02-12
    • 2012-07-13
    • 2022-08-06
    • 2019-07-06
    • 1970-01-01
    • 1970-01-01
    • 2017-01-11
    • 2021-06-24
    相关资源
    最近更新 更多