asp.net - OWIN 自托管 CookieAuthentication 和旧版 .NET 4.0 应用程序/FormsAuthenticationTicket

标签 asp.net asp.net-web-api forms-authentication owin formsauthenticationticket

我有两个bounded contexts :

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

前者是现有的成熟产品,但是,它缺乏架构(SmartUI)导致代码库难以维护,对可扩展性和可伸缩性的担忧现在更加明显。

我们正在通过引入新的后端应用程序(可通过 OWIN/WebAPI 服务公开)来迭代解决此问题。

目前我们只想在新应用程序中利用 cookie 身份验证。最初,我认为使用基于 FormsAuthenticationTicket 的现有 cookie 身份验证/验证会很容易。显然这不是真的。

在我们的 WebForms 应用程序中,我们使用 MachineKey 来指定解密 key 和验证 key 来支持我们的网络场。在.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 之间的转换。

不幸的是,我找不到有关 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 key 。然而,我读到 MS 使用 MachineKey 来进行 key 派生函数 - 考虑到其他设置,如 AppIsolation、AppVirtualLocation、AppId 等。基本上,这是在黑暗中拍摄的,我需要一些光线!

我们当前的方法

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

相关帖子

Accepting ASP.NET Forms Authentication cookies in an OWIN-hosted SignalR implementation?

最佳答案

最初对于是否可以在 app.config 中使用 元素有些困惑。进一步的原型(prototype)设计表明,我可以使用以下代码在两个有界上下文之间成功共享单个 FormsAuthenticationTicket。

理想情况下,我们将实现一个适当的授权服务器来启用 OpenID Connect、Forms、WS-Fed 等,并让这两个应用程序都通过不记名 token 运行。然而,这在短期内效果很好。希望这有帮助!

我已经测试并验证了这两个应用程序的加密/解密是否成功,以及 formsauthticket 超时的滑动。您应该注意 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);
    }
}

关于asp.net - OWIN 自托管 CookieAuthentication 和旧版 .NET 4.0 应用程序/FormsAuthenticationTicket,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/23251550/

相关文章:

c# - 使用smtp端口发送邮件时的异常处理

knockout.js - KnockoutJS 映射,来自 WebAPI 的 JSON

c# - ASP.net 表单例份验证 - 保护对我不起作用

c# - 我想知道如何将值传递给另一个 View 并在单击 asp.net mvc 中的更新按钮时更新我的​​详细信息

c# - DB 中的 System.Web.UI.WebControls.TextBox

asp.net - 如何禁用 ASP.NET 页面的自动数据绑定(bind)?

c# - 将所有 NLog 日志绑定(bind)回 WebAPI 中的原始请求的方法?

c# - .NET Core 2.1 中 UserManager 的 AutoSaveChanges

sharepoint-2010 - SharePoint 2010 自定义 WCF 服务 - Windows 和 FBA 身份验证

C# Forms Authentication .ASPXAUTH Cookie for SSO