迁移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)→ MSALAcquireTokenOnBehalfOf - ADAL
AcquireTokenAsync(resource, clientCredential)→ MSALAcquireTokenForClient - ADAL
AcquireTokenSilentAsync→ MSALAcquireTokenSilent
- ADAL
- 账户标识:MSAL使用
Account对象,通过GetAccountByIdAsync获取,格式为{tenantId}.{objectId}。
三、额外注意事项
- 区分委派权限(用于
AcquireTokenByAuthorizationCode和AcquireTokenOnBehalfOf)与应用权限(用于AcquireTokenForClient),确保两类权限都已正确配置并授予同意。 - MSAL推荐使用
{resource}/.default作为scope,Azure AD会自动返回该应用已被授予的所有对应权限。
内容的提问来源于stack exchange,提问作者Neo
相关产品推荐
相关产品推荐

