Claim
本章只对应 soulsoft_identity_claims 这个库本身。
它只提供三层身份数据模型:
Claim:一条声明ClaimsIdentity:一组声明 + 一个认证类型ClaimsPrincipal:一组身份
核心结构
这套模型最重要的关系是:
Claim -> ClaimsIdentity -> ClaimsPrincipal也就是说:
- 单条信息放在
Claim - 同一个身份下的多条信息放在
ClaimsIdentity - 一个请求或用户可以同时拥有多个身份,最终放在
ClaimsPrincipal
最小示例
import soulsoft_identity_claims.*
let id = ClaimsIdentity(Some("Bearer"))
id.addClaim(ClaimTypes.Sub, "user-001")
id.addClaim(ClaimTypes.Name, "spire")
id.addClaim(ClaimTypes.Role, "admin")
let principal = ClaimsPrincipal([id])
let userId = principal.findFirstValue(ClaimTypes.Sub) ?? String.empty
let userName = principal.findFirstValue(ClaimTypes.Name) ?? String.empty
let isAdmin = principal.isInRole("admin")这里没有任何“用户对象”或“权限对象”。整个身份信息就是这些声明本身。
Claim
Claim 只有三个字段:
typevaluevalueType
构造方式:
let c1 = Claim("role", "admin")
let c2 = Claim("age", "18", ClaimValueTypes.Integer)如果你不传 valueType,默认值就是:
ClaimValueTypes.String它的字符串形式也很直接:
println(c1.toString())
// role = admin [http://www.w3.org/2001/XMLSchema#string]ClaimsIdentity
ClaimsIdentity 表示一个身份。它内部持有:
- 一个
ArrayList<Claim> - 一个可选的
authenticationType
最容易忽略的实现细节是:
isAuthenticated == authenticationType.isSome()也就是说,是否“已认证”只看 authenticationType 有没有值,不看声明内容。
例如:
let anonymous = ClaimsIdentity()
println(anonymous.isAuthenticated) // false
let bearer = ClaimsIdentity(Some("Bearer"))
println(bearer.isAuthenticated) // true常用操作
let identity = ClaimsIdentity(Some("AuthA"))
identity.addClaim(ClaimTypes.Name, "spire")
identity.addClaim(ClaimTypes.Role, "admin")
identity.addClaims([
Claim(ClaimTypes.Email, "spire@example.com"),
Claim("tenant", "default")
])查询接口分成三类:
findAll(...)findFirst(...)findFirstValue(...)
以及两个存在性判断:
hasClaim(predicate)hasClaim(type, value)
匹配语义
这一点要特别注意。源码里这里用的是精确匹配,不做忽略大小写处理:
identity.findAll("role")
identity.hasClaim("role", "admin")所以:
"role"和"Role"不是一回事"admin"和"Admin"也不是一回事
ClaimsPrincipal
ClaimsPrincipal 是多个 ClaimsIdentity 的容器。
let bearer = ClaimsIdentity(Some("AuthA"))
bearer.addClaim(ClaimTypes.Sub, "user-001")
let second = ClaimsIdentity(Some("AuthB"))
second.addClaim(ClaimTypes.Role, "admin")
let principal = ClaimsPrincipal([bearer, second])它的查询会跨所有 identity 聚合:
let sub = principal.findFirstValue(ClaimTypes.Sub)
let role = principal.findFirstValue(ClaimTypes.Role)
let allRoles = principal.findAll(ClaimTypes.Role)主身份
principal.identity 默认返回第一个 identity。
这是由一个静态选择器决定的:
ClaimsPrincipal.setPrimaryIdentitySelector { identities =>
identities |> first
}你可以改掉它:
ClaimsPrincipal.setPrimaryIdentitySelector { identities =>
let items = identities |> collectArray
if (items.isEmpty()) {
None
} else {
Some(items[items.size - 1])
}
}这里还有一个实现层含义:
- 这个选择器是
ClaimsPrincipal的全局静态状态 - 改掉后会影响后续所有
principal.identity的行为
角色判断
isInRole(role) 的实现也很简单:
- 遍历所有 identity
- 检查是否存在
ClaimTypes.Role - 且 value 等于传入的
role
所以它只认:
ClaimTypes.Role不会自动去看 ClaimTypes.Roles、scope 或其它自定义声明。
序列化与克隆
这三个核心类型都实现了序列化:
Claim.serialize() / deserialize(...)ClaimsIdentity.serialize() / deserialize(...)ClaimsPrincipal.serialize() / deserialize(...)
同时也都支持深拷贝:
let copiedIdentity = identity.clone()
let copiedPrincipal = principal.clone()clone() 的行为是“复制数据”,不是共享内部集合:
ClaimsIdentity.clone()会逐条复制 claimClaimsPrincipal.clone()会逐个调用 identity 的clone()
ClaimTypes
ClaimTypes 是一组字符串常量,源码里按类别分了几组。
标准令牌常量
ClaimTypes.Sub
ClaimTypes.Exp
ClaimTypes.Iss
ClaimTypes.Aud
ClaimTypes.Iat
ClaimTypes.JtiOpenID Connect
ClaimTypes.Name
ClaimTypes.Email
ClaimTypes.EmailVerified
ClaimTypes.PhoneNumber
ClaimTypes.PreferredUsername
ClaimTypes.Picture
ClaimTypes.Sid角色 / 权限
ClaimTypes.Role
ClaimTypes.Roles
ClaimTypes.Scope
ClaimTypes.Permissions组织或业务标识
ClaimTypes.TenantId
ClaimTypes.ClientId
ClaimTypes.UserId
ClaimTypes.EmployeeId这些常量本质上只是字符串别名,没有额外行为。
ClaimValueTypes
ClaimValueTypes 也是字符串常量,只是用来描述 Claim.valueType。
常见值包括:
ClaimValueTypes.String
ClaimValueTypes.Boolean
ClaimValueTypes.Integer
ClaimValueTypes.Date
ClaimValueTypes.DateTime
ClaimValueTypes.Email它们来自几类命名空间:
- XML Schema
- SOAP
- XML Signature
- XQuery
- XACML
这个库本身不会根据 valueType 自动做类型转换。它只是把这个元数据保存下来。
几个实用结论
1. 这个库只负责身份数据模型
它不负责:
- 登录流程
- 声明校验
- 授权决策
2. isAuthenticated 只看 authenticationType
不是看有没有 claim,也不是看 claim 是否完整。
3. 类型和值匹配默认是精确匹配
ClaimsIdentity.findAll(type)、hasClaim(type, value) 都没有做大小写归一化。
4. isInRole 只检查 ClaimTypes.Role
如果你把角色放在 "roles" 或 "permissions",isInRole 不会自动识别。
5. principal.identity 受全局静态选择器影响
如果你调用了 ClaimsPrincipal.setPrimaryIdentitySelector(...),就相当于改了整个进程里主身份的选取规则。