呵虐姆寺第一步,看源码,分析类的结构
不用去反编译,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
第二步,理解三者的关系
- Claim:声明的基本单元
职责
表示一个键值对形式的声明(如 "name" = "Alice"、"role" = "Admin")。
不仅包含类型(Type)和值(Value),还携带元数据,如:
ValueType:值的数据类型(如字符串、整数等,默认为 string)。
Issuer:声明的颁发者(如 "https://login.microsoft.com")。
OriginalIssuer:原始颁发者(用于联合身份场景)。
Properties:可扩展的键值对字典,用于存储额外信息。
特点
不可变性:一旦创建,其核心属性(Type/Value)通常不可更改。
轻量级:设计为数据载体,无业务逻辑。
语义丰富:通过标准声明类型(如 ClaimTypes.Name、ClaimTypes.Role)实现互操作性。
- ClaimsIdentity:代表一个身份(Identity)
职责
封装一组相关的 Claim,构成一个完整的身份视图。
提供身份的上下文信息:
AuthenticationType:认证方式(如 "Cookies"、"JwtBearer")。
IsAuthenticated:是否已通过认证(依赖 AuthenticationType 非空)。
Name:通常映射自 NameClaimType 类型的声明(默认为 ClaimTypes.Name)。
RoleClaimType:用于角色判断的声明类型(默认为 ClaimTypes.Role)。
关键行为
AddClaim(Claim):动态添加声明(常用于中间件或策略授权)。
FindFirst(string type):查找首个匹配类型的声明。
HasClaim(...):检查是否存在特定声明。
设计思想
一个主体(如用户)可以有多个身份(例如:本地登录身份 + 社交账号身份)。
支持多身份源(如 Windows 身份、JWT 身份、自定义身份)。
- 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)访问。
- 三者协作关系
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 完整性(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"
}
]
}
]
}
- 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);
}
}
- 使用示例
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);
}
- 性能优化建议
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;