如何配置 IIS 以在路由使端点路径与物理路径不同时定义特定端点所需的客户端证书

How to configure IIS to define Client Certificate required on a specific endpoint when routing make endpoint path different from physical path

我实现了一个 dotnet 核心 Api,其中端点是根据 Controller 属性 Route 定义的。 我有例如 2 个端点 api/controller1 和 api/controller2

我想配置 IIS,以便控制器 1 忽略客户端证书,而控制器 2 需要客户端证书。 在我的Api中,我是这样实现主机的

        public static IWebHostBuilder CreateWebHostBuilder(string[] args) =>
        WebHost.CreateDefaultBuilder(args)
            .UseStartup<Startup>()
            .ConfigureKestrel(o =>
            {
                o.ConfigureHttpsDefaults(o=>o.ClientCertificateMode=ClientCertificateMode.AllowCertificate);
            })
            .UseIISIntegration()
            .ConfigureLogging(logging =>
            {
                logging.ClearProviders();
                logging.SetMinimumLevel(LogLevel.Debug);
            })
            .UseNLog();

和配置的服务

    services.AddSingleton<CertificateValidationService>();

    services.Configure<IISOptions>(options =>
    {
        options.ForwardClientCertificate = true;
    });
    services.AddAuthentication()
        .AddCertificate(x =>
        {
            x.AllowedCertificateTypes = CertificateTypes.All;
            x.ValidateValidityPeriod = true;
            x.RevocationMode = X509RevocationMode.NoCheck;
            x.Events = new CertificateAuthenticationEvents
            {
                OnCertificateValidated = context =>
                {
                    _logger.Trace("Enters OnCertificateValidated");
                    var validationService =
                        context.HttpContext.RequestServices.GetService<CertificateValidationService>();
                    if (validationService.ValidateCertificate(context.ClientCertificate))
                    {
                        _logger.Trace("OnCertificateValidated success");
                        context.Success();
                    }
                    else
                    {
                        _logger.Trace("OnCertificateValidated fail");
                        context.Fail("invalid certificate");
                    }

                    return Task.CompletedTask;
                },
                OnAuthenticationFailed = context =>
                {
                    _logger.Trace("Enters OnAuthenticationFailed");
                    context.Fail("invalid certificate");
                    return Task.CompletedTask;
                }
            };
        });

这里是Startup.cs

的Configure方法中的中间件管道配置
            if (env.IsLocal())
        {
            app.UseDeveloperExceptionPage();
        }
        else
        {
            app.UseExceptionHandler(appBuilder =>
            {
                appBuilder.Use(async (context, next) =>
                {
                    var error = context.Features[typeof(IExceptionHandlerFeature)] as IExceptionHandlerFeature;
                    if (error != null && error.Error is SecurityTokenExpiredException)
                    {
                        _logger.Warn($"No valid token provided. {error.Error.Message}");
                        context.Response.StatusCode = 401;
                        context.Response.ContentType = "application/json";
                        await context.Response.WriteAsync(JsonConvert.SerializeObject(new
                        {
                            IpUrl = _globalSettings.IdP.Url,
                            SpName = _globalSettings.IdP.Name,
                            Authenticate = context.Request.GetEncodedUrl(),
                            //State = 401,
                            Msg = "Token expired"
                        }));
                    }
                    else if (error?.Error != null)
                    {
                        _logger.Error($"Unexpected error - {error.Error.Message}");
                        context.Response.StatusCode = 500;
                        context.Response.ContentType = "application/json";
                        await context.Response.WriteAsync(JsonConvert.SerializeObject(new
                        {
                            State = 500,
                            Msg = error.Error.Message
                        }));
                    }
                    else
                    {
                        await next();
                    }
                });
            });
            // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
            app.UseHsts();
        }
        app.UseHttpsRedirection();
        app.UseRouting();

        app.UseCors("AllowOrigin");
        app.UseAuthentication();
        app.UseAuthorization();

        app.UseSwagger(SwaggerHelper.ConfigureSwagger);
        app.UseSwaggerUI(SwaggerHelper.ConfigureSwaggerUi);

        app.UseEndpoints(endpoints => endpoints.MapControllers());

我尝试使用 web.config 位置,但“路径”api/controller2 实际上并不存在(已路由)所以它没有效果

我在 app 文件夹中创建了伪造的 api/controller2 文件夹来设置 SSL 要求。不幸的是,我得到了一个 405,因为我失去了路由并且这些文件夹后面没有任何东西。

我唯一的方法是在 api 应用程序级别“接受”证书。但是,我的前端,一旦它第一次查询我的 API 就在它只使用 api/controller1

时要求证书

有没有办法或者我必须构建和部署一个特定的 API 来保护它,另一个不使用客户端证书?

不幸的是,这是不可能的。证书验证发生在 TLS 级别,即在实际请求到达 ASP.NET 核心之前,因此您无法通过路由进行区分。它甚至在您可以实现这样的逻辑之前就失败了。

我们遇到了类似的问题,我们不得不设置两个应用程序,一个有证书验证,一个没有。具有证书验证功能的应用程序调用具有“正常”(在我们的示例中为 JWT 机器对机器)身份验证并传递证书参数的另一个应用程序。

这是official docu that states this:

Can I configure my app to require a certificate only on certain paths? This isn't possible. Remember the certificate exchange is done at the start of the HTTPS conversation, it's done by the server before the first request is received on that connection so it's not possible to scope based on any request fields.

我有类似的问题。 我找到了 iis express 的解决方案。 我想iis也是这样解决的,以后再写(如果行得通的话)。

但是关于解决方案(起始条件):

  1. 我正在 visual studio 进行舞台测试,我 运行 网络核心应用程序在 iis express 下集成在 VS 中。
  2. 对于我的解决方案,当用户转到 url '/certificate/apply/'(仅在此页面上)时,我需要请求用户证书。
  3. 项目名称是'TestCore'

步骤:

  1. 在 visual studio 项目文件夹中,您需要找到隐藏文件夹 .vs,在此文件夹中,您需要找到文件夹 'config' 和文件 'applicationhost.config'

  2. 在此文件中,您需要找到与您的项目配置类似的以下部分:

       <location path="TestCore" inheritInChildApplications="false">
     <system.webServer>
       <modules>
         <remove name="WebMatrixSupportModule" />
       </modules>
       <handlers>
         <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
       </handlers>
       <aspNetCore processPath="%LAUNCHER_PATH%" stdoutLogEnabled="false" hostingModel="InProcess" startupTimeLimit="3600" requestTimeout="23:00:00" />
       <httpCompression>
         <dynamicTypes>
           <add mimeType="text/event-stream" enabled="false" />
         </dynamicTypes>
       </httpCompression>
     </system.webServer>
    
  3. 克隆(复制-粘贴)文件中的此部分并修改副本(更改路径并添加 sequryti 部分):

     <location path="TestCore/certificate/apply" inheritInChildApplications="false">
     <system.webServer>
         <modules>
             <remove name="WebMatrixSupportModule" />
         </modules>
         <handlers>
             <add name="aspNetCore" path="*" verb="*" modules="AspNetCoreModuleV2" resourceType="Unspecified" />
         </handlers>
         <aspNetCore processPath="%LAUNCHER_PATH%" stdoutLogEnabled="false" hostingModel="InProcess" startupTimeLimit="3600" requestTimeout="23:00:00" />
         <httpCompression>
             <dynamicTypes>
                 <add mimeType="text/event-stream" enabled="false" />
             </dynamicTypes>
         </httpCompression>
         <security>
             <access sslFlags="SslNegotiateCert" />
         </security>
     </system.webServer>
    
  4. 尝试启动项目(对我来说它工作正常)。

我希望我(或其他人)能为 IIS 找到相同的方法。