如何在.NET Core Identity中筛选角色,避免存入Cookie声明
当用户拥有大量角色时,默认的Identity会将所有角色写入Cookie的Claims中,这会导致Cookie体积超标、登录耗时增加,最终引发网站访问故障。以下是几种可行的解决方案:
方案一:自定义ClaimsPrincipalFactory筛选角色
通过重写UserClaimsPrincipalFactory,在生成用户Claims时只保留需要的角色,避免将500个角色全部塞入Cookie。
1. 创建自定义Claims工厂类
public class CustomUserClaimsPrincipalFactory : UserClaimsPrincipalFactory<ApplicationUser, ApplicationRole> { public CustomUserClaimsPrincipalFactory(UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager, IOptions<IdentityOptions> optionsAccessor) : base(userManager, roleManager, optionsAccessor) { } public override async Task<ClaimsPrincipal> CreateAsync(ApplicationUser user) { // 先调用基类生成默认Claims var principal = await base.CreateAsync(user); var identity = (ClaimsIdentity)principal.Identity; // 移除默认添加的所有角色Claims var existingRoleClaims = identity.FindAll(ClaimTypes.Role).ToList(); foreach (var claim in existingRoleClaims) { identity.RemoveClaim(claim); } // 从数据库获取用户所有角色,按需筛选(示例:只保留带特定前缀的角色) var userRoles = await UserManager.GetRolesAsync(user); var filteredRoles = userRoles.Where(role => role.StartsWith("Allowed_")).ToList(); // 将筛选后的角色添加到Claims中 foreach (var role in filteredRoles) { identity.AddClaim(new Claim(ClaimTypes.Role, role)); } return principal; } }
2. 注册自定义工厂到DI容器
在Program.cs中添加以下代码,替换默认的Claims工厂:
builder.Services.AddScoped<IUserClaimsPrincipalFactory<ApplicationUser>, CustomUserClaimsPrincipalFactory>();
方案二:实时从数据库校验角色(不存角色到Cookie)
如果需要权限的实时性(比如角色变更后立即生效),可以不在Cookie中存储角色,而是在授权时直接从数据库查询用户角色。
1. 自定义授权处理器
public class CustomRoleAuthorizationHandler : AuthorizationHandler<RolesAuthorizationRequirement> { private readonly UserManager<ApplicationUser> _userManager; public CustomRoleAuthorizationHandler(UserManager<ApplicationUser> userManager) { _userManager = userManager; } protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, RolesAuthorizationRequirement requirement) { if (!context.User.Identity.IsAuthenticated) { context.Fail(); return; } var user = await _userManager.GetUserAsync(context.User); if (user == null) { context.Fail(); return; } // 查询用户当前所有角色,可在此处添加筛选逻辑 var userRoles = await _userManager.GetRolesAsync(user); if (requirement.AllowedRoles.Any(role => userRoles.Contains(role))) { context.Succeed(requirement); } else { context.Fail(); } } }
2. 注册授权处理器
builder.Services.AddScoped<IAuthorizationHandler, CustomRoleAuthorizationHandler>();
3. 禁止默认角色Claims生成
修改Identity配置,避免自动添加角色到Claims:
builder.Services.AddIdentity<ApplicationUser, ApplicationRole>(options => { // 其他原有配置... options.ClaimsIdentity.RoleClaimType = null; }) .AddEntityFrameworkStores<MyDbContext>() .AddDefaultTokenProviders();
方案三:使用Session存储认证Ticket
将完整的认证Ticket存储在Session或分布式缓存中,Cookie仅存储一个Session ID,大幅减小Cookie体积。
1. 实现ITicketStore
public class MemoryCacheTicketStore : ITicketStore { private readonly IMemoryCache _cache; private const string TicketKeyPrefix = "AuthTicket_"; public MemoryCacheTicketStore(IMemoryCache cache) { _cache = cache; } public async Task<string> StoreAsync(AuthenticationTicket ticket) { var ticketId = TicketKeyPrefix + Guid.NewGuid().ToString(); _cache.Set(ticketId, ticket, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(20) }); return await Task.FromResult(ticketId); } public async Task RenewAsync(string key, AuthenticationTicket ticket) { _cache.Set(key, ticket, new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(20) }); await Task.CompletedTask; } public async Task<AuthenticationTicket> RetrieveAsync(string key) { return await Task.FromResult(_cache.Get<AuthenticationTicket>(key)); } public async Task RemoveAsync(string key) { _cache.Remove(key); await Task.CompletedTask; } }
2. 配置Cookie认证使用Session存储
// 配置Session builder.Services.AddSession(options => { options.IdleTimeout = TimeSpan.FromMinutes(20); options.Cookie.HttpOnly = true; options.Cookie.IsEssential = true; }); // 修改Cookie认证配置,使用自定义Ticket存储 builder.Services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.Cookie.Name = ".AspNet.ApplicationCookie"; options.SessionStore = new MemoryCacheTicketStore(new MemoryCache(new MemoryCacheOptions())); options.LoginPath = "/Account/Login/Id"; });
注意:如果是分布式部署,需要改用Redis等分布式缓存实现
ITicketStore,避免Session数据无法共享。
内容的提问来源于stack exchange,提问作者ebk
相关产品推荐
相关产品推荐

