基于IdentityServer4的多租户SSO问题:无法获取acr_value传递的租户ID
解决IdentityServer4多租户场景下AccountController无法获取acr_value的问题
我之前在做多租户SSO的时候也碰到过一模一样的问题,咱们一步步拆解解决:
第一步:在AccountController中正确获取acr_value
acr_value是随认证请求传递到IdentityServer的,你不能直接在UserStore的构造函数里拿它(因为UserStore是作用域/单例服务,构造时请求上下文还没绑定),得先在AccountController的登录逻辑里把它提取出来。
推荐用IdentityServer官方提供的IIdentityServerInteractionService来解析请求上下文,比直接读Request.Query更规范:
private readonly IIdentityServerInteractionService _interaction; private readonly UserManager<ApplicationUser> _userManager; public AccountController(IIdentityServerInteractionService interaction, UserManager<ApplicationUser> userManager) { _interaction = interaction; _userManager = userManager; } public async Task<IActionResult> Login(string returnUrl = null) { // 获取授权上下文,里面包含所有认证请求参数 var authContext = await _interaction.GetAuthorizationContextAsync(returnUrl); // 解析acr_values中的租户ID(注意处理多值情况,比如acr_values可能是"tenant:clientId1 other:xxx") var tenantId = string.Empty; if (!string.IsNullOrEmpty(authContext?.AcrValues)) { var tenantAcrItem = authContext.AcrValues.Split(' ') .FirstOrDefault(item => item.StartsWith("tenant:")); if (!string.IsNullOrEmpty(tenantAcrItem)) { tenantId = tenantAcrItem.Split(':')[1]; } } // 把租户ID存起来,后续传给UserStore使用 ViewData["TenantId"] = tenantId; // ... 其他登录页面逻辑 }
如果不想用IIdentityServerInteractionService,也可以直接从请求参数里读:
var acrValues = HttpContext.Request.Query["acr_values"].FirstOrDefault(); // 解析逻辑同上
第二步:让UserStore拿到租户ID
因为UserStore的方法是在登录验证时调用的,你有两种方式传递租户ID:
方式1:修改UserStore方法,显式传递租户ID
修改你的自定义UserStore,给需要多租户过滤的方法加tenantId参数:
public async Task<ApplicationUser> FindByNameAsync(string normalizedUserName, string tenantId, CancellationToken cancellationToken = default) { return await _context.Users .FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.TenantId == tenantId, cancellationToken); }
然后在AccountController的登录验证逻辑里调用:
var userName = model.UserName; var normalizedUserName = _userManager.NormalizeName(userName); var tenantId = ViewData["TenantId"] as string; var user = await _userStore.FindByNameAsync(normalizedUserName, tenantId);
方式2:通过IHttpContextAccessor让UserStore自动获取
如果不想修改所有UserStore方法的签名,可以注册IHttpContextAccessor,让UserStore从当前请求上下文里拿租户ID:
首先在Startup.cs里注册服务:
services.AddHttpContextAccessor();
然后在自定义UserStore里注入并使用:
private readonly IHttpContextAccessor _httpContextAccessor; public CustomUserStore(ApplicationDbContext context, IHttpContextAccessor httpContextAccessor) { _context = context; _httpContextAccessor = httpContextAccessor; } public async Task<ApplicationUser> FindByNameAsync(string normalizedUserName, CancellationToken cancellationToken = default) { // 从当前请求中解析租户ID var acrValues = _httpContextAccessor.HttpContext.Request.Query["acr_values"].FirstOrDefault(); var tenantId = string.Empty; if (!string.IsNullOrEmpty(acrValues)) { var tenantAcrItem = acrValues.Split(' ') .FirstOrDefault(item => item.StartsWith("tenant:")); if (!string.IsNullOrEmpty(tenantAcrItem)) { tenantId = tenantAcrItem.Split(':')[1]; } } return await _context.Users .FirstOrDefaultAsync(u => u.NormalizedUserName == normalizedUserName && u.TenantId == tenantId, cancellationToken); }
注意事项
- 确保你的UserStore是作用域生命周期(ASP.NET Core Identity默认的UserStore就是作用域的),否则
IHttpContextAccessor无法正确获取当前请求上下文。 - 解析acr_values时要考虑多值情况(多个acr参数用空格分隔),避免只取第一个值导致租户ID解析错误。
内容的提问来源于stack exchange,提问作者Francisco Vera Voronisky
相关产品推荐
相关产品推荐

