4

過去兩天,我一直在扼殺我的大腦,試圖瞭解如何使用內置於ASP.NET WebAPI 2中的身份驗證,並將Google用作外部身份驗證,與OAuth 2,我很迷茫。我已按照this tutorial在我的Android客戶端上設置登錄按鈕,並將「idToken」發送到Web API。我也跟着這個(現在已過時)tutorial設置谷歌作爲外部登錄。使用Google身份驗證從Android客戶端使用WebAPI2網站

問題發生在我嘗試發送時,我收到了{"error":"unsupported_grant_type"}作爲迴應。其他一些教程會讓我相信,mysite.com/token的POST不包含正確的數據。這意味着我要麼不正確地在客戶端建立請求,我在後端不正確地處理它,我將它發送到錯誤的URL,或者我正在做一些完全錯誤的事情。

我發現這SO answer它說來從/ api/Accounts/ExternalLogins獲取一個URL,但登錄按鈕已經給我提供給我的訪問令牌(如果我理解正確的話)。

如果有人能夠幫助我從這裏開始到完成確切的過程,那將是驚人的。

更新:好的,所以這裏有一些事情,自從我問這個問題以來,我已經學會了。

  1. website.com/token URI是WebAPI2模板中內置OAuth服務器的重定向。這對於這個特定的問題沒有用處。

  2. id_token是一個編碼的JWT令牌。

  3. website.com/signin-google URI是普通Google登錄的重定向,但不接受這些令牌。

  4. 我可能必須編寫自己的AuthenticationFilter,它使用Google Client library通過Google API進行授權。

更新2:我還在努力讓這個AuthenticationFilter實現。事情似乎進展順利,但我陷入了一些困難。我一直在使用this example來獲取令牌驗證碼,並且使用this tutorial來獲得AuthenticationFilter代碼。結果是兩者的混合。一旦完成,我會在這裏發佈它作爲答案。

這裏是我當前的問題:

  1. 產生的IPrincipal作爲輸出。驗證示例生成ClaimPrincipal,但AuthenticationFilter示例代碼使用UserManager將用戶名與現有用戶相匹配並返回該主體。在驗證示例中創建的ClaimsPrincipal直接不會與現有用戶自動關聯,因此我需要嘗試將某些索賠元素與現有用戶進行匹配。那我該怎麼做?

  2. 對於這是什麼,我仍然有一個不完整的想法。我目前使用Authentication標頭來使用自定義方案傳遞我的id_token字符串:「goog_id_token」。客戶端必須使用此自定義AuthenticationFilter爲API上調用的每個方法發送其id_token。我不知道這通常會在專業環境中完成。這似乎是一個普遍的用例,會有大量關於它的信息,但我沒有看到它。我已經看到了正常的OAuth2流程,並且由於我只使用了一個ID令牌,而不是一個訪問令牌,所以我對ID令牌應該用於什麼,它落在流中的位置感到有些迷失,它應該生活在一個HTTP數據包中。而因爲我不知道這些事情,所以我一直在努力。

回答

1

哇,我做到了。我想到了。我......我無法相信。

正如我在問題更新2中提到的,此代碼是從谷歌的官方API C#示例和Microsoft的Custom AuthenticationFilter教程和代碼示例彙編而成的。我要在這裏粘貼AuthorizeAsync(),並查看每個代碼塊的作用。如果您認爲您遇到問題,請隨時提及。

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken) 
{ 
    bool token_valid = false; 
    HttpRequestMessage request = context.Request; 

    // 1. Look for credentials in the request 
    //Trace.TraceInformation(request.ToString()); 
    string idToken = request.Headers.Authorization.Parameter.ToString(); 

客戶端將授權標頭字段添加到方案後跟一個空格,後跟id標記。它看起來像Authorization: id-token-goog IaMS0m3.Tok3nteXt...。將ID標記放在Google文檔中給出的正文中對此過濾器沒有任何意義,因此我決定將它放在標題中。出於某種原因,很難從HTTP數據包中提取自定義標題,所以我只是決定使用帶有ID標記的自定義方案的Authorization標頭。

// 2. If there are no credentials, do nothing. 
    if (idToken == null) 
    { 
     Trace.TraceInformation("No credentials."); 
     return; 
    } 

    // 3. If there are credentials, but the filter does not recognize 
    // the authentication scheme, do nothing. 
    if (request.Headers.Authorization.Scheme != "id-token-goog") 
     // Replace this with a more succinct Scheme title. 
    { 
     Trace.TraceInformation("Bad scheme."); 
     return; 
    } 

這整個過濾器的一點是忽略過濾器不治理(AUTH陌生計劃等),並就它的應該管理的要求判決的請求。允許有效的身份驗證傳遞給下游的AuthorizeFilter或直接傳遞給Controller。

我編制了「id-token-goog」這個方案,因爲我不知道這個用例是否有現有的方案。如果有的話,請讓我知道,我會解決它。我想現在並不特別重要,只要我的客戶都知道這個計劃。

// 4. If there are credentials that the filter understands, try to validate them. 
    if (idToken != null) 
    { 
     JwtSecurityToken token = new JwtSecurityToken(idToken); 
     JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler(); 
     // Configure validation 
     Byte[][] certBytes = getCertBytes(); 
     Dictionary<String, X509Certificate2> certificates = 
      new Dictionary<String, X509Certificate2>(); 

     for (int i = 0; i < certBytes.Length; i++) 
     { 
      X509Certificate2 certificate = 
       new X509Certificate2(certBytes[i]); 
      certificates.Add(certificate.Thumbprint, certificate); 
     } 
     { 
      // Set up token validation 
      TokenValidationParameters tvp = new TokenValidationParameters() 
      { 
       ValidateActor = false, // check the profile ID 
       ValidateAudience = 
        (CLIENT_ID != ConfigurationManager 
         .AppSettings["GoogClientID"]), // check the client ID 
       ValidAudience = CLIENT_ID, 

       ValidateIssuer = true, // check token came from Google 
       ValidIssuer = "accounts.google.com", 

       ValidateIssuerSigningKey = true, 
       RequireSignedTokens = true, 
       CertificateValidator = X509CertificateValidator.None, 
       IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) => 
       { 
        return identifier.Select(x => 
        { 
         // TODO: Consider returning null here if you have case sensitive JWTs. 
         /*if (!certificates.ContainsKey(x.Id)) 
         { 
          return new X509SecurityKey(certificates[x.Id]); 
         }*/ 
         if (certificates.ContainsKey(x.Id.ToUpper())) 
         { 
          return new X509SecurityKey(certificates[x.Id.ToUpper()]); 
         } 
         return null; 
        }).First(x => x != null); 
       }, 
       ValidateLifetime = true, 
       RequireExpirationTime = true, 
       ClockSkew = TimeSpan.FromHours(13) 
      }; 

這和谷歌的例子沒有任何變化。我幾乎不知道它做了什麼。這基本上在創建JWTSecurityToken(標記字符串的解析,解碼版本)中設置了一些魔力,並設置了驗證參數。我不確定爲什麼本節的底部部分在它自己的語句塊中,但它與CLIENT_ID和該比較有關。我不知道何時或爲什麼CLIENT_ID的值將永遠改變,但顯然有必要...

  try 
      { 
       // Validate using the provider 
       SecurityToken validatedToken; 
       ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken); 
       if (cp != null) 
       { 
        cancellationToken.ThrowIfCancellationRequested(); 
        ApplicationUserManager um = 
         context 
         .Request 
         .GetOwinContext() 
         .GetUserManager<ApplicationUserManager>(); 

從OWIN背景下獲取用戶的經理。我不得不在context intellisense中搜索,直到找到GetOwinCOntext(),然後發現我必須添加using Microsoft.Aspnet.Identity.Owin;才能添加包含方法GetUserManager<>()的部分類。

    ApplicationUser au = 
         await um 
          .FindAsync(
           new UserLoginInfo(
            "Google", 
            token.Subject) 
          ); 

這是我必須解決的最後一件事。再次,我不得不通過um Intellisense來查找所有Find函數及其覆蓋。我從我的數據庫中的Identity Framework創建的表中注意到,有一個名爲UserLogin的行,其行包含提供者,提供者密鑰和用戶FK。 FindAsync()需要一個UserLoginInfo對象,其中只包含提供程序字符串和提供程序密鑰。我有一種預感,現在這兩件事情是相關的。我還記得,有一個令牌格式的字段,其中包括一個以1開頭的長鍵字段。

validatedToken似乎基本上是空的,不是null,而是一個空的SecurityToken。這就是爲什麼我使用token而不是validatedToken。我在想這肯定有什麼問題,但是由於cp不是null,這是驗證失敗的有效檢查,所以原始標記有效就足夠了。

    // If there is no user with those credentials, return 
        if (au == null) 
        { 
         return; 
        } 

        ClaimsIdentity identity = 
         await um 
         .ClaimsIdentityFactory 
         .CreateAsync(um, au, "Google"); 
        context.Principal = new ClaimsPrincipal(identity); 
        token_valid = true; 

在這裏,我要創建一個新的ClaimsPrincipal因爲在驗證上面創建的一個是空的(顯然這是正確的)。猜測CreateAsync()的第三個參數應該是什麼。它似乎是這樣工作的。

   } 
      } 
      catch (Exception e) 
      { 
       // Multiple certificates are tested. 
       if (token_valid != true) 
       { 
        Trace.TraceInformation("Invalid ID Token."); 
        context.ErrorResult = 
         new AuthenticationFailureResult(
          "Invalid ID Token.", request); 
       } 
       if (e.Message.IndexOf("The token is expired") > 0) 
       { 
        // TODO: Check current time in the exception for clock skew. 
        Trace.TraceInformation("The token is expired."); 
        context.ErrorResult = 
         new AuthenticationFailureResult(
          "Token is expired.", request); 
       } 
       Trace.TraceError("Error occurred: " + e.ToString()); 
      } 
     } 
    }   
} 

其餘的只是異常捕捉。

感謝您檢查了這一點。希望您可以查看我的源代碼,並查看哪些組件來自哪個代碼庫。

+0

對於Android客戶端谷歌表示,請按照「跨客戶端流量」https://developers.google.com/identity/protocols/CrossClientAuth#accessTokens。使用GoogleAuthUtil.getToken()獲取JWT令牌的位置。這不工作,並使用標準的OWIN流程來驗證谷歌令牌 – Haroon

+1

@LavaHot - 你能提供完整的代碼嗎? –

+0

謝謝!!!!!你幫了我很多!幫助我理解這個完整的解決方案:http://stackoverflow.com/questions/27754171/mvc-5-web-api-with-facebook-access-token-to-registerexternal-without-need-of-coo#answer- 28298790 –

相關問題