开箱即用只能通过函数键(API-Key) Function Keys (API-Keys)来保护Azure函数,而函数键有时可能不符合您的需求。当使用HttpTrigger时,幸运的是,我们可以访问当前的请求,并因此能够实现我们自己的自定义身份验证/授权方法。例如,您可以使用OpenID提供者发出的JWT访问令牌来控制身份验证/授权。
在这篇文章中,您将学习如何验证JWT访问令牌以及控制对Azure函数的访问。
首先让我们看看函数。
public static class HelloWorldFunction { [FunctionName("HelloWorld")] public static async Task<HttpResponseMessage> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = null)]HttpRequestMessage req) { // Authentication boilerplate code start ClaimsPrincipal principal; if ((principal = await Security.ValidateTokenAsync(req.Headers.Authorization)) == null) { return req.CreateResponse(HttpStatusCode.Unauthorized); } // Authentication boilerplate code end return req.CreateResponse(HttpStatusCode.OK, "Hello " + principal.Identity.Name); } }
将授权级别设置为匿名是非常重要的,因为我们想要跳过Azure函数所做的所有检查。然后我们需要向每个函数添加“认证样板代码”,我们希望用JWT访问令牌保护。不幸的是,目前还没有通用的方法来添加它,例如via属性。
现在我们需要实现验证方法。
using Microsoft.IdentityModel.Protocols; using Microsoft.IdentityModel.Protocols.OpenIdConnect; using Microsoft.IdentityModel.Tokens; using System; using System.IdentityModel.Tokens.Jwt; using System.Net.Http.Headers; using System.Security.Claims; using System.Threading; using System.Threading.Tasks; namespace FunctionApp5 { public static class Security { private static readonly IConfigurationManager<OpenIdConnectConfiguration> _configurationManager; static Security() { var issuer = Environment.GetEnvironmentVariable("ISSUER"); var documentRetriever = new HttpDocumentRetriever(); documentRetriever.RequireHttps = issuer.StartsWith("https://"); _configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>( $"{issuer}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever(), documentRetriever ); } public static async Task<ClaimsPrincipal> ValidateTokenAsync(AuthenticationHeaderValue value) { if (value?.Scheme != "Bearer") { return null; } var config = await _configurationManager.GetConfigurationAsync(CancellationToken.None); var issuer = Environment.GetEnvironmentVariable("ISSUER"); var audience = Environment.GetEnvironmentVariable("AUDIENCE"); var validationParameter = new TokenValidationParameters() { RequireSignedTokens = true, ValidAudience = audience, ValidateAudience = true, ValidIssuer = issuer, ValidateIssuer = true, ValidateIssuerSigningKey = true, ValidateLifetime = true, IssuerSigningKeys = config.SigningKeys }; ClaimsPrincipal result = null; var tries = 0; while (result == null && tries <= 1) { try { var handler = new JwtSecurityTokenHandler(); result = handler.ValidateToken(value.Parameter, validationParameter, out var token); } catch (SecurityTokenSignatureKeyNotFoundException) { // This exception is thrown if the signature key of the JWT could not be found. // This could be the case when the issuer changed its signing keys, so we trigger a // refresh and retry validation. _configurationManager.RequestRefresh(); tries++; } catch (SecurityTokenException) { return null; } } return result; } } }
这里我们使用一个ConfigurationManager来从OpenID提供者检索签名密钥。将ConfigurationManager作为静态的非常重要,因为它缓存配置是为了减少对OpenID提供者的HTTP请求。ConfigurationManager会在配置超时后刷新配置。接下来,在ValidateTokenAsync方法中,我们设置了TokenValidationParameter。在这里,我们希望尽可能多地启用验证。
一旦我们配置了验证参数,我们就调用ValidateToken。如果无法验证令牌,则抛出SecurityTokenException。有几个派生异常,但在本例中,我们只关心SecurityTokenSignatureKeyNotFoundException异常,如果JwtSecurityTokenHandler无法找到JWT中指定的签名键,就会抛出该异常。当发布者在ConfigurationManager获取配置之后更改了它的签名密钥时,就可能出现这种情况。因此,我们在ConfigurationManger上触发一次刷新,并重试验证JWT。
如果验证成功,我们返回一个ClaimsPrincipal,其中包含令牌提供的声明。
如果您不使用OpenID,则需要更改ConfigurationManager选项。或者,您可以删除ConfigurationManger并通过TokenValidationParameters提供静态签名键。
原文:https://blog.wille-zone.de/post/secure-azure-functions-with-jwt-token/
本文:http://jiagoushi.pro/node/1376
讨论:请加入知识星球【快速和低代码开发】或者小号【it_training】或者QQ群【11107767】
- 登录 发表评论
- 14 次浏览
最新内容
- 4 days 10 hours ago
- 4 days 12 hours ago
- 4 days 12 hours ago
- 1 week ago
- 1 week ago
- 1 week ago
- 1 week ago
- 1 week ago
- 1 week 5 days ago
- 1 week 5 days ago