当前位置: 首页 > article >正文

ClaimsPrincipal序列化为Json的正确姿势

第二步理解三者的关系1.Claim声明的基本单元职责表示一个键值对形式的声明如name Alice、role Admin。不仅包含类型Type和值Value还携带元数据如ValueType值的数据类型如字符串、整数等默认为string。Issuer声明的颁发者如https://login.microsoft.com。OriginalIssuer原始颁发者用于联合身份场景。Properties可扩展的键值对字典用于存储额外信息。特点不可变性一旦创建其核心属性Type/Value通常不可更改。轻量级设计为数据载体无业务逻辑。语义丰富通过标准声明类型如ClaimTypes.Name、ClaimTypes.Role实现互操作性。2.ClaimsIdentity代表一个身份Identity职责封装一组相关的Claim构成一个完整的身份视图。提供身份的上下文信息AuthenticationType认证方式如Cookies、JwtBearer。IsAuthenticated是否已通过认证依赖AuthenticationType非空。Name通常映射自NameClaimType类型的声明默认为ClaimTypes.Name。RoleClaimType用于角色判断的声明类型默认为ClaimTypes.Role。关键行为AddClaim(Claim)动态添加声明常用于中间件或策略授权。FindFirst(string type)查找首个匹配类型的声明。HasClaim(...)检查是否存在特定声明。设计思想一个主体如用户可以有多个身份例如本地登录身份 社交账号身份。支持多身份源如 Windows 身份、JWT 身份、自定义身份。3.ClaimsPrincipal代表一个安全主体Principal职责代表当前用户或系统实体是安全上下文的顶层容器。包含一个或多个ClaimsIdentity对象通过Identities集合。提供便捷属性访问主身份Identity属性返回第一个ClaimsIdentity。聚合所有身份的声明Claims属性返回所有ClaimsIdentity中声明的并集。关键行为IsInRole(string role)检查是否属于某角色依据RoleClaimType声明。FindFirst(string claimType)跨所有身份查找首个匹配声明。HasClaim(...)检查是否存在指定声明。与线程上下文集成在 ASP.NET Core 中HttpContext.User的类型就是ClaimsPrincipal。可通过Thread.CurrentPrincipal.NET Framework或IHttpContextAccessor.NET Core访问。4. 三者协作关系ClaimsPrincipal │ ├── ClaimsIdentity #1 (e.g., from JWT) │ ├── Claim: { Typename, ValueAlice } │ ├── Claim: { Typerole, ValueAdmin } │ └── Claim: { Typeemail, Valuealiceexample.com } │ └── ClaimsIdentity #2 (e.g., from external provider) ├── Claim: { Typesub, Value12345 } └── Claim: { Typeidp, ValueGoogle }第三步设计序列化模型DTO理解了ClaimsPrincipal的内部结构后我们需要设计可序列化的 DTO 来承载这些数据。关键是要保留足够的信息以便在反序列化时能够完整还原原始对象。1. 设计原则在设计序列化模型时我们需要遵循以下原则1.1 完整性Completeness保留所有关键信息确保反序列化后的对象与原始对象等价Claim的所有核心属性Type、Value、ValueType、Issuer、OriginalIssuer、PropertiesClaimsIdentity的认证上下文AuthenticationType、NameClaimType、RoleClaimTypeClaimsPrincipal的多身份支持Identities 集合1.2 可序列化性Serializability使用简单类型string、int、bool 等和标准集合List、Dictionary避免接口类型、只读属性、私有构造函数等不友好的序列化特性兼容主流序列化器System.Text.Json、Newtonsoft.Json1.3 可读性Readability生成的 JSON 应当清晰易读方便调试和日志记录{ identities: [ { authenticationType: Bearer, nameClaimType: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name, roleClaimType: http://schemas.microsoft.com/ws/2008/06/identity/claims/role, claims: [ { type: name, value: Alice, valueType: http://www.w3.org/2001/XMLSchema#string, issuer: LOCAL AUTHORITY } ] } ] }2. DTO 类设计2.1 ClaimDtopublic class ClaimDto { /// summary /// 声明类型如 name、role、email /// /summary public string Type { get; set; } string.Empty; /// summary /// 声明值 /// /summary public string Value { get; set; } string.Empty; /// summary /// 值的数据类型默认为 XMLSchema#string /// /summary public string ValueType { get; set; } ClaimValueTypes.String; /// summary /// 声明颁发者 /// /summary public string Issuer { get; set; } ClaimsIdentity.DefaultIssuer; /// summary /// 原始颁发者联合身份场景 /// /summary public string OriginalIssuer { get; set; } ClaimsIdentity.DefaultIssuer; /// summary /// 自定义属性集合 /// /summary public Dictionarystring, string? Properties { get; set; } /// summary /// 从 Claim 创建 DTO /// /summary public static ClaimDto FromClaim(Claim claim) { var dto new ClaimDto { Type claim.Type, Value claim.Value, ValueType claim.ValueType, Issuer claim.Issuer, OriginalIssuer claim.OriginalIssuer }; // 仅在有自定义属性时才序列化 if (claim.Properties.Count 0) { dto.Properties new Dictionarystring, string(claim.Properties); } return dto; } /// summary /// 转换为 Claim 对象 /// /summary public Claim ToClaim() { var claim new Claim(Type, Value, ValueType, Issuer, OriginalIssuer); // 还原自定义属性 if (Properties ! null) { foreach (var prop in Properties) { claim.Properties[prop.Key] prop.Value; } } return claim; } }2.2 ClaimsIdentityDtopublic class ClaimsIdentityDto { /// summary /// 认证类型如 Bearer、Cookies /// /summary public string? AuthenticationType { get; set; } /// summary /// 用于标识用户名的声明类型 /// /summary public string NameClaimType { get; set; } ClaimsIdentity.DefaultNameClaimType; /// summary /// 用于标识角色的声明类型 /// /summary public string RoleClaimType { get; set; } ClaimsIdentity.DefaultRoleClaimType; /// summary /// 声明集合 /// /summary public ListClaimDto Claims { get; set; } new(); /// summary /// 从 ClaimsIdentity 创建 DTO /// /summary public static ClaimsIdentityDto FromClaimsIdentity(ClaimsIdentity identity) { return new ClaimsIdentityDto { AuthenticationType identity.AuthenticationType, NameClaimType identity.NameClaimType, RoleClaimType identity.RoleClaimType, Claims identity.Claims.Select(ClaimDto.FromClaim).ToList() }; } /// summary /// 转换为 ClaimsIdentity 对象 /// /summary public ClaimsIdentity ToClaimsIdentity() { var claims Claims.Select(c c.ToClaim()).ToList(); return new ClaimsIdentity( claims, AuthenticationType, NameClaimType, RoleClaimType ); } }2.3 ClaimsPrincipalDtopublic class ClaimsPrincipalDto { /// summary /// 身份集合 /// /summary public ListClaimsIdentityDto Identities { get; set; } new(); /// summary /// 从 ClaimsPrincipal 创建 DTO /// /summary public static ClaimsPrincipalDto FromClaimsPrincipal(ClaimsPrincipal principal) { return new ClaimsPrincipalDto { Identities principal.Identities .Select(ClaimsIdentityDto.FromClaimsIdentity) .ToList() }; } /// summary /// 转换为 ClaimsPrincipal 对象 /// /summary public ClaimsPrincipal ToClaimsPrincipal() { var identities Identities.Select(i i.ToClaimsIdentity()).ToList(); return new ClaimsPrincipal(identities); } }3. 使用示例3.1 序列化using System.Text.Json; // 获取当前用户的 ClaimsPrincipal var principal httpContext.User; // 转换为 DTO var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); // 序列化为 JSON var json JsonSerializer.Serialize(dto, new JsonSerializerOptions { WriteIndented true, // 格式化输出 DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull // 忽略 null 值 }); // 记录到日志或存储 logger.LogInformation(User principal: {Principal}, json);3.2 反序列化// 从 JSON 还原 var dto JsonSerializer.DeserializeClaimsPrincipalDto(json); // 转换为 ClaimsPrincipal var principal dto?.ToClaimsPrincipal(); // 使用还原的对象 if (principal ! null) { httpContext.User principal; // 或者传递给其他服务 }3.3 实际应用场景场景 1分布式会话存储// 将用户身份序列化后存储到 Redis public async Task SaveUserSession(string sessionId, ClaimsPrincipal principal) { var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); var json JsonSerializer.Serialize(dto); await redis.StringSetAsync($session:{sessionId}, json, TimeSpan.FromHours(1)); } // 从 Redis 还原用户身份 public async TaskClaimsPrincipal? LoadUserSession(string sessionId) { var json await redis.StringGetAsync($session:{sessionId}); if (json.IsNullOrEmpty) return null; var dto JsonSerializer.DeserializeClaimsPrincipalDto(json); return dto?.ToClaimsPrincipal(); }场景 2微服务间传递身份// 在 HTTP 请求头中传递用户身份 public async TaskHttpResponseMessage CallDownstreamService(ClaimsPrincipal principal) { var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); var json JsonSerializer.Serialize(dto); var request new HttpRequestMessage(HttpMethod.Get, https://api.example.com/data); request.Headers.Add(X-User-Principal, Convert.ToBase64String(Encoding.UTF8.GetBytes(json))); return await httpClient.SendAsync(request); }场景 3审计日志// 记录用户操作时保存完整的身份信息 public void LogUserAction(ClaimsPrincipal principal, string action, object data) { var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); var auditLog new { Timestamp DateTime.UtcNow, Principal dto, Action action, Data data }; var json JsonSerializer.Serialize(auditLog, new JsonSerializerOptions { WriteIndented true }); File.AppendAllText(audit.log, json Environment.NewLine); }4. 性能优化建议4.1 按需序列化如果只需要部分信息如用户名和角色可以创建简化版的 DTOpublic class SimplePrincipalDto { public string? Name { get; set; } public Liststring Roles { get; set; } new(); public static SimplePrincipalDto FromClaimsPrincipal(ClaimsPrincipal principal) { return new SimplePrincipalDto { Name principal.Identity?.Name, Roles principal.Claims .Where(c c.Type ClaimTypes.Role) .Select(c c.Value) .ToList() }; } }4.2 缓存序列化结果对于频繁访问的用户身份信息可以缓存序列化结果private readonly ConcurrentDictionarystring, string _cache new(); public string GetCachedJson(ClaimsPrincipal principal) { var key principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? string.Empty; return _cache.GetOrAdd(key, _ { var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); return JsonSerializer.Serialize(dto); }); }4.3 避免序列化敏感信息在序列化前过滤敏感声明public static ClaimsPrincipalDto FromClaimsPrincipalSafe(ClaimsPrincipal principal) { // 定义敏感声明类型 var sensitiveTypes new HashSetstring { password, secret, token }; var dto new ClaimsPrincipalDto { Identities principal.Identities.Select(identity new ClaimsIdentityDto { AuthenticationType identity.AuthenticationType, NameClaimType identity.NameClaimType, RoleClaimType identity.RoleClaimType, Claims identity.Claims .Where(c !sensitiveTypes.Contains(c.Type.ToLowerInvariant())) .Select(ClaimDto.FromClaim) .ToList() }).ToList() }; return dto; }第四步通过自定义 Converter 进行序列化和反序列化上一步我们使用 DTO 模式实现了序列化需要手动转换对象。如果你希望更透明地序列化ClaimsPrincipal可以使用System.Text.Json的自定义转换器JsonConverter。这样可以直接对原始对象进行序列化无需中间的 DTO 层。1. 自定义 Converter 的优势1.1 透明性直接序列化原始类型无需手动转换// DTO 方式需要显式转换 var dto ClaimsPrincipalDto.FromClaimsPrincipal(principal); var json JsonSerializer.Serialize(dto); // Converter 方式直接序列化 var json JsonSerializer.Serialize(principal, options);1.2 集中化序列化逻辑集中在 Converter 中使用时只需配置一次var options new JsonSerializerOptions(); options.Converters.Add(new ClaimsPrincipalConverter()); // 全局使用无需每次转换1.3 类型安全反序列化直接返回目标类型无需额外转换var principal JsonSerializer.DeserializeClaimsPrincipal(json, options); // 直接得到 ClaimsPrincipal不是 DTO2. 实现自定义 Converter2.1 ClaimConverterusing System.Security.Claims; using System.Text.Json; using System.Text.Json.Serialization; public class ClaimConverter : JsonConverterClaim { public override Claim? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType ! JsonTokenType.StartObject) { throw new JsonException(Expected StartObject token); } string? type null; string? value null; string? valueType ClaimValueTypes.String; string? issuer ClaimsIdentity.DefaultIssuer; string? originalIssuer ClaimsIdentity.DefaultIssuer; Dictionarystring, string? properties null; while (reader.Read()) { if (reader.TokenType JsonTokenType.EndObject) { break; } if (reader.TokenType ! JsonTokenType.PropertyName) { throw new JsonException(Expected PropertyName token); } string propertyName reader.GetString()!; reader.Read(); switch (propertyName.ToLowerInvariant()) { case type: type reader.GetString(); break; case value: value reader.GetString(); break; case valuetype: valueType reader.GetString() ?? ClaimValueTypes.String; break; case issuer: issuer reader.GetString() ?? ClaimsIdentity.DefaultIssuer; break; case originalissuer: originalIssuer reader.GetString() ?? ClaimsIdentity.DefaultIssuer; break; case properties: properties JsonSerializer.DeserializeDictionarystring, string(ref reader, options); break; default: reader.Skip(); break; } } if (string.IsNullOrEmpty(type) || value null) { throw new JsonException(Claim must have Type and Value); } var claim new Claim(type, value, valueType, issuer, originalIssuer); if (properties ! null) { foreach (var prop in properties) { claim.Properties[prop.Key] prop.Value; } } return claim; } public override void Write(Utf8JsonWriter writer, Claim value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString(type, value.Type); writer.WriteString(value, value.Value); writer.WriteString(valueType, value.ValueType); writer.WriteString(issuer, value.Issuer); writer.WriteString(originalIssuer, value.OriginalIssuer); if (value.Properties.Count 0) { writer.WritePropertyName(properties); JsonSerializer.Serialize(writer, value.Properties, options); } writer.WriteEndObject(); } }2.2 ClaimsIdentityConverterpublic class ClaimsIdentityConverter : JsonConverterClaimsIdentity { public override ClaimsIdentity? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType ! JsonTokenType.StartObject) { throw new JsonException(Expected StartObject token); } string? authenticationType null; string nameClaimType ClaimsIdentity.DefaultNameClaimType; string roleClaimType ClaimsIdentity.DefaultRoleClaimType; ListClaim? claims null; while (reader.Read()) { if (reader.TokenType JsonTokenType.EndObject) { break; } if (reader.TokenType ! JsonTokenType.PropertyName) { throw new JsonException(Expected PropertyName token); } string propertyName reader.GetString()!; reader.Read(); switch (propertyName.ToLowerInvariant()) { case authenticationtype: authenticationType reader.GetString(); break; case nameclaimtype: nameClaimType reader.GetString() ?? ClaimsIdentity.DefaultNameClaimType; break; case roleclaimtype: roleClaimType reader.GetString() ?? ClaimsIdentity.DefaultRoleClaimType; break; case claims: claims JsonSerializer.DeserializeListClaim(ref reader, options); break; default: reader.Skip(); break; } } return new ClaimsIdentity( claims ?? new ListClaim(), authenticationType, nameClaimType, roleClaimType ); } public override void Write(Utf8JsonWriter writer, ClaimsIdentity value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WriteString(authenticationType, value.AuthenticationType); writer.WriteString(nameClaimType, value.NameClaimType); writer.WriteString(roleClaimType, value.RoleClaimType); writer.WritePropertyName(claims); JsonSerializer.Serialize(writer, value.Claims.ToList(), options); writer.WriteEndObject(); } }2.3 ClaimsPrincipalConverterpublic class ClaimsPrincipalConverter : JsonConverterClaimsPrincipal { public override ClaimsPrincipal? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { if (reader.TokenType ! JsonTokenType.StartObject) { throw new JsonException(Expected StartObject token); } ListClaimsIdentity? identities null; while (reader.Read()) { if (reader.TokenType JsonTokenType.EndObject) { break; } if (reader.TokenType ! JsonTokenType.PropertyName) { throw new JsonException(Expected PropertyName token); } string propertyName reader.GetString()!; reader.Read(); if (propertyName.Equals(identities, StringComparison.OrdinalIgnoreCase)) { identities JsonSerializer.DeserializeListClaimsIdentity(ref reader, options); } else { reader.Skip(); } } return new ClaimsPrincipal(identities ?? new ListClaimsIdentity()); } public override void Write(Utf8JsonWriter writer, ClaimsPrincipal value, JsonSerializerOptions options) { writer.WriteStartObject(); writer.WritePropertyName(identities); JsonSerializer.Serialize(writer, value.Identities.ToList(), options); writer.WriteEndObject(); } }3. 使用自定义 Converter3.1 基本使用// 配置序列化选项 var options new JsonSerializerOptions { WriteIndented true, DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull, Converters { new ClaimConverter(), new ClaimsIdentityConverter(), new ClaimsPrincipalConverter() } }; // 序列化 var principal httpContext.User; var json JsonSerializer.Serialize(principal, options); // 反序列化 var deserializedPrincipal JsonSerializer.DeserializeClaimsPrincipal(json, options);3.2 全局配置在 ASP.NET Core 中可以全局配置序列化选项// Program.cs 或 Startup.cs builder.Services.ConfigureJsonOptions(options { options.JsonSerializerOptions.Converters.Add(new ClaimConverter()); options.JsonSerializerOptions.Converters.Add(new ClaimsIdentityConverter()); options.JsonSerializerOptions.Converters.Add(new ClaimsPrincipalConverter()); });3.3 创建扩展方法为了更方便使用可以创建扩展方法public static class ClaimsPrincipalExtensions { private static readonly JsonSerializerOptions DefaultOptions new() { WriteIndented true, DefaultIgnoreCondition JsonIgnoreCondition.WhenWritingNull, Converters { new ClaimConverter(), new ClaimsIdentityConverter(), new ClaimsPrincipalConverter() } }; /// summary /// 将 ClaimsPrincipal 序列化为 JSON /// /summary public static string ToJson(this ClaimsPrincipal principal, JsonSerializerOptions? options null) { return JsonSerializer.Serialize(principal, options ?? DefaultOptions); } /// summary /// 从 JSON 反序列化为 ClaimsPrincipal /// /summary public static ClaimsPrincipal? FromJson(string json, JsonSerializerOptions? options null) { return JsonSerializer.DeserializeClaimsPrincipal(json, options ?? DefaultOptions); } /// summary /// 将 ClaimsPrincipal 安全地序列化为 JSON过滤敏感信息 /// /summary public static string ToJsonSafe(this ClaimsPrincipal principal, HashSetstring? sensitiveTypes null) { sensitiveTypes ?? new HashSetstring(StringComparer.OrdinalIgnoreCase) { password, secret, token, apikey }; // 创建过滤后的副本 var filteredIdentities principal.Identities.Select(identity { var filteredClaims identity.Claims .Where(c !sensitiveTypes.Contains(c.Type)) .ToList(); return new ClaimsIdentity( filteredClaims, identity.AuthenticationType, identity.NameClaimType, identity.RoleClaimType ); }).ToList(); var filteredPrincipal new ClaimsPrincipal(filteredIdentities); return filteredPrincipal.ToJson(); } }使用扩展方法// 序列化 var json httpContext.User.ToJson(); // 安全序列化自动过滤敏感信息 var safeJson httpContext.User.ToJsonSafe(); // 反序列化 var principal ClaimsPrincipalExtensions.FromJson(json);4. 实际应用示例4.1 中间件中传递用户身份public class UserPrincipalMiddleware { private readonly RequestDelegate _next; public UserPrincipalMiddleware(RequestDelegate next) { _next next; } public async Task InvokeAsync(HttpContext context) { // 如果请求头包含序列化的用户身份则还原 if (context.Request.Headers.TryGetValue(X-User-Principal, out var principalHeader)) { try { var json Encoding.UTF8.GetString(Convert.FromBase64String(principalHeader!)); var principal ClaimsPrincipalExtensions.FromJson(json); if (principal ! null) { context.User principal; } } catch (Exception ex) { // 记录错误但不中断请求 Console.WriteLine($Failed to deserialize principal: {ex.Message}); } } await _next(context); } }4.2 分布式缓存public class UserSessionService { private readonly IDistributedCache _cache; public UserSessionService(IDistributedCache cache) { _cache cache; } public async Task SaveSessionAsync(string sessionId, ClaimsPrincipal principal) { var json principal.ToJson(); var bytes Encoding.UTF8.GetBytes(json); await _cache.SetAsync(sessionId, bytes, new DistributedCacheEntryOptions { AbsoluteExpirationRelativeToNow TimeSpan.FromHours(1) }); } public async TaskClaimsPrincipal? GetSessionAsync(string sessionId) { var bytes await _cache.GetAsync(sessionId); if (bytes null) return null; var json Encoding.UTF8.GetString(bytes); return ClaimsPrincipalExtensions.FromJson(json); } }5. DTO vs Converter如何选择5.1 使用 DTO 的场景优点显式转换逻辑清晰可以灵活定制传输的数据结构更容易进行数据验证和转换不依赖特定的序列化框架适用场景API 数据传输需要版本控制和向后兼容跨语言/跨平台通信需要数据脱敏或转换的场景长期存储的数据格式示例// 为前端提供简化的用户信息 public class UserInfoDto { public string UserId { get; set; } public string Name { get; set; } public Liststring Roles { get; set; } public static UserInfoDto FromPrincipal(ClaimsPrincipal principal) { return new UserInfoDto { UserId principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? , Name principal.Identity?.Name ?? , Roles principal.Claims .Where(c c.Type ClaimTypes.Role) .Select(c c.Value) .ToList() }; } }5.2 使用 Converter 的场景优点透明序列化使用更简洁保持原始类型无需转换全局配置一次处处可用更好地支持嵌套对象序列化适用场景内部服务通信同技术栈临时缓存和会话存储调试和日志记录需要完整保留对象状态示例// 缓存完整的用户身份 public async Task CacheUserPrincipal(string key, ClaimsPrincipal principal) { // 直接序列化无需转换 var json principal.ToJson(); await _cache.SetStringAsync(key, json, TimeSpan.FromMinutes(30)); }5.3 混合使用在实际项目中两种方式可以共存public class UserService { // 对外 API使用 DTO public UserInfoDto GetPublicUserInfo(ClaimsPrincipal principal) { return UserInfoDto.FromPrincipal(principal); } // 内部缓存使用 Converter public async Task CacheUserSession(string sessionId, ClaimsPrincipal principal) { var json principal.ToJson(); await SaveToCache(sessionId, json); } // 审计日志使用 Converter安全模式 public void LogUserAction(ClaimsPrincipal principal, string action) { var json principal.ToJsonSafe(); _logger.LogInformation(Action: {Action}, User: {User}, action, json); } }6. 性能对比using BenchmarkDotNet.Attributes; using BenchmarkDotNet.Running; [MemoryDiagnoser] public class SerializationBenchmark { private ClaimsPrincipal _principal; private JsonSerializerOptions _options; [GlobalSetup] public void Setup() { var claims new[] { new Claim(ClaimTypes.Name, Alice), new Claim(ClaimTypes.Email, aliceexample.com), new Claim(ClaimTypes.Role, Admin), new Claim(ClaimTypes.Role, User) }; var identity new ClaimsIdentity(claims, Bearer); _principal new ClaimsPrincipal(identity); _options new JsonSerializerOptions { Converters { new ClaimConverter(), new ClaimsIdentityConverter(), new ClaimsPrincipalConverter() } }; } [Benchmark] public string SerializeWithDto() { var dto ClaimsPrincipalDto.FromClaimsPrincipal(_principal); return JsonSerializer.Serialize(dto); } [Benchmark] public string SerializeWithConverter() { return JsonSerializer.Serialize(_principal, _options); } [Benchmark] public ClaimsPrincipal DeserializeWithDto() { var dto ClaimsPrincipalDto.FromClaimsPrincipal(_principal); var json JsonSerializer.Serialize(dto); var deserializedDto JsonSerializer.DeserializeClaimsPrincipalDto(json); return deserializedDto!.ToClaimsPrincipal(); } [Benchmark] public ClaimsPrincipal DeserializeWithConverter() { var json JsonSerializer.Serialize(_principal, _options); return JsonSerializer.DeserializeClaimsPrincipal(json, _options)!; } } // 运行基准测试 // BenchmarkRunner.RunSerializationBenchmark();预期结果Converter 方式通常略快少一次对象转换内存使用相近两者性能差异在大多数场景下可忽略7. 注意事项7.1 空值处理确保 Converter 正确处理 null 值public override void Write(Utf8JsonWriter writer, ClaimsPrincipal? value, JsonSerializerOptions options) { if (value null) { writer.WriteNullValue(); return; } // ... 正常序列化逻辑 }7.2 循环引用虽然 ClaimsPrincipal 结构简单不会出现循环引用但在扩展时需注意var options new JsonSerializerOptions { ReferenceHandler ReferenceHandler.IgnoreCycles, // 处理循环引用 Converters { /* ... */ } };7.3 版本兼容性序列化格式应考虑向后兼容public override ClaimsIdentity? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { // 为新增字段提供默认值 string nameClaimType ClaimsIdentity.DefaultNameClaimType; // 兼容旧版本 JSON可能缺少某些字段 // ... }

相关文章:

ClaimsPrincipal序列化为Json的正确姿势

第二步,理解三者的关系 1. Claim:声明的基本单元 职责 表示一个键值对形式的声明(如 "name" "Alice"、"role" "Admin")。不仅包含类型(Type)和值&#xff08…...

3个核心功能让视频创作者内容采集效率提升300%的实战指南

3个核心功能让视频创作者内容采集效率提升300%的实战指南 【免费下载链接】douyin-downloader A practical Douyin downloader for both single-item and profile batch downloads, with progress display, retries, SQLite deduplication, and browser fallback support. 抖音…...

深度解析Godot资源提取:构建专业级解包方案

深度解析Godot资源提取:构建专业级解包方案 【免费下载链接】godot-unpacker godot .pck unpacker 项目地址: https://gitcode.com/gh_mirrors/go/godot-unpacker 在游戏开发与逆向工程领域,Godot资源提取技术已成为开发者探索游戏内部结构的核心…...

SEGGER J-Flash V8.94添加芯片方法

1.打开J-Flash安装路径&#xff08;一般为\SEGGER\JLink_V894&#xff09; 在此路径下新建文件JLinkDevices.xml2.编辑xml文件 <DataBase><Device><ChipInfo Vendor"Renergy" /*厂家*/Name"RN8213B_SOC_V2" /*芯片型号*/WorkRAMAddr&q…...

解压缩软件分享-Banizip

解压缩软件分享-Banizip蓝奏云地址https://wwbdt.lanzoul.com/ijspk3mbduxi 密码:9y00百度网盘地址通过网盘分享的文件&#xff1a;BANDIZIP6-SETUP.EXE 链接: https://pan.baidu.com/s/1VBovOqT-M7kiv2b9YuJGIw?pwdrc87 提取码: rc87 为什么推荐这个呢&#xff0c;因为这个支…...

春秋云境CVE-2018-12613

1.阅读靶场介绍index.php中存在一处文件包含逻辑可以理解为对url做处理能想到的就是../../../../../flag2.启动靶场如上图所示看到这个界面我想到的就是select flag from flag出来但是本博主没有找到哟期待各位大神的评论3.poc这里博主是通过这个处理过的url夺旗的如下所示url在…...

从一线装维经验看,扩展式智能插座更适合多路监测与项目落地

作为一名做了12年现场电气安装与运维的一线装维人员&#xff0c;今天想聊聊智能插座。这些年接触过的智能插座不少&#xff0c;市面上的产品确实五花八门&#xff0c;外观、功能、结构都不一样。选择多了&#xff0c;对用户来说未必是好事&#xff0c;反而更容易挑花眼。尤其一…...

数据库基础知识----数据库大观

这里写目录标题绪论发展历程数据模型三层模式两层映像基本概念关系数据库简介&#xff08;基本术语&#xff09;关系模型组成数据结构数据操纵数据完整性规则关系代数五个基本操作并差笛卡尔积投影&#xff08;π&#xff09;选择四个组合操作交连接除法关系数据库语言----SQL简…...

具备百万并发用户执行能力,静态页面加载的平均响应时间低于1.1毫秒, 事务请求处理成功率100%

在Tomcat性能测试与技术选型中&#xff0c;“具备百万并发用户执行能力”“静态页面加载平均响应时间低于1.1毫秒”“事务请求处理成功率100%”是常被提及的理想指标。这些指标看似彰显系统高性能&#xff0c;实则需结合计算机底层原理、操作系统限制、工程实践场景综合研判。本…...

如何高效构建雷达系统:Python雷达模拟的完整实战指南

如何高效构建雷达系统&#xff1a;Python雷达模拟的完整实战指南 【免费下载链接】radarsimpy Radar Simulator built with Python and C 项目地址: https://gitcode.com/gh_mirrors/ra/radarsimpy RadarSimPy是一个基于Python和C构建的开源雷达模拟器&#xff0c;为雷达…...

Steam Achievement Manager终极指南:如何完全掌控你的Steam成就系统

Steam Achievement Manager终极指南&#xff1a;如何完全掌控你的Steam成就系统 【免费下载链接】SteamAchievementManager A manager for game achievements in Steam. 项目地址: https://gitcode.com/gh_mirrors/st/SteamAchievementManager Steam Achievement Manage…...

WarcraftHelper终极指南:如何让魔兽争霸3在现代电脑上焕然新生 [特殊字符]

WarcraftHelper终极指南&#xff1a;如何让魔兽争霸3在现代电脑上焕然新生 &#x1f3ae; 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper 还在为魔兽争…...

【愚公系列】《剪映+DeepSeek+即梦:短视频制作》047-转场:短视频一气呵成的秘密(转场类型)

&#x1f48e;【行业认证权威头衔】 ✔ 华为云天团核心成员&#xff1a;特约编辑/云享专家/开发者专家/产品云测专家 ✔ 开发者社区全满贯&#xff1a;CSDN博客&商业化双料专家/阿里云签约作者/腾讯云内容共创官/掘金&亚马逊&51CTO顶级博主 ✔ 技术生态共建先锋&am…...

【python】MacOS下永久配置pip镜像源

核心方法&#xff1a;修改 pip 的配置文件在 macOS 上&#xff0c;您需要创建或修改一个位于用户主目录下的配置文件 pip.conf。详细步骤第一步&#xff1a;打开终端按 Command 空格键 打开 Spotlight 搜索。输入“终端”或“Terminal”&#xff0c;然后按回车键打开。第二步&…...

Jupyter notebook打不开本地文件,有关目录存放问题

Jupyter notebook打不开本地文件&#xff0c;有关目录存放问题 基于Anaconda下载后&#xff0c;点击Jupyter notebook无法打开文件目录问题&#xff0c;或者需要更改打开的文件目录&#xff0c;主要解决方法&#xff1a;修改配置文件和路径。 第一步&#xff1a;修改配置文件 打…...

LFM2.5-1.2B-Thinking在Ollama上的真实体验:生成速度、内容质量实测

LFM2.5-1.2B-Thinking在Ollama上的真实体验&#xff1a;生成速度、内容质量实测 1. 模型初体验与部署 1.1 第一印象&#xff1a;轻量但强大 当我第一次在Ollama上看到LFM2.5-1.2B-Thinking这个模型时&#xff0c;最吸引我的是它"小身材大能量"的特点。作为一个仅有…...

小白也能轻松上手!通义千问2.5-7B+Ollama快速入门

小白也能轻松上手&#xff01;通义千问2.5-7BOllama快速入门 1. 为什么选择通义千问2.5-7B&#xff1f; 通义千问2.5-7B-Instruct是阿里云2024年9月发布的中等规模开源大模型&#xff0c;拥有70亿参数&#xff0c;专为指令跟随任务优化。这个模型特别适合想在本地运行AI但又不…...

Kandinsky-5.0-I2V-Lite-5s部署案例:中小企业用其替代高价视频外包,降本70%

Kandinsky-5.0-I2V-Lite-5s部署案例&#xff1a;中小企业用其替代高价视频外包&#xff0c;降本70% 1. 为什么中小企业需要关注这个方案 对于中小企业来说&#xff0c;视频制作一直是个头疼的问题。传统外包制作5秒短视频的平均成本在2000-5000元不等&#xff0c;而使用Kandi…...

重获数据自主权:WechatDecrypt让你掌控数字记忆

重获数据自主权&#xff1a;WechatDecrypt让你掌控数字记忆 【免费下载链接】WechatDecrypt 微信消息解密工具 项目地址: https://gitcode.com/gh_mirrors/we/WechatDecrypt 在数字时代&#xff0c;我们的聊天记录、社交关系和工作信息都存储在第三方平台上&#xff0c;…...

效率提升秘籍:用快马AI自动生成9·1免费素材的可复用组件

效率提升秘籍&#xff1a;用快马AI自动生成91免费素材的可复用组件 最近在做一个需要整合大量91免费素材的项目&#xff0c;发现每次都要手动编写重复的展示代码&#xff0c;效率实在太低。经过一番摸索&#xff0c;我找到了用快马平台快速生成可复用组件的方法&#xff0c;效…...

wechat-need-web:基于Manifest V3的微信网页版访问架构解析与实现方案

wechat-need-web&#xff1a;基于Manifest V3的微信网页版访问架构解析与实现方案 【免费下载链接】wechat-need-web 让微信网页版可用 / Allow the use of WeChat via webpage access 项目地址: https://gitcode.com/gh_mirrors/we/wechat-need-web 微信网页版访问限制…...

用快马AI快速原型:十分钟搭建可视化算术表达式编译器

最近在学习编译原理&#xff0c;发现很多概念特别抽象&#xff0c;特别是词法分析、语法分析这些环节。正好在InsCode(快马)平台上尝试做了一个可视化算术表达式计算器&#xff0c;把整个编译过程直观展示出来&#xff0c;效果意外地好。分享下我的实现思路&#xff0c;特别适合…...

全球人形机器人革命浪潮涌动,特斯拉/微美全息聚焦AI具身量产应用新突破!

近日&#xff0c;特斯拉(TSLA.US)马斯克宣布开启人类历史上规模最大的芯片制造项目——TERAFAB&#xff0c;目标实现每年超过1太瓦的算力产出。该设施将整合芯片设计、光刻、制造等全流程&#xff0c;其生产的部分芯片未来将直接用于驱动特斯拉电动车以及人形机器人。人形机器人…...

OpenCode-Tokenscope 安装和使用指南

OpenCode-Tokenscope 安装和使用指南全面的 OpenCode AI 会话 token 使用分析和成本追踪插件安装 方法 1: npm (推荐) 步骤 1: 全局安装 npm install -g ramtinj95/opencode-tokenscope步骤 2: 配置 opencode.json 在以下位置之一创建 opencode.json&#xff1a; 项目根目录~/.…...

3步实现飞书文档高效转换:Cloud Document Converter全场景解决方案

3步实现飞书文档高效转换&#xff1a;Cloud Document Converter全场景解决方案 【免费下载链接】cloud-document-converter Convert Lark Doc to Markdown 项目地址: https://gitcode.com/gh_mirrors/cl/cloud-document-converter 一、三大痛点&#xff1a;飞书文档管理…...

UNTRUNC:视频修复破局者——从文件截断到数据重生的技术解密

UNTRUNC&#xff1a;视频修复破局者——从文件截断到数据重生的技术解密 【免费下载链接】untrunc Restore a damaged (truncated) mp4, m4v, mov, 3gp video. Provided you have a similar not broken video. 项目地址: https://gitcode.com/gh_mirrors/unt/untrunc 诊…...

二、PXE+Kickstart 无人值守批量部署操作系统;使用物理路由器的dhcp:ProxyDHCP+TFTP+HTTP+Kickstart应答文件(VMware测试环境)

前文不使用物理设备的 DHCP &#xff0c;选择自行安装 DHCP 服务进行的PXEKickstart 无人值守部署操作系统的方法难以适用于家庭或企业环境&#xff0c;本文尝试一种使用物理设备&#xff08;家庭路由器、企业交换机&#xff09;的DHCP功能批量部署物理机操作系统的方案。 建议…...

WarcraftHelper:魔兽争霸3兼容性救星,让经典游戏在现代电脑上重生!

WarcraftHelper&#xff1a;魔兽争霸3兼容性救星&#xff0c;让经典游戏在现代电脑上重生&#xff01; 【免费下载链接】WarcraftHelper Warcraft III Helper , support 1.20e, 1.24e, 1.26a, 1.27a, 1.27b 项目地址: https://gitcode.com/gh_mirrors/wa/WarcraftHelper …...

树莓派 AP 模式作为中继器或子路由器配置

树莓派 AP 模式作为中继器或子路由器配置设备&#xff1a;Raspberry Pi 4B W | 日期&#xff1a;2026-04-02 WiFi 芯片&#xff1a;BCM43455 | 系统&#xff1a;Raspberry Pi OS (64-bit)一、环境信息项目值设备型号Raspberry Pi Zero 2 WWiFi 芯片BCM43455内核版本6.6.x操作系…...

GCP 成本优化指南

5 分钟速览 我想… 用什么 预期效果 看钱花在哪了 Billing Reports + Cost Table 按服务/项目/标签拆分费用 费用超了自动告警 Budget Alerts 50%/80%/100% 阈值通知 深度分析费用趋势 BigQuery 费用导出 自定义 SQL 分析任意维度 降低计算成本 CUD / Spot VM 计算费用降 30%-7…...