简短版本 有没有办法确定由.net-Core-2中的CookieAuthentication机制创建的cookie的内容
上下文
我目前正致力于使网站符合GDPR标准,其中包括记录我们网站上使用和设置的所有cookie。
我们使用asp.net core 2和Identity来处理用户登录和注销。问题是所有的cookie都按身份确定,以及如何记录它们。
Cookie 1.
此cookie很简单,它包含登录信息,权限等。 如果删除它,您将从我们的网站注销。这对我们的应用程序至关重要,因此无需担心。
问题是以下cookie(注意:它是一个cookie分为三个部分)
它是由CookieAuthentication使用和设置的,但实际上似乎并没有做任何事情。您可以从浏览器中删除它们,仍然可以使用该网站看起来很好。例如,进行更改并保存我,并且不会强行注销。
这会产生问题,因为根据新的GDPR法律,如果这些cookie块是可选的,则用户应该能够停用它们。 (以后的问题)
然后问题变成:如何找出这个cookie的作用以及它包含的信息。两者同样重要的是找出答案。
尝试过的方法:我已经尝试查看源代码以找到答案,但项目开发在过去的两年中一直处于开启和关闭状态,而且非常拼凑。 我也尝试使用这篇帖子How to manually decrypt an ASP.NET Core Authentication cookie?作为指导解密cookie,但我只用C#开发不到4个月所以我无法使它工作,因为它似乎针对的是较旧/不同.net-core的版本。
注意!由于这是我关于堆栈的第一个问题,我欢迎提供有关如何使我的问题对本网站的普通民众更清楚或更有用的建议和提示。
Edit_1: 那么在启动文件中确实有很多配置
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AutomaticAuthenticate = true
});
有一些代码可以使用microsoft启用OpenId。但是这会被放在这个cookie中吗?
// 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();
}
}
答案 0 :(得分:2)
如果存储的Principal对于单个cookie来说太大(想想很多角色或声明),那么你会得到一个chunked cookie。 Cookie的大小有限,因此可以启用分块以允许主体的完全序列化。
当谈到在ASP.NET Core中使用OAuth / OIDC / WS-Fed时,通常它会被序列化为cookie身份验证cookie,以便在请求之间保存它。 OAuth可以将大量的声明置于其结果中,因此您可以使用分块cookie。
所以它真的是一个大饼干,在多个部分,它都是相同的cookie。因此,当您的网站按预期运行时,它们不是可选的。 (这不是GDPR的法律建议,需要来自实际的律师)。
另外,ASP.NET Core 2.1附带了对可选cookie的支持,您可以根据需要标记cookie(Cookie身份验证根据需要标记其cookie),除非用户同意,否则ASP.NET不会编写可选cookie他们的使用。