不能靠“enumerable校验”防止篡改会员等级,因其仅为遍历协议,无安全边界;等级须由可信Grain统一管控、持久化并审计,禁止暴露可变引用。

不能靠“enumerable 校验”防止用户篡改内存中的会员等级。
原因很直接:IEnumerable<T> 或 IAsyncEnumerable<T> 是数据遍历协议,不是安全边界。它们不提供完整性保护、访问控制或防篡改能力。把会员等级存在内存里,再用 yield return 或 await foreach 暴露出去,本质上只是把当前内存值“读出来”,而读取过程本身无法阻止上游(比如恶意 Grain 调用、被劫持的客户端请求、或带漏洞的业务逻辑)已把等级改错。
真正起作用的是信任边界设计和状态管控机制,不是枚举方式。
会员等级必须由可信服务统一管理
- 等级不是客户端传入的字段,也不是前端渲染时临时计算的值
- 所有等级判定逻辑应封装在受控 Grain 中(如
MembershipGrain),且只响应经过认证/鉴权的内部调用 - 外部请求(如 HTTP API)只能触发“行为”(如
UpgradeRequest),不能直接SetLevel(5)
示例正确做法:
public class MembershipGrain : Grain, IMembershipGrain
{
private readonly IReminderService _reminders;
private MembershipLevel _level;
public async Task UpgradeToPremium()
{
// 内部校验:是否满足付费、时效、风控等条件
if (await CanUpgrade())
{
_level = MembershipLevel.Premium;
await _reminders.RegisterOrUpdateReminder("renew-check", ...);
}
}
public Task<MembershipLevel> GetCurrentLevel() => Task.FromResult(_level);
}避免在内存中暴露可变等级引用
- 不要把
MembershipLevel存在共享集合(如ConcurrentDictionary<Guid, MembershipLevel>)并让多个组件直接读写 - 更不要用
IEnumerable<MembershipLevel>封装一堆可变对象然后到处传递——这等于把锁钥交出去
常见错误:
// ❌ 危险:外部可随时修改
public static IEnumerable<MembershipLevel> AllLevels => _levels.Values; // 返回可变引用
// ✅ 安全:只返回不可变快照
public IReadOnlyList<MembershipLevel> GetActiveLevels()
=> _levels.Values.Select(x => x.CloneAsReadOnly()).ToList();敏感状态需配合持久化与审计
- 等级变更必须落库(如
AdoNetReminderTable或AzureBasedReminderTable),并记录操作人、时间、来源 IP、签名上下文 - 启用 Orleans 的
LifecycleObserver或自定义ILifecycleParticipant,在 Grain 激活/失活时做一致性检查 - 对高权限等级(如 VIP、Admin),增加运行时断言:
if (_level >= MembershipLevel.VIP && !await IsTrustedSource()) throw new SecurityException("VIP level requires elevated trust context");
不复杂但容易忽略:校验不在循环里,而在入口、存储、传播路径的每一道闸门。

















