打造.NET平台的Lombok:实现构造函数注入、日志注入、构造者模式代码生成等功能

呵虐姆寺第一步,看源码,分析类的结构

不用去反编译,Github上有现成的:https://github.com/dotnet/runtime/tree/main/src/libraries/System.Security.Claims/src/System/Security/Claims

就不挨个贴代码了,我直接整理成类图。

contains

contains

1

1

0..*

0..*

Claim

+String Type

+String Value

+String ValueType

+String Issuer

+String OriginalIssuer

+Properties: IDictionary

+Claim(string type, string value)

+Claim(string type, string value, string valueType)

+ToString() : string

ClaimsIdentity

+String AuthenticationType

+bool IsAuthenticated

+String Name

+String NameClaimType

+String RoleClaimType

+IEnumerable Claims

+ClaimsIdentity()

+ClaimsIdentity(string authenticationType)

+ClaimsIdentity(IEnumerable claims)

+AddClaim(Claim claim) : void

+FindFirst(string type) : Claim

+HasClaim(string type, string value) : bool

+HasClaim(Predicate match) : bool

ClaimsPrincipal

+IEnumerable Identities

+ClaimsIdentity Identity

+IEnumerable Claims

+string Identity.AuthenticationType

+bool Identity.IsAuthenticated

+string Identity.Name

+ClaimsPrincipal()

+ClaimsPrincipal(ClaimsIdentity identity)

+ClaimsPrincipal(IEnumerable identities)

+FindFirst(string claimType) : Claim

+HasClaim(string type, string value) : bool

+IsInRole(string role) : bool

ClaimsPrincipal

第二步,理解三者的关系

  1. Claim:声明的基本单元

职责

表示一个键值对形式的声明(如 "name" = "Alice"、"role" = "Admin")。

不仅包含类型(Type)和值(Value),还携带元数据,如:

ValueType:值的数据类型(如字符串、整数等,默认为 string)。

Issuer:声明的颁发者(如 "https://login.microsoft.com")。

OriginalIssuer:原始颁发者(用于联合身份场景)。

Properties:可扩展的键值对字典,用于存储额外信息。

特点

不可变性:一旦创建,其核心属性(Type/Value)通常不可更改。

轻量级:设计为数据载体,无业务逻辑。

语义丰富:通过标准声明类型(如 ClaimTypes.Name、ClaimTypes.Role)实现互操作性。

  1. ClaimsIdentity:代表一个身份(Identity)

职责

封装一组相关的 Claim,构成一个完整的身份视图。

提供身份的上下文信息:

AuthenticationType:认证方式(如 "Cookies"、"JwtBearer")。

IsAuthenticated:是否已通过认证(依赖 AuthenticationType 非空)。

Name:通常映射自 NameClaimType 类型的声明(默认为 ClaimTypes.Name)。

RoleClaimType:用于角色判断的声明类型(默认为 ClaimTypes.Role)。

关键行为

AddClaim(Claim):动态添加声明(常用于中间件或策略授权)。

FindFirst(string type):查找首个匹配类型的声明。

HasClaim(...):检查是否存在特定声明。

设计思想

一个主体(如用户)可以有多个身份(例如:本地登录身份 + 社交账号身份)。

支持多身份源(如 Windows 身份、JWT 身份、自定义身份)。

  1. 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)访问。

  1. 三者协作关系

ClaimsPrincipal

├── ClaimsIdentity #1 (e.g., from JWT)

│ ├── Claim: { Type="name", Value="Alice" }

│ ├── Claim: { Type="role", Value="Admin" }

│ └── Claim: { Type="email", Value="alice@example.com" }

└── ClaimsIdentity #2 (e.g., from external provider)

├── Claim: { Type="sub", Value="12345" }

└── Claim: { Type="idp", Value="Google" }

第三步,设计序列化模型(DTO)

理解了 ClaimsPrincipal 的内部结构后,我们需要设计可序列化的 DTO 来承载这些数据。关键是要保留足够的信息,以便在反序列化时能够完整还原原始对象。

  1. 设计原则

在设计序列化模型时,我们需要遵循以下原则:

1.1 完整性(Completeness)

保留所有关键信息,确保反序列化后的对象与原始对象等价:

Claim 的所有核心属性(Type、Value、ValueType、Issuer、OriginalIssuer、Properties)

ClaimsIdentity 的认证上下文(AuthenticationType、NameClaimType、RoleClaimType)

ClaimsPrincipal 的多身份支持(Identities 集合)

1.2 可序列化性(Serializability)

使用简单类型(string、int、bool 等)和标准集合(List、Dictionary)

避免接口类型、只读属性、私有构造函数等不友好的序列化特性

兼容主流序列化器(System.Text.Json、Newtonsoft.Json)

1.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"

}

]

}

]

}

  1. DTO 类设计

2.1 ClaimDto

public class ClaimDto

{

///
/// 声明类型(如 "name"、"role"、"email")

///

public string Type { get; set; } = string.Empty;

///
/// 声明值

///

public string Value { get; set; } = string.Empty;

///
/// 值的数据类型(默认为 XMLSchema#string)

///

public string ValueType { get; set; } = ClaimValueTypes.String;

///
/// 声明颁发者

///

public string Issuer { get; set; } = ClaimsIdentity.DefaultIssuer;

///
/// 原始颁发者(联合身份场景)

///

public string OriginalIssuer { get; set; } = ClaimsIdentity.DefaultIssuer;

///
/// 自定义属性集合

///

public Dictionary? Properties { get; set; }

///
/// 从 Claim 创建 DTO

///

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 Dictionary(claim.Properties);

}

return dto;

}

///
/// 转换为 Claim 对象

///

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 ClaimsIdentityDto

public class ClaimsIdentityDto

{

///
/// 认证类型(如 "Bearer"、"Cookies")

///

public string? AuthenticationType { get; set; }

///
/// 用于标识用户名的声明类型

///

public string NameClaimType { get; set; } = ClaimsIdentity.DefaultNameClaimType;

///
/// 用于标识角色的声明类型

///

public string RoleClaimType { get; set; } = ClaimsIdentity.DefaultRoleClaimType;

///
/// 声明集合

///

public List Claims { get; set; } = new();

///
/// 从 ClaimsIdentity 创建 DTO

///

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()

};

}

///
/// 转换为 ClaimsIdentity 对象

///

public ClaimsIdentity ToClaimsIdentity()

{

var claims = Claims.Select(c => c.ToClaim()).ToList();

return new ClaimsIdentity(

claims,

AuthenticationType,

NameClaimType,

RoleClaimType

);

}

}

2.3 ClaimsPrincipalDto

public class ClaimsPrincipalDto

{

///
/// 身份集合

///

public List Identities { get; set; } = new();

///
/// 从 ClaimsPrincipal 创建 DTO

///

public static ClaimsPrincipalDto FromClaimsPrincipal(ClaimsPrincipal principal)

{

return new ClaimsPrincipalDto

{

Identities = principal.Identities

.Select(ClaimsIdentityDto.FromClaimsIdentity)

.ToList()

};

}

///
/// 转换为 ClaimsPrincipal 对象

///

public ClaimsPrincipal ToClaimsPrincipal()

{

var identities = Identities.Select(i => i.ToClaimsIdentity()).ToList();

return new ClaimsPrincipal(identities);

}

}

  1. 使用示例

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.Deserialize(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 Task LoadUserSession(string sessionId)

{

var json = await redis.StringGetAsync($"session:{sessionId}");

if (json.IsNullOrEmpty) return null;

var dto = JsonSerializer.Deserialize(json);

return dto?.ToClaimsPrincipal();

}

场景 2:微服务间传递身份

// 在 HTTP 请求头中传递用户身份

public async Task 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);

}

  1. 性能优化建议

4.1 按需序列化

如果只需要部分信息(如用户名和角色),可以创建简化版的 DTO:

public class SimplePrincipalDto

{

public string? Name { get; set; }

public List 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 ConcurrentDictionary _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 HashSet

{

"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;