1

我想讓用戶通過SoundCloud爲我的ASP.NET MVC 4項目進行身份驗證。由於沒有.NET SDK,我寫了一個自定義OAuth2Client來處理認證。將客戶端添加到我的AuthConfig.cs後,它適當地顯示爲登錄選項。問題是,當我點擊按鈕登錄,它總是返回如何讓用戶使用SoundCloud登錄我的網站

Login Failure. 

Unsuccessful login with service. 

甚至沒有要求我登錄SoundCloud。問題是什麼?我爲GitHub實現了一個非常類似的客戶端,並且沒有任何問題。

這裏是我的客戶:

public class SoundCloudOAuth2Client : OAuth2Client 
{ 
    private const string ENDUSERAUTHLINK = "https://soundcloud.com/connect"; 
    private const string TOKENLINK = "https://api.soundcloud.com/oauth2/token"; 
    private readonly string _clientID; 
    private readonly string _clientSecret; 

    public SoundCloudOAuth2Client(string clientID, string clientSecret) : base("SoundCloud") 
    { 
     if (string.IsNullOrWhiteSpace(clientID)) { 
       throw new ArgumentNullException("clientID"); 
     } 

     if (string.IsNullOrWhiteSpace(clientSecret)) { 
       throw new ArgumentNullException("clientSecret"); 
     } 

     _clientID = clientID; 
     _clientSecret = clientSecret; 
    } 

    protected override Uri GetServiceLoginUrl(Uri returnUrl) 
    { 
     StringBuilder serviceUrl = new StringBuilder(); 
     serviceUrl.Append(ENDUSERAUTHLINK); 
     serviceUrl.AppendFormat("?client_id={0}", _clientID); 
     serviceUrl.AppendFormat("&response_type={0}", "code"); 
     serviceUrl.AppendFormat("&scope={0}", "non-expiring"); 
     serviceUrl.AppendFormat("&redirect_uri={0}", System.Uri.EscapeDataString(returnUrl.ToString())); 

     return new Uri(serviceUrl.ToString()); 
    } 

    public override void RequestAuthentication(HttpContextBase context, Uri returnUrl) 
    { 
     base.RequestAuthentication(context, returnUrl); 
    } 

    protected override IDictionary<string, string> GetUserData(string accessToken) 
    { 
     IDictionary<String, String> extraData = new Dictionary<String, String>(); 

     var webRequest = (HttpWebRequest)WebRequest.Create("https://api.soundcloud.com/me.json?oauth_token=" + accessToken); 
     webRequest.Method = "GET"; 
     string response = ""; 
     using (HttpWebResponse webResponse = HttpWebResponse)webRequest.GetResponse()) 
     { 
      using (StreamReader reader = new StreamReader(webResponse.GetResponseStream())) 
      { 
       response = reader.ReadToEnd(); 
      } 
     } 

     var json = JObject.Parse(response); 
     string id = (string)json["id"]; 
     string username = (string)json["username"]; 
     string permalinkUrl = (string)json["permalink_url"]; 

     extraData = new Dictionary<String, String> 
     { 
      {"SCAccessToken", accessToken}, 
      {"username", username}, 
      {"permalinkUrl", permalinkUrl}, 
      {"id", id}           
     }; 

     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(TOKENLINK);  
     webRequest.Method = "POST"; 
     webRequest.ContentType = "application/x-www-form-urlencoded"; 

     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"]; 

     return accessToken; 
    } 

    public override AuthenticationResult VerifyAuthentication(HttpContextBase context, Uri returnPageUrl) 
    {  
     string code = context.Request.QueryString["code"]; 
     string u = context.Request.Url.ToString(); 

     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; 

     if (!userData.TryGetValue("username", out name) && !userData.TryGetValue("name", out name)) 
     { 
      name = id; 
     } 

     return new AuthenticationResult(
      isSuccessful: true, provider: "SoundCloud", providerUserId: id, userName: name, extraData: userData); 
    } 
} 

AuthConfig.cs

public static void RegisterAuth() 
{ 
    OAuthWebSecurity.RegisterClient(new SoundCloudOAuth2Client(
     clientID: MyValues.MyClientID, 
     clientSecret: MyValues.MyClientSECRET), 
     displayName: "SoundCloud", 
     extraData: null); 

    OAuthWebSecurity.RegisterClient(new GitHubOAuth2Client(
     appId: MyValues.GITHUBAPPID, 
     appSecret: MyValues.GITHUBAPPSECRET), "GitHub", null); 

    OAuthWebSecurity.RegisterGoogleClient(); 
    OAuthWebSecurity.RegisterYahooClient(); 
} 

回答

3

有解決多個問題,從運行的第一個功能:GetServiceLoginUrl(Uri returnUrl)

returnUrl,這是自動創建的,包含了SoundCloud不喜歡的&符號。您需要刪除&符號,並確保您的SoundCloud帳戶中的「重定向URI的身份驗證」完全匹配匹配的內容(查詢字符串和全部)。以下是對正在被默認發送作爲RETURNURL一個例子:

https://localhost:44301/Account/ExternalLoginCallback?__provider__=SoundCloud&__sid__=blahblahyoursid 

第一步是刪除&__sid__值。您可以去掉sid值並將其作爲state參數傳遞,以防萬一您需要它。新功能如下所示:

protected override Uri GetServiceLoginUrl(Uri returnUrl) 
{ 
    StringBuilder serviceUrl = new StringBuilder(); 
    string sid = String.Empty; 
    if (returnUrl.ToString().Contains("__sid__")) 
    { 
     int index = returnUrl.ToString().IndexOf("__sid__") + 8; 
     int len = returnUrl.ToString().Length; 
     sid = returnUrl.ToString().Substring(index, len - index-1); 
    } 

    string redirectUri = returnUrl.ToString().Contains('&') ? 
    returnUrl.ToString().Substring(0,returnUrl.ToString().IndexOf("&")) : 
    returnUrl.ToString(); 
    serviceUrl.Append(ENDUSERAUTHLINK); 
    serviceUrl.AppendFormat("?client_id={0}", _clientID); 
    serviceUrl.AppendFormat("&response_type={0}", "code"); 
    serviceUrl.AppendFormat("&scope={0}", "non-expiring"); 
    serviceUrl.AppendFormat("&state={0}", sid); 
    serviceUrl.AppendFormat("&redirect_uri={0}", System.Uri.EscapeDataString(redirectUri)); 

    return new Uri(serviceUrl.ToString()); 
} 

解決了部分問題。 SoundlCoud中的重定向URI現在只是https://localhost:44301/Account/ExternalLoginCallback?__provider__=SoundCloud)。但嘗試進行身份驗證仍然會返回false。下一個問題解決的是在AccountController.cs,具體如下:

[AllowAnonymous] 
public ActionResult ExternalLoginCallback(string returnUrl) 

,因爲在第一線,它試圖返回:

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

,這不適合我的自定義OAuth2Client運行,因爲VerifyAuthentication需要不同的參數。通過檢測,如果是的SoundCloud客戶端對其進行修復,然後使用定製VerifyAuthentication:

[AllowAnonymous] 
public ActionResult ExternalLoginCallback(string returnUrl) 
{ 
    AuthenticationResult result; 
    var context = this.HttpContext; 
    string p = Tools.GetProviderNameFromQueryString(context.Request.QueryString); 

    if (!String.IsNullOrEmpty(p) && p.ToLower() == "soundcloud") 
    { 
     result = new SoundCloudOAuth2Client(
       clientID: MyValues.SCCLIENTID, 
       clientSecret: MyValues.SCCLIENTSECRET).VerifyAuthentication(this.HttpContext, new Uri(String.Format("{0}/Account/ExternalLoginCallback?__provider__=SoundCloud", context.Request.Url.GetLeftPart(UriPartial.Authority).ToString()))); 
    } 
    else 
    { 
     result = OAuthWebSecurity.VerifyAuthentication(Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl })); 
    } 

其中

public static string GetProviderNameFromQueryString(NameValueCollection queryString) 
{ 
    var result = queryString["__provider__"]; 
    ///commented out stuff 
    return result; 
} 

在那之後,一切工作正常,你可以成功地驗證。您可以配置GetUserData以獲取要保存的SoundCloud數據,然後將其保存到UserProfile或相關表格中。關鍵部分是SCAccessToken,因爲這是您未來需要上傳到其帳戶的內容。

+0

任何想法如何獲得使用Owin的SoundCloud身份驗證? – gldraphael 2014-12-12 17:09:12

+0

@gldraphael不,對不起。我最近使用Sound Cloud auth的所有功能都在node.js中。雖然我會認爲(希望)自從我寫這篇文章以後,會有一個關於nuget或者其他東西的軟件包。 – MikeSmithDev 2014-12-12 18:25:46