Aar*_*ron 15 c# asp.net azure async-await azure-active-directory
我按照Microsoft的Hello Key Vault示例应用程序中的示例在我的ASP.Net MVC Web应用程序上设置了Azure Keyvault .
Azure KeyVault(Active Directory)AuthenticationResult默认情况下有一个小时到期.因此,一小时后,您必须获得一个新的身份验证令牌.在获得我的第一个AuthenticationResult令牌后,KeyVault正在按预期工作,但在1小时到期后,它无法获得新令牌.
不幸的是,我的生产环境失败让我意识到这一点,因为我从未测试过去一小时的开发.
无论如何,经过两天多的努力弄清楚我的keyvault代码出了什么问题,我提出了一个解决方案来修复我的所有问题 - 删除异步代码 - 但感觉非常hacky.我想找出为什么它首先不起作用.
我的代码看起来像这样:
public AzureEncryptionProvider() //class constructor
{
_keyVaultClient = new KeyVaultClient(GetAccessToken);
_keyBundle = _keyVaultClient
.GetKeyAsync(_keyVaultUrl, _keyVaultEncryptionKeyName)
.GetAwaiter().GetResult();
}
private static readonly string _keyVaultAuthClientId =
ConfigurationManager.AppSettings["KeyVaultAuthClientId"];
private static readonly string _keyVaultAuthClientSecret =
ConfigurationManager.AppSettings["KeyVaultAuthClientSecret"];
private static readonly string _keyVaultEncryptionKeyName =
ConfigurationManager.AppSettings["KeyVaultEncryptionKeyName"];
private static readonly string _keyVaultUrl =
ConfigurationManager.AppSettings["KeyVaultUrl"];
private readonly KeyBundle _keyBundle;
private readonly KeyVaultClient _keyVaultClient;
private static async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(
_keyVaultAuthClientId,
_keyVaultAuthClientSecret);
var context = new AuthenticationContext(
authority,
TokenCache.DefaultShared);
var result = context.AcquireToken(resource, clientCredential);
return result.AccessToken;
}
Run Code Online (Sandbox Code Playgroud)
GetAccessToken方法签名必须是异步才能传递给新的KeyVaultClient构造函数,因此我将签名保留为async,但我删除了await关键字.
使用await关键字(它应该是这样的,并且在样本中):
private static async Task<string> GetAccessToken(string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(_keyVaultAuthClientId, _keyVaultAuthClientSecret);
var context = new AuthenticationContext(authority, null);
var result = await context.AcquireTokenAsync(resource, clientCredential);
return result.AccessToken;
}
Run Code Online (Sandbox Code Playgroud)
该程序在我第一次运行时工作正常.并且一小时,AcquireTokenAsync返回相同的原始身份验证令牌,这很棒.但是一旦令牌到期,AcquiteTokenAsync应该获得一个新的令牌,其新的到期日期.它没有 - 应用程序只是挂起.没有错误返回,什么都没有.
所以调用AcquireToken而不是AcquireTokenAsync解决了这个问题,但我不明白为什么.你还会注意到我在我的示例代码中使用async将'null'而不是'TokenCache.DefaultShared'传递给AuthenticationContext构造函数.这是为了迫使toke立即过期而不是一小时后过期.否则,您必须等待一个小时才能重现该行为.
我能够在一个全新的MVC项目中再次重现这一点,所以我认为它与我的具体项目没有任何关系.任何见解将不胜感激.但就目前而言,我只是不使用异步.
Sha*_*tin 25
你EncryptionProvider()
在打电话GetAwaiter().GetResult()
.这会阻塞线程,并在后续令牌请求中导致死锁.以下代码与您的代码相同,但将事物分开以便于解释.
public AzureEncryptionProvider() // runs in ThreadASP
{
var client = new KeyVaultClient(GetAccessToken);
var task = client.GetKeyAsync(KeyVaultUrl, KeyVaultEncryptionKeyName);
var awaiter = task.GetAwaiter();
// blocks ThreadASP until GetKeyAsync() completes
var keyBundle = awaiter.GetResult();
}
Run Code Online (Sandbox Code Playgroud)
AzureEncryptionProvider()
在我们称之为ThreadASP的内容中运行.AzureEncryptionProvider()
电话GetKeyAsync()
.GetKeyAsync()
返回一个Task
.GetResult()
阻塞ThreadASP直到GetKeyAsync()
完成.GetKeyAsync()
调用GetAccessToken()
另一个线程.GetAccessToken()
并GetKeyAsync()
完成,释放ThreadASP.GetKeyAsync()
调用GetAccessToken()
ThreadASP(不在单独的线程上.)GetKeyAsync()
返回一个Task
.GetResult()
阻塞ThreadASP直到GetKeyAsync()
完成.GetAccessToken()
必须等到ThreadASP空闲,ThreadASP必须等到GetKeyAsync()
完成,GetKeyAsync()
必须等到GetAccessToken()
完成.哦,哦.必须有一些流控制GetKeyAsync()
依赖于我们的访问令牌缓存的状态.流控制决定是否GetAccessToken()
在自己的线程上运行以及在什么时候返回Task
.
为避免死锁,最佳做法是"一直使用异步".当我们调用异步方法(例如GetKeyAsync()
来自外部库)时,尤其如此.这是重要的不是方法,通过同步以武力Wait()
,Result
或GetResult()
.相反,使用async
和await
因为await
暂停方法而不是阻塞整个线程.
public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
var provider = new EncryptionProvider();
await provider.GetKeyBundle();
var x = provider.MyKeyBundle;
return View();
}
}
Run Code Online (Sandbox Code Playgroud)
由于构造函数不能是异步的(因为异步方法必须返回a Task
),我们可以将异步内容放入单独的公共方法中.
public class EncryptionProvider
{
//
// authentication properties omitted
public KeyBundle MyKeyBundle;
public EncryptionProvider() { }
public async Task GetKeyBundle()
{
var keyVaultClient = new KeyVaultClient(GetAccessToken);
var keyBundleTask = await keyVaultClient
.GetKeyAsync(KeyVaultUrl, KeyVaultEncryptionKeyName);
MyKeyBundle = keyBundleTask;
}
private async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
TokenCache.DefaultShared.Clear(); // reproduce issue
var authContext = new AuthenticationContext(authority, TokenCache.DefaultShared);
var clientCredential = new ClientCredential(ClientIdWeb, ClientSecretWeb);
var result = await authContext.AcquireTokenAsync(resource, clientCredential);
var token = result.AccessToken;
return token;
}
}
Run Code Online (Sandbox Code Playgroud)
我的原始答案有这个控制台应用程序 它作为初始故障排除步骤.它没有重现这个问题.
控制台应用程序每五分钟循环一次,反复询问新的访问令牌.在每个循环中,它输出当前时间,到期时间和检索到的密钥的名称.
在我的机器上,控制台应用程序运行了1.5小时,并在原始文件到期后成功检索到密钥.
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using Microsoft.Azure.KeyVault;
using Microsoft.IdentityModel.Clients.ActiveDirectory;
namespace ConsoleApp
{
class Program
{
private static async Task RunSample()
{
var keyVaultClient = new KeyVaultClient(GetAccessToken);
// create a key :)
var keyCreate = await keyVaultClient.CreateKeyAsync(
vault: _keyVaultUrl,
keyName: _keyVaultEncryptionKeyName,
keyType: _keyType,
keyAttributes: new KeyAttributes()
{
Enabled = true,
Expires = UnixEpoch.FromUnixTime(int.MaxValue),
NotBefore = UnixEpoch.FromUnixTime(0),
},
tags: new Dictionary<string, string> {
{ "purpose", "StackOverflow Demo" }
});
Console.WriteLine(string.Format(
"Created {0} ",
keyCreate.KeyIdentifier.Name));
// retrieve the key
var keyRetrieve = await keyVaultClient.GetKeyAsync(
_keyVaultUrl,
_keyVaultEncryptionKeyName);
Console.WriteLine(string.Format(
"Retrieved {0} ",
keyRetrieve.KeyIdentifier.Name));
}
private static async Task<string> GetAccessToken(
string authority, string resource, string scope)
{
var clientCredential = new ClientCredential(
_keyVaultAuthClientId,
_keyVaultAuthClientSecret);
var context = new AuthenticationContext(
authority,
TokenCache.DefaultShared);
var result = await context.AcquireTokenAsync(resource, clientCredential);
_expiresOn = result.ExpiresOn.DateTime;
Console.WriteLine(DateTime.UtcNow.ToShortTimeString());
Console.WriteLine(_expiresOn.ToShortTimeString());
return result.AccessToken;
}
private static DateTime _expiresOn;
private static string
_keyVaultAuthClientId = "xxxxx-xxx-xxxxx-xxx-xxxxx",
_keyVaultAuthClientSecret = "xxxxx-xxx-xxxxx-xxx-xxxxx",
_keyVaultEncryptionKeyName = "MYENCRYPTIONKEY",
_keyVaultUrl = "https://xxxxx.vault.azure.net/",
_keyType = "RSA";
static void Main(string[] args)
{
var keepGoing = true;
while (keepGoing)
{
RunSample().GetAwaiter().GetResult();
// sleep for five minutes
System.Threading.Thread.Sleep(new TimeSpan(0, 5, 0));
if (DateTime.UtcNow > _expiresOn)
{
Console.WriteLine("---Expired---");
Console.ReadLine();
}
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
归档时间: |
|
查看次数: |
10489 次 |
最近记录: |