Azure AD多租户多数据库场景:如何按租户配置分布式令牌缓存
多租户场景下动态切换DistributedSqlServerCache的实现方案
针对你遇到的无法根据租户动态切换令牌缓存连接字符串的问题,核心解决思路是自定义一个IDistributedCache的实现,封装原生SqlServerCache,并在运行时根据当前请求的租户信息动态创建/复用对应租户的缓存实例。
步骤1:实现租户信息获取服务
首先需要一个服务从当前请求上下文提取租户ID,并获取对应租户的缓存数据库连接字符串:
public interface ITenantProvider { string GetCurrentTenantId(); string GetTenantConnectionString(); } public class HttpContextTenantProvider : ITenantProvider { private readonly IHttpContextAccessor _httpContextAccessor; private readonly IConfiguration _configuration; public HttpContextTenantProvider(IHttpContextAccessor httpContextAccessor, IConfiguration configuration) { _httpContextAccessor = httpContextAccessor; _configuration = configuration; } public string GetCurrentTenantId() { // 从Azure AD用户Claims中提取租户ID(对应claim类型为http://schemas.microsoft.com/identity/claims/tenantid) return _httpContextAccessor.HttpContext?.User.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value; } public string GetTenantConnectionString() { var tenantId = GetCurrentTenantId(); // 从配置中读取对应租户的缓存连接字符串(配置键格式示例:Cache_{TenantId}) return _configuration.GetConnectionString($"Cache_{tenantId}"); } }
步骤2:实现租户感知的分布式缓存
自定义IDistributedCache实现,内部根据租户信息复用SqlServerCache实例:
using Microsoft.Extensions.Caching.SqlServer; using Microsoft.Extensions.Options; using System.Collections.Concurrent; public class TenantDistributedCache : IDistributedCache { private readonly ITenantProvider _tenantProvider; private readonly IServiceProvider _serviceProvider; // 缓存已创建的租户缓存实例,避免重复初始化 private readonly ConcurrentDictionary<string, IDistributedCache> _tenantCachePool = new(); public TenantDistributedCache(ITenantProvider tenantProvider, IServiceProvider serviceProvider) { _tenantProvider = tenantProvider; _serviceProvider = serviceProvider; } private IDistributedCache GetCurrentTenantCache() { var tenantId = _tenantProvider.GetCurrentTenantId() ?? throw new InvalidOperationException("当前请求无法识别租户ID"); var connectionString = _tenantProvider.GetTenantConnectionString() ?? throw new InvalidOperationException($"未配置租户{tenantId}的缓存数据库连接字符串"); // 用租户ID+连接字符串哈希作为缓存键,确保连接字符串变更时重新创建实例 var cacheKey = $"{tenantId}_{connectionString.GetHashCode()}"; return _tenantCachePool.GetOrAdd(cacheKey, _ => { var cacheOptions = new SqlServerCacheOptions { ConnectionString = connectionString, TableName = "DistributedCache", SchemaName = "cache" }; // 用服务提供者初始化原生SqlServerCache实例 return ActivatorUtilities.CreateInstance<SqlServerCache>(_serviceProvider, Options.Create(cacheOptions)); }); } // 所有缓存操作委托给当前租户的缓存实例 public byte[] Get(string key) => GetCurrentTenantCache().Get(key); public Task<byte[]> GetAsync(string key, CancellationToken token = default) => GetCurrentTenantCache().GetAsync(key, token); public void Set(string key, byte[] value, DistributedCacheEntryOptions options) => GetCurrentTenantCache().Set(key, value, options); public Task SetAsync(string key, byte[] value, DistributedCacheEntryOptions options, CancellationToken token = default) => GetCurrentTenantCache().SetAsync(key, value, options, token); public void Refresh(string key) => GetCurrentTenantCache().Refresh(key); public Task RefreshAsync(string key, CancellationToken token = default) => GetCurrentTenantCache().RefreshAsync(key, token); public void Remove(string key) => GetCurrentTenantCache().Remove(key); public Task RemoveAsync(string key, CancellationToken token = default) => GetCurrentTenantCache().RemoveAsync(key, token); }
步骤3:注册服务到DI容器
在Program.cs(或Startup.cs)中替换默认的分布式缓存实现:
var builder = WebApplication.CreateBuilder(args); // 注册HttpContext访问器,用于获取当前请求上下文 builder.Services.AddHttpContextAccessor(); // 注册租户信息提供者 builder.Services.AddScoped<ITenantProvider, HttpContextTenantProvider>(); // 替换默认IDistributedCache为自定义的租户感知缓存 builder.Services.AddScoped<IDistributedCache, TenantDistributedCache>(); // 注意:无需再调用AddDistributedSqlServerCache,我们将动态创建实例
关键配置与注意事项
- 租户连接字符串配置:在
appsettings.json中按租户ID配置缓存数据库连接字符串:
"ConnectionStrings": { "Cache_tenant-guid-1": "Server=your-sql-server;Database=Tenant1_Cache;Trusted_Connection=True;Encrypt=True;", "Cache_tenant-guid-2": "Server=your-sql-server;Database=Tenant2_Cache;Trusted_Connection=True;Encrypt=True;" }
- 缓存表初始化:确保每个租户的缓存数据库都存在
cache.DistributedCache表,可以手动执行以下SQL脚本初始化:
CREATE SCHEMA cache; CREATE TABLE cache.DistributedCache ( Id NVARCHAR(449) NOT NULL PRIMARY KEY, Value VARBINARY(MAX) NOT NULL, ExpiresAtTime DATETIMEOFFSET NOT NULL, SlidingExpirationInSeconds BIGINT NULL, AbsoluteExpiration DATETIMEOFFSET NULL );
- Azure AD令牌缓存集成:Microsoft.Identity.Web的令牌缓存默认依赖
IDistributedCache,替换后会自动使用租户隔离的缓存,无需额外修改令牌缓存配置。
内容的提问来源于stack exchange,提问作者S. ten Brinke
相关产品推荐
相关产品推荐

