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

多外部客户场景下Asp.net Core应用的SSO实现方案咨询

低成本多客户AD SSO + .NET Identity 整合方案

核心思路

基于ASP.NET Core自带的OpenID Connect(OIDC)中间件实现,无需付费商业组件,同时保留原.NET Identity的本地登录、2FA功能,通过外部认证提供者映射本地用户的方式统一权限体系。

具体实现步骤

1. 扩展.NET Identity用户模型

在现有ApplicationUser类中添加用于关联外部SSO身份的字段,实现本地用户与外部AD身份的绑定:

public class ApplicationUser : IdentityUser
{
    // 存储外部认证提供者的唯一标识(比如AD的ObjectId)
    public string? ExternalProviderUserId { get; set; }
    // 标记用户所属的客户租户(区分不同AD客户)
    public string? TenantId { get; set; }
    // 记录外部认证提供者名称(用于区分不同客户的AD服务)
    public string? ExternalProviderName { get; set; }
}

通过EF Core迁移更新数据库,确保这些字段被持久化。

2. 配置多租户OIDC认证中间件

在Program.cs中,针对每个需要SSO的客户AD单独配置OIDC认证方案,读取配置文件中的多客户AD信息批量注册:

var builder = WebApplication.CreateBuilder(args);

// 保留原有的.NET Identity服务配置
builder.Services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
    .AddEntityFrameworkStores<ApplicationDbContext>()
    .AddDefaultTokenProviders();

// 读取appsettings中的多客户AD配置
var adClients = builder.Configuration.GetSection("AdSsoClients").Get<List<AdSsoClientConfig>>();

foreach (var client in adClients)
{
    builder.Services.AddAuthentication()
        .AddOpenIdConnect(client.ProviderName, client.DisplayName, options =>
        {
            options.Authority = client.Authority; // 客户AD的OIDC权威地址,如Azure AD的https://login.microsoftonline.com/{tenantId}/v2.0
            options.ClientId = client.ClientId; // 客户AD中注册的应用ClientId
            options.ClientSecret = client.ClientSecret; // 应用ClientSecret
            options.ResponseType = "code";
            options.Scope.Add("openid");
            options.Scope.Add("profile");
            options.Scope.Add("email");
            
            // 每个客户使用独立的回调路径,避免冲突
            options.CallbackPath = $"/signin-oidc-{client.ProviderName}";
            
            // 登录成功后映射到本地用户
            options.Events = new OpenIdConnectEvents
            {
                OnTokenValidated = async context =>
                {
                    var externalUserId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier);
                    var email = context.Principal.FindFirstValue(ClaimTypes.Email);
                    var tenantId = client.TenantId;
                    var providerName = client.ProviderName;

                    var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<ApplicationUser>>();
                    var user = await userManager.FindByEmailAsync(email);

                    if (user == null)
                    {
                        // 本地无用户时自动创建(可根据业务需求改为人工审核)
                        user = new ApplicationUser
                        {
                            UserName = email,
                            Email = email,
                            ExternalProviderUserId = externalUserId,
                            TenantId = tenantId,
                            ExternalProviderName = providerName
                        };
                        await userManager.CreateAsync(user);
                    }
                    else
                    {
                        // 同步外部身份信息到本地用户
                        user.ExternalProviderUserId = externalUserId;
                        user.TenantId = tenantId;
                        user.ExternalProviderName = providerName;
                        await userManager.UpdateAsync(user);
                    }

                    // 将本地用户ID和租户ID加入Claims,用于后续权限验证
                    var claimsIdentity = (ClaimsIdentity)context.Principal.Identity;
                    claimsIdentity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.Id));
                    claimsIdentity.AddClaim(new Claim("TenantId", tenantId));
                }
            };
        });
}

var app = builder.Build();
// 启用认证授权中间件
app.UseAuthentication();
app.UseAuthorization();
// 其他中间件配置...

自定义配置类AdSsoClientConfig:

public class AdSsoClientConfig
{
    public string ProviderName { get; set; } = string.Empty; // 唯一标识该AD提供者,如"CustomerA_AD"
    public string DisplayName { get; set; } = string.Empty; // 登录页面显示名称,如"客户A企业登录"
    public string Authority { get; set; } = string.Empty;
    public string ClientId { get; set; } = string.Empty;
    public string ClientSecret { get; set; } = string.Empty;
    public string TenantId { get; set; } = string.Empty;
}

appsettings.json中的配置示例:

"AdSsoClients": [
  {
    "ProviderName": "CustomerA_AD",
    "DisplayName": "客户A企业登录",
    "Authority": "https://login.microsoftonline.com/xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx/v2.0",
    "ClientId": "yyyyyyy-yyyy-yyyy-yyyy-yyyyyyyyyyyy",
    "ClientSecret": "zzzzzzzzzzzzzzzzzzzzzzzzz",
    "TenantId": "CustomerA"
  },
  {
    "ProviderName": "CustomerB_AD",
    "DisplayName": "客户B企业登录",
    "Authority": "https://login.microsoftonline.com/aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa/v2.0",
    "ClientId": "bbbbbbbb-bbbb-bbbb-bbbb-bbbbbbbbbbbb",
    "ClientSecret": "ccccccccccccccccccccccccccc",
    "TenantId": "CustomerB"
  }
]

3. 修改登录页面,添加多SSO选项

在原有本地登录表单下方,添加各个客户的SSO登录按钮,引导用户选择对应的企业登录入口:

<!-- 本地登录表单 -->
<form asp-action="Login" method="post">
    <!-- 邮箱、密码输入框及登录按钮 -->
</form>

<!-- SSO登录选项 -->
<div class="sso-options">
    <h4>企业账户登录</h4>
    @foreach (var client in Configuration.GetSection("AdSsoClients").Get<List<AdSsoClientConfig>>())
    {
        <a href="/Identity/Account/ExternalLogin?provider=@client.ProviderName&returnUrl=@Url.Content("~/")" 
           class="btn btn-primary mt-2">@client.DisplayName</a>
    }
</div>

4. 权限体系整合

结合TenantId和.NET Identity的角色/声明系统,实现租户隔离和权限控制:

  • 添加租户专属授权策略:
public void ConfigureServices(IServiceCollection services)
{
    services.AddAuthorization(options =>
    {
        options.AddPolicy("TenantAccess", policy =>
            policy.RequireClaim("TenantId"));
    });
}
  • 在控制器或Action上应用策略:
[Authorize(Policy = "TenantAccess")]
public IActionResult TenantSpecificData()
{
    var tenantId = User.FindFirstValue("TenantId");
    // 根据租户ID过滤业务数据
    return View();
}

关键注意事项

  • 用户创建策略:自动创建本地用户适合开放场景,若需人工审核,可将外部登录信息存入待审核表,审核通过后再创建本地用户。
  • 2FA兼容:SSO登录后,若本地用户启用了2FA,需在OnTokenValidated事件后判断用户TwoFactorEnabled状态,跳转至.NET Identity的2FA验证页面。
  • 安全配置:OIDC的ClientSecret等敏感信息需通过环境变量或密钥管理服务存储,避免硬编码。
  • 客户AD配置指导:需告知客户在其Azure AD或AD FS中注册应用,配置正确的回调地址、权限范围(openid、profile、email)。

内容的提问来源于stack exchange,提问作者chilluk

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.12 08:13:09