如何将IdentityServer4身份映射至.NET MVC/.NET Core等Web应用
Great question! Let's walk through a clear, production-ready architecture for mapping your centralized AD/IdentityServer4 (IDSR4) identity across your web apps, plus refine your current setup to make identity reuse seamless.
分离式SSO架构示例
This setup keeps components decoupled while ensuring consistent identity across all apps:
- IdentityServer4 Server: Centralized identity provider that authenticates users against AD, issues tokens, and manages client/app permissions
- Web Apps (ASP.NET MVC/ABP): Relying parties that use OpenID Connect to redirect users to IDSR4 for login, then map the returned identity to their local session
- Protected APIs: Resource servers that validate JWT tokens from IDSR4 to authorize requests and access user identity claims
Core Flow
- User accesses a protected route in your web app → redirected to IDSR4 login page
- User enters AD credentials → IDSR4 validates against AD
- IDSR4 issues
id_token(for user identity) andaccess_token(for API access), redirects back to the web app - Web app validates tokens, creates a local cookie session, and syncs AD identity to its local user store (if needed)
- Web app uses the
access_tokento call protected APIs, which validate the token and retrieve user claims directly
Identity Mapping Implementation
1. Web App (ASP.NET MVC/ABP) Setup
Your current OpenID Connect configuration is on the right track—here's how to refine it for reliable identity mapping:
Refined OpenID Connect Configuration
public void Configuration(IAppBuilder app) { app.UseAbp(); // Clear default claim filters to preserve all AD claims from IDSR4 JwtSecurityTokenHandler.DefaultInboundClaimFilter.Clear(); AntiForgeryConfig.UniqueClaimTypeIdentifier = JwtClaimTypes.Subject; app.UseCookieAuthentication(new CookieAuthenticationOptions { AuthenticationType = "Cookies" }); var openIdConfig = new OpenIdConnectAuthenticationOptions { Authority = "http://localhost:5443", ClientId = "demo", ClientSecret = "password", ResponseType = "id_token token", // Request both identity and access tokens SignInAsAuthenticationType = "Cookies", RedirectUri = "http://localhost:6234/", PostLogoutRedirectUri = "http://localhost:6234", Scope = "openid profile api1", // Request profile claims + API access RequireHttpsMetadata = false, Notifications = new OpenIdConnectAuthenticationNotifications { SecurityTokenValidated = async n => { // Fetch full user claims from IDSR4's userinfo endpoint var userInfoClient = new UserInfoClient( new Uri(n.Options.Authority + "/connect/userinfo"), n.ProtocolMessage.AccessToken); var userInfo = await userInfoClient.GetAsync(); // Create a new identity with AD-mapped claim types var nid = new ClaimsIdentity( n.AuthenticationTicket.Identity.AuthenticationType, ClaimTypes.Name, // Map AD display name to standard Name claim ClaimTypes.Role); // Add all AD claims from userinfo nid.AddClaims(userInfo.Claims.Select(c => new Claim(c.Type, c.Value))); // Add critical token metadata for future use nid.AddClaim(new Claim("id_token", n.ProtocolMessage.IdToken)); nid.AddClaim(new Claim("access_token", n.ProtocolMessage.AccessToken)); nid.AddClaim(new Claim("expires_at", DateTimeOffset.Now.AddSeconds(int.Parse(n.ProtocolMessage.ExpiresIn)).ToString())); // Update the authentication ticket with the new identity n.AuthenticationTicket = new AuthenticationTicket(nid, n.AuthenticationTicket.Properties); // Sync AD identity to your ABP local user store await SyncAbpUserAsync(nid); } } }; app.UseOpenIdConnectAuthentication(openIdConfig); app.UseExternalSignInCookie("Cookies"); app.MapSignalR(); } // Helper to sync AD user to ABP's local user table (no local password management) private async Task SyncAbpUserAsync(ClaimsIdentity identity) { var samAccountName = identity.FindFirst("sam_account_name")?.Value; var displayName = identity.FindFirst(ClaimTypes.Name)?.Value; var email = identity.FindFirst(ClaimTypes.Email)?.Value; if (string.IsNullOrEmpty(samAccountName)) return; // Check if user already exists in ABP's store var existingUser = await UserManager.FindByNameAsync(samAccountName); if (existingUser == null) { // Create new user with random password (users never log in locally) var newUser = new AbpUser { UserName = samAccountName, Name = displayName, EmailAddress = email, IsActive = true }; await UserManager.CreateAsync(newUser, Guid.NewGuid().ToString()); } else { // Update existing user with latest AD info existingUser.Name = displayName; existingUser.EmailAddress = email; await UserManager.UpdateAsync(existingUser); } }
2. Protected API Setup
For APIs, skip cookie sessions and validate JWT tokens directly from IDSR4:
// ASP.NET Core API ConfigureServices public void ConfigureServices(IServiceCollection services) { services.AddControllers(); // Configure JWT authentication against IDSR4 services.AddAuthentication("Bearer") .AddJwtBearer("Bearer", options => { options.Authority = "http://localhost:5443"; options.RequireHttpsMetadata = false; options.Audience = "api1"; // Match IDSR4's ApiResource name }); // Add authorization policy for API access services.AddAuthorization(options => { options.AddPolicy("ApiAccess", policy => { policy.RequireAuthenticatedUser(); policy.RequireClaim("scope", "api1"); }); }); } // Configure middleware public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { // ... other middleware (exception handling, static files) app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllers(); }); } // Example API controller to access user identity [ApiController] [Route("api/[controller]")] [Authorize("ApiAccess")] public class UserController : ControllerBase { [HttpGet("me")] public IActionResult GetCurrentUser() { // Retrieve AD claims directly from the validated token var userId = User.FindFirst("sam_account_name")?.Value; var userName = User.FindFirst(ClaimTypes.Name)?.Value; var email = User.FindFirst(ClaimTypes.Email)?.Value; return Ok(new { UserId = userId, UserName = userName, Email = email }); } }
IDSR4 Configuration Refinements
Your current IDSR4 setup works for testing—here's how to make it production-ready and improve claim management:
public void ConfigureServices(IServiceCollection services) { services.AddMvc(); services.AddIdentityServer() // Replace dev signing credential with a real certificate for production .AddSigningCredential(new X509Certificate2("path/to/your/cert.pfx", "cert-password")) .AddInMemoryIdentityResources(InMemoryInitConfig.GetIdentityResources()) .AddInMemoryApiResources(InMemoryInitConfig.GetApiResources()) .AddInMemoryClients(InMemoryInitConfig.GetClients()) .AddLdapUsers<OpenLdapAppUser>(Configuration.GetSection("IdentityServerLdap"), UserStore.InMemory) // Add custom profile service to include AD claims in tokens/userinfo .AddProfileService<LdapProfileService>(); } // Custom profile service to expose AD attributes as claims public class LdapProfileService : IProfileService { private readonly ILdapUserStore<OpenLdapAppUser> _userStore; public LdapProfileService(ILdapUserStore<OpenLdapAppUser> userStore) { _userStore = userStore; } public async Task GetProfileDataAsync(ProfileDataRequestContext context) { var subjectId = context.Subject.GetSubjectId(); var user = await _userStore.FindByIdAsync(subjectId); if (user == null) throw new ArgumentException("User not found in AD"); // Map AD attributes to standard and custom claims var claims = new List<Claim> { new Claim(ClaimTypes.Name, user.DisplayName), new Claim(ClaimTypes.Email, user.Email), new Claim("sam_account_name", user.SamAccountName), // Add AD group roles as role claims (adjust based on your AD structure) new Claim(ClaimTypes.Role, "AppUser") }; context.IssuedClaims.AddRange(claims); } public async Task IsActiveAsync(IsActiveContext context) { var subjectId = context.Subject.GetSubjectId(); var user = await _userStore.FindByIdAsync(subjectId); context.IsActive = user != null && user.IsActive; } }
内容的提问来源于stack exchange,提问作者Mirusky
相关产品推荐
相关产品推荐

