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

迁移ADAL到MSAL遇Token无Scope问题及代码转换需求

问题解决与代码迁移方案

一、AcquireTokenForClient生成Token无Scope的问题说明与解决

1. 现象本质

AcquireTokenForClient属于客户端凭据流,这类流用于获取应用自身的权限(而非用户委派权限)。该模式下生成的Token不会通过scope声明体现权限,而是通过roles字段返回已授予的应用权限。你解析JWT时看不到scope是正常行为,只要roles包含所需权限,就能正常调用Graph API。

2. 验证与确认步骤

  • 检查Azure AD应用注册的应用权限:确保已添加目标Graph API权限(如User.Read.All),且已完成管理员同意(需确认权限配置界面中“授予管理员同意”按钮已触发)。
  • 解析Token确认权限:使用JWT解析工具查看Token内容,确认roles字段包含你需要的权限条目。

3. 代码优化建议

避免重复创建IConfidentialClientApplication实例,建议将其注册为单例服务注入:

// 在Startup.cs/Program.cs中注册单例
services.AddSingleton<IConfidentialClientApplication>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    return ConfidentialClientApplicationBuilder
        .Create(config["Authentication:AzureAd:ClientId"])
        .WithClientSecret(config["Authentication:AzureAd:ClientSecret"])
        .WithAuthority($"{config["Authentication:AzureAd:AADInstance"]}{config["Authentication:AzureAd:TenantId"]}")
        .Build();
});

在UserInfo.cs中注入使用:

private readonly IConfidentialClientApplication _app;

public UserInfo(IConfidentialClientApplication app)
{
    _app = app;
}

// 获取Token逻辑
var scopes = new string[] { "https://graph.microsoft.com/.default" };
var authenticationResult = await _app.AcquireTokenForClient(scopes).ExecuteAsync();

二、ADAL GetAuthorizationHeaderCore方法的MSAL迁移实现

1. 依赖准备

安装必要的MSAL包:

Install-Package Microsoft.Identity.Client
Install-Package Microsoft.Identity.Web.TokenCache

注册分布式Token缓存(按需选择缓存实现,示例用内存缓存):

// 在Startup.cs/Program.cs中配置
services.AddDistributedMemoryCache();
services.AddMsalDistributedTokenCache();

2. 迁移后的代码实现

private readonly IConfidentialClientApplication _app;
private readonly IHttpContextAccessor _httpContextAccessor;
private readonly IOptions<AzureADOptions> _authOptions;
private readonly IConfidentialClientApplication _backgroundServiceApp;

// 通过构造函数注入依赖
public YourService(IConfidentialClientApplication app, 
                   IHttpContextAccessor httpContextAccessor,
                   IOptions<AzureADOptions> authOptions,
                   IConfidentialClientApplication backgroundServiceApp)
{
    _app = app;
    _httpContextAccessor = httpContextAccessor;
    _authOptions = authOptions;
    _backgroundServiceApp = backgroundServiceApp;
}

private async Task<string> GetAuthorizationHeaderCore(string resource)
{
    var httpContext = _httpContextAccessor.HttpContext;
    var objectId = httpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/objectidentifier")?.Value;
    var userTenantId = httpContext.User.FindFirst("http://schemas.microsoft.com/identity/claims/tenantid")?.Value;

    AuthenticationResult authenticationResult;
    var accessToken = await GetTokenWithFallBackAsync(httpContext, "access_token");

    if (accessToken != null)
    {
        if (httpContext.Items["isAuthenticatedService"] is bool isService && isService)
        {
            // 客户端凭据流:对应ADAL的AcquireTokenAsync(resource, backgroundServiceClientCredential)
            var scopes = new[] { $"{resource}/.default" };
            authenticationResult = await _backgroundServiceApp.AcquireTokenForClient(scopes)
                .ExecuteAsync();
        }
        else
        {
            // 代表用户流:对应ADAL的AcquireTokenAsync(resource, clientCredential, userAssertion)
            var userPrincipalName = httpContext.User.Identity.Name;
            var userAssertion = new UserAssertion(accessToken, "urn:ietf:params:oauth:grant-type:jwt-bearer", userPrincipalName);
            var scopes = new[] { $"{resource}/.default" };
            authenticationResult = await _app.AcquireTokenOnBehalfOf(scopes, userAssertion)
                .ExecuteAsync();
        }
    }
    else
    {
        // 静默获取Token:对应ADAL的AcquireTokenSilentAsync
        var scopes = new[] { $"{resource}/.default" };
        var account = await _app.GetAccountByIdAsync($"{userTenantId}.{objectId}");
        if (account == null)
        {
            throw new InvalidOperationException("Token缓存中未找到用户账户");
        }
        authenticationResult = await _app.AcquireTokenSilent(scopes, account)
            .ExecuteAsync();
    }

    return $"Bearer {authenticationResult.AccessToken}";
}

3. 关键差异说明

  • Token缓存:MSAL自动处理Token缓存,无需手动实现DistributedTokenCache,通过AddMsalDistributedTokenCache即可集成分布式缓存。
  • 方法映射:
    • ADAL AcquireTokenAsync(resource, clientCredential, userAssertion) → MSAL AcquireTokenOnBehalfOf
    • ADAL AcquireTokenAsync(resource, clientCredential) → MSAL AcquireTokenForClient
    • ADAL AcquireTokenSilentAsync → MSAL AcquireTokenSilent
  • 账户标识:MSAL使用Account对象,通过GetAccountByIdAsync获取,格式为{tenantId}.{objectId}。

三、额外注意事项

  • 区分委派权限(用于AcquireTokenByAuthorizationCode和AcquireTokenOnBehalfOf)与应用权限(用于AcquireTokenForClient),确保两类权限都已正确配置并授予同意。
  • MSAL推荐使用{resource}/.default作为scope,Azure AD会自动返回该应用已被授予的所有对应权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 12:35:21