2017-10-31 11 views
1

Open ID Connect 키 순환을 지원하기 위해 owin middleware 베어러 토큰 인증 구성에 대한 지침이 필요합니다. 서명 키의베어러 토큰 용 Owin 미들웨어 JWT 키 순환을 지원하는 인증

회전은 다음과 같은 방법으로 수행 할 수 있습니다

Opend Id Connect spec는 키 순환에 대해 다음과 말한다. 서명자는 자신의 키를 jwks_uri 위치에 게시하고 각 메시지의 JOSE 헤더에 서명 키의 아이를 포함시켜 검증 자에게 서명을 확인하는 데 사용할 키를 나타냅니다. jwks_uri 위치의 JWK Set에 새 키를 주기적으로 추가하여 키를 롤오버 할 수 있습니다. 서명자는 재량에 따라 새로운 키를 사용할 수 있으며, 아이 값을 사용하여 검증 자에게 변경 사항을 알릴 수 있습니다. 검증자는 jwks_uri 위치로 돌아가 낯선 아이 값을 볼 때 키를 다시 검색한다는 것을 알고 있습니다. 나는이 주제에 찾을 수

가장 비슷한 질문은 이것이다 : SecurityTokenSignatureKeyNotFoundException in OWIN OpenID Connect middleware connecting to Google

솔루션 않는 새로운 개인 키를 발급 시간과 시간 사이의 오류를 얻을 것이다으로 꽤 작업 클라이언트는 자신의 공개 키 캐시를 새로 고칩니다.

그래서 로컬에 캐시되지 않은 아이가있는 유효한 서명 된 만료되지 않은 JWT 토큰을 찾을 때마다 누락 된 공용 JWK 키를 다운로드하도록 클라이언트를 구성하려고합니다.

나는 현재 IdentityServer3.AccessTokenValidation을 사용하고 있지만 인식 할 수없는 아이가있는 토큰을 수신하면 클라이언트는 새 키를 다운로드하지 않습니다.

내가 Microsoft.Owin.Security.Jwt 얼핏 있었다 -> UseJwtBearerAuthentication 또한 Microsoft.Owin.Security.OpenIdConnect -> UseOpenIdConnectAuthentication 하지만 너무 멀리하지 않았다.

위의 패키지를 키 순환을 지원하도록 확장/구성 할 방향을 찾고 있습니다.

답변

1

나는 system.IdentityModel.Tokens.Jwt 라이브러리를 사용하여 알아 냈다. 버전 관리에 많은 어려움을 겪었으므로 사용하지 않은 nuget 패키지가 포함되었습니다. Microsoft.IdentityModel.Tokens.Jwt에 많은 문제가있어서 그 방법을 포기했습니다. 어쨌든 패키지는 다음과 같습니다.

<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net462" /> 
<package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net462" /> 
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net462" /> 
<package id="System.Net.Http" version="4.1.0" targetFramework="net462" /> 
<package id="System.Security.Cryptography.Algorithms" version="4.2.0" targetFramework="net462" /> 
<package id="System.Security.Cryptography.Encoding" version="4.0.0" targetFramework="net462" /> 
<package id="System.Security.Cryptography.Primitives" version="4.0.0" targetFramework="net462" /> 
<package id="System.Security.Cryptography.X509Certificates" version="4.1.0" targetFramework="net462" /> 

그리고 여기에 코드가 있습니다. 작동 방식은 사용자 지정 키 확인자를 설정하는 것입니다. 이 키 리졸버는 토큰이 전달 될 때마다 호출됩니다. 아이 캐시 미스가 발생하면 토큰 서비스에 최신 키 세트를 다운로드하라는 새로운 요청을합니다. 처음에는 키의 여러 부분 (즉 만료되지 않았거나 유효한 발급자)을 확인하려고했지만 토큰이 올바르게 서명되었는지 확인할 수 없으면 이러한 체크를 추가하는 것이 무의미하기 때문에이를 결정했습니다. 공격자는 원하는대로 설정할 수 있습니다.

using Microsoft.IdentityModel.Protocols; 
using System; 
using System.Collections.Generic; 
using System.IdentityModel.Tokens; 
using System.Linq; 
using System.Net.Http; 
using System.Security.Cryptography; 
using System.Threading; 
using System.Threading.Tasks; 

public class ValidationMiddleware 
{ 
    private readonly Func<IDictionary<string, object>, Task> next; 
    private readonly Func<string> tokenAccessor; 
    private readonly ConfigurationManager<OpenIdConnectConfiguration> configurationManager; 

    private readonly Object locker = new Object(); 
    private Dictionary<string, SecurityKey> securityKeys = new Dictionary<string, SecurityKey>(); 

    public ValidationMiddleware(Func<IDictionary<string, object>, Task> next, Func<string> tokenAccessor) 
    { 
     this.next = next; 
     this.tokenAccessor = tokenAccessor; 

     configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
      "url to open id connect token service", 
      new HttpClient(new WebRequestHandler())) 
     { 
      // Refresh the keys once an hour 
      AutomaticRefreshInterval = new TimeSpan(1, 0, 0) 
     }; 
    } 

    public async Task Invoke(IDictionary<string, object> environment) 
    { 
     var token = tokenAccessor(); 

     var validationParameters = new TokenValidationParameters 
     { 
      ValidAudience = "my valid audience", 
      ValidIssuer = "url to open id connect token service", 
      ValidateLifetime = true, 
      RequireSignedTokens = true, 
      RequireExpirationTime = true, 
      ValidateAudience = true, 
      ValidateIssuer = true, 
      IssuerSigningKeyResolver = MySigningKeyResolver, // Key resolver gets called for every token 
     }; 

     JwtSecurityTokenHandler.InboundClaimTypeMap.Clear(); 

     var tokenHandler = new JwtSecurityTokenHandler(); 
     var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken); 

     // Assign Claims Principal to the context. 

     await next.Invoke(environment); 
    } 

    private SecurityKey MySigningKeyResolver(string token, SecurityToken securityToken, SecurityKeyIdentifier keyIdentifier, TokenValidationParameters validationParameters) 
    { 
     var kid = keyIdentifier.OfType<NamedKeySecurityKeyIdentifierClause>().FirstOrDefault().Id; 

     if (!securityKeys.TryGetValue(kid, out SecurityKey securityKey)) 
     { 
      lock (locker) 
      { 
       // Double lock check to ensure that only the first thread to hit the lock gets the latest keys. 
       if (!securityKeys.TryGetValue(kid, out securityKey)) 
       { 
        // TODO - Add throttling around this so that an attacker can't force tonnes of page requests. 

        // Microsoft's Async Helper 
        var result = AsyncHelper.RunSync(async() => await configurationManager.GetConfigurationAsync()); 

        var latestSecurityKeys = new Dictionary<string, SecurityKey>(); 
        foreach (var key in result.JsonWebKeySet.Keys) 
        { 
         var rsa = RSA.Create(); 
         rsa.ImportParameters(new RSAParameters 
         { 
          Exponent = Base64UrlEncoder.DecodeBytes(key.E), 
          Modulus = Base64UrlEncoder.DecodeBytes(key.N), 
         }); 
         latestSecurityKeys.Add(key.Kid, new RsaSecurityKey(rsa)); 

         if (kid == key.Kid) 
         { 
          securityKey = new RsaSecurityKey(rsa); 
         } 
        } 

        // Explicitly state that this assignment needs to be atomic. 
        Interlocked.Exchange(ref securityKeys, latestSecurityKeys); 
       } 
      } 
     } 

     return securityKey; 
    } 
} 

하여 키의 점점 주위에 일부 제한 토큰 서비스에 많은 왕복 강제 악의적 인 사용자를 중지하는 것이 만들 것입니다.