我有一个 ASP.NET 4.6 Web API 服务作为 Azure 应用服务在单个区域的单个应用服务计划中运行。我们正在修改此服务,使其部署在多个区域,并在前面有一个负载均衡器,每个区域都有自己的应用服务计划。因此,我们需要确保在每个应用服务计划上使用相同的计算机密钥,以防止用户在负载均衡器将其定向到不同的服务器时注销。
我们的应用程序已经使用 Azure 在单个应用服务计划上自动提供的机器密钥运行了一段时间。为了避免导致所有客户在过渡期间注销,我计划提取此现有计算机密钥,然后将其部署到其他区域的新应用程序服务计划上。听起来很简单,对吧?
然而提取这个密钥被证明是一个挑战。
我已经尝试过此处列出的解决方案:获取当前 ASP.NET 机器密钥 https://stackoverflow.com/q/1755130/37725
虽然每种方法确实返回某种密钥,但该密钥似乎与实际用于生成不记名令牌或保护刷新令牌票证的密钥不匹配。当我将这些密钥部署到其他服务器时,不记名令牌仍然被视为无效,并且尝试使用现有的刷新令牌会导致invalid_grant
回复。
此外,即使我在 web.config 中手动设置机器密钥(或在运行时使用诸如this https://blog.nilayparikh.com/azure/web/set-machinekey-on-azure-web-app/),提取的机器密钥都不与我手动设置的机器密钥匹配,这提供了进一步的证据,表明它们返回的任何内容都不是实际使用的机器密钥。在我的本地开发计算机和 Azure 中都是如此。
作为参考,这是我用来以三种不同方式提取解密和验证密钥的代码(删除了一些安全密码):
[DllImport(@"C:\Windows\Microsoft.NET\Framework64\v4.0.30319\webengine4.dll")]
internal static extern int EcbCallISAPI(IntPtr pECB, int iFunction, byte[] bufferIn, int sizeIn, byte[] bufferOut, int sizeOut);
[Route("machine-key-test")]
public async Task<JObject> GetMachineKeys()
{
return new JObject(
new JProperty("A", GetAdminData()),
new JProperty("B", GetAdminDataNoIsolateApps()),
new JProperty("C", GetAdminDataPre45()));
JObject GetAdminData()
{
string appPath = "/";
byte[] genKeys = new byte[1024];
byte[] autogenKeys = new byte[1024];
int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);
if (res == 1)
{
// Same as above
int validationKeySize = 64;
int decryptionKeySize = 24;
byte[] validationKey = new byte[validationKeySize];
byte[] decryptionKey = new byte[decryptionKeySize];
Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);
int pathHash = StringComparer.InvariantCultureIgnoreCase.GetHashCode(appPath);
validationKey[0] = (byte)(pathHash & 0xff);
validationKey[1] = (byte)((pathHash & 0xff00) >> 8);
validationKey[2] = (byte)((pathHash & 0xff0000) >> 16);
validationKey[3] = (byte)((pathHash & 0xff000000) >> 24);
decryptionKey[0] = (byte)(pathHash & 0xff);
decryptionKey[1] = (byte)((pathHash & 0xff00) >> 8);
decryptionKey[2] = (byte)((pathHash & 0xff0000) >> 16);
decryptionKey[3] = (byte)((pathHash & 0xff000000) >> 24);
var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
return new JObject(
new JProperty("D", decrptionKeyString),
new JProperty("V", validationKeyString));
}
return null;
}
JObject GetAdminDataNoIsolateApps()
{
string appPath = "/";
byte[] genKeys = new byte[1024];
byte[] autogenKeys = new byte[1024];
int res = EcbCallISAPI(IntPtr.Zero, 4, genKeys, genKeys.Length, autogenKeys, autogenKeys.Length);
if (res == 1)
{
// Same as above
int validationKeySize = 64;
int decryptionKeySize = 24;
byte[] validationKey = new byte[validationKeySize];
byte[] decryptionKey = new byte[decryptionKeySize];
Buffer.BlockCopy(autogenKeys, 0, validationKey, 0, validationKeySize);
Buffer.BlockCopy(autogenKeys, validationKeySize, decryptionKey, 0, decryptionKeySize);
var decrptionKeyString = decryptionKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
var validationKeyString = validationKey.Aggregate(new StringBuilder(), (acc, c) => acc.AppendFormat("{0:x2}", c), acc => acc.ToString());
return new JObject(
new JProperty("D", decrptionKeyString),
new JProperty("V", validationKeyString));
}
return null;
}
JObject GetAdminDataPre45()
{
// https://stackoverflow.com/a/35954339/37725
byte[] autogenKeys = (byte[]) typeof(HttpRuntime)
.GetField("s_autogenKeys", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
Type t = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
"System.Web.Security.Cryptography.MachineKeyMasterKeyProvider");
ConstructorInfo ctor = t.GetConstructors(BindingFlags.Instance | BindingFlags.NonPublic)[0];
Type ckey = typeof(System.Web.Security.DefaultAuthenticationEventArgs).Assembly.GetType(
"System.Web.Security.Cryptography.CryptographicKey");
ConstructorInfo ckeyCtor = ckey.GetConstructors(BindingFlags.Instance | BindingFlags.Public)[0];
Object ckeyobj = ckeyCtor.Invoke(new object[] {autogenKeys});
object o = ctor.Invoke(new object[] {new MachineKeySection(), null, null, ckeyobj, null});
var encKey = t.GetMethod("GetEncryptionKey").Invoke(o, null);
byte[] encBytes = ckey.GetMethod("GetKeyMaterial").Invoke(encKey, null) as byte[];
var vldKey = t.GetMethod("GetValidationKey").Invoke(o, null);
byte[] vldBytes = ckey.GetMethod("GetKeyMaterial").Invoke(vldKey, null) as byte[];
string decryptionKey = BitConverter.ToString(encBytes);
decryptionKey = decryptionKey.Replace("-", "");
string validationKey = BitConverter.ToString(vldBytes);
validationKey = validationKey.Replace("-", "");
return new JObject(
new JProperty("D", decryptionKey),
new JProperty("V", validationKey));
}
}
这是我得到的输出的示例:
{
"A": {
"D": "b298ba4ef5e8421e178770f50ee5414dd0aa1698afc3169d",
"V": "b298ba4e3ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
},
"B": {
"D": "dc509c9af5e8421e178770f50ee5414dd0aa1698afc3169d",
"V": "84246e973ed466051c60e4c8646ece2546f27e8b9b2e9a569453eaab6b2a4e93bc08a3171ea61972adfd83c97d21bbcfad2acd3e6d35668f5458f8d7c8f55913"
},
"C": {
"D": "A2EDFD4ECE75A91F8E38D62B569248B14CE9193DD42E543E0D4BA5C9E2BED912",
"V": "DC6144A79985DEF712FABC729871A79FF2CF0DD73CBA617C3764D234DA1B63AD"
}
}
我尝试依次使用这些密钥集,既没有显式设置解密和验证算法,也没有指定与定义的密钥长度相匹配的各种算法组合here https://learn.microsoft.com/en-us/previous-versions/dotnet/netframework-4.0/w8h3skw9(v=vs.100),没有运气。
正如我所说,这些密钥都不会与我在我的计算机中手动设置的机器密钥匹配web.config
,或者我在代码中手动设置。
我得出的结论是,我要么做了一些微不足道的错误,要么我必须通过将所有服务器上的机器密钥更改为新密钥来强制所有用户注销。我希望有人能够指出我正确的方向。