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

.NET Core & AzureAD:长运行进程中保留用户上下文调用下游API

解决长运行任务中调用下游API的用户上下文丢失问题

核心思路:长运行任务无法依赖请求上下文的用户令牌,必须提前获取**刷新令牌(Refresh Token)**并持久化,后续通过刷新令牌获取新的访问令牌来执行OBO(On-Behalf-Of)流程调用下游API。

步骤1:调整认证配置,确保获取刷新令牌

首先需要在配置中添加offline_access权限,让应用能获取刷新令牌。修改appsettings.json中的下游API配置:

"MyDownstreamApi": {
  "BaseUrl": "https://your-downstream-api-endpoint",
  "Scopes": "api://your-downstream-api-client-id/access_as_user offline_access"
}

同时在Startup.cs的认证配置中,确保令牌获取逻辑支持刷新令牌:

services
    .AddMicrosoftIdentityWebApiAuthentication(Configuration)
    .EnableTokenAcquisitionToCallDownstreamApi()
    .AddDownstreamWebApi(
        "MyDownstreamApi",
        Configuration.GetSection("MyDownstreamApi"))
    .AddInMemoryTokenCaches();

步骤2:启动长任务时获取并存储刷新令牌

在PUT /long-running-logic的控制器方法中,获取当前用户的刷新令牌并与任务GUID绑定存储:

[HttpPut("long-running-logic")]
public async Task<IActionResult> StartLongRunningTask([FromServices] ITokenAcquisition tokenAcquisition, [FromServices] ILongRunningTaskRegistry taskRegistry)
{
    // 获取包含刷新令牌的认证结果
    var authResult = await tokenAcquisition.GetAuthenticationResultForUserAsync(
        new[] { "api://your-downstream-api-client-id/access_as_user", "offline_access" },
        includeRefreshToken: true);

    // 生成任务GUID
    var taskId = Guid.NewGuid();

    // 存储任务信息:刷新令牌、用户ID、任务状态等
    taskRegistry.RegisterTask(taskId, new LongRunningTaskInfo
    {
        RefreshToken = authResult.RefreshToken,
        UserId = User.FindFirstValue(ClaimTypes.NameIdentifier),
        Status = TaskStatus.Running
    });

    // 启动后台长任务(建议用IHostedService或Hangfire等可靠框架,而非直接Task.Run)
    _ = Task.Run(async () => await ExecuteLongRunningTask(taskId, taskRegistry));

    return Ok(new { TaskId = taskId });
}

步骤3:长任务中用刷新令牌获取访问令牌并调用下游API

首先在Startup.cs注册机密客户端应用,用于执行OBO流程:

services.AddSingleton<IConfidentialClientApplication>(sp =>
{
    var config = sp.GetRequiredService<IConfiguration>();
    var azureAdConfig = config.GetSection("AzureAd").Get<MicrosoftIdentityOptions>();
    
    return ConfidentialClientApplicationBuilder
        .Create(azureAdConfig.ClientId)
        .WithClientSecret(azureAdConfig.ClientSecret)
        .WithAuthority(new Uri($"{azureAdConfig.Instance}{azureAdConfig.TenantId}"))
        .Build();
});

然后在长任务逻辑中,使用刷新令牌获取访问令牌并调用下游API:

private async Task ExecuteLongRunningTask(Guid taskId, ILongRunningTaskRegistry taskRegistry, [FromServices] IConfidentialClientApplication confidentialClientApp, [FromServices] IConfiguration config)
{
    var taskInfo = taskRegistry.GetTask(taskId);
    if (taskInfo == null) return;

    try
    {
        var downstreamApiScopes = new[] { "api://your-downstream-api-client-id/access_as_user" };
        var azureAdConfig = config.GetSection("AzureAd").Get<MicrosoftIdentityOptions>();

        // 用刷新令牌获取新的访问令牌
        var result = await confidentialClientApp.AcquireTokenByRefreshToken(downstreamApiScopes, taskInfo.RefreshToken)
            .WithAccount(new Account(taskInfo.UserId, taskInfo.UserId, azureAdConfig.TenantId))
            .ExecuteAsync();

        // 调用下游API
        using var httpClient = new HttpClient();
        httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
        var downstreamBaseUrl = config.GetValue<string>("MyDownstreamApi:BaseUrl");
        var response = await httpClient.GetAsync($"{downstreamBaseUrl}/your-target-endpoint");
        
        // 更新任务状态和结果
        taskInfo.Status = TaskStatus.Completed;
        taskInfo.Result = await response.Content.ReadAsStringAsync();
    }
    catch (MsalUiRequiredException ex)
    {
        // 刷新令牌过期或无效,标记任务失败
        taskInfo.Status = TaskStatus.Failed;
        taskInfo.ErrorMessage = "用户授权已过期,请重新发起请求";
    }
    catch (Exception ex)
    {
        taskInfo.Status = TaskStatus.Failed;
        taskInfo.ErrorMessage = ex.Message;
    }
    finally
    {
        taskRegistry.UpdateTask(taskId, taskInfo);
    }
}

关键注意事项

  • 后台任务可靠性:避免直接用Task.Run,建议使用IHostedService、Hangfire或Azure Functions等框架,确保任务在应用重启后不会丢失。
  • 刷新令牌存储:刷新令牌是敏感信息,需加密存储(比如用加密数据库字段),禁止明文存储。
  • 令牌过期处理:如果刷新令牌过期,无法再获取新的访问令牌,需通知用户重新发起请求。
  • 权限配置:确保客户端应用在Azure AD中已被授予调用下游API的权限,且用户已同意相关权限。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.27 14:22:41