You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

如何在Elsa工作流的SendHttpRequest活动中使用Bearer令牌调用受Identity Server保护的API及解决HttpContextAccessor返回null问题

解决Elsa工作流中使用SendHttpRequest携带Bearer令牌调用API的问题

首先,我们来分析你遇到的HttpContextAccessor返回null的核心原因:
Elsa工作流的执行场景分为两种——由HTTP请求触发(比如通过API或仪表板手动启动)后台异步执行(比如定时任务、事件驱动)。后者是脱离HTTP请求上下文运行的,这时候HttpContextAccessor自然拿不到当前请求的上下文,返回null是必然结果。

接下来,我们针对不同场景给出最佳实现方案:


方案1:针对HTTP请求触发的工作流(修正当前代码)

如果你的工作流都是由用户在仪表板操作或HTTP API触发的,我们可以先修复HttpContext获取的问题:

1.1 修正GetAccessToken的实现

不要在构造函数中直接获取HttpContext,而是在Handle方法中动态获取,同时确保这个处理器被注册为Scoped(和请求生命周期绑定):

public class GetAccessToken : INotificationHandler<EvaluatingJavaScriptExpression>
{
    private readonly IHttpContextAccessor _httpContextAccessor;

    // 只注入IHttpContextAccessor,不要直接拿HttpContext
    public GetAccessToken(IHttpContextAccessor httpContextAccessor)
    {
        _httpContextAccessor = httpContextAccessor;
    }

    public async Task Handle(EvaluatingJavaScriptExpression notification, CancellationToken cancellationToken)
    {
        var httpContext = _httpContextAccessor.HttpContext;
        if (httpContext == null)
        {
            // 处理无上下文的情况,比如后台执行时可以抛出警告或返回空
            notification.Engine.SetValue("accessToken", string.Empty);
            return;
        }
        
        var accessToken = await httpContext.GetTokenAsync("access_token");
        notification.Engine.SetValue("accessToken", accessToken);
    }
}

1.2 正确注册处理器

Startup.cs中把GetAccessToken注册为Scoped:

services.AddScoped<INotificationHandler<EvaluatingJavaScriptExpression>, GetAccessToken>();

(之前如果注册成Singleton,会导致处理器实例被复用,无法获取到当前请求的HttpContext


方案2:通用方案(适配所有执行场景)

如果你的工作流存在后台执行的情况(比如定时任务),上面的方案就不适用了。这时候我们需要更稳健的方式:

2.1 方式A:触发工作流时传递令牌

在触发工作流(比如通过API或仪表板)时,把当前用户的访问令牌作为工作流变量传入:

// 比如在控制器中触发工作流
public async Task<IActionResult> StartWorkflow([FromServices] IWorkflowLaunchpad workflowLaunchpad)
{
    var accessToken = await HttpContext.GetTokenAsync("access_token");
    var variables = new Dictionary<string, object> { ["AccessToken"] = accessToken };
    await workflowLaunchpad.StartWorkflowAsync("YourWorkflowId", variables: variables);
    return Ok();
}

然后在Elsa仪表板的SendHttpRequest活动中,直接引用这个变量作为Authorization头:

Bearer {{ AccessToken }}

2.2 方式B:使用客户端凭据模式(后台任务专用)

如果工作流是后台自动执行的(不需要用户参与),建议使用Identity Server的客户端凭据模式来获取令牌,而不是依赖用户的令牌:

  1. 在Identity Server中为你的Elsa应用添加客户端凭据类型的授权
  2. 在Elsa中创建一个服务类来获取客户端令牌:
public class ClientTokenService
{
    private readonly IConfiguration _configuration;

    public ClientTokenService(IConfiguration configuration)
    {
        _configuration = configuration;
    }

    public async Task<string> GetClientAccessTokenAsync()
    {
        var client = new HttpClient();
        var disco = await client.GetDiscoveryDocumentAsync(_configuration["IdentityServer:Authority"]);
        
        var tokenResponse = await client.RequestClientCredentialsTokenAsync(new ClientCredentialsTokenRequest
        {
            Address = disco.TokenEndpoint,
            ClientId = _configuration["IdentityServer:ClientId"],
            ClientSecret = _configuration["IdentityServer:ClientSecret"],
            Scope = "your-api-scope"
        });

        if (tokenResponse.IsError)
            throw new InvalidOperationException(tokenResponse.Error);
        
        return tokenResponse.AccessToken;
    }
}
  1. SendHttpRequest活动中,使用Elsa的表达式调用这个服务获取令牌:
await services.getService('ClientTokenService').getClientAccessTokenAsync()

然后把这个结果作为Authorization头的值。


方案3:利用Elsa的内置身份认证机制

Elsa支持为工作流设置身份信息,你可以在工作流启动时关联当前用户的身份,然后在活动中获取用户的令牌:

  1. 确保Elsa启用了身份认证:
services.AddElsa(elsa =>
{
    elsa.AddHttpActivities(options =>
    {
        options.AuthenticationScheme = "Cookies";
    });
});
  1. 在工作流中,通过WorkflowExecutionContext获取用户的声明或令牌(需要自定义活动或表达式扩展)。

最后总结一下:

  • 如果工作流都是用户触发的,方案1简单直接;
  • 如果存在后台执行的工作流,方案2的两种方式更合适;
  • 客户端凭据模式是后台任务调用受保护API的标准做法,推荐优先考虑。

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

火山引擎 最新活动