无论使用ITicketStore还是IDistributedCache,重新启动服务器时,身份validationCookie都会停止工作

我的最终目标是能够在Heroku中重新启动我的dyno,并且当应用程序再次启动时,已经login的所有用户仍然login。我已经针对数据库运行迁移,以设置IDistributedCache正在使用的表那么我已经实现了使用IDistributedCache 。 当我在本地尝试时,这是有效的:如果我注释掉opt.SessionStore我必须在每次重新启动应用程序时都login,但是不需要。 但是,当我loginHeroku应用程序时,必须在重新启动后再次login。 我得到这个警告,我不知道它是否与它有关:

 2017-10-03T13:45:09.144170+00:00 app[web.1]: [40m[1m[33mwarn[39m[22m[49m: Microsoft.AspNetCore.DataProtection.KeyManagement.XmlKeyManager[35] 2017-10-03T13:45:09.144192+00:00 app[web.1]: No XML encryptor configured. Key {bb36a43c-d5ed-4d03-9381-0e91868cd7a0} may be persisted to storage in unencrypted form. 

我有一些日志,所以当我login时,我可以在日志中看到这样的东西:

 2017-10-03T13:44:48.848325+00:00 app[web.1]: Retriving Oskar Klintrot with key be94b055-4493-4602-9e72-2bcb6f009de2 

但是,在重新启动后,日志中什么也没有显示出来。 可以认为cookie是无效的或应用程序甚至不试图检索用户som会话存储了吗? 是甚至有必要使用ITicketStore ,而不是通常存储在cookie中的用户(文档说This is most commonly used to mitigate issues with serializing large identities into cookies. )。


产品规格:

  • dotnet核心2.0
  • 在Heroku上运行爱好testing的Docker

Startup.cs

 public class Startup { private readonly IHostingEnvironment CurrentEnvironment; public Startup(IConfiguration configuration, IHostingEnvironment env) { Configuration = configuration; CurrentEnvironment = env; } public IConfiguration Configuration { get; } // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { #region Dependency Injection services.AddTransient<ITicketStore, DistributedCacheTicketStore>(); #endregion #region Database var sqlConnectionString = Configuration["CONNECTION_STRING"]; services.AddDbContext<IntranetApiContext>(opt => opt.UseSqlServer(sqlConnectionString)); #endregion #region Migrate Database var dbContext = services.BuildServiceProvider().GetService<IntranetApiContext>(); dbContext.Database.Migrate(); #endregion #region Options services.Configure<DistributedCacheTicketStoreOptions>(options => { options.TimeoutMinutes = 60 * 24 * 30; }); #endregion #region Distributed Cache services.AddDistributedSqlServerCache(options => { options.ConnectionString = sqlConnectionString; options.SchemaName = "dbo"; options.TableName = "DistributedCache"; }); #endregion #region Session services.AddSession(options => { options.IdleTimeout = TimeSpan.FromDays(30); options.Cookie.HttpOnly = true; }); #endregion #region Authentication services.AddAuthentication(sharedOptions => { sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; }) .AddCookie(opt => { opt.LoginPath = new PathString("/Login"); opt.Cookie = new CookieBuilder { HttpOnly = true, SecurePolicy = CookieSecurePolicy.None, // TODO: Change when SSL enforcement is implemented, right now both http and https works }; opt.SessionStore = services .BuildServiceProvider() .GetService<ITicketStore>(); }); #endregion #region Mvc // Add framework services. services.AddMvc(config => { // Add authentication everywhere var policy = new AuthorizationPolicyBuilder() .RequireAuthenticatedUser() .Build(); config.Filters.Add(new AuthorizeFilter(policy)); }); #endregion } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. public void Configure(IApplicationBuilder app, IHostingEnvironment env, ILoggerFactory loggerFactory) { #region Session app.UseSession(); #endregion #region Cookie Authentication app.UseAuthentication(); #endregion #region Logging loggerFactory.AddConsole(Configuration.GetSection("Logging")); loggerFactory.AddDebug(); #endregion #region Mvc app.UseMvc(); #endregion } } 

DistributedCacheTicketStore.cs

 public class DistributedCacheTicketStore : ITicketStore { private readonly DistributedCacheTicketStoreOptions _options; private readonly IDistributedCache _distributedCache; private readonly IDataProtector _dataProtector; public DistributedCacheTicketStore( IOptions<DistributedCacheTicketStoreOptions> options, IDistributedCache distributedCache, IDataProtectionProvider dataProtectionProvider) { _options = options.Value; _distributedCache = distributedCache; _dataProtector = dataProtectionProvider.CreateProtector(GetType().FullName); } public async Task<string> StoreAsync(AuthenticationTicket ticket) { var key = Guid.NewGuid().ToString(); await RenewAsync(key, ticket); return key; } public async Task RenewAsync(string key, AuthenticationTicket ticket) { Console.WriteLine($"Saving {ticket.Principal.Identity.Name} with key {key}"); var ticketBytes = _dataProtector.Protect(TicketSerializer.Default.Serialize(ticket)); await _distributedCache.SetAsync(key, ticketBytes, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(_options.TimeoutMinutes) }); } public async Task<AuthenticationTicket> RetrieveAsync(string key) { var ticketBytes = await _distributedCache.GetAsync(key); var ticket = TicketSerializer.Default.Deserialize(_dataProtector.Unprotect(ticketBytes)); Console.WriteLine($"Retriving {ticket.Principal.Identity.Name} with key {key}"); return ticket; } public async Task RemoveAsync(string key) { Console.WriteLine($"Will try to remove key {key}"); var ticketBytes = await _distributedCache.GetStringAsync(key); if (ticketBytes.IsNotNull()) { await _distributedCache.RemoveAsync(key); Console.WriteLine($"Key {key} removed"); } else { Console.WriteLine($"Key {key} missing"); } } } 

DistributedCacheTicketStoreOptions.cs

 public class DistributedCacheTicketStoreOptions { public double TimeoutMinutes { get; set; } } 

AuthenticationController.cs

  [AllowAnonymous] [HttpPost] public async Task<IActionResult> Login(string username, string password, string returnUrl) { if (ModelState.IsValid) { var claimsPrinciple = await _userService.GetUserAsync(username, password); if (claimsPrinciple == null) { ViewData["ReturnUrl"] = returnUrl; ModelState.AddModelError("Login", "Username or password is incorrect."); return View(); }; await HttpContext.SignInAsync( claimsPrinciple, new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddDays(30), } ); if (Url.IsLocalUrl(returnUrl)) { return Redirect(returnUrl); } } ViewData["ReturnUrl"] = returnUrl; return View(); } 

Migration_DistributedCache.cs

 public partial class Migration_DistributedCache : Migration { protected override void Up(MigrationBuilder migrationBuilder) { migrationBuilder.Sql(@"CREATE TABLE [dbo].[DistributedCache] ( [Id] NVARCHAR (449) COLLATE SQL_Latin1_General_CP1_CS_AS NOT NULL, [Value] VARBINARY (MAX) NOT NULL, [ExpiresAtTime] DATETIMEOFFSET (7) NOT NULL, [SlidingExpirationInSeconds] BIGINT NULL, [AbsoluteExpiration] DATETIMEOFFSET (7) NULL, PRIMARY KEY CLUSTERED ([Id] ASC) ); GO CREATE NONCLUSTERED INDEX [Index_ExpiresAtTime] ON [dbo].[DistributedCache]([ExpiresAtTime] ASC);"); } protected override void Down(MigrationBuilder migrationBuilder) { migrationBuilder.DropIndex( name: "Index_ExpiresAtTime", table: "DistributedCache"); migrationBuilder.DropTable( name: "DistributedCache"); } }