2017-10-25 3 views
0

웹 서비스를 호출하는 UWP 앱을 개발하고 있습니다. 이를 위해 Windows.Web.Http 네임 스페이스의 HttpClient 객체를 사용하고 생성자에 IHttpFilter 객체를 전달합니다. 이 필터는 인증 프로세스를 담당합니다. 나는이 솔루션을 기반으로 link이고 인증 로직은 this을 기반으로합니다.IHttpFilter.SendRequestAsync에서 예기치 않은 시간에 메서드가 호출되었습니다.

내가 뭘 잘못하고 있는지 모르겠지만이 예외가 있습니다. 예기치 않은 시간에 메서드가 호출되었습니다. (HRESULT 예외 : 0x8000000E)

테스트중인 시나리오는 유효하다고 간주 되더라도 토큰이 유효하지 않은 경우입니다. 이 경우 (now> TokenManager.Token.ExpiresOn) 조건이 false 인 경우 권한 부여 헤더가 추가되고 (유효하지 않은 토큰과 함께) 요청이 전송 된 다음 http 응답 코드와 www-authenticate 헤더가 검사되고 if 재 시도를 수행하려면 새로 고침 토큰을 사용하여 새 액세스 토큰을 요청해야합니다. 예외가 throw 될 때이 줄에 도달했을 때 (두 번째) : 응답 = InnerFilter.SendRequestAsync (요청)을 기다립니다 .AsTask (cancellationToken, progress);

내가 뭘 잘못하고 있는지 전혀 모르겠다. 나는 사람들이이 오류를 가지고있는 또 다른 질문을 보았다. 대개 작업의 완료를 기다리지 않고 작업 결과를 얻으려고하지만 모든 비동기 메서드에서 await 키워드를 사용하기 때문이다.

public class AuthFilter : HttpFilter 
{ 
    public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request) 
    { 
     return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) => 
     { 
      var retry = true; 
      if (TokenManager.TokenExists) 
      { 
       var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); 
       if (now > TokenManager.Token.ExpiresOn) 
       { 
        retry = false; 
        await RefreshTokenAsync(); 
       } 
       request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken); 
      } 

      HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress); 

      cancellationToken.ThrowIfCancellationRequested(); 

      if (response.StatusCode == HttpStatusCode.Unauthorized) 
      { 
       var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer"); 
       if (authHeader != null) 
       { 
        var challenge = ParseChallenge(authHeader.Parameters); 
        if (challenge.Error == "token_expired" && retry) 
        { 
         var success = await RefreshTokenAsync(); 
         if (success) 
         { 
          request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken); 
          response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress); 
         } 
        } 
       } 
      } 

      return response; 
     }); 
    } 

    private async Task<bool> RefreshTokenAsync() 
    { 
     using (var httpClient = new HttpClient()) 
     { 
      httpClient.DefaultRequestHeaders.Accept.Add(new HttpMediaTypeWithQualityHeaderValue("application/json")); 
      var content = new HttpStringContent(JsonConvert.SerializeObject(new { RefreshToken = TokenManager.Token.RefreshToken }), UnicodeEncoding.Utf8, "application/json"); 
      var response = await httpClient.PostAsync(SettingsService.Instance.WebApiUri.Append("api/login/refresh-token"), content); 
      if (response.IsSuccessStatusCode) 
       TokenManager.Token = JsonConvert.DeserializeObject<Token>(await response.Content.ReadAsStringAsync()); 
      return response.IsSuccessStatusCode; 
     } 
    } 

    private (string Realm, string Error, string ErrorDescription) ParseChallenge(IEnumerable<HttpNameValueHeaderValue> input) 
    { 
     var realm = input.SingleOrDefault(x => x.Name == "realm")?.Value ?? string.Empty; 
     var error = input.SingleOrDefault(x => x.Name == "error")?.Value ?? string.Empty; 
     var errorDescription = input.SingleOrDefault(x => x.Name == "error_description")?.Value ?? string.Empty; 
     return (realm, error, errorDescription); 
    } 

    public override void Dispose() 
    { 
     InnerFilter.Dispose(); 
     GC.SuppressFinalize(this); 
    } 
} 

public abstract class HttpFilter : IHttpFilter 
{ 
    public IHttpFilter InnerFilter { get; set; } 
    public HttpFilter() 
    { 
     InnerFilter = new HttpBaseProtocolFilter(); 
    } 
    public abstract IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request); 
    public abstract void Dispose(); 
} 

편집 :

이 링크 (link, link)에 따르면, 내가 두 번 같은 요청을 보낼 수없는 것 같다. 하지만 다른 인증 헤더를 사용하여 동일한 요청을 다시 보내야합니다. 어떻게하면 될까요?

답변

1

이 좋아, 오랜 시간 동안이 고민 후, 나는이 일을 결국 :

public override IAsyncOperationWithProgress<HttpResponseMessage, HttpProgress> SendRequestAsync(HttpRequestMessage request) 
{ 
    return AsyncInfo.Run<HttpResponseMessage, HttpProgress>(async (cancellationToken, progress) => 
    { 
     var retry = true; 
     if (TokenManager.TokenExists) 
     { 
      var now = DateTimeOffset.UtcNow.ToUnixTimeSeconds(); 
      if (now > TokenManager.Token.ExpiresOn) 
      { 
       retry = false; 
       await RefreshTokenAsync(); 
      } 
      request.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken); 
     } 

     HttpResponseMessage response = await InnerFilter.SendRequestAsync(request).AsTask(cancellationToken, progress); 

     cancellationToken.ThrowIfCancellationRequested(); 

     if (response.StatusCode == HttpStatusCode.Unauthorized) 
     { 
      var authHeader = response.Headers.WwwAuthenticate.SingleOrDefault(x => x.Scheme == "Bearer"); 
      if (authHeader != null) 
      { 
       var challenge = ParseChallenge(authHeader.Parameters); 
       if (challenge.Error == "token_expired" && retry) 
       { 
        var secondRequest = request.Clone(); 
        var success = await RefreshTokenAsync(); 
        if (success) 
        { 
         secondRequest.Headers.Authorization = new HttpCredentialsHeaderValue(TokenManager.Token.TokenType, TokenManager.Token.AccessToken); 
         response = await InnerFilter.SendRequestAsync(secondRequest).AsTask(cancellationToken, progress); 
        } 
       } 
      } 
     } 

     return response; 
    }); 
} 

public static HttpRequestMessage Clone(this HttpRequestMessage request) 
{ 
    var clone = new HttpRequestMessage(request.Method, request.RequestUri) 
    { 
     Content = request.Content 
    }; 
    foreach (KeyValuePair<string, object> prop in request.Properties.ToList()) 
    { 
     clone.Properties.Add(prop); 
    } 
    foreach (KeyValuePair<string, string> header in request.Headers.ToList()) 
    { 
     clone.Headers.Add(header.Key, header.Value); 
    } 

    return clone; 
} 

내가 요청을 다시 보낼 필요하기 때문에, 내가 첫 번째 복제 두 번째 요청을했다.