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

Identity Server结合AAD授权问题:获Token后无法进入Angular项目

问题描述

需要通过Identity Server与Azure Active Directory(AAD)完成授权,流程为在Identity Server页面输入账号密码,由AAD处理授权。目前已成功获取AAD返回的Token,但无法进入Angular项目。

无AAD介入的Identity Server授权可正常运行,添加AAD登录按钮的授权方式也可正常工作,但需求是通过Identity Server处理账号密码的方式完成登录。本人为授权技术新手,可能存在操作误区,恳请提供解决方案。

Client配置代码
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,
    }
}
Identity Server登录方法代码
[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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 21:14:58