我按照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;
}
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;
}
第一次运行程序时程序运行正常。并且一小时,AcquireTokenAsync返回相同的原始身份验证令牌,这很棒。但是一旦令牌到期,AcquiteTokenAsync应该获得一个新的令牌,其新的到期日期。而且它没有 - 应用程序只是挂起。没有错误返回,什么都没有。
因此调用AcquireToken而不是AcquireTokenAsync解决了这个问题,但我不明白为什么。你还会注意到我已经过了“无效”#39;代替&#39; TokenCache.DefaultShared&#39;使用async进入示例代码中的AuthenticationContext构造函数。这是为了迫使toke立即过期而不是一小时后过期。否则,您必须等待一个小时才能重现该行为。
我能够在一个全新的MVC项目中重现这一点,所以我不认为它与我的具体项目有任何关系。任何见解将不胜感激。但就目前而言,我只是没有使用异步。
答案 0 :(得分:23)
您的<section class="products">
<h1>Fans</h1>
<p>
<ul class="fans">
<li>
<a href="#">
<figure>
<img class="fans" src="http://placehold.it/300/300" alt="Shrouded"/>
<figcaption>Shrouded</figcaption>
</figure>
</a>
</li>
<li>
<a href="#">
<figure>
<img class="fans" src="http://placehold.it/300/300" alt="Shrouded" />
<figcaption>Shallow Recess</figcaption>
</figure>
</a>
</li>
</ul>
</p>
</section>
正在呼叫GetAwaiter().GetResult()
。这会阻塞线程,并在后续令牌请求中导致死锁。以下代码与您的代码相同,但将事物分开以便于解释。
EncryptionProvider()
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();
}
运行在我们称之为ThreadASP的内容中。AzureEncryptionProvider()
来电AzureEncryptionProvider()
。GetKeyAsync()
返回Task
。GetResult()
阻止ThreadASP,直到GetKeyAsync()
完成。GetKeyAsync()
在另一个主题上调用GetKeyAsync()
。GetAccessToken()
和GetAccessToken()
完成,释放ThreadASP。GetKeyAsync()
在ThreadASP上调用GetKeyAsync()
(不在单独的线程上。)GetAccessToken()
会返回GetKeyAsync()
。Task
阻止ThreadASP,直到GetResult()
完成。GetKeyAsync()
必须等到ThreadASP空闲,ThreadASP必须等到GetAccessToken()
完成,GetKeyAsync()
必须等到GetKeyAsync()
完成。哦,哦。 GetAccessToken()
中必须有一些流控制依赖于我们的访问令牌缓存的状态。流控制决定是否在自己的线程上运行GetKeyAsync()
以及在何时返回GetAccessToken()
。
为避免死锁,最佳做法是“一直使用异步”。当我们调用来自外部库的异步方法(例如Task
)时尤其如此。重要的是不要强制该方法与Wait()
,Result
或GetKeyAsync()
同步。相反,请使用async
and await
,因为GetResult()
会暂停该方法而不是阻止整个帖子。
await
由于构造函数不能是异步的(因为异步方法必须返回public class HomeController : Controller
{
public async Task<ActionResult> Index()
{
var provider = new EncryptionProvider();
await provider.GetKeyBundle();
var x = provider.MyKeyBundle;
return View();
}
}
),我们可以将异步内容放入单独的公共方法中。
Task
神秘解决了。 :)这是帮助我理解的a final reference。
我的原始答案有这个控制台应用程序。它作为初始故障排除步骤。 它没有重现问题。
控制台应用程序每五分钟循环一次,反复询问新的访问令牌。在每个循环中,它输出当前时间,到期时间和检索到的密钥的名称。
在我的计算机上,控制台应用程序运行了1.5小时,并在原始文件到期后成功检索到密钥。
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;
}
}
答案 1 :(得分:-1)
我遇到了同样的挑战。我假设您还看到了https://azure.microsoft.com/en-us/documentation/articles/key-vault-use-from-web-application/
上发布的样本该示例与我的代码所做的事情之间存在很大差异(我认为代码的意图是)。在示例中,它们检索secrete并将其作为Utils类的静态成员存储在Web应用程序中。因此,该示例在应用程序的整个运行时间内检索一次秘密。
就我而言,我在应用程序运行时的不同时间为不同目的检索不同的密钥。
此外,您链接到的示例下载使用X.509证书对Web应用程序进行KeyVault身份验证,而不是客户端密钥。这也可能存在问题。
我看到与@ shaun-luttin的聊天结束了你的僵局,但这不是我想的全部故事。我不使用.GetAwaiter()。GetResult()或从ctor调用异步方法。