跳转到主要内容
Chinese, Simplified

开箱即用只能通过函数键(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】

Tags
 
Article
知识星球
 
微信公众号
 
视频号