.NET 8中无法将ClaimsIdentity转换为MyBespokeIdentity的问题
问题原因分析
- ASP.NET Core认证管道默认行为差异:.NET Framework中可直接将自定义
MyBespokeIdentity实例赋值给User.Identity,且认证系统会保留该类型;但ASP.NET Core的认证中间件(如Cookie认证)默认仅根据票据中的声明数据创建ClaimsIdentity实例,不会自动复用自定义身份类型。 - 票据序列化/反序列化逻辑不同:.NET Framework的Forms认证会序列化整个自定义身份对象的类型信息;而ASP.NET Core的认证票据默认仅序列化声明集合,丢失了自定义类型的元数据,因此从票据恢复身份时只能生成基础的
ClaimsIdentity。 - 身份对象构建时机差异:.NET Framework中身份对象通常在登录时一次性创建并保持;ASP.NET Core则在每个请求的认证阶段重新构建身份对象,完全基于存储的票据数据,不会保留登录时的自定义类型实例。
解决建议
以下是几种适配ASP.NET Core环境的可行方案,可根据项目需求选择:
方案一:将自定义身份的属性转为扩展方法(推荐)
放弃强制类型转换,把MyBespokeIdentity中的属性逻辑封装为ClaimsIdentity或ClaimsPrincipal的扩展方法,直接从声明集合中提取数据,完全贴合ASP.NET Core的身份系统设计。
示例代码:
public static class IdentityExtensions { // 对应原MyBespokeIdentity中的自定义属性 public static string GetUserId(this ClaimsIdentity identity) { return identity.FindFirst(ClaimTypes.NameIdentifier)?.Value; } public static string GetUserDisplayName(this ClaimsIdentity identity) { return identity.FindFirst("DisplayName")?.Value ?? identity.Name; } // 其他自定义属性对应的扩展方法 }
使用方式:
// 控制器/页面中直接调用,无需类型转换 var userId = User.Identity.GetUserId(); var displayName = User.Identity.GetUserDisplayName();
优点:实现简单,无额外配置,完全兼容ASP.NET Core认证系统,后续维护成本低。
方案二:自定义认证票据的序列化逻辑
如果必须保留MyBespokeIdentity的类型实例,可通过自定义票据序列化器,在序列化时保留自定义类型信息,反序列化时重建MyBespokeIdentity。
示例代码:
public class CustomTicketSerializer : TicketSerializer { protected override void Write(BinaryWriter writer, AuthenticationTicket ticket) { // 先写入自定义类型标识(如果是MyBespokeIdentity) var isCustomIdentity = ticket.Principal.Identity is MyBespokeIdentity; writer.Write(isCustomIdentity); if (isCustomIdentity) { // 写入MyBespokeIdentity的额外数据(如果有) var customIdentity = (MyBespokeIdentity)ticket.Principal.Identity; writer.Write(customIdentity.CustomProperty); // 假设存在自定义属性 } // 调用基类方法写入标准票据数据 base.Write(writer, ticket); } protected override AuthenticationTicket Read(BinaryReader reader) { var isCustomIdentity = reader.ReadBoolean(); var ticket = base.Read(reader); if (isCustomIdentity && ticket.Principal.Identity is ClaimsIdentity claimsIdentity) { // 读取自定义数据,重建MyBespokeIdentity var customProperty = reader.ReadString(); var customIdentity = new MyBespokeIdentity(claimsIdentity.Claims) { CustomProperty = customProperty }; ticket = new AuthenticationTicket(new ClaimsPrincipal(customIdentity), ticket.Properties, ticket.AuthenticationScheme); } return ticket; } } // 在Program.cs中配置Cookie认证使用自定义序列化器 services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme) .AddCookie(options => { options.TicketDataFormat = new TicketDataFormat( new DataProtectorShim( DataProtectionProvider.Create("CookieAuth").CreateProtector( typeof(CookieAuthenticationMiddleware).FullName, CookieAuthenticationDefaults.AuthenticationScheme, "v2")), new CustomTicketSerializer()); });
优点:保留了自定义身份类型的完整性;缺点:实现复杂,需要处理序列化细节,且与ASP.NET Core认证系统耦合度较高。
方案三:请求管道中替换身份对象
在认证完成后,通过自定义中间件将默认的ClaimsIdentity替换为MyBespokeIdentity实例,无需修改序列化逻辑。
示例代码:
public class CustomIdentityMiddleware { private readonly RequestDelegate _next; public CustomIdentityMiddleware(RequestDelegate next) { _next = next; } public async Task Invoke(HttpContext context) { if (context.User.Identity is ClaimsIdentity claimsIdentity && claimsIdentity.IsAuthenticated) { // 从现有ClaimsIdentity中提取声明,创建MyBespokeIdentity var customIdentity = new MyBespokeIdentity(claimsIdentity.Claims) { // 复制原Identity的属性(如Name、AuthenticationType等) Name = claimsIdentity.Name, AuthenticationType = claimsIdentity.AuthenticationType }; // 替换当前用户的Identity context.User = new ClaimsPrincipal(customIdentity); } await _next(context); } } // 在Program.cs中注册中间件,需放在认证中间件之后 app.UseAuthentication(); app.UseMiddleware<CustomIdentityMiddleware>(); app.UseAuthorization();
优点:实现相对简单,无需修改登录逻辑;缺点:每个请求都要重新创建自定义身份对象,存在轻微性能开销,且如果自定义身份有额外状态,需要手动同步。
内容的提问来源于stack exchange,提问作者EvilDr
相关产品推荐
相关产品推荐

