Microsoft Graph:如何避免每次启动进程时重复进行授权验证
解决方案
核心思路
- 可以完全避免每次启动的重复身份验证:通过微软身份验证库(MSAL)的持久化令牌缓存,将首次验证后获得的刷新令牌存储到本地磁盘,后续进程启动时直接读取缓存的刷新令牌换取访问令牌,无需重新走设备码验证流程。
- 默认的
DeviceCodeCredential自带的缓存是内存级别的,进程关闭后就会丢失,所以需要自己基于带持久缓存的PublicClientApplication封装适配Graph SDK的令牌凭据。
具体实现步骤
1. 安装依赖包
首先在C#项目中安装以下Nuget包:Microsoft.Graph、Microsoft.Identity.Client、Microsoft.Identity.Client.Extensions.Msal
2. 初始化带持久缓存的公共客户端应用
using Microsoft.Identity.Client; using Microsoft.Identity.Client.Extensions.Msal; using Azure.Core; using Microsoft.Graph; // 配置参数 private const string ClientId = "你的应用注册客户端ID"; private const string TenantId = "organizations"; // 单租户场景可替换为实际租户ID private static readonly string[] Scopes = { "Tasks.ReadWrite", "Group.Read.All" }; // 按需替换为实际需要的Graph权限 // 初始化PublicClientApplication var pca = PublicClientApplicationBuilder .Create(ClientId) .WithAuthority($"https://login.microsoftonline.com/{TenantId}") .Build(); // 配置磁盘持久化缓存,适配Linux环境 var storageProperties = new StorageCreationPropertiesBuilder( "msal_cache.bin", // 缓存文件名 MsalCacheHelper.UserRootDirectory) // Linux下默认路径为~/.local/share/MSAL/ // 无头Linux服务器无桌面密钥环时,取消下一行注释使用明文存储(生产环境请严格限制文件权限) // .WithUnprotectedFile() .Build(); var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties); cacheHelper.RegisterCache(pca.UserTokenCache);
3. 自定义适配Graph SDK的令牌凭据
实现TokenCredential抽象类,内部调用MSAL接口获取令牌:
public class MsalPublicClientCredential : TokenCredential { private readonly IPublicClientApplication _pca; private readonly string[] _scopes; private readonly Func<DeviceCodeInfo, CancellationToken, Task> _deviceCodeCallback; public MsalPublicClientCredential(IPublicClientApplication pca, string[] scopes, Func<DeviceCodeInfo, CancellationToken, Task> deviceCodeCallback) { _pca = pca; _scopes = scopes; _deviceCodeCallback = deviceCodeCallback; } public override async ValueTask<AccessToken> GetTokenAsync(TokenRequestContext requestContext, CancellationToken cancellationToken) { // 优先尝试从缓存静默获取令牌 var accounts = await _pca.GetAccountsAsync(); var firstAccount = accounts.FirstOrDefault(); AuthenticationResult authResult; try { authResult = await _pca.AcquireTokenSilent(_scopes, firstAccount) .ExecuteAsync(cancellationToken); } catch (MsalUiRequiredException) { // 缓存不存在/失效时走设备码验证流程 authResult = await _pca.AcquireTokenWithDeviceCode(_scopes, _deviceCodeCallback) .ExecuteAsync(cancellationToken); } return new AccessToken(authResult.AccessToken, authResult.ExpiresOn); } public override AccessToken GetToken(TokenRequestContext requestContext, CancellationToken cancellationToken) { return GetTokenAsync(requestContext, cancellationToken).AsTask().GetAwaiter().GetResult(); } }
4. 初始化GraphServiceClient
// 设备码回调逻辑,可按需自定义输出方式 async Task DeviceCodeCallback(DeviceCodeInfo info, CancellationToken ct) { Console.WriteLine(info.Message); await Task.CompletedTask; } // 创建自定义凭据 var tokenCredential = new MsalPublicClientCredential(pca, Scopes, DeviceCodeCallback); // 初始化Graph客户端,后续直接调用即可 var graphClient = new GraphServiceClient(tokenCredential, Scopes);
Linux环境注意事项
- 带桌面环境的Linux版本中,MSAL缓存默认会用系统密钥环加密存储,安全性更高。
- 无头服务器无密钥环服务时,配置明文缓存后需要严格限制缓存文件的读写权限,仅允许运行进程的用户访问。
- 刷新令牌默认有效期为90天,只要用户未主动撤销权限,90天内启动进程都无需重新验证,到期后会自动触发一次设备码流程更新凭证。
内容的提问来源于stack exchange,提问作者Luca Ottaviano
相关产品推荐
相关产品推荐

