如何通过DeviceCode认证访问Azure Blob Storage及多资源令牌静默获取
使用DeviceCode认证获取多资源令牌并创建BlobServiceClient的解决方案
问题背景
在IoT设备配置流程中,需要通过DeviceCode认证让技术人员授权应用访问Azure Blob Storage并下载文件。现有代码可成功获取IoT Central的访问令牌,但存在以下问题:
- 无法复用首次认证的Refresh Token,避免重复用户交互
- 不清楚如何静默获取Blob Storage的访问令牌
- 不知道如何用认证结果创建BlobServiceClient
- 设备无浏览器,必须使用DeviceCode认证方式
核心解决方案
MSAL库会自动处理Refresh Token的缓存与复用,无需手动操作。核心要点:
- 复用同一个
IPublicClientApplication实例,保证令牌缓存共享 - 通过
AcquireTokenSilent方法静默获取其他资源的令牌,仅在缓存无有效令牌时触发交互(首次认证后无需重复操作) - 针对Blob Storage的专属scope调用静默获取流程
修改后的完整代码示例
1. 类成员与PCA初始化
将IPublicClientApplication设为类成员,避免每次获取令牌时重新创建导致缓存丢失:
public class Constants { public const string ClientApplicationId = "app-id"; public const string TenantId = "tenant-id"; public const string AppName = "registered-desktop-app-name"; public const string ApiVersion = "2022-07-31"; public const string DeviceTemplateId = "dtmi:modelDefinition:e2eStandard;1"; public static string[] IOTCScopes = new[] { "https://apps.azureiotcentral.com/.default" }; public static string[] StorageScopes = new[] { "https://storage.azure.com/.default" }; } private IPublicClientApplication _pca; private CancellationTokenSource _cancellationTokenSource; private AuthenticationResult? _iotcAuthResult; private AuthenticationResult? _blobSAuthResult; public string AuthInstruction { get; set; } // 在构造函数中初始化PCA实例 public YourClassName() { _pca = PublicClientApplicationBuilder .Create(Constants.ClientApplicationId) .WithAuthority($"https://login.microsoftonline.com/{Constants.TenantId}") // 必须指定具体租户,DeviceCode不支持common/consumers端点 .Build(); }
2. 通用令牌获取方法
封装支持多scope的令牌获取逻辑,自动处理静默获取与DeviceCode交互:
async Task<AuthenticationResult> GetTokenForScopes(string[] scopes) { _cancellationTokenSource = new CancellationTokenSource(); var accounts = await _pca.GetAccountsAsync(); try { // 优先从缓存静默获取令牌 return await _pca.AcquireTokenSilent(scopes, accounts.FirstOrDefault()) .ExecuteAsync(_cancellationTokenSource.Token); } catch (MsalUiRequiredException) { // 缓存无有效令牌时,触发DeviceCode认证 return await AcquireByDeviceCodeAsync(scopes); } } private async Task<AuthenticationResult> AcquireByDeviceCodeAsync(string[] scopes) { try { var result = await _pca.AcquireTokenWithDeviceCode(scopes, deviceCodeResult => { this.AuthInstruction = deviceCodeResult.Message; return Task.CompletedTask; }).ExecuteAsync(_cancellationTokenSource.Token); return result; } catch (MsalServiceException ex) { Debug.WriteLine($"服务端错误: {ex.Message}"); } catch (OperationCanceledException ex) { Debug.WriteLine($"操作已取消: {ex.Message}"); } catch (MsalClientException ex) { Debug.WriteLine($"客户端错误: {ex.Message}"); } return null; }
3. 认证流程优化
修改Login方法,实现一次交互获取多资源令牌:
public async Task LoginAzureAD() { // 获取IoT Central令牌(首次触发DeviceCode交互) _iotcAuthResult = await GetTokenForScopes(Constants.IOTCScopes); if (_iotcAuthResult != null) { AuthInstruction = $"已认证用户 {_iotcAuthResult.Account.Username} (IoT Central)"; } else { AuthInstruction = "IoT Central认证失败,请重试或联系管理员"; return; } // 静默获取Blob Storage令牌,无需再次用户交互 _blobSAuthResult = await GetTokenForScopes(Constants.StorageScopes); if (_blobSAuthResult != null) { AuthInstruction = $"已获取Blob Storage访问权限,用户: {_blobSAuthResult.Account.Username}"; } else { AuthInstruction = "Blob Storage认证失败,请重试或联系管理员"; } }
4. 创建BlobServiceClient
使用Blob Storage的访问令牌创建客户端:
public BlobServiceClient CreateBlobServiceClient() { if (_blobSAuthResult == null) throw new InvalidOperationException("Blob Storage未完成认证"); // 自定义TokenCredential适配Azure SDK接口 var tokenCredential = new BlobTokenCredential(_blobSAuthResult.AccessToken); // 替换为你的存储账户URL string storageAccountUrl = "https://yourstorageaccount.blob.core.windows.net/"; return new BlobServiceClient(new Uri(storageAccountUrl), tokenCredential); } // 实现TokenCredential接口,适配MSAL令牌 private class BlobTokenCredential : TokenCredential { private readonly string _accessToken; public BlobTokenCredential(string accessToken) { _accessToken = accessToken; } public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) { // 令牌有效期通常为1小时,这里设为59分钟留缓冲 return new AccessToken(_accessToken, DateTimeOffset.UtcNow.AddMinutes(59)); } public override ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) { return new ValueTask<AccessToken>(GetToken(requestContext, cancellationToken)); } }
关键注意事项
- 应用权限配置:需在Azure AD应用注册中添加Blob Storage的API权限(如
Storage Blob Data Reader),并确保管理员已同意 - 租户限制:DeviceCode流不支持
common或consumers租户,必须指定具体的TenantId - 令牌持久化:默认缓存为内存级,若需设备重启后保留认证状态,需实现
ITokenCacheSerializer序列化缓存
内容的提问来源于stack exchange,提问作者Duncan Groenewald
相关产品推荐
相关产品推荐

