【问题标题】:Configuring cookies in Asp.Net Core 3.1 with Identity在 Asp.Net Core 3.1 中使用 Identity 配置 cookie
【发布时间】:2020-11-22 11:36:33
【问题描述】:

我正在使用带有 Identity 的 Asp.Net Core 3.1。这是我在 Startup 类中的所有配置。 如果他们的帐户在使用该应用程序时过期,我正在尝试强制登录用户注销。我应该正确配置 cookie,但在使用 AddIdentity 时我被困在如何做到这一点。

这是我的启动

 // This method gets called by the runtime. Use this method to add services to the container.
        public void ConfigureServices(IServiceCollection services)
        {
        services.AddDbContext<ApplicationDbContext>(options =>
           options.UseSqlServer(
               Configuration.GetConnectionString("DefaultConnection")));

        services.AddIdentity<IdentityUser, IdentityRole>(options => {
            options.SignIn.RequireConfirmedAccount = false;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders(); 

        services.AddIdentityCore<ApplicationUser>()
            .AddRoles<IdentityRole>()
            .AddClaimsPrincipalFactory<UserClaimsPrincipalFactory<ApplicationUser, IdentityRole>>()
            .AddEntityFrameworkStores<ApplicationDbContext>()
            //.AddDefaultTokenProviders()  
            .AddDefaultUI(); 

        services.AddSingleton<IEmailSender, EmailSender>();
        services.Configure<EmailOptions>(Configuration);
   
        services.AddHangfire(config => config.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));
        services.AddHangfireServer();

        services.AddControllersWithViews(); //?
        services.AddRazorPages().AddRazorRuntimeCompilation(); //?
        services.AddScoped<IExpirationJob, ExpirationJob>();
        services.AddScoped<IReminderJob, EmailReminder>();
        services.AddSingleton<IActionContextAccessor, ActionContextAccessor>();
        services.Configure<IdentityOptions>(options =>
        {
            // Password settings.
            //options.Password.RequireDigit = true;
            //options.Password.RequireLowercase = true;
            //options.Password.RequireNonAlphanumeric = true;
            //options.Password.RequireUppercase = true;
            //options.Password.RequiredLength = 6;
            //options.Password.RequiredUniqueChars = 1;

            // Lockout settings.
            //options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
            //options.Lockout.MaxFailedAccessAttempts = 5;
            //options.Lockout.AllowedForNewUsers = true;

            // User settings.
            //options.User.AllowedUserNameCharacters =
            //    "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-._@+ ";
            //options.User.RequireUniqueEmail = false;
        });

        services.ConfigureApplicationCookie(config =>
        {
            config.Cookie.Name = "my.Cookie";
            config.LoginPath = "/Home/Login";
            config.AccessDeniedPath = "/Identity/Account/AccessDenied";
            config.Cookie.HttpOnly = true;
            config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
        });
     }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public void Configure(IApplicationBuilder app,
        IWebHostEnvironment env,
        IRecurringJobManager recurringJobManager,
        IServiceProvider serviceProvider)
    {
        if (env.IsDevelopment())
        {
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseStaticFiles();


        app.UseHangfireDashboard();
        //app.UseHangfireDashboard("/hangfire", new DashboardOptions()
        //{
        //    Authorization = new[] { new CustomAuthorizeFilter() }
        //});

        app.UseRouting();

        app.UseAuthentication();
        app.UseAuthorization();
      
     
        app.UseAuthentication();
        app.UseAuthorization();
      
        recurringJobManager.AddOrUpdate(
           "End Users Subscription",
           () => serviceProvider.GetService<IExpirationJob>().SetExpired(),
           Cron.Minutely
           );

        recurringJobManager.AddOrUpdate(
           "Send End of Subscription Reminder",
           () => serviceProvider.GetService<IReminderJob>().SendReminder(),
           Cron.Daily
           );

        app.Use(async (context, next) =>
        {
            _ = ExpirationJob.SetExpired(context);//pass the HttpContext to SetExpired
            await next();
        });

        app.UseEndpoints(endpoints =>
        {
            endpoints.MapControllerRoute(
                name: "default",
                pattern: "{controller=Home}/{action=Index}/{id?}");
            endpoints.MapRazorPages();
        });
    }

这是我的 ValidateAsync

 public class ValidateAsync
{
    public static async Task ValidatingAsync(CookieValidatePrincipalContext context)
    {
        context = context ?? throw new ArgumentNullException(nameof(context));
        var claimsIdentity = context.Principal.Identity as ClaimsIdentity;
        if (claimsIdentity?.Claims == null || !claimsIdentity.Claims.Any())
        {
            await RejectPrincipal();
            return;
        }
        UserManager<IdentityUser> userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<IdentityUser>>();
        var user = await userManager.FindByNameAsync(context.Principal.FindFirstValue(ClaimTypes.NameIdentifier));
        if (user == null || user.SecurityStamp != context.Principal.FindFirst(new ClaimsIdentityOptions().SecurityStampClaimType)?.Value)
        {
            await RejectPrincipal();
            return;
        }
        async Task RejectPrincipal()
        {
            context.RejectPrincipal();
            await context.HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
        }
    }
}

这是我的 SetExpired 方法

      public interface IExpirationJob
{
    Task SetExpired();

}
public class ExpirationJob : IExpirationJob
{
    private readonly ApplicationDbContext _db;
    private readonly IEmailSender _emailSender;
    private readonly HttpContext _context;

    public ExpirationJob(ApplicationDbContext db, IEmailSender emailSender, HttpContext context)
    {
        _db = db;
        _emailSender = emailSender;
        _context = context;
    }

    public async Task SetExpired()
    {
        foreach(var item in _db.Institution)
        {
            if (item.SubsEndDate != null)
            {
                if (item.SubsEndDate <= DateTime.Now) 
                {
                   
                    item.Status = SD.StatusExpired;

                    Guid securityStamp = Guid.NewGuid();
                    item.Admin.SecurityStamp = securityStamp.ToString();

                    _context.Response.Cookies.Append("my.Cookie", "expired");
                }
            }
        }
        await _db.SaveChangesAsync();
    }
}

【问题讨论】:

    标签: c# .net asp.net-mvc asp.net-core cookies


    【解决方案1】:

    了解业务逻辑后,可以在中间件中获取HttpContext,修改cookie值强制注销。这是一个例子。

    首先,在ConfigureServices中配置基本cookie。

     services.ConfigureApplicationCookie(config =>
            {
                config.Cookie.Name = "my.Cookie";
                config.LoginPath = "/Home/Login";
                config.AccessDeniedPath = "/Identity/Account/AccessDenied";
                config.Cookie.HttpOnly = true;
                config.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
            });
    

    其次,配置中间件。

    public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
           IExpirationJob expirationJob)
        {
            //...
            app.UseRouting();
            app.UseAuthentication();
            app.UseAuthorization();
            app.Use(async(context,next)=>
            {
                expirationJob.SetExpired(context);//pass the HttpContext to SetExpired
                await next();
            });
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
    

    三、通过数据库的过期时间SubscriptionEndDate 判断是否覆盖cookie。

    public async Task SetExpired(HttpContext httpContext)
        {
            foreach (var item in _db.Institution)
            {
                if (item.SubscriptionEndDate != null)
                {
                    if (item.SubscriptionEndDate == DateTime.Today)
                    {
                        item.Status = SD.StatusExpired;
                        Guid securityStamp = Guid.NewGuid();
                        item.SecurityStamp = securityStamp;
                        
                        httpContext.Response.Cookies.Append("my.Cookie", "expired");
                    }
                }
            }
            await _db.SaveChangesAsync();
        }
    

    那么,cookie 无效,该用户将无法访问资源。

    更新:

    另一种方法,如果您想将HttpContext 作为参数传递给SetExpired

    1. 您可以编写一个静态类,如下所示:
    公共静态类 MyClass { 公共静态 HttpContext http { 获取;放; } }
    1. 然后,您可以在 Statup.cs 中为http 赋值。

       public void Configure(IApplicationBuilder app, IWebHostEnvironment env,
          IExpirationJob expirationJob)
       {
           //...
           app.UseRouting();
           app.UseAuthentication();
           app.UseAuthorization();
           app.Use(async(context,next)=>
           {
               MyClass.http = context;
               expirationJob.SetExpired();
               await next();
           });
           app.UseEndpoints(endpoints =>
           {
               endpoints.MapControllerRoute(
                   name: "default",
                   pattern: "{controller=Home}/{action=Index}/{id?}");
           });
       }
      
    2. 用这个得到它。

       public async Task SetExpired()
       {
           var path=MyClass.http.Request.Path;
      
           //foreach (var item in _db.Institution)
           //{
           //    if (item.SubsEndDate != null)
           //    {
           //...
           await _db.SaveChangesAsync();
       }
      

    【讨论】:

    • 非常感谢@Karney!我真的很感谢你的努力。但是在这里我得到了什么,我不想涉及更多问题,ExpirationJob.SetExpired(context) 这一行抛出“CS0120 非静态字段方法或属性需要对象引用”和“警告 CS4014:因为这个调用不等待,在调用完成之前继续执行当前方法。请考虑将 'await' 运算符应用于调用结果。"
    • 另外,可以在public interface IExpirationJob{Task SetExpired(HttpContext httpContext);}中添加(HttpContext httpContext)吗?否则,它会引发错误。如果我这样做了,Startup 中的 This Job 也会抛出错误 -> recurringJobManager.AddOrUpdate("End Users Subscription",() =&gt; serviceProvider.GetService&lt;IExpirationJob&gt;().SetExpired(), Cron.Minutely); 我也可以通过 (HttpContext httpContext) 吗?
    • 这可能是静态成员和非静态成员相互引用造成的
    • 好的,解决了。但是我怎样才能在 context 不接受任何参数的情况下将其传递给 SetExpired
    • 上下文包含当前路径和cookie。您可以编写一个静态类并将上下文分配给静态属性。这个静态类被设置为全局调用。这是一种方法,你可以根据需要改变它。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2020-06-08
    • 1970-01-01
    • 2020-12-30
    • 2020-11-28
    • 2020-12-11
    • 2016-11-18
    • 1970-01-01
    相关资源
    最近更新 更多