asp.net - 将 SessionStore (ITicketStore) 添加到我的应用程序 cookie 会使我的数据保护提供程序无法工作

标签 asp.net cookies asp.net-core asp.net-core-2.0

tl;博士

  • 拥有 .NET Core 2.0 应用程序,该应用程序使用数据保护提供程序,在我域中的所有网站上保留一个 key 文件。
  • 工作正常,但是,应用程序 cookie 变得太大。
  • 使用 ITicketStore 在 cookie 上实现了 SessionStore
  • Cookie 的大小大大减少了,但是,来自 DPP 的 key 不再在我的网站上保留。

我应该在我的 ITicketStore 实现中做些什么来解决这个问题吗?我假设是这样,因为这是出现问题的地方,但是,我无法弄清楚。

一些片段:


Startup.cs --> ConfigureServices()

var keysFolder = $@"c:\temp\_WebAppKeys\{_env.EnvironmentName.ToLower()}";
var protectionProvider = DataProtectionProvider.Create(new DirectoryInfo(keysFolder));
var dataProtector = protectionProvider.CreateProtector(
            "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
            "Cookies",
            "v2");

--snip--

services.AddSingleton<ITicketStore, TicketStore>();

--snip--

services.AddDataProtection()
    .PersistKeysToFileSystem(new DirectoryInfo(keysFolder))
    .SetApplicationName("app_auth");

services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = ".XAUTH";
    options.Cookie.Domain = ".domain.com";
    options.ExpireTimeSpan = TimeSpan.FromDays(7);
    options.LoginPath = "/Account/Login";
    options.DataProtectionProvider = protectionProvider;
    options.TicketDataFormat = new TicketDataFormat(dataProtector);
    options.CookieManager = new ChunkingCookieManager();
    options.SessionStore = services.BuildServiceProvider().GetService<ITicketStore>();
});

TicketStore.cs

public class TicketStore : ITicketStore
{
    private IMemoryCache _cache;
    private const string KeyPrefix = "AuthSessionStore-";

public TicketStore(IMemoryCache cache)
{
    _cache = cache;
}

public Task RemoveAsync(string key)
{
    _cache.Remove(key);
    return Task.FromResult(0);
}

public Task RenewAsync(string key, AuthenticationTicket ticket)
{
    var options = new MemoryCacheEntryOptions
    {
        Priority = CacheItemPriority.NeverRemove
    };
    var expiresUtc = ticket.Properties.ExpiresUtc;

    if (expiresUtc.HasValue)
    {
        options.SetAbsoluteExpiration(expiresUtc.Value);
    }

    options.SetSlidingExpiration(TimeSpan.FromMinutes(60));

    _cache.Set(key, ticket, options);

    return Task.FromResult(0);
}

public Task<AuthenticationTicket> RetrieveAsync(string key)
{
    AuthenticationTicket ticket;
    _cache.TryGetValue(key, out ticket);
    return Task.FromResult(ticket);
}

public async Task<string> StoreAsync(AuthenticationTicket ticket)
{
    var key = KeyPrefix + Guid.NewGuid();
    await RenewAsync(key, ticket);
    return key;
}

最佳答案

我也遇到了这个问题。

Microsoft.Owin.Security.Cookies中的SessionIdClaim值为“Microsoft.Owin.Security.Cookies-SessionId”,而Microsoft.AspNetCore.Authentication.Cookies中的SessionIdClaim值为“Microsoft.AspNetCore.Authentication.Cookies-SessionId ".

即使您实现了分布式 session 存储(例如使用 RedisCacheTicketStore),也会由于 AspNetCore 端的此代码而导致 SessionId Missing 错误,如下所述:https://mikerussellnz.github.io/.NET-Core-Auth-Ticket-Redis/

我能够使用新字符串重新编译 AspNetKatana 项目,然后在 .NET Core 端找到了 SessionID。

此外,似乎 AuthenticationTicket 类不同,所以我能够通过实现一个转换方法来转换 Microsoft.Owin.Security.AuthenticationTicket 票证到 Microsoft.AspNetCore.Authentication.AuthenticationTicket 票证,然后使用 AspNetCore 序列化程序 (Microsoft.AspNetCore.Authentication.TicketSerializer) 存储票证。

public Microsoft.AspNetCore.Authentication.AuthenticationTicket ConvertTicket(Microsoft.Owin.Security.AuthenticationTicket ticket)
{
    Microsoft.AspNetCore.Authentication.AuthenticationProperties netCoreAuthProps = new Microsoft.AspNetCore.Authentication.AuthenticationProperties();
    netCoreAuthProps.IssuedUtc = ticket.Properties.IssuedUtc;
    netCoreAuthProps.ExpiresUtc = ticket.Properties.ExpiresUtc;
    netCoreAuthProps.IsPersistent = ticket.Properties.IsPersistent;
    netCoreAuthProps.AllowRefresh = ticket.Properties.AllowRefresh;
    netCoreAuthProps.RedirectUri = ticket.Properties.RedirectUri;

    ClaimsPrincipal cp = new ClaimsPrincipal(ticket.Identity);

    Microsoft.AspNetCore.Authentication.AuthenticationTicket netCoreTicket = new Microsoft.AspNetCore.Authentication.AuthenticationTicket(cp, netCoreAuthProps, "Cookies");

    return netCoreTicket;
}   
private static Microsoft.AspNetCore.Authentication.TicketSerializer _netCoreSerializer = Microsoft.AspNetCore.Authentication.TicketSerializer.Default;

private static byte[] SerializeToBytesNetCore(Microsoft.AspNetCore.Authentication.AuthenticationTicket source)
{
    return _netCoreSerializer.Serialize(source);
}

有了这些额外的方法,RenwAsync 方法可以改成这样:

      public Task RenewAsync(string key, Microsoft.Owin.Security.AuthenticationTicket ticket)
        {
            var options = new DistributedCacheEntryOptions();
            var expiresUtc = ticket.Properties.ExpiresUtc;
            if (expiresUtc.HasValue)
            {
                options.SetAbsoluteExpiration(expiresUtc.Value);
            }

            var netCoreTicket = ConvertTicket(ticket);                      
// convert to .NET Core format     
            byte[] netCoreVal = SerializeToBytesNetCore(netCoreTicket);     
// serialize ticket using .NET Core Serializer
            _cache.Set(key, netCoreVal, options);


            return Task.FromResult(0);
        }

我不确定这是否是最好的方法,但它似乎适用于我的测试项目,诚然我没有在生产中使用它,希望它能有所帮助。

更新 #1:避免重新编译的替代方法

看起来这也可以通过在 OWIN 端使用两个 SessionId 声明值重新创建 cookie 来实现。这将允许您在不重新编译的情况下使用标准库。我今天早上试过了,但还没有机会彻底测试它,尽管在我最初的测试中它确实在两边正确地加载了声明。基本上,如果您将身份验证票证修改为具有两个 SessionId 声明,它将在两个应用程序中找到该 session 。此代码片段获取 cookie,对其取消保护,添加附加声明,然后替换 CookieAuthenticationProvider 的 OnValidateIdentity 事件中的 cookie。

string cookieName = "myappname";
string KatanaSessionIdClaim = "Microsoft.Owin.Security.Cookies-SessionId";
string NetCoreSessionIdClaim = "Microsoft.AspNetCore.Authentication.Cookies-SessionId";

Microsoft.Owin.Security.Interop.ChunkingCookieManager cookieMgr = new ChunkingCookieManager();

OnValidateIdentity = ctx =>
{
    var incomingIdentity = ctx.Identity;
    var cookie = cookieMgr.GetRequestCookie(ctx.OwinContext, cookieName);
    if (cookie != null)
    {
        var ticket = TicketDataFormat.Unprotect(cookie);
        if (ticket != null)
        {
            Claim claim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(KatanaSessionIdClaim));
            Claim netCoreSessionClaim = ticket.Identity.Claims.FirstOrDefault(c => c.Type.Equals(NetCoreSessionIdClaim));
            if (netCoreSessionClaim == null)
            {
                // adjust cookie options as needed.
                CookieOptions opts = new CookieOptions();
                opts.Expires = ticket.Properties.ExpiresUtc == null ? 
    DateTime.Now.AddDays(14) : ticket.Properties.ExpiresUtc.Value.DateTime;
                opts.HttpOnly = true;
                opts.Path = "/";
                opts.Secure = true;

                netCoreSessionClaim = new Claim(NetCoreSessionIdClaim, claim.Value);
                ticket.Identity.AddClaim(netCoreSessionClaim);
                string newCookieValue = TicketDataFormat.Protect(ticket);
                cookieMgr.DeleteCookie(ctx.OwinContext, cookieName, opts);
                cookieMgr.AppendResponseCookie(ctx.OwinContext, cookieName, newCookieValue, opts);
            }
        }
    }
}

如果有更好的方法,我很想知道,或者换出 cookie 的更好地方。

关于asp.net - 将 SessionStore (ITicketStore) 添加到我的应用程序 cookie 会使我的数据保护提供程序无法工作,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48510143/

相关文章:

采用 {ID}-{Slug} 格式的 ASP.NET MVC 路由约束

c# - .NET (ApiController)/jQuery .ajax : what to return from POST?

asp.net-core - .Net Core 运行时环境 (KRE) bin 文件夹中的程序集的用途是什么?

c# - 从控制台应用程序引用时,静态文件不适用于 AspNetCore Web 应用程序

.net - 在 _layouts 下从 ASP.NET 运行 STSADM

javascript - 如何使用js通过cookie发送参数

ruby-on-rails-3 - 我如何在 rails rspec 中测试 cookie 的过期时间

javascript - 如何使用cookie来检测用户是否已经提交了表单?

asp.net - 分支 ASP.NET Core 管道身份验证

asp.net - 解析服务器端标签时出错