Identity Server结合AAD授权问题:获Token后无法进入Angular项目
需要通过Identity Server与Azure Active Directory(AAD)完成授权,流程为在Identity Server页面输入账号密码,由AAD处理授权。目前已成功获取AAD返回的Token,但无法进入Angular项目。
无AAD介入的Identity Server授权可正常运行,添加AAD登录按钮的授权方式也可正常工作,但需求是通过Identity Server处理账号密码的方式完成登录。本人为授权技术新手,可能存在操作误区,恳请提供解决方案。
new Client { AlwaysSendClientClaims = true, AccessTokenType = AccessTokenType.Jwt, RefreshTokenExpiration = TokenExpiration.Absolute, IdentityTokenLifetime = 300, AuthorizationCodeLifetime = 300, AccessTokenLifetime = 3600, UpdateAccessTokenClaimsOnRefresh = true, AllowOfflineAccess = true, ClientId = MyClients.Portal, ClientName = "ortal client", ClientSecrets = { new Secret(MySecrets.Portal.Sha256()) }, AllowedGrantTypes = GrantTypes.HybridAndClientCredentials, AllowAccessTokensViaBrowser = true, AlwaysIncludeUserClaimsInIdToken = true, RequireConsent = false, RequirePkce = false, AbsoluteRefreshTokenLifetime = coreSettings.AbsoluteRefreshTokenLifetimeInSeconds, RedirectUris = { $"{coreSettings.Portal}/auth-callback", $"{coreSettings.Portal}/silent-refresh.html" }, PostLogoutRedirectUris = { coreSettings.IdentityServer }, AllowedCorsOrigins = { coreSettings.ApiService, coreSettings.Portal }, AllowedScopes = { StandardScopes.OpenId, StandardScopes.Profile, MyScopes.Api, } }
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model) { var client = new HttpClient(); var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest { Address = "https://login.microsoftonline.com/<tenet ID>/oauth2/v2.0/token", ClientId = "ClientId", ClientSecret = "ClientSecret", Scope = "openid profile User.Read", UserName = model.Email, Password = model.Password }); if (!tokenResponse.IsError) { var redirectUrl = Url.Action("ExternalLoginCallback", new { token = tokenResponse.AccessToken }); return Redirect(redirectUrl); } } [HttpGet("ExternalLoginCallback")] public async Task<IActionResult> ExternalLoginCallback(string token) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var userInfoResponse = await client.GetAsync("https://graph.microsoft.com/v1.0/me"); if (!userInfoResponse.IsSuccessStatusCode) { return BadRequest("Invalid token"); } var userInfo = await userInfoResponse.Content.ReadAsAsync<UserInfo>(); await HttpContext.SignInAsync(new IdentityServerUser(userInfo.Id.ToString()) { DisplayName = userInfo.DisplayName, AdditionalClaims = new List<Claim> { new(JwtClaimTypes.Email, userInfo.Mail ?? userInfo.UserPrincipalName) } }); return Redirect(_coreSettings.Portal); }
核心问题是跳过了Identity Server授权流程的关键步骤,直接重定向到Angular项目,导致Identity Server未向Angular返回所需的授权凭证,前端无法完成登录验证。以下是具体修正方案:
1. 保留授权流程的returnUrl参数
用户访问Angular时会被重定向到Identity Server登录页,URL中会携带returnUrl参数——这是Identity Server回调Angular的核心标识。需要在登录方法中接收并传递该参数:
[HttpPost] [ValidateAntiForgeryToken] public async Task<IActionResult> Login(LoginInputModel model, string returnUrl = null) { var client = new HttpClient(); var tokenResponse = await client.RequestPasswordTokenAsync(new PasswordTokenRequest { Address = "https://login.microsoftonline.com/<tenant ID>/oauth2/v2.0/token", ClientId = "ClientId", ClientSecret = "ClientSecret", Scope = "openid profile User.Read", UserName = model.Email, Password = model.Password }); if (!tokenResponse.IsError) { // 传递returnUrl到回调方法 var redirectUrl = Url.Action("ExternalLoginCallback", new { token = tokenResponse.AccessToken, returnUrl }); return Redirect(redirectUrl); } // 错误时返回登录页,携带错误信息和returnUrl ModelState.AddModelError(string.Empty, "用户名或密码错误"); return View(model); }
2. 完成Identity Server授权响应流程
在回调方法中,不能直接跳转到Angular,需要通过Identity Server的交互服务完成授权响应,生成并返回授权凭证给前端回调地址:
// 注入交互服务 private readonly IIdentityServerInteractionService _interaction; private readonly ICoreSettings _coreSettings; public YourLoginController(IIdentityServerInteractionService interaction, ICoreSettings coreSettings) { _interaction = interaction; _coreSettings = coreSettings; } [HttpGet("ExternalLoginCallback")] public async Task<IActionResult> ExternalLoginCallback(string token, string returnUrl = null) { var client = new HttpClient(); client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token); var userInfoResponse = await client.GetAsync("https://graph.microsoft.com/v1.0/me"); if (!userInfoResponse.IsSuccessStatusCode) { ModelState.AddModelError(string.Empty, "无效的AAD令牌"); return View("Login"); } var userInfo = await userInfoResponse.Content.ReadAsAsync<UserInfo>(); // 登录用户到Identity Server await HttpContext.SignInAsync(new IdentityServerUser(userInfo.Id.ToString()) { DisplayName = userInfo.DisplayName, AdditionalClaims = new List<Claim> { new(JwtClaimTypes.Email, userInfo.Mail ?? userInfo.UserPrincipalName), new(JwtClaimTypes.Name, userInfo.DisplayName) } }); // 处理授权请求,返回凭证给客户端 if (!string.IsNullOrEmpty(returnUrl) && _interaction.IsValidReturnUrl(returnUrl)) { var context = await _interaction.GetAuthorizationContextAsync(returnUrl); if (context != null) { return Redirect(returnUrl); } } // 无有效returnUrl时跳转Portal首页 return Redirect(_coreSettings.Portal); }
3. 检查Angular端OAuth配置
确保Angular的OAuth客户端配置(如使用angular-oauth2-oidc)正确指向Identity Server授权端点,且/auth-callback回调地址与Client配置中的RedirectUris完全一致。
4. 注意AAD密码授权的限制
AAD密码授权流存在诸多限制:
- 不支持开启MFA的用户
- 不兼容Azure AD B2C
- 微软官方不推荐生产环境使用,优先建议采用授权码流(即带AAD登录按钮的方式)
若必须使用密码流,需确保AAD应用已启用allowPublicClient或配置为机密客户端,且权限配置正确。
内容的提问来源于stack exchange,提问作者Viktor Bylbas

