首页 > 解决方案 > 创建的 CookieAuthentication cookie 的内容?

问题描述

短版: 有没有办法确定 .net-Core-2 中的 CookieAuthentication 机制创建的 cookie 的内容

背景:
我目前正在努力使网站符合 GDPR,除其他外,这意味着记录我们网站上使用和设置的所有 cookie。我们使用 asp.net core 2 和 Identity 来处理用户登录和注销。问题是由身份设置的所有 cookie 以及如何记录它们。饼干 1。

该 cookie 很简单,它包含登录信息、权限等。如果您删除它,您将从我们的网站注销。这对我们的应用程序至关重要,因此无需担心。

问题是以下cookie(注意。它是一个cookie分为三部分)

它由 cookieAuthentication 使用和设置,但似乎并没有做任何事情。您可以从浏览器中删除它们,但仍然可以正常使用该站点。例如,进行更改并保存 i,您将不会被强制注销。

这带来了一个问题,因为根据新的 GDPR 法律,如果这些 cookie 块是可选的,则用户应该能够停用它们。(以后有问题)

那么问题就变成了:我怎样才能知道这个 cookie 做了什么以及它包含什么信息。找出两者同样重要。

尝试过的方法:我试图查看源代码以找到答案,但项目开发在过去 2 年中一直断断续续,而且相当拼凑。我还尝试使用这篇文章如何手动解密 ASP.NET Core 身份验证 cookie 来解密 cookie?作为指南,但我只在 C# 中开发不到 4 个月,所以我无法让它工作,因为它似乎针对的是旧版本/不同版本的 .net-core。

笔记!由于这是我在堆栈上的第一个问题,我欢迎有关如何使我的问题对本网站的普通大众更清晰或更有用的建议和提示。

Edit_1: 启动文件中确实没有太多配置

app.UseCookieAuthentication(new CookieAuthenticationOptions
{
    AutomaticAuthenticate = true
});

有一些代码可以使用 microsoft 启用 OpenId。但这会放在这个饼干里吗?

// Configure the OWIN Pipeline to use OpenId Connect Authentication
app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
{
    ClientId = Configuration["ClientId"],
    Authority = string.Format(Configuration["AadInstance"], 
    Configuration["Tenant"]),
    PostLogoutRedirectUri = Configuration["PostLogoutRedirectUri"],
    ClientSecret = Configuration["ClientSecret"],
    TokenValidationParameters = new TokenValidationParameters
    {
        ValidateIssuer = false
    },
    Events = new OpenIdConnectEvents
    {
        OnAuthenticationFailed = OnAuthenticationFailed,
        OnRedirectToIdentityProvider = (context) =>
        {
            if (context.HttpContext.Request.Query.ContainsKey("domain"))
            {
                context.ProtocolMessage.DomainHint = 
                    context.HttpContext.Request.Query["domain"];
            }

        return Task.FromResult(0);
        },
        OnTokenValidated = OnTokenValidated
    }
});

edit_2: 删除了 DI 和 misc 日志记录,但这基本上是整个文件

using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.IdentityModel.Tokens;
using Newtonsoft.Json.Serialization;
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;

public class Startup
{
    public UserManager<ApplicationUser> UserManager { get; set; }
    public SignInManager<ApplicationUser> SignInManager { get; set; }
    public IConfigurationRoot Configuration { get; set; }

    public Startup(IHostingEnvironment env)
    {
        // Set up configuration sources.
        var builder = new ConfigurationBuilder()
            .SetBasePath(env.ContentRootPath)
            .AddJsonFile("appsettings.json")
            .AddJsonFile($"appsettings.{env.EnvironmentName}.json", optional: true);

        if (env.IsDevelopment())
        {
            // For more details on using the user secret store see http://go.microsoft.com/fwlink/?LinkID=532709
            builder.AddUserSecrets<Startup>();
        }

        builder.AddEnvironmentVariables();
        Configuration = builder.Build();
    }


    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        // Adds a default in-memory implementation of IDistributedCache
        services.AddMemoryCache();

        services.AddSession();

        services.AddDbContext<ApplicationDbContext>(options =>
            options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));

        services.AddHangfire(x => x.UseSqlServerStorage(Configuration.GetConnectionString("DefaultConnection")));

        services.AddLocalization(options => options.ResourcesPath = "Resources");

        services.AddIdentity<ApplicationUser, IdentityRole>(o => {
            // configure identity options
            o.Password.RequireDigit = true;
            o.Password.RequireLowercase = true;
            o.Password.RequireUppercase = true;
            o.Password.RequireNonAlphanumeric = false;
            o.Password.RequiredLength = 8;
        })
            .AddEntityFrameworkStores<ApplicationDbContext>()
            .AddDefaultTokenProviders();

        services.AddMvc()
            .AddViewLocalization(LanguageViewLocationExpanderFormat.Suffix, options => options.ResourcesPath = "Resources")
            .AddDataAnnotationsLocalization()
            .AddJsonOptions(options =>
            {
                options.SerializerSettings.ContractResolver = new DefaultContractResolver();
            });

        // OpenID Connect Authentication Requires Cookie Auth
        services.AddAuthentication(options =>
        {
            options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
        });
    }

    // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
    public async void Configure(
        IApplicationBuilder app,
        IHostingEnvironment env,
        ILoggerFactory loggerFactory,
        SampleDataInitializer sampleData,
        SeedData seedData)
    {
        app.UseSession();

        if (env.IsDevelopment())
        {
            app.UseBrowserLink();
            app.UseDeveloperExceptionPage();
            app.UseDatabaseErrorPage();

            try
            {
                using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                    .CreateScope())
                {
                    serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                            .Database.Migrate();
                }
            }
            catch { }
        }
        else
        {
            app.UseExceptionHandler("/Home/Error");

            // For more details on creating database during deployment see http://go.microsoft.com/fwlink/?LinkID=615859
            try
            {
                using (var serviceScope = app.ApplicationServices.GetRequiredService<IServiceScopeFactory>()
                    .CreateScope())
                {
                    serviceScope.ServiceProvider.GetService<ApplicationDbContext>()
                            .Database.Migrate();
                }
            }
            catch { }
        }

        app.UseStaticFiles();
        app.UseIdentity();

        app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AutomaticAuthenticate = true
        });

        // Configure the OWIN Pipeline to use OpenId Connect Authentication
        app.UseOpenIdConnectAuthentication(new OpenIdConnectOptions
        {
            ClientId = Configuration["ClientId"],
            Authority = string.Format(Configuration["AadInstance"], Configuration["Tenant"]),
            PostLogoutRedirectUri = Configuration["PostLogoutRedirectUri"],
            ClientSecret = Configuration["ClientSecret"],
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidateIssuer = false
            },
            Events = new OpenIdConnectEvents
            {
                OnAuthenticationFailed = OnAuthenticationFailed,
                OnRedirectToIdentityProvider = (context) =>
                {
                    if (context.HttpContext.Request.Query.ContainsKey("domain"))
                    {
                        context.ProtocolMessage.DomainHint = context.HttpContext.Request.Query["domain"];
                    }

                    return Task.FromResult(0);
                },
                OnTokenValidated = OnTokenValidated
            }

        });

        app.UseMvc(routes =>
        {
            routes.MapRoute(
                name: "default",
                template: "{controller=Home}/{action=Index}/{id?}");
        });

        await seedData.SeedBuyOutPoliciesForDepartments();
    }


    private async Task<Task> OnTokenValidated(TokenValidatedContext context)
    {
        try
        {
            // Get tenant Id from Claims
            var claim = context.Ticket.Principal.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid");

            if (claim?.Value == null)
            {
                context.HttpContext.Session.SetError("Not able to access tenant id from claim");
                return Task.FromResult(true);
            }

            var tenantId = claim.Value;

            var roleClaims = context.Ticket.Principal.Claims.Where(c => c.Type.ToLower().EndsWith("identity/claims/role")).ToList();

            //// Get group claims from logged in user
            var groupClaims = context.Ticket.Principal.Claims.Where(c => c.Type.ToLower() == "groups").ToList();

            var checkClaim = context.HttpContext.Session.GetString("CheckClaim");

            if (!string.IsNullOrEmpty(checkClaim))
            {
                context.HttpContext.Session.Remove("CheckClaim");

                var claimInfo = "";

                foreach (var currentUserClaim in context.Ticket.Principal.Claims)
                {
                    claimInfo += $"Type: {currentUserClaim.Type} Value: {currentUserClaim.Value} <br/>";
                }

                // Add groups to claimInfo string
                claimInfo += "<br/>Groups: <br/>";
                foreach (var groupClaim in groupClaims)
                {
                    claimInfo += $"{groupClaim.Value}, ";
                }

                // Add role(s) to claimInfo string
                claimInfo += "<br />Roles: <br />";
                foreach (var roleClaim in roleClaims)
                {
                    claimInfo += $"{roleClaim.Value}, ";
                }

                context.HttpContext.Session.SetString("Claim", claimInfo);

                return Task.FromResult(true);
            }

            var applicationDbContext = context.HttpContext.RequestServices.GetService<ApplicationDbContext>();

            // Check that tenant id exist in database
            var customer = applicationDbContext.Customers.Include(c => c.Departments).FirstOrDefault(x => x.TenantId == tenantId);
            if (customer == null)
            {
                context.HttpContext.Session.SetError("Your company is not configured to use log in with work account");
                return Task.FromResult(true);
            }

            // Used to find user in database or create new user
            var userManager = context.HttpContext.RequestServices.GetService<UserManager<ApplicationUser>>();

            //// Used to update user role
            var roleManager = context.HttpContext.RequestServices.GetService<RoleManager<IdentityRole>>();

            // Get email adress from AD user
            var email = context.Ticket.Principal.Identity.Name;

            // Check if user exist in database
            var user = await applicationDbContext.Users.Include(u => u.Department).FirstOrDefaultAsync(u => u.Email == email);


            if (user == null)
            {
                if (!customer.AllowCreateUserOnSignIn)
                {
                    context.HttpContext.Session.SetError(
                        "Your user does not exist in the portal and your company is not configured to allow creation of user on sign in. Please contact your administrator.", customer.ErrorText);

                    return Task.FromResult(true);
                }

                // Create new user with AdUser property set to true
                var givenName =
                    context.Ticket.Principal.Claims.FirstOrDefault(
                        x => x.Type.ToLower().Contains("givenname"));
                var surName =
                    context.Ticket.Principal.Claims.FirstOrDefault(
                        x => x.Type.ToLower().Contains("surname"));

                user = new ApplicationUser
                {
                    AdUser = true,
                    UserName = email,
                    Email = email,
                    FirstName = givenName != null ? givenName.Value : "",
                    LastName = surName != null ? surName.Value : "",
                };

                if (user.DepartmentId == null)
                {
                    user.DepartmentId = customer.Departments.FirstOrDefault(d => d.Default)?.Id;
                }

                // Add user to database
                var result = await userManager.CreateAsync(user);

                if (!result.Succeeded)
                {
                    context.HttpContext.Session.SetError(
                        "Your user does not exist in the portal. Please contact your administrator", customer.ErrorText);
                    return Task.FromResult(true);
                }
            }
            else
            {
                //Not allowed to login if user is deactivated/resigned
                if (user.ResignedDate != null)
                {
                    context.HttpContext.Session.SetError("Your user is deactivated. Please contact your administrator", customer.ErrorText);
                    return Task.FromResult(true);
                }
            }

            // Compare tenant id with user tenant
            var userCustomer = await applicationDbContext.Customers.FirstOrDefaultAsync(c => c.Id == user.Department.CustomerId);
            if (userCustomer == null || userCustomer.TenantId != tenantId)
            {
                context.HttpContext.Session.SetError("Your user does not exist in the portal. Please contact your administrator", customer.ErrorText);
                return Task.FromResult(true);
            }

            // Sign in user
            var signInManager = context.HttpContext.RequestServices.GetService<SignInManager<ApplicationUser>>();
            await signInManager.SignInAsync(user, true);
        }
        catch (Exception ex)
        {
            context.HttpContext.Session.SetError("Sorry, an error occured during log in. Please contact your administrator");
        }

        return Task.FromResult(0);
    }

    // Entry point for the application.
    public static void Main(string[] args)
    {
        var host = new WebHostBuilder()
            .UseKestrel()
            .UseContentRoot(Directory.GetCurrentDirectory())
            .UseIISIntegration()
            .UseStartup<Startup>()
            .Build();

        host.Run();
    }
}

标签: c#cookiesasp.net-core

解决方案


如果存储的 Principal 对于单个 cookie 来说太大(想想很多角色或声明),您会得到一个分块的 cookie。Cookie 的大小有限,因此分块将启动以允许主体的完全序列化。

在 ASP.NET Core 中使用 OAuth/OIDC/WS-Fed 时,是的,通常它会被序列化为 cookie 身份验证 cookie,以便跨请求保存。OAuth 可以将大量声明变成其结果,因此您可以使用分块 cookie。

所以它真的是一个大饼干,在多个部分中,都是同一个饼干。因此,当您的网站按预期运行时,它们不是可选的。(这不是 GDPR 的法律建议,需要来自实际律师)。

顺便说一句,ASP.NET Core 2.1 支持可选 cookie,您可以将 cookie 标记为必需或不标记(Cookie 身份验证将其 cookie 标记为必需),除非用户同意使用,否则 ASP.NET 不会编写可选 cookie .


推荐阅读