15

DotNetOpenAuth と MVC4 を使用して Google 用のカスタム OAuth2Client を実装する際に問題が発生しています。

Googleエンドポイントhttps://accounts.google.com/o/oauth2/authへの認証リクエストを正常に行うことができるようになりました

Google は、ユーザーが自分のアプリケーションから自分のアカウントにアクセスすることを許可するかどうかを尋ねます。これまでのところすべて順調です。ユーザーが [OK] をクリックすると、Google は期待どおりにコールバック URL を呼び出します。

問題は、OAuthWebSecurity クラス (Microsoft.Web.WebPages.OAuth) で VerifyAuthentication メソッドを呼び出すときです。

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

それは常にAuthenticationResultを返していIsSuccessful = falseますProvider = ""

このコードを調べたところ、OAuthWebSecurity クラスはプロバイダー名を取得しようとしました。

Request.QueryString["__provider__"]

ただし、Google はこの情報をクエリ文字列で送り返しません。私が実装した他のプロバイダー (LinkedIn) は、プロバイダー名を送り返していますが、すべて正常に動作しています。

Microsoft.Web.WebPages.OAuth クラスを放棄し、それらなしで DotNetOpenAuth を使用する以外に、この時点で何ができるかわかりませんが、誰かが私が試すことができる別の解決策を持っていることを望んでいました...

私は広範囲に検索しましたが、助けになるものを見つけることができないようです... 同じことをしている人々の例を見つけるだけでも非常に困難であることがわかり、本当に驚きました.

どんな助けでも大歓迎です!

4

2 に答える 2

12

更新: Matt Johnson が以下で言及しているように、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 リクエストにパラメーターを送信することができます。このパラメーターは、コールバックを行うときに単純に返されます。そのため、これを使用して Google のプロバイダー名を渡し、これを確認しますの不在"__provider__"

このようなもの:

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

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

        return result;
    }

次に、Google 用のカスタム OAuth2Client を実装し、Microsoft ラッパーをバイパスして、自分で VerifyAuthentication メソッドを手動で呼び出します。

 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);
    }
于 2012-12-10T10:12:05.700 に答える
0

コールバック URL の末尾にプロバイダークエリ パラメーターを追加できます。例: https://mywebsite.com/Account/ExternalLoginCallback? プロバイダー=google

あなたはそれを手に入れ、回避策は必要ありません。

于 2014-05-16T13:30:13.663 に答える