多外部客户场景下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
相关产品推荐
相关产品推荐

