You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为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. 登录流程

  1. 触发登录: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"}, // 传递租户标识
  ),
);
  1. 租户选择:如果APP未携带租户标识,会跳转到Identity Server的统一登录页面,用户输入租户信息后,系统跳转到对应AAD登录页面
  2. 授权回调:用户在AAD完成登录授权后,回调到APP的redirect_uri,flutter_appauth自动获取Identity Server颁发的id_token、access_token和refresh_token
  3. 安全存储:将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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.22 23:54:05