OWIN自主CookieAuthentication和旧版.NET 4.0应用程序/ FormsAuthenticationTicket

Dan*_*iel 13 asp.net forms-authentication formsauthenticationticket asp.net-web-api owin

我有两个有限的背景:

  1. ASP.NET 4.0 MVC/WebForms应用程序
  2. OWIN自托管与ASP.NET Web API 2

前者是现有的成熟产品,然而,它缺乏架构(SmartUI)导致难以维护的代码库,现在更加明显可见的可扩展性和可扩展性.

我们通过引入一个新的后端应用程序来迭代地解决这个问题 - 可以通过OWIN/WebAPI服务进行公开.

目前,我们只希望在新应用程序中利用cookie身份验证.最初,我认为使用基于FormsAuthenticationTicket的现有cookie身份验证/验证将是轻而易举的.显然这不是真的.

在我们的WebForms应用程序中,我们使用MachineKey来指定我们的decryptionKey和validationKey以支持我们的Web场.在.NET4中,如果我没有弄错的话,默认算法是AES.我认为如果默认值不够,利用这些信息构建我们自己的TicketDataFormat会很简单.

第一件事:

  • 如果你自主机与OWIN,默认TicketDataFormat使用DPAPI并没有 ASP.NET IIS的machineKey.
  • 在.NET 4.5中,Microsoft使MVC/WebForms MachineKey管道更具可扩展性.您可以使用自己的实现替换它,而不仅仅是更改算法.

理想情况下,我们不打算将我们的主应用程序更新到.NET 4.5来替换cookie加密.有谁知道将OWIN的CookieAuthentication与现有的FormsAuthenticationTicket集成的方法?

我们试图创建自定义: IDataProtector,SecureDataFormat<AuthenticationTicket>,IDataSerializer<AuthenticationTicket>的实现.IDataSerializer将负责FormsAuthenticationTicket和AuthenticationTicket之间的转换.

不幸的是,我无法找到有关微软机票加密的准确信息.以下是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;
    }
}
Run Code Online (Sandbox Code Playgroud)

这假设Microsoft将IV添加到字节数组中.这也假设MachineKey是使用的AES密钥.但是,我已经读过MS使用MachineKey作为密钥派生函数 - 考虑其他设置,如AppIsolation,AppVirtualLocation,AppId等.基本上,这是一个黑暗的镜头,我需要一些光!

我们当前的方法

我们目前正在使用辅助cookie进行原型设计,以便与现有的.ASPXAUTH一起为新的应用程序上下文建立标识.不幸的是,这意味着在AuthenticationTicket和FormsAuthenticationTicket中保持会话同步滑动.

相关文章

在OWIN托管的SignalR实现中接受ASP.NET Forms身份验证cookie?

Dan*_*iel 14

关于我是否可以在app.config中使用<machineKey>元素存在一些初步的困惑.进一步的原型设计表明,我可以使用以下代码在两个有界上下文之间成功共享一个FormsAuthenticationTicket.

理想情况下,我们将实现适当的授权服务器以启用OpenID Connect,Forms,WS-Fed等,并使两个应用程序都在承载令牌之外运行.但是,这在短期内很有效.希望这可以帮助!

我已经测试并验证了两个应用程序的成功加密/解密,formauthticket timeout的滑动.您应该注意ticketCompatibilityMode的web.config formsConuthentication设置.


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())
        });
Run Code Online (Sandbox Code Playgroud)
<!-- 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" />
Run Code Online (Sandbox Code Playgroud)
public class HexEncoder : ITextEncoder
{
    public String Encode(Byte[] data)
    {
        return data.ToHexadecimal();
    }

    public Byte[] Decode(String text)
    {
        return text.ToBytesFromHexadecimal();
    }
}
Run Code Online (Sandbox Code Playgroud)
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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)
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);
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 刚刚看到你的消息@AshleyMedway.你还需要代码样本吗?ToNameValueCollection只是一种扩展方法,允许您指定如何将字符串拆分为键值对的字符.CustomClaim只是一个静态字符串类,代表声明类型.(即http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name)UserIdentity继承自ClaimsIdentity,但通过提供特定于我正在处理的应用程序的新属性来封装声明.(SiteId,SiteKey等) (2认同)