【问题标题】:How do I configure ASP.NET WebApi to validate bearer tokens against an OpenID Connect server?如何配置 ASP.NET WebApi 以针对 OpenID Connect 服务器验证不记名令牌?
【发布时间】:2020-03-07 23:55:55
【问题描述】:

我正在编写一个从另一个服务接收 POST 的服务,其中包括一个包含不记名令牌的授权标头。此令牌是从 OpenID Connect 服务器独立获得的(我们开发环境中的 Keycloak,但不一定在生产环境中)。我们的服务不需要获取或发行代币;它只需要能够验证它们。

我们正在使用带有自托管 ASP.NET WebApi(OWIN 4 等)的 .NET Framework 4.8。

配置方面,我们掌握的信息是:

目的是我们从 OpenID 服务器的元数据端点“http://keycloak:8080/auth/realms/demo/.well-known/openid-configuration”动态获取颁发者公钥。目前我有类似的东西:

WebApp.Start(startOptions, builder => {
    var config = ...
    // ... Set up routes etc ...
    config.Filters.Add(new HostAuthenticationFilter("Bearer"));
    builder.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        ClientId = "js-client",
        Authority = "http://keycloak:8080/auth/realms/demo/",
        RequireHttpsMetadata = false,
        SignInAsAuthenticationType = "Bearer",
    });
    builder.UseWebApi(config);
}));

控制器动作如下:

[HttpGet]
[HttpPost]
[Authorize]
public IHttpActionResult Receive([FromBody] string dto) => Ok();

目前,它总是返回 401 Unauthorized 并带有消息“授权已被拒绝” request' 与令牌的有效性无关。

Wireshark 显示我们的服务从未尝试联系 Keycloak 服务器以获取 OIDC 元数据,所以我猜授权处理程序甚至没有找到令牌。

我也查看了 UseJwtBearerAuthentication 和 UseOAuthAuthorizationServer,但它们似乎需要更多信息,而不仅仅是 OIDC 端点(这并不奇怪),或者他们需要自定义提供程序实现。

这似乎不是一个不寻常的用例,我需要实现自己的验证器,所以大概我遗漏了什么? Google 搜索出现了数百个示例,这些示例似乎仅与 ASP.NET Core 相关或不涵盖非交互式用例。

【问题讨论】:

    标签: asp.net owin openid-connect


    【解决方案1】:

    通过检查 OpenIdConnectAuthenticationMiddleware 的来源,我设法在这方面取得了进展。

    JwtBearer 中间件处理颁发者的验证,但需要知道公钥。由于我需要避免直接配置,因此需要向 OIDC 服务器询问。

    这可以使用 ConfigurationManager 来完成,它应该为我们处理缓存等:

    private JwtBearerAuthenticationOptions GetJwtBearerTokenAuthenticationOptions(string issuer, IConfigurationManager<OpenIdConnectConfiguration> configurationManager)
    {
        return new JwtBearerAuthenticationOptions
        {
            Realm = "demo",
            TokenValidationParameters = new TokenValidationParameters
            {
                ValidateLifetime = true,
                ValidateIssuerSigningKey = true,
                // ... etc ...
                IssuerSigningKeyResolver = (token, securitytoken, kid, validationparameters) =>
                    configurationManager.GetConfigurationAsync(CancellationToken.None).GetAwaiter().GetResult().SigningKeys,
                ValidIssuer = issuer.TrimEnd('/'),
            }
        };
    }
    

    (不幸的是,解析器委托不能异步,所以我无法正常等待。)

    ConfigurationManager 可以这样构造(基于 OpenIdConnectAuthenticationMiddleware 的内部):

    private IConfigurationManager<OpenIdConnectConfiguration> GetOIDCConfigurationManager(string issuer)
    {
        var httpClient = new HttpClient(new WebRequestHandler());
        httpClient.DefaultRequestHeaders.UserAgent.ParseAdd("Demo OpenIdConnect middleware");
        httpClient.Timeout = TimeSpan.FromMinutes(1);
        httpClient.MaxResponseContentBufferSize = 10485760L;
        var httpRetriever = new HttpDocumentRetriever(httpClient) { RequireHttps = false };
    
        return new ConfigurationManager<OpenIdConnectConfiguration>($"{issuer}.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever(), httpRetriever);
    }
    

    这些可以按如下方式使用:

    const string issuer = "http://keycloak:8080/auth/realms/demo/";
    var configurationManager = GetOIDCConfigurationManager(issuer);
    
    builder.UseJwtBearerAuthentication(GetJwtBearerTokenAuthenticationOptions(issuer, configurationManager));
    

    这一切似乎都有效,虽然我非常想知道是否有更简单的方法......?

    显然,任何在生产中使用它的人都应该RequireHttps = true

    【讨论】:

      【解决方案2】:

      这些供应商技术堆栈通常令人困惑 - 但正如 my blog post 中所述,有一个基于标准的解决方案正在进行中。在很多情况下,您必须自己编写代码,在这种情况下,您需要下载 JWKS 密钥 like this

      【讨论】:

        猜你喜欢
        • 2015-06-27
        • 2018-08-14
        • 2014-09-12
        • 2015-05-27
        • 2018-11-11
        • 2018-01-17
        • 2020-07-25
        • 2022-11-18
        相关资源
        最近更新 更多