如何为Identity Server 4配置多租户Azure AD适配多客户独立AAD场景
多租户Azure AD与Identity Server 4集成方案及Flutter适配流程
一、Identity Server 4多租户Azure AD配置
1. 租户配置存储
先把每个客户的Azure AD配置持久化到数据库(比如SQL Server、PostgreSQL),建议表结构包含以下字段:
- TenantId:客户AAD的租户ID(如
xxx.onmicrosoft.com或GUID) - TenantCode:自定义租户标识(用于用户输入或APP传递)
- DisplayName:租户名称(显示在登录页面)
- ClientId:AAD中注册的应用ID
- ClientSecret:AAD应用的客户端密钥
- Authority:AAD授权端点前缀(
https://login.microsoftonline.com/{TenantId})
2. 动态注册OpenID Connect身份提供者
Identity Server默认OIDC中间件是静态配置的,无法直接支持多租户,需要动态加载每个租户的AAD配置:
- 在用户选择租户后,通过
IIdentityServerBuilder动态添加对应OIDC认证方案 - 示例代码:
var tenant = await _tenantRepo.GetByTenantCode(tenantCode); if (tenant == null) throw new ArgumentException("无效租户"); services.AddAuthentication() .AddOpenIdConnect($"aad-{tenant.TenantId}", tenant.DisplayName, options => { options.Authority = tenant.Authority; options.ClientId = tenant.ClientId; options.ClientSecret = tenant.ClientSecret; options.ResponseType = "code"; options.Scope.AddRange(new[] { "openid", "profile", "email", "offline_access" }); options.CallbackPath = $"/signin-oidc-{tenant.TenantId}"; options.SignedOutCallbackPath = $"/signout-callback-oidc-{tenant.TenantId}"; options.MapInboundClaims = false; // 将AAD的oid映射为Identity Server的sub声明 options.ClaimActions.MapJsonKey("sub", "oid"); // 添加租户ID到用户声明,用于后续区分租户 options.Events = new OpenIdConnectEvents { OnTokenValidated = context => { context.Principal!.Identities.First().AddClaim(new Claim("tenant_id", tenant.TenantId)); return Task.CompletedTask; } }; });
3. 统一登录页面改造
登录页面要支持用户选择/输入租户:
- 提供输入框让用户输入
TenantCode或公司邮箱(可通过微软租户发现API自动识别TenantId) - 提交后后端验证租户有效性,触发对应AAD方案的认证挑战:
[HttpPost] public IActionResult SelectTenant(string tenantCode) { var tenant = _tenantRepo.GetByTenantCode(tenantCode); if (tenant == null) { ModelState.AddModelError("", "租户不存在,请检查输入"); return View(); } // 跳转到对应AAD登录页面 return Challenge(new AuthenticationProperties { RedirectUri = "/connect/authorize/callback" }, $"aad-{tenant.TenantId}"); }
4. 回调与身份断言处理
- 确保每个租户的AAD应用已配置回调URL为
https://your-identity-server/signin-oidc-{TenantId} - 在OIDC事件中添加租户ID声明,确保系统能区分不同租户的用户
- 自定义
IClaimsService,将AAD返回的声明映射到系统所需的用户信息(如姓名、邮箱)
二、Flutter应用适配工作流程
1. 依赖选择
使用flutter_appauth处理OAuth2授权流程,flutter_secure_storage存储敏感token:
dependencies: flutter_appauth: ^6.0.0 flutter_secure_storage: ^9.0.0
2. 登录流程
- 触发登录:APP启动后,用户点击登录按钮,调用
flutter_appauth发起授权请求,可提前传递租户标识(如果用户已保存):
final AuthorizationTokenResponse? authResult = await appAuth.authorizeAndExchangeCode( AuthorizationTokenRequest( "your-identity-server-client-id", "com.your.app://callback", // 需在Identity Server和AAD中配置 issuer: "https://your-identity-server-url", scopes: ["openid", "profile", "email", "api", "offline_access"], additionalParameters: {"tenant": "customer1"}, // 传递租户标识 ), );
- 租户选择:如果APP未携带租户标识,会跳转到Identity Server的统一登录页面,用户输入租户信息后,系统跳转到对应AAD登录页面
- 授权回调:用户在AAD完成登录授权后,回调到APP的
redirect_uri,flutter_appauth自动获取Identity Server颁发的id_token、access_token和refresh_token - 安全存储:将token存入
flutter_secure_storage,避免明文存储:
final storage = FlutterSecureStorage(); await storage.write(key: "access_token", value: authResult?.accessToken); await storage.write(key: "refresh_token", value: authResult?.refreshToken); await storage.write(key: "id_token", value: authResult?.idToken);
3. API调用流程
- 每次调用后端API时,从安全存储中读取
access_token,放入请求头的Authorization: Bearer {token}字段 - 后端API通过Identity Server的JWKS端点验证token有效性,并提取
tenant_id声明判断用户所属租户
4. Token刷新流程
当access_token过期时,使用refresh_token向Identity Server请求新token:
final TokenResponse? refreshResult = await appAuth.refreshToken( RefreshTokenRequest( "your-identity-server-client-id", refreshToken!, issuer: "https://your-identity-server-url", ), ); // 更新存储中的token await storage.write(key: "access_token", value: refreshResult?.accessToken); await storage.write(key: "refresh_token", value: refreshResult?.refreshToken);
5. 注销流程
- 调用
flutter_appauth的注销方法,同时触发Identity Server和AAD的单点注销:
await appAuth.endSession(EndSessionRequest( idTokenHint: idToken, issuer: "https://your-identity-server-url", postLogoutRedirectUri: "com.your.app://logout-callback", )); // 清除本地存储的token await storage.deleteAll();
关键注意事项
- 每个客户的AAD应用必须配置正确的回调URL(指向Identity Server的OIDC回调端点)
- Identity Server需要启用CORS,允许Flutter APP的域名/包名访问
- 租户标识的传递要确保安全,避免被篡改(可在后端验证租户有效性)
- 生产环境要启用HTTPS,避免token泄露
内容的提问来源于stack exchange,提问作者Ahmed Anwar
相关产品推荐
相关产品推荐

