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

Azure Function v4如何支持多JWT发行方与受众验证?

兼容多JWT发行方(Auth0 + Azure AD B2C)的Azure Function实现方案

由于HexMaster.Functions.JwtBinding原生不支持多发行方/受众验证,你可以通过以下几种方案实现兼容:


方案一:手动实现多令牌验证(快速适配)

直接在函数内提取令牌并分别验证Auth0和Azure AD B2C的规则,只要任意一方验证通过即可视为合法:

public static class ProtectedFunction
{
    [FunctionName("SecureEndpoint")]
    public static async Task<IActionResult> Run(
        [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req,
        ILogger log)
    {
        // 提取请求头中的Bearer令牌
        var authHeader = req.Headers["Authorization"].FirstOrDefault();
        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
        {
            return new UnauthorizedResult();
        }
        var token = authHeader.Substring("Bearer ".Length);

        // 分别验证两个发行方的令牌
        var isAuth0Valid = await ValidateToken(token, 
            Environment.GetEnvironmentVariable("Auth0:Issuer"),
            Environment.GetEnvironmentVariable("Auth0:Audience"),
            $"{Environment.GetEnvironmentVariable("Auth0:Issuer")}/.well-known/jwks.json");
        
        var isAzureValid = await ValidateToken(token,
            Environment.GetEnvironmentVariable("AzureADB2C:Issuer"),
            Environment.GetEnvironmentVariable("AzureADB2C:Audience"),
            $"https://{Environment.GetEnvironmentVariable("AzureADB2C:TenantId")}.b2clogin.com/{Environment.GetEnvironmentVariable("AzureADB2C:TenantId")}.onmicrosoft.com/{Environment.GetEnvironmentVariable("AzureADB2C:Policy")}/discovery/v2.0/keys");

        if (!isAuth0Valid && !isAzureValid)
        {
            return new UnauthorizedResult();
        }

        // 后续业务逻辑
        return new OkObjectResult("Token validated successfully");
    }

    private static async Task<bool> ValidateToken(string token, string issuer, string audience, string jwksUrl)
    {
        try
        {
            // 获取发行方的签名密钥
            var httpClient = new HttpClient();
            var jwksResponse = await httpClient.GetFromJsonAsync<JsonWebKeySet>(jwksUrl);
            
            var validationParams = new TokenValidationParameters
            {
                ValidIssuer = issuer,
                ValidAudience = audience,
                ValidateIssuerSigningKey = true,
                IssuerSigningKeys = jwksResponse.GetSigningKeys(),
                ValidateLifetime = true
            };

            // 验证令牌
            new JwtSecurityTokenHandler().ValidateToken(token, validationParams, out _);
            return true;
        }
        catch
        {
            // 验证失败时返回false,继续尝试下一个发行方
            return false;
        }
    }
}

方案二:自定义多JWT绑定(保留原有代码风格)

如果想延续[JwtBinding]的特性使用习惯,可以自己实现一个支持多发行方的自定义绑定:

  1. 创建自定义绑定属性:
[AttributeUsage(AttributeTargets.Parameter)]
public class MultiJwtBindingAttribute : Attribute
{
    // 可以通过构造函数或配置读取多组Issuer/Audience
}
  1. 实现绑定提供者与验证逻辑:
public class MultiJwtBindingProvider : IBindingProvider
{
    public Task<IBinding> TryCreateAsync(BindingProviderContext context)
    {
        var attribute = context.Parameter.GetCustomAttribute<MultiJwtBindingAttribute>();
        if (attribute == null) return Task.FromResult<IBinding>(null);

        // 从配置读取多组验证规则(示例用分号分隔)
        var issuerPairs = Environment.GetEnvironmentVariable("MultiJwt:IssuerAudiencePairs").Split(';');
        var validationRules = issuerPairs.Select(pair => 
        {
            var parts = pair.Split(',');
            return new { Issuer = parts[0], Audience = parts[1] };
        });

        return Task.FromResult<IBinding>(new MultiJwtBinding(validationRules));
    }
}

public class MultiJwtBinding : IBinding
{
    private readonly IEnumerable<dynamic> _validationRules;

    public MultiJwtBinding(IEnumerable<dynamic> rules)
    {
        _validationRules = rules;
    }

    public async Task<IValueProvider> BindAsync(BindingContext context, CancellationToken cancellationToken)
    {
        var req = await context.GetHttpRequestDataAsync();
        var authHeader = req.Headers["Authorization"].FirstOrDefault();
        if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
        {
            throw new UnauthorizedAccessException("Missing or invalid token");
        }
        var token = authHeader.Substring("Bearer ".Length);

        // 遍历所有验证规则,找到第一个通过的
        foreach (var rule in _validationRules)
        {
            try
            {
                var jwksUrl = rule.Issuer.Contains("auth0") 
                    ? $"{rule.Issuer}/.well-known/jwks.json" 
                    : $"https://{rule.Issuer.Split('/')[2]}/discovery/v2.0/keys";
                
                if (await ValidateToken(token, rule.Issuer, rule.Audience, jwksUrl))
                {
                    // 解析令牌为你的AuthorizedModel
                    var handler = new JwtSecurityTokenHandler();
                    var jwtToken = handler.ReadJwtToken(token);
                    var model = new AuthorizedModel
                    {
                        // 从jwtToken.Claims中提取需要的字段
                    };
                    return new ConstantValueProvider(model);
                }
            }
            catch { continue; }
        }

        throw new UnauthorizedAccessException("Token failed all validation rules");
    }

    // 复用方案一中的ValidateToken方法
    private static async Task<bool> ValidateToken(string token, string issuer, string audience, string jwksUrl)
    {
        // 实现同方案一
    }

    // 实现IBinding接口的其他必要方法(如CanBind、GetBindingData等)
}
  1. 在Program.cs中注册自定义绑定:
var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        builder.AddBinding<MultiJwtBindingProvider>();
    })
    .Build();

host.Run();

方案三:使用全局中间件(统一验证)

通过Azure Function v4的中间件功能,全局处理所有请求的令牌验证:

var host = new HostBuilder()
    .ConfigureFunctionsWorkerDefaults(builder =>
    {
        builder.UseMiddleware<MultiJwtValidationMiddleware>();
    })
    .Build();

host.Run();

// 中间件实现
public class MultiJwtValidationMiddleware : IFunctionsWorkerMiddleware
{
    public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next)
    {
        var req = await context.GetHttpRequestDataAsync();
        if (req == null)
        {
            await next(context);
            return;
        }

        var authHeader = req.Headers["Authorization"].FirstOrDefault();
        if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer "))
        {
            var token = authHeader.Substring("Bearer ".Length);
            var isValid = await ValidateTokenAgainstAuth0(token) || await ValidateTokenAgainstAzureADB2C(token);
            
            if (!isValid)
            {
                var response = req.CreateResponse(HttpStatusCode.Unauthorized);
                await response.WriteAsync("Invalid token");
                context.GetInvocationResult().Value = response;
                return;
            }
        }

        await next(context);
    }

    // 复用方案一中的ValidateTokenAgainstAuth0和ValidateTokenAgainstAzureADB2C方法
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 10:17:31