OAuth2 和 DotNetOpenAuth - 实现 Google 自定义客户端 [英] OAuth2 and DotNetOpenAuth - implementing Google custom client

查看:44
本文介绍了OAuth2 和 DotNetOpenAuth - 实现 Google 自定义客户端的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我在使用 DotNetOpenAuth 和 MVC4 为谷歌实现自定义 OAuth2Client 时遇到问题.

I'm having an issue implementing custom OAuth2Client for google using DotNetOpenAuth and MVC4.

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

I've got to the point where I can successfully make the authorization request to the google endpoint https://accounts.google.com/o/oauth2/auth

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

and Google asks if the user will allow my application access to their account. All good so far. When the user clicks 'OK', google then calls my callback URL as expected.

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

The problem is when I call the VerifyAuthentication method on the OAuthWebSecurity class (Microsoft.Web.WebPages.OAuth)

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

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

It's always returning an AuthenticationResult with IsSuccessful = false and Provider = ""

我已经研究了这个代码,OAuthWebSecurity 类试图从

I've looked into the code for this, and the OAuthWebSecurity class tries to get the Provider name from

Request.QueryString["__provider__"]

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

but Google is not sending this information back in the querystring. The other provider I have implemented (LinkedIn) is sending the provider name back and it all works just fine.

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

I'm not sure what I can do from this point, apart from abandon the Microsoft.Web.WebPages.OAuth classes and just use DotNetOpenAuth without them, but I was hoping someone might have another solution I can try...

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

I've searched extensively, but can't seem to find anything to help ... I've found it really difficult even just to find examples of people doing the same thing, which has really surprised me.

非常感谢任何帮助!

推荐答案

更新:正如 Matt Johnson 在下面提到的,他已经打包了一个解决方案,您可以从 GitHub 获得:https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2

Update: As Matt Johnson mentions below he has packaged up a solution to this which you can get from GitHub: https://github.com/mj1856/DotNetOpenAuth.GoogleOAuth2

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

As he notes: DNOA and OAuthWebSecurity for ASP.Net MVC 4 ship with only an OpenId provider for Google. This is an OAuth2 client that you can use instead.

重要 - 如果您使用的是 ASP.Net MVC 5,则此软件包不适用.您应该改用 Microsoft.Owin.Security.Google.(它还随 VS 2013 中的 MVC 5 入门模板一起提供.)

IMPORTANT - If you are using ASP.Net MVC 5, this package is not applicable. You should use Microsoft.Owin.Security.Google instead. (It also ships with the MVC 5 starter templates in VS 2013.)

我最后通过在请求进来时捕获请求并自己检查它来自哪个提供者来解决这个问题.Google 允许您向名为state"的 OAuth 请求发送一个参数,他们在进行回调时直接将其直接传回给您,因此我使用它来传递 google 的提供者名称,并在"__provider__" 的缺失.

I got round this in the end by catching the request when it comes in, and doing my own check to see which provider it has come from. Google allow you to send a parameter to the OAuth request called 'state', which they simply pass straight back to you when they make the callback, so I'm using this to pass the provider name for google, and I check for this in the absence of the "__provider__".

像这样:

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

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

        return result;
    }

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

I've then implemented a custom OAuth2Client for Google, and I manually call the VerifyAuthentication method on that myself, bypassing the Microsoft wrapper stuff.

 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 包装器为其他提供商保留我已经拥有的东西.

This has allowed me to keep the stuff I already had in place for the other providers using the Microsoft wrappers.

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

As requested by @1010100 1001010, here is my custom OAuth2Client for Google (NOTE: IT NEEDS SOME TIDYING! I HAVEN'T GOT ROUND TO TIDYING THE CODE UP YET. It does work though) :

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);
    }

这篇关于OAuth2 和 DotNetOpenAuth - 实现 Google 自定义客户端的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆