Asp.net-Mvc-4

OAuth2 和 DotNetOpenAuth - 實現 Google 自定義客戶端

  • April 15, 2015

我在使用 DotNetOpenAuth 和 MVC4 為 google 實現自定義 OAuth2Client 時遇到問題。

我已經到了可以成功向Google端點 https://accounts.google.com/o/oauth2/auth發出授權請求的地步

Google詢問使用者是否允許我的應用程序訪問他們的帳戶。到目前為止一切都很好。當使用者點擊“確定”時,Google會按預期呼叫我的回調 URL。

問題是當我在 OAuthWebSecurity 類 (Microsoft.Web.WebPages.OAuth) 上呼叫 VerifyAuthentication 方法時

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

它總是返回一個 AuthenticationResultIsSuccessful = falseProvider = ""

我已經查看了此程式碼,並且 OAuthWebSecurity 類嘗試從中獲取 Provider 名稱

Request.QueryString["__provider__"]

但Google沒有在查詢字元串中發回這些資訊。我實現的另一個提供程序(LinkedIn)正在發回提供程序名稱,一切正常。

我不確定從這一點開始我能做什麼,除了放棄 Microsoft.Web.WebPages.OAuth 類並在沒有它們的情況下使用 DotNetOpenAuth,但我希望有人可以嘗試另一種解決方案……

我進行了廣泛的搜尋,但似乎找不到任何幫助……我發現即使只是找到做同樣事情的人的例子也很困難,這真的讓我感到驚訝。

非常感謝任何幫助!

更新:正如馬特約翰遜在下面提到的,他已經打包了一個解決方案,您可以從 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);
   }

引用自:https://stackoverflow.com/questions/13727466