OWIN Self-Host CookieAuthentication & Legacy .NET 4.0 Application / FormsAuthenticationTicket
У меня есть два ограниченных контекста:
- Приложение ASP.NET 4.0 MVC/WebForms
- OWIN Self-Hosted w/ ASP.NET Web API 2
Первый - это уже хорошо зарекомендовавший себя продукт, однако его отсутствие архитектуры (SmartUI) привело к тому, что кодовая база, которую трудно поддерживать, с проблемами расширяемости и масштабируемости, стала более заметной.
Мы итеративно решаем эту проблему, представляя новое бэкэнд-приложение, доступное через сервисы OWIN/WebAPI.
В настоящее время мы стремимся использовать аутентификацию cookie только в новом приложении. Первоначально я думал, что будет проще использовать существующую аутентификацию / проверку файлов cookie на основе FormsAuthenticationTicket. Очевидно, это не так.
В нашем приложении WebForms мы используем MachineKey для обозначения наших decryptionKey и validationKey для поддержки нашей веб-фермы. В.NET4 алгоритм по умолчанию - AES, если я не ошибаюсь. Я предполагал, что было бы просто использовать эту информацию для создания нашего собственного TicketDataFormat, если по умолчанию не будет достаточно.
Первые уроки:
- При самостоятельном размещении с OWIN по умолчанию TicketDataFormat использует DPAPI, а не ASP.NET IIS MachineKey.
- В.NET 4.5 Microsoft сделала конвейер MachineCey MVC / WebForms более расширяемым. Вы можете заменить его своей собственной реализацией, а не просто изменить алгоритм.
В идеале мы не собираемся обновлять наше основное приложение до.NET 4.5, чтобы заменить шифрование cookie. Кто-нибудь знает способ интеграции CookieAuthentication OWIN с существующим FormsAuthenticationTicket?
Мы попытались создать кастом:IDataProtector
, SecureDataFormat<AuthenticationTicket>
, IDataSerializer<AuthenticationTicket>
Реализации. IDataSerializer будет отвечать за перевод между FormsAuthenticationTicket и AuthenticationTicket.
К сожалению, я не могу найти точную информацию о билетах Microsoft. Вот наш пример идеи для IDataProtector:
public byte[] Unprotect(byte[] protectedData)
{
using (var crypto = new AesCryptoServiceProvider())
{
byte[] result = null;
const Int32 blockSize = 16;
crypto.KeySize = 192;
crypto.Key = "<MachineKey>".ToBytesFromHexadecimal();
crypto.IV = protectedData.Take(blockSize).ToArray();
crypto.Padding = PaddingMode.None; // This prevents a padding exception thrown.
using (var decryptor = crypto.CreateDecryptor(crypto.Key, crypto.IV))
using (var msDecrypt = new MemoryStream(protectedData.Skip(blockSize).Take(protectedData.Length - blockSize).ToArray()))
{
using (var csDecrypt = new CryptoStream(msDecrypt, decryptor, CryptoStreamMode.Read))
{
result = new byte[protectedData.Length - blockSize];
csDecrypt.Read(result, 0, result.Length);
}
}
return result;
}
}
Это предполагает, что Microsoft добавляет IV к байтовому массиву. Это также предполагает, что MachineKey - это используемый ключ AES. Тем не менее, я прочитал, что MS использует MachineKey для функции получения ключа - принимая во внимание другие настройки, такие как AppIsolation, AppVirtualLocation, AppId и т. Д. В основном, это был выстрел в темноте, и мне нужно немного света!
Наш текущий подход
В настоящее время мы создаем прототип с использованием вторичного файла cookie для идентификации нового контекста приложения вместе с существующим.ASPXAUTH. К сожалению, это означает синхронизацию скольжения сеансов как в AuthenticationTicket, так и в FormsAuthenticationTicket.
Похожие сообщения
1 ответ
Сначала возникла путаница в том, могу ли я использовать элемент
В идеале мы реализуем соответствующий сервер авторизации для включения OpenID Connect, Forms, WS-Fed и т. Д., И оба приложения будут работать с токенами на предъявителя. Тем не менее, это работает хорошо в краткосрочной перспективе. Надеюсь это поможет!
Я проверил и проверил успешное шифрование / дешифрование с обоими приложениями, сдвиг тайм-аута формы. Вы должны помнить о своем параметре web.config formsAuthentication для ticketCompatibilityMode.
appBuilder.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = FormsAuthentication.FormsCookieName,
CookieDomain = FormsAuthentication.CookieDomain,
CookiePath = FormsAuthentication.FormsCookiePath,
CookieSecure = CookieSecureOption.SameAsRequest,
AuthenticationMode = AuthenticationMode.Active,
ExpireTimeSpan = FormsAuthentication.Timeout,
SlidingExpiration = true,
AuthenticationType = "Forms",
TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(
new FormsAuthenticationTicketSerializer(),
new FormsAuthenticationTicketDataProtector(),
new HexEncoder())
});
<!-- app.config for OWIN Host - Only used for compatibility with existing auth ticket. -->
<authentication mode="Forms">
<forms domain=".hostname.com" protection="All" ... />
</authentication>
<machineKey validationKey="..." decryptionKey="..." validation="SHA1" />
public class HexEncoder : ITextEncoder
{
public String Encode(Byte[] data)
{
return data.ToHexadecimal();
}
public Byte[] Decode(String text)
{
return text.ToBytesFromHexadecimal();
}
}
public class FormsAuthenticationTicketDataProtector : IDataProtector
{
public Byte[] Protect(Byte[] userData)
{
FormsAuthenticationTicket ticket;
using (var memoryStream = new MemoryStream(userData))
{
var binaryFormatter = new BinaryFormatter();
ticket = binaryFormatter.Deserialize(memoryStream) as FormsAuthenticationTicket;
}
if (ticket == null)
{
return null;
}
try
{
var encryptedTicket = FormsAuthentication.Encrypt(ticket);
return encryptedTicket.ToBytesFromHexadecimal();
}
catch
{
return null;
}
}
public Byte[] Unprotect(Byte[] protectedData)
{
FormsAuthenticationTicket ticket;
try
{
ticket = FormsAuthentication.Decrypt(protectedData.ToHexadecimal());
}
catch
{
return null;
}
if (ticket == null)
{
return null;
}
using (var memoryStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(memoryStream, ticket);
return memoryStream.ToArray();
}
}
}
public class FormsAuthenticationTicketSerializer : IDataSerializer<AuthenticationTicket>
{
public Byte[] Serialize(AuthenticationTicket model)
{
var userTicket = new FormsAuthenticationTicket(
2,
model.Identity.GetClaimValue<String>(CustomClaim.UserName),
new DateTime(model.Properties.IssuedUtc.Value.UtcDateTime.Ticks, DateTimeKind.Utc),
new DateTime(model.Properties.ExpiresUtc.Value.UtcDateTime.Ticks, DateTimeKind.Utc),
model.Properties.IsPersistent,
String.Format(
"AuthenticationType={0};SiteId={1};SiteKey={2};UserId={3}",
model.Identity.AuthenticationType,
model.Identity.GetClaimValue<String>(CustomClaim.SiteId),
model.Identity.GetClaimValue<String>(CustomClaim.SiteKey),
model.Identity.GetClaimValue<String>(CustomClaim.UserId)),
FormsAuthentication.FormsCookiePath);
using (var dataStream = new MemoryStream())
{
var binaryFormatter = new BinaryFormatter();
binaryFormatter.Serialize(dataStream, userTicket);
return dataStream.ToArray();
}
}
public AuthenticationTicket Deserialize(Byte[] data)
{
using (var dataStream = new MemoryStream(data))
{
var binaryFormatter = new BinaryFormatter();
var ticket = binaryFormatter.Deserialize(dataStream) as FormsAuthenticationTicket;
if (ticket == null)
{
return null;
}
var userData = ticket.UserData.ToNameValueCollection(';', '=');
var authenticationType = userData["AuthenticationType"];
var siteId = userData["SiteId"];
var siteKey = userData["SiteKey"];
var userId = userData["UserId"];
var claims = new[]
{
CreateClaim(CustomClaim.UserName, ticket.Name),
CreateClaim(CustomClaim.UserId, userId),
CreateClaim(CustomClaim.AuthenticationMethod, authenticationType),
CreateClaim(CustomClaim.SiteId, siteId),
CreateClaim(CustomClaim.SiteKey, siteKey)
};
var authTicket = new AuthenticationTicket(new UserIdentity(claims, authenticationType), new AuthenticationProperties());
authTicket.Properties.IssuedUtc = new DateTimeOffset(ticket.IssueDate);
authTicket.Properties.ExpiresUtc = new DateTimeOffset(ticket.Expiration);
authTicket.Properties.IsPersistent = ticket.IsPersistent;
return authTicket;
}
}
private Claim CreateClaim(String type, String value)
{
return new Claim(type, value, ClaimValueTypes.String, CustomClaim.Issuer);
}
}