停止共享不记名令牌解密的 webapi2 发生了什么变化?
What Changed in webapi2 that stops shared bearer token decryption?
我对使用同一数据库的 2 个 api 有特殊要求。如果我使用 VS2013 中的默认模板创建一个项目,并将来自同一台机器上其他 api 的访问令牌传递给它,它将正确解密并加载用户。然而,第二次我打开 nuget 包管理器控制台并键入 "Update-Package" 以更新所有模板的包它不再解密访问令牌和 returns 未经授权的访问?
我想在删除所有模板膨胀之前更新它。发生了什么变化?有没有办法对其进行编码,使其在我更新后正常工作?
这个周末我真的解决了这个问题。我的猜测是您从 3.0.0 更新到 3.0.1(谈论 Microsoft.Owin.Security
)。
程序集包含一个名为 TokenSerializer
的 class。如果你反编译代码,你会看到它有一个内部格式版本。在 3.0.0 中,内部格式为“2”。在 3.0.1 中,他们将内部格式版本提高到“3”。这会导致 .Deserialize
方法退出。除了破坏语义版本控制的愚蠢之外,序列化和反序列化方法是相同的,这意味着它们 可以 更改代码以接受两种格式 或 实现为我们迁移。
我最终做的是将代码的 3.0.0 版本内联为一个新的 class(因为我不能在同一个进程中托管两个程序集并且建立一个新进程太多了麻烦),我只是手动尝试反序列化两者。工作正常,但确实很讨厌。
更新代码:
public class RefreshTokenTicketRepository : AzureTableStorageRepository<RefreshTokenTicketDto>, IRefreshTokenTicketRepository
{
private static readonly TicketSerializer _ticketSerializer = new TicketSerializer();
private static readonly TicketSerializer_v2 _ticketSerializer_v2 = new TicketSerializer_v2();
private const string tableName = "refreshtokentickets";
public const string PartitionKey = "refreshtokentickets";
public enum SerializationFormatVersion
{
vCurrent,
v2,
Unknown
}
private readonly ILogger _logger;
public RefreshTokenTicketRepository(IHoursTrackerCloudStorageAccountFactory storageAccountFactory, ILogger logger)
: base(storageAccountFactory, tableName)
{
_logger = logger.ForContext<RefreshTokenTicketRepository>();
}
public async Task<AuthenticationTicket> GetAsync(string refreshToken)
{
using (LogContext.PushProperty("Refresh Token", refreshToken))
{
if (String.IsNullOrEmpty(refreshToken))
{
_logger.Debug("Null or empty refresh token");
return null;
}
RefreshTokenTicketDto refreshTokenTicketDto = await GetAsync(PartitionKey, refreshToken);
if (refreshTokenTicketDto == null)
{
_logger.Debug("Authentication ticket not found");
return null;
}
SerializationFormatVersion version;
AuthenticationTicket authenticationTicket = Deserialize(refreshTokenTicketDto.RefreshTokenTicket, out version);
if (authenticationTicket != null && version != SerializationFormatVersion.vCurrent)
{
_logger.Information("Updating authentication ticket serialization format from {Version}", version);
await InsertOrReplaceAsync(new RefreshTokenTicketDto(refreshToken, _ticketSerializer.Serialize(authenticationTicket)));
}
return authenticationTicket;
}
}
public Task PersistAsync(string refreshToken, AuthenticationTicket refreshTokenTicket)
{
return PersistAsync(new RefreshTokenTicketDto(refreshToken, _ticketSerializer.Serialize(refreshTokenTicket)));
}
/// <summary>
/// In Microsoft.Owin.Security 3.0.1, Microsoft changed the internal version format from 2 to 3.
/// This caused the TokenSerializer to return null for otherwise valid AuthenticationTickets.
/// So we decompiled the v2 implementation and inlined it below and update the the bytes to reflect v3.
/// </summary>
/// <param name="authenticationTicketBytes"></param>
/// <param name="version">The version of the AuthenticationTicket</param>
/// <returns></returns>
public AuthenticationTicket Deserialize(byte[] authenticationTicketBytes, out SerializationFormatVersion version)
{
_logger.Debug("Deserializing authentication ticket");
AuthenticationTicket authenticationTicket = _ticketSerializer.Deserialize(authenticationTicketBytes);
if (authenticationTicket != null)
{
version = SerializationFormatVersion.vCurrent;
_logger.Debug("Authentication ticket version is {Version}", version);
return authenticationTicket;
}
authenticationTicket = _ticketSerializer_v2.Deserialize(authenticationTicketBytes);
if (authenticationTicket != null)
{
version = SerializationFormatVersion.v2;
_logger.Debug("Authentication ticket version is {Version}", version);
return authenticationTicket;
}
version = SerializationFormatVersion.Unknown;
_logger.Debug("Authentication ticket version is {Version}", version);
return null;
}
public byte[] Serialize(AuthenticationTicket authenticationTicket)
{
return _ticketSerializer.Serialize(authenticationTicket);
}
public byte[] Serialize_v2(AuthenticationTicket authenticationTicket)
{
return _ticketSerializer_v2.Serialize(authenticationTicket);
}
#region Decompiled Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer from Microsoft.Owin.Security 3.0.0
private class TicketSerializer_v2 : IDataSerializer<AuthenticationTicket>
{
private const int FormatVersion = 2;
public byte[] Serialize(AuthenticationTicket model)
{
using (var memoryStream = new MemoryStream())
{
using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Optimal))
{
using (var writer = new BinaryWriter(gzipStream))
{
Write(writer, model);
}
}
return memoryStream.ToArray();
}
}
public AuthenticationTicket Deserialize(byte[] data)
{
using (var memoryStream = new MemoryStream(data))
{
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
using (var reader = new BinaryReader(gzipStream))
{
return Read(reader);
}
}
}
}
private static void Write(BinaryWriter writer, AuthenticationTicket model)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (model == null)
{
throw new ArgumentNullException("model");
}
writer.Write(FormatVersion);
ClaimsIdentity identity = model.Identity;
writer.Write(identity.AuthenticationType);
WriteWithDefault(writer, identity.NameClaimType, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
WriteWithDefault(writer, identity.RoleClaimType, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
writer.Write(identity.Claims.Count());
foreach (Claim claim in identity.Claims)
{
WriteWithDefault(writer, claim.Type, identity.NameClaimType);
writer.Write(claim.Value);
WriteWithDefault(writer, claim.ValueType, "http://www.w3.org/2001/XMLSchema#string");
WriteWithDefault(writer, claim.Issuer, "LOCAL AUTHORITY");
WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer);
}
var bootstrapContext = identity.BootstrapContext as BootstrapContext;
if (bootstrapContext == null || string.IsNullOrWhiteSpace(bootstrapContext.Token))
{
writer.Write(0);
}
else
{
writer.Write(bootstrapContext.Token.Length);
writer.Write(bootstrapContext.Token);
}
PropertiesSerializer.Write(writer, model.Properties);
}
private static AuthenticationTicket Read(BinaryReader reader)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (reader.ReadInt32() != FormatVersion)
{
return null;
}
string authenticationType = reader.ReadString();
string str1 = ReadWithDefault(reader, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
string roleType = ReadWithDefault(reader, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
int length = reader.ReadInt32();
var claimArray = new Claim[length];
for (var index = 0; index != length; ++index)
{
string type = ReadWithDefault(reader, str1);
string str2 = reader.ReadString();
string valueType = ReadWithDefault(reader, "http://www.w3.org/2001/XMLSchema#string");
string str3 = ReadWithDefault(reader, "LOCAL AUTHORITY");
string originalIssuer = ReadWithDefault(reader, str3);
claimArray[index] = new Claim(type, str2, valueType, str3, originalIssuer);
}
var identity = new ClaimsIdentity(claimArray, authenticationType, str1, roleType);
if (reader.ReadInt32() > 0)
{
identity.BootstrapContext = new BootstrapContext(reader.ReadString());
}
AuthenticationProperties properties = PropertiesSerializer.Read(reader);
return new AuthenticationTicket(identity, properties);
}
private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
{
if (string.Equals(value, defaultValue, StringComparison.Ordinal))
{
writer.Write("[=10=]");
}
else
{
writer.Write(value);
}
}
private static string ReadWithDefault(BinaryReader reader, string defaultValue)
{
string a = reader.ReadString();
if (string.Equals(a, "[=10=]", StringComparison.Ordinal))
{
return defaultValue;
}
return a;
}
}
#endregion
}
我对使用同一数据库的 2 个 api 有特殊要求。如果我使用 VS2013 中的默认模板创建一个项目,并将来自同一台机器上其他 api 的访问令牌传递给它,它将正确解密并加载用户。然而,第二次我打开 nuget 包管理器控制台并键入 "Update-Package" 以更新所有模板的包它不再解密访问令牌和 returns 未经授权的访问?
我想在删除所有模板膨胀之前更新它。发生了什么变化?有没有办法对其进行编码,使其在我更新后正常工作?
这个周末我真的解决了这个问题。我的猜测是您从 3.0.0 更新到 3.0.1(谈论 Microsoft.Owin.Security
)。
程序集包含一个名为 TokenSerializer
的 class。如果你反编译代码,你会看到它有一个内部格式版本。在 3.0.0 中,内部格式为“2”。在 3.0.1 中,他们将内部格式版本提高到“3”。这会导致 .Deserialize
方法退出。除了破坏语义版本控制的愚蠢之外,序列化和反序列化方法是相同的,这意味着它们 可以 更改代码以接受两种格式 或 实现为我们迁移。
我最终做的是将代码的 3.0.0 版本内联为一个新的 class(因为我不能在同一个进程中托管两个程序集并且建立一个新进程太多了麻烦),我只是手动尝试反序列化两者。工作正常,但确实很讨厌。
更新代码:
public class RefreshTokenTicketRepository : AzureTableStorageRepository<RefreshTokenTicketDto>, IRefreshTokenTicketRepository
{
private static readonly TicketSerializer _ticketSerializer = new TicketSerializer();
private static readonly TicketSerializer_v2 _ticketSerializer_v2 = new TicketSerializer_v2();
private const string tableName = "refreshtokentickets";
public const string PartitionKey = "refreshtokentickets";
public enum SerializationFormatVersion
{
vCurrent,
v2,
Unknown
}
private readonly ILogger _logger;
public RefreshTokenTicketRepository(IHoursTrackerCloudStorageAccountFactory storageAccountFactory, ILogger logger)
: base(storageAccountFactory, tableName)
{
_logger = logger.ForContext<RefreshTokenTicketRepository>();
}
public async Task<AuthenticationTicket> GetAsync(string refreshToken)
{
using (LogContext.PushProperty("Refresh Token", refreshToken))
{
if (String.IsNullOrEmpty(refreshToken))
{
_logger.Debug("Null or empty refresh token");
return null;
}
RefreshTokenTicketDto refreshTokenTicketDto = await GetAsync(PartitionKey, refreshToken);
if (refreshTokenTicketDto == null)
{
_logger.Debug("Authentication ticket not found");
return null;
}
SerializationFormatVersion version;
AuthenticationTicket authenticationTicket = Deserialize(refreshTokenTicketDto.RefreshTokenTicket, out version);
if (authenticationTicket != null && version != SerializationFormatVersion.vCurrent)
{
_logger.Information("Updating authentication ticket serialization format from {Version}", version);
await InsertOrReplaceAsync(new RefreshTokenTicketDto(refreshToken, _ticketSerializer.Serialize(authenticationTicket)));
}
return authenticationTicket;
}
}
public Task PersistAsync(string refreshToken, AuthenticationTicket refreshTokenTicket)
{
return PersistAsync(new RefreshTokenTicketDto(refreshToken, _ticketSerializer.Serialize(refreshTokenTicket)));
}
/// <summary>
/// In Microsoft.Owin.Security 3.0.1, Microsoft changed the internal version format from 2 to 3.
/// This caused the TokenSerializer to return null for otherwise valid AuthenticationTickets.
/// So we decompiled the v2 implementation and inlined it below and update the the bytes to reflect v3.
/// </summary>
/// <param name="authenticationTicketBytes"></param>
/// <param name="version">The version of the AuthenticationTicket</param>
/// <returns></returns>
public AuthenticationTicket Deserialize(byte[] authenticationTicketBytes, out SerializationFormatVersion version)
{
_logger.Debug("Deserializing authentication ticket");
AuthenticationTicket authenticationTicket = _ticketSerializer.Deserialize(authenticationTicketBytes);
if (authenticationTicket != null)
{
version = SerializationFormatVersion.vCurrent;
_logger.Debug("Authentication ticket version is {Version}", version);
return authenticationTicket;
}
authenticationTicket = _ticketSerializer_v2.Deserialize(authenticationTicketBytes);
if (authenticationTicket != null)
{
version = SerializationFormatVersion.v2;
_logger.Debug("Authentication ticket version is {Version}", version);
return authenticationTicket;
}
version = SerializationFormatVersion.Unknown;
_logger.Debug("Authentication ticket version is {Version}", version);
return null;
}
public byte[] Serialize(AuthenticationTicket authenticationTicket)
{
return _ticketSerializer.Serialize(authenticationTicket);
}
public byte[] Serialize_v2(AuthenticationTicket authenticationTicket)
{
return _ticketSerializer_v2.Serialize(authenticationTicket);
}
#region Decompiled Microsoft.Owin.Security.DataHandler.Serializer.TicketSerializer from Microsoft.Owin.Security 3.0.0
private class TicketSerializer_v2 : IDataSerializer<AuthenticationTicket>
{
private const int FormatVersion = 2;
public byte[] Serialize(AuthenticationTicket model)
{
using (var memoryStream = new MemoryStream())
{
using (var gzipStream = new GZipStream(memoryStream, CompressionLevel.Optimal))
{
using (var writer = new BinaryWriter(gzipStream))
{
Write(writer, model);
}
}
return memoryStream.ToArray();
}
}
public AuthenticationTicket Deserialize(byte[] data)
{
using (var memoryStream = new MemoryStream(data))
{
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
{
using (var reader = new BinaryReader(gzipStream))
{
return Read(reader);
}
}
}
}
private static void Write(BinaryWriter writer, AuthenticationTicket model)
{
if (writer == null)
{
throw new ArgumentNullException("writer");
}
if (model == null)
{
throw new ArgumentNullException("model");
}
writer.Write(FormatVersion);
ClaimsIdentity identity = model.Identity;
writer.Write(identity.AuthenticationType);
WriteWithDefault(writer, identity.NameClaimType, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
WriteWithDefault(writer, identity.RoleClaimType, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
writer.Write(identity.Claims.Count());
foreach (Claim claim in identity.Claims)
{
WriteWithDefault(writer, claim.Type, identity.NameClaimType);
writer.Write(claim.Value);
WriteWithDefault(writer, claim.ValueType, "http://www.w3.org/2001/XMLSchema#string");
WriteWithDefault(writer, claim.Issuer, "LOCAL AUTHORITY");
WriteWithDefault(writer, claim.OriginalIssuer, claim.Issuer);
}
var bootstrapContext = identity.BootstrapContext as BootstrapContext;
if (bootstrapContext == null || string.IsNullOrWhiteSpace(bootstrapContext.Token))
{
writer.Write(0);
}
else
{
writer.Write(bootstrapContext.Token.Length);
writer.Write(bootstrapContext.Token);
}
PropertiesSerializer.Write(writer, model.Properties);
}
private static AuthenticationTicket Read(BinaryReader reader)
{
if (reader == null)
{
throw new ArgumentNullException("reader");
}
if (reader.ReadInt32() != FormatVersion)
{
return null;
}
string authenticationType = reader.ReadString();
string str1 = ReadWithDefault(reader, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name");
string roleType = ReadWithDefault(reader, "http://schemas.microsoft.com/ws/2008/06/identity/claims/role");
int length = reader.ReadInt32();
var claimArray = new Claim[length];
for (var index = 0; index != length; ++index)
{
string type = ReadWithDefault(reader, str1);
string str2 = reader.ReadString();
string valueType = ReadWithDefault(reader, "http://www.w3.org/2001/XMLSchema#string");
string str3 = ReadWithDefault(reader, "LOCAL AUTHORITY");
string originalIssuer = ReadWithDefault(reader, str3);
claimArray[index] = new Claim(type, str2, valueType, str3, originalIssuer);
}
var identity = new ClaimsIdentity(claimArray, authenticationType, str1, roleType);
if (reader.ReadInt32() > 0)
{
identity.BootstrapContext = new BootstrapContext(reader.ReadString());
}
AuthenticationProperties properties = PropertiesSerializer.Read(reader);
return new AuthenticationTicket(identity, properties);
}
private static void WriteWithDefault(BinaryWriter writer, string value, string defaultValue)
{
if (string.Equals(value, defaultValue, StringComparison.Ordinal))
{
writer.Write("[=10=]");
}
else
{
writer.Write(value);
}
}
private static string ReadWithDefault(BinaryReader reader, string defaultValue)
{
string a = reader.ReadString();
if (string.Equals(a, "[=10=]", StringComparison.Ordinal))
{
return defaultValue;
}
return a;
}
}
#endregion
}