2015-04-20 35 views
11

MVC web sitemi yeni OpenID Connect standardını kullanacak şekilde yükseltmeye çalışıyorum. OWIN katman yazılımı oldukça sağlam görünüyor, ancak maalesef sadece "form_post" yanıt tipini destekliyor. Bu, Google'ın uyumlu olmadığı anlamına gelir; çünkü bir "#" ifadesinden sonra tüm belirteçleri döndürür, böylece sunucuya asla ulaşmazlar ve hiçbir zaman aracıları tetiklemezler.Google OpenID Bağlantısını Doğrulama JWT ID Jetonu

Ara katmandaki yanıt işleyicilerini tetiklemeye çalıştım, ancak bu hiç işe yaramıyor gibi görünüyor, bu yüzden döndürülen istemleri ayrıştırıp bir denetleyiciye POST'ları gönderen basit bir javascript dosyası var. işlem için eylem.

Sorun, sunucu tarafında aldığımda bile onları doğru şekilde ayrıştırılamam. Hata Böyle görünüyor olsun:

IDX10500: Signature validation failed. Unable to resolve  
SecurityKeyIdentifier: 'SecurityKeyIdentifier 
(
    IsReadOnly = False, 
    Count = 1, 
    Clause[0] = System.IdentityModel.Tokens.NamedKeySecurityKeyIdentifierClause 
), 
token: '{ 
    "alg":"RS256", 
    "kid":"073a3204ec09d050f5fd26460d7ddaf4b4ec7561" 
}. 
{ 
    "iss":"accounts.google.com", 
    "sub":"100330116539301590598", 
    "azp":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com", 
    "nonce":"7c8c3656118e4273a397c7d58e108eb1", 
    "email_verified":true, 
    "aud":"1061880999501-b47blhmmeprkvhcsnqmhfc7t20gvlgfl.apps.googleusercontent.com", 
    "iat":1429556543,"exp\":1429560143 
    }'." 
} 

Benim jeton doğrulama kodu ı İmza doğrulama JWTS ama ben için tamamen gerekli olmadığını biliyoruz IdentityServer

private async Task<IEnumerable<Claim>> ValidateIdentityTokenAsync(string idToken, string state) 
    { 
     // New Stuff 
     var token = new JwtSecurityToken(idToken); 
     var jwtHandler = new JwtSecurityTokenHandler(); 
     byte[][] certBytes = getGoogleCertBytes(); 

     for (int i = 0; i < certBytes.Length; i++) 
     { 
      var certificate = new X509Certificate2(certBytes[i]); 
      var certToken = new X509SecurityToken(certificate); 

      // Set up token validation 
      var tokenValidationParameters = new TokenValidationParameters(); 
      tokenValidationParameters.ValidAudience = googleClientId; 
      tokenValidationParameters.IssuerSigningToken = certToken; 
      tokenValidationParameters.ValidIssuer = "accounts.google.com"; 

      try 
      { 
       // Validate 
       SecurityToken jwt; 
       var claimsPrincipal = jwtHandler.ValidateToken(idToken, tokenValidationParameters, out jwt); 
       if (claimsPrincipal != null) 
       { 
        // Valid 
        idTokenStatus = "Valid"; 
       } 
      } 
      catch (Exception e) 
      { 
       if (idTokenStatus != "Valid") 
       { 
        // Invalid? 

       } 
      } 
     } 

     return token.Claims; 
    } 

    private byte[][] getGoogleCertBytes() 
    { 
     // The request will be made to the authentication server. 
     WebRequest request = WebRequest.Create(
      "https://www.googleapis.com/oauth2/v1/certs" 
     ); 

     StreamReader reader = new StreamReader(request.GetResponse().GetResponseStream()); 

     string responseFromServer = reader.ReadToEnd(); 

     String[] split = responseFromServer.Split(':'); 

     // There are two certificates returned from Google 
     byte[][] certBytes = new byte[2][]; 
     int index = 0; 
     UTF8Encoding utf8 = new UTF8Encoding(); 
     for (int i = 0; i < split.Length; i++) 
     { 
      if (split[i].IndexOf(beginCert) > 0) 
      { 
       int startSub = split[i].IndexOf(beginCert); 
       int endSub = split[i].IndexOf(endCert) + endCert.Length; 
       certBytes[index] = utf8.GetBytes(split[i].Substring(startSub, endSub).Replace("\\n", "\n")); 
       index++; 
      } 
     } 
     return certBytes; 
    } 

gelişmekte iyi insanlar tarafından özetlenen örneği aşağıda nasıl kapatılacağı hakkında en ufak bir fikrim yok. Herhangi bir fikir?

cevap

6

Sorun, JWT'de anahtarın anahtar tanımlayıcısı olan JWT'yi imzalamak için kullanılan kid sorundur. JWKs URI'den elle bir sertifika dizisi oluşturduğunuzdan, anahtar tanımlayıcı bilgilerini kaybedersiniz. Ancak doğrulama prosedürü bunu gerektirir.

tokenValidationParameters.IssuerSigningKeyResolver ayarını, tokenValidationParameters.IssuerSigningToken numaralı telefondan yukarıda belirttiğiniz aynı tuşa geri döndürecek bir işleve ayarlamanız gerekir. Bu temsilci, çalışma zamanını herhangi bir 'eşleşen' semantiği yok saymasını ve yalnızca anahtarı kullanmayı öğütlemesini sağlamaktır.

fazla bilgi için bu makaleye bakın: JwtSecurityTokenHandler 4.0.0 Breaking Changes?

Düzenleme: kod: Ben Googles' X509 Sertifikaları ayrıştırmak için Json.NET kullanır benim biraz geliştirilmiş versiyonunu yayınlamak düşündüm

tokenValidationParameters.IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => { return new X509SecurityKey(certificate); }; 
+0

, bu mükemmel çalıştı:

Bu artık benim çalışma kodudur. Yardım için teşekkürler. Kod şöyle görünür: 'tokenValidationParameters.IssuerSigningKeyResolver = (keyfi, ilan, bunlar, parametreler) => {yeni X509SecurityKey (sertifika) return ; }; – ReimTime

+0

thx, eksiksizlik için yanıta eklendi –

6

ve "çocuk" (anahtar kimliği) temel alınarak kullanılacak anahtarı eşleştirir. Asimetrik kripto genellikle oldukça pahalı olduğundan, bu her sertifikayı denemekten biraz daha verimlidir. Microsoft'ta

static Lazy<Dictionary<string, X509Certificate2>> Certificates = new Lazy<Dictionary<string, X509Certificate2>>(FetchGoogleCertificates); 
    static Dictionary<string, X509Certificate2> FetchGoogleCertificates() 
    { 
     using (var http = new HttpClient()) 
     { 
      var json = http.GetStringAsync("https://www.googleapis.com/oauth2/v1/certs").Result; 

      var dictionary = JsonConvert.DeserializeObject<Dictionary<string, string>>(json); 
      return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value))); 
     } 
    } 

    JwtSecurityToken ValidateIdentityToken(string idToken) 
    { 
     var token = new JwtSecurityToken(idToken); 
     var jwtHandler = new JwtSecurityTokenHandler(); 

     var certificates = Certificates.Value; 

     try 
     { 
      // Set up token validation 
      var tokenValidationParameters = new TokenValidationParameters(); 
      tokenValidationParameters.ValidAudience = _clientId; 
      tokenValidationParameters.ValidIssuer = "accounts.google.com"; 
      tokenValidationParameters.IssuerSigningTokens = certificates.Values.Select(x => new X509SecurityToken(x)); 
      tokenValidationParameters.IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)); 
      tokenValidationParameters.IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) => 
      { 
       return identifier.Select(x => 
       { 
        if (!certificates.ContainsKey(x.Id)) 
         return null; 

        return new X509SecurityKey(certificates[ x.Id ]); 
       }).First(x => x != null); 
      }; 

      SecurityToken jwt; 
      var claimsPrincipal = jwtHandler.ValidateToken(idToken, tokenValidationParameters, out jwt); 
      return (JwtSecurityToken)jwt; 
     } 
     catch (Exception ex) 
     { 
      _trace.Error(typeof(GoogleOAuth2OpenIdHybridClient).Name, ex); 
      return null; 
     } 
    } 
+0

Kod snippet'iniz için çok teşekkürler! Bu genel anahtarları/https://www.googleapis.com/oauth2/v3/certs ait yanıtından sertifikaları oluşturmak için bir yol olup olmadığını hala merak ediyorum (RSACryptoServiceProvider ile denedim ama maalesef başarısız oldu.) – Robar

+1

@Robar : v1 uç noktası yakında ne zaman gidecek? Fark ettiğim bir başka şey de google'ın günlükleri hakkında ipuçlarını döndürmesidir, bu yüzden önbellek özetleriyle uğraşmanız ve ardından tekrar sertifikaları almanız gerekir. –

+0

Umarım ancak bulma belgenin geçerli 'jwks_uri' v3 uç noktası (https://accounts.google.com/.well-known/openid-configuration bakınız). İpleri bir bitiş süresine sahip bir önbellek içine koyarak, sorunu dönen döndürücülerle zaten ele aldım. Ben HTTP yanıtı bir 'max-age' kümesi vardır, certs alır HTTP isteğinden son kullanma süresini almak. doğrulama ilk denemede başarısız olursa, ayrıca ben certs biri yeniden alma yapmak. – Robar

1

millet OpenId Bağlan destekleyen Azure V2 B2C Önizleme son nokta için kod örneğini yayınladı:

Ayrıca demode WebClient'ı ve manuel dize ayrıştırma kodu kaldırıldı. aşağıdaki gibi kod basitleştirilmiş OpenIdConnectionCachingSecurityTokenProvider yardımcı sınıfıyla, here Bkz: OAuthBearer Ara Katman kaldıraç olmadığından

app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions 
{ 
    AccessTokenFormat = new JwtFormat(new TokenValidationParameters 
    { 
     ValidAudiences = new[] { googleClientId }, 
    }, new OpenIdConnectCachingSecurityTokenProvider("https://accounts.google.com/.well-known/openid-configuration"))}); 

Bu sınıf gereklidir. STS tarafından varsayılan olarak açık olan OpenID Connect meta veri uç noktası.

public class OpenIdConnectCachingSecurityTokenProvider : IIssuerSecurityTokenProvider 
{ 
    public ConfigurationManager<OpenIdConnectConfiguration> _configManager; 
    private string _issuer; 
    private IEnumerable<SecurityToken> _tokens; 
    private readonly string _metadataEndpoint; 

    private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim(); 

    public OpenIdConnectCachingSecurityTokenProvider(string metadataEndpoint) 
    { 
     _metadataEndpoint = metadataEndpoint; 
     _configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint); 

     RetrieveMetadata(); 
    } 

    /// <summary> 
    /// Gets the issuer the credentials are for. 
    /// </summary> 
    /// <value> 
    /// The issuer the credentials are for. 
    /// </value> 
    public string Issuer 
    { 
     get 
     { 
      RetrieveMetadata(); 
      _synclock.EnterReadLock(); 
      try 
      { 
       return _issuer; 
      } 
      finally 
      { 
       _synclock.ExitReadLock(); 
      } 
     } 
    } 

    /// <summary> 
    /// Gets all known security tokens. 
    /// </summary> 
    /// <value> 
    /// All known security tokens. 
    /// </value> 
    public IEnumerable<SecurityToken> SecurityTokens 
    { 
     get 
     { 
      RetrieveMetadata(); 
      _synclock.EnterReadLock(); 
      try 
      { 
       return _tokens; 
      } 
      finally 
      { 
       _synclock.ExitReadLock(); 
      } 
     } 
    } 

    private void RetrieveMetadata() 
    { 
     _synclock.EnterWriteLock(); 
     try 
     { 
      OpenIdConnectConfiguration config = _configManager.GetConfigurationAsync().Result; 
      _issuer = config.Issuer; 
      _tokens = config.SigningTokens; 
     } 
     finally 
     { 
      _synclock.ExitWriteLock(); 
     } 
    } 
} 
1

Johannes Rudolph'un cevabına dayanarak çözümlerimi gönderin. Çözümü çözmem gereken IssuerSigningKeyResolver Delegate'de bir derleyici hatası var.Ben bunu nasıl anladım kez

using Microsoft.IdentityModel.Tokens; 
using System; 
using System.Collections.Generic; 
using System.IdentityModel.Tokens.Jwt; 
using System.Linq; 
using System.Net.Http; 
using System.Security.Claims; 
using System.Security.Cryptography.X509Certificates; 
using System.Text; 
using System.Threading.Tasks; 

namespace QuapiNet.Service 
{ 
    public class JwtTokenValidation 
    { 
     public async Task<Dictionary<string, X509Certificate2>> FetchGoogleCertificates() 
     { 
      using (var http = new HttpClient()) 
      { 
       var response = await http.GetAsync("https://www.googleapis.com/oauth2/v1/certs"); 

       var dictionary = await response.Content.ReadAsAsync<Dictionary<string, string>>(); 
       return dictionary.ToDictionary(x => x.Key, x => new X509Certificate2(Encoding.UTF8.GetBytes(x.Value))); 
      } 
     } 

     private string CLIENT_ID = "xxxxx.apps.googleusercontent.com"; 

     public async Task<ClaimsPrincipal> ValidateToken(string idToken) 
     { 
      var certificates = await this.FetchGoogleCertificates(); 

      TokenValidationParameters tvp = new TokenValidationParameters() 
      { 
       ValidateActor = false, // check the profile ID 

       ValidateAudience = true, // check the client ID 
       ValidAudience = CLIENT_ID, 

       ValidateIssuer = true, // check token came from Google 
       ValidIssuers = new List<string> { "accounts.google.com", "https://accounts.google.com" }, 

       ValidateIssuerSigningKey = true, 
       RequireSignedTokens = true, 
       IssuerSigningKeys = certificates.Values.Select(x => new X509SecurityKey(x)), 
       IssuerSigningKeyResolver = (token, securityToken, kid, validationParameters) => 
       { 
        return certificates 
        .Where(x => x.Key.ToUpper() == kid.ToUpper()) 
        .Select(x => new X509SecurityKey(x.Value)); 
       }, 
       ValidateLifetime = true, 
       RequireExpirationTime = true, 
       ClockSkew = TimeSpan.FromHours(13) 
      }; 

      JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler(); 
      SecurityToken validatedToken; 
      ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken); 

      return cp; 
     } 
    } 
}