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]的特性使用习惯,可以自己实现一个支持多发行方的自定义绑定:
- 创建自定义绑定属性:
[AttributeUsage(AttributeTargets.Parameter)] public class MultiJwtBindingAttribute : Attribute { // 可以通过构造函数或配置读取多组Issuer/Audience }
- 实现绑定提供者与验证逻辑:
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等) }
- 在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
相关产品推荐
相关产品推荐

