如何在EF Core中为SQL行级安全(RLS)设置会话上下文 多租户Azure场景
最高效方案实现指引
首选方案:加密租户标识内嵌租户ID(性能最优)
直接在生成加密租户标识时,将整数类型的租户ID作为明文的一部分加密到字符串中。服务端拿到加密串后,使用对称加密算法(如AES-256)直接解密即可拿到租户ID,无需任何额外的数据库查询或映射查找,性能损耗几乎可以忽略。
注意:加密密钥建议存储在Azure Key Vault中,不要硬编码到应用代码内,避免密钥泄露风险。
次选方案:二级缓存存储租户映射(适配加密串无法修改的场景)
如果现有加密租户标识的明文内容无法调整,只能存放租户业务标识(如租户编码),则采用二级缓存方案避免每次请求查询数据库:
- 客户端将加密租户标识放在自定义请求头(如
X-Encrypted-Tenant)中传递,避免放在URL产生泄露风险 - 服务端解密得到原始租户业务标识后,先查询当前请求级存储
HttpContext.Items,如果本次请求已经解析过租户ID直接复用 - 再查询内存缓存(单实例部署)或Azure Redis Cache(多实例部署),如果命中缓存直接返回对应租户ID
- 缓存未命中时才查询租户表获取对应主键ID,写入缓存后返回,缓存过期时间可设置为12~24小时,租户信息变更时主动失效对应缓存即可
全局设置会话上下文实现(EF Core拦截器)
无需在业务代码中重复编写逻辑,通过拦截器全局自动注入会话上下文:
- 注册
IHttpContextAccessor用于获取当前请求的租户信息
services.AddHttpContextAccessor();
- 实现自定义EF Core命令拦截器
public class TenantSessionContextInterceptor : IDbCommandInterceptor { private readonly IHttpContextAccessor _httpContextAccessor; private const string TenantSessionKey = "TenantId"; public TenantSessionContextInterceptor(IHttpContextAccessor httpContextAccessor) { _httpContextAccessor = httpContextAccessor; } public override InterceptionResult<DbDataReader> ReaderExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<DbDataReader> result) { AppendSessionContext(command); return base.ReaderExecuting(command, eventData, result); } public override InterceptionResult<int> NonQueryExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<int> result) { AppendSessionContext(command); return base.NonQueryExecuting(command, eventData, result); } public override InterceptionResult<object> ScalarExecuting(DbCommand command, CommandEventData eventData, InterceptionResult<object> result) { AppendSessionContext(command); return base.ScalarExecuting(command, eventData, result); } private void AppendSessionContext(DbCommand command) { var context = _httpContextAccessor.HttpContext; if (context == null || !context.Items.TryGetValue("CurrentTenantId", out var tenantIdObj) || tenantIdObj is not int tenantId) { throw new UnauthorizedAccessException("缺少有效租户信息"); } var setCmd = command.CreateCommand(); setCmd.CommandText = $"EXEC sp_set_session_context @key=N'{TenantSessionKey}', @value=@TenantId"; setCmd.Parameters.Add(new SqlParameter("@TenantId", SqlDbType.Int) { Value = tenantId }); setCmd.ExecuteNonQuery(); } }
- 注册拦截器到DbContext
services.AddDbContext<YourDbContext>((sp, options) => { options.UseSqlServer("Azure SQL连接字符串") .AddInterceptors(sp.GetRequiredService<TenantSessionContextInterceptor>()); });
内容的提问来源于stack exchange,提问作者Divyang Vyas
相关产品推荐
相关产品推荐

