OAuth2 和 DotNetOpenAuth - 實現 Google 自定義客戶端
我在使用 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 }));它總是返回一個 AuthenticationResult
IsSuccessful = false和Provider = ""我已經查看了此程式碼,並且 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); }