65.9K
CodeProject 正在变化。 阅读更多。
Home

Web API 想法 2/3 - 处理 HTTPS

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.99/5 (122投票s)

2014年11月4日

CPOL

7分钟阅读

viewsIcon

152653

downloadIcon

2505

与 ASP.NET Web API 相关的项目。通过关于数据流、使用 HTTPS 和扩展 Web API 文档的文章,涉及该技术的大部分方面。

这是关于主题的延续部分。有关回顾,请参阅 WebAPI 想法 - 简介 文章。

目录

使用 HTTPS

HTTPS/SSL(Hypertext Transfer Protocol Secure/Secure Socket Layer)是一种协议,用于在计算机网络(如互联网)上安全地传输消息/资源。使用此协议的站点可以通过查看站点 URL 轻松识别。例如,在 Google Chrome 中浏览启用了 HTTPS 的站点时,会显示一个锁形图标,后跟“https”和站点 URL 的其余部分。请参见下图。

Chrome 浏览器
已验证的 Https 未验证的 Https

那么,我们可以从这些图片中观察到哪些内容?

  • 证书 - 技术上称为数字证书,它是一个数字签名的声明,将公钥的值绑定到持有相应私钥的个人、设备或服务的标识。
  • 证书颁发机构 (CA) - 是颁发数字证书的可信实体。从上图可以看出,VeriSign 是一个 CA,它验证 URL 是否为受信任的 URL。

那么,如果您访问了启用了 HTTPS 的网站,会发生什么?

  • 您信任浏览器使用现有的受信任 CA。
  • 您信任 CA 来验证受信任的站点。
  • 该站点包含一个可验证的证书。
  • CA 会识别并验证站点的证书。如果成功,浏览器地址栏会显示一个绿色密钥图标以及 https 站点 URL。如果失败,浏览器地址栏会显示一个红色密钥图标,其中包含被划掉的 https 站点 URL。失败的访问还会显示一个警告/错误内容,说明“这可能不是您要查找的网站!”“此网站的安全证书有问题”。具体消息内容因浏览器而异。
  • 最后,您允许浏览器访问该站点。

注意:证书的验证和身份验证背后有许多过程,本文档不会涵盖。

参与的项目

  • WebAPISecureSocketLayering
  • WebAPIClient
  • WebAPICommonLibrary
  • POCOLibrary
  • 资产

那么,如何在 Web API 服务中使用此协议?该框架通过定义、验证和使用服务提供了多种使用此协议的方法。各种方法和步骤将很快解释。

  1. 在 IIS 中部署的 Web API 服务上启用 HTTPS(高级步骤)

    • 获取 SSL 证书
    • 在服务所在的服务器上安装证书
    • 为服务创建 HTTPS 绑定
      • 将证书绑定到服务
      • 选择“默认网站”,然后选择“操作”窗格中的“编辑网站”->“绑定...”
      • 点击“添加”
      • 类型:https
      • IP 地址:“所有未分配”
      • 端口:443
      • SSL 证书:[您的证书名称]
      • 点击“确定”
      • 点击“关闭”
    • 浏览网站,确保您可以通过 HTTPS 进行通信

    详细信息可在参考部分找到。

  2. 在 IIS 7.5 上创建自签名证书并将其绑定到 Web API 服务的 HTTPS 类型

    • 打开 IIS
    • 选择“网站”文件夹
    • 选择“默认网站”
    • 在“功能视图”中,双击“服务器证书”
    • 在“操作”窗格中,点击“创建自签名证书”
    • 在“创建自签名证书”页面上,为证书指定一个[友好名称],然后点击“确定”。
    • 这将创建一个证书并将其添加到“受信任的根证书颁发机构”下。如果未添加,请按照“Assets/Trusted Root Certification Authorities”文件夹下的演示图片进行操作。
    • 将证书绑定到所需的 Web API 服务。有关绑定,请参阅上一小节 将证书绑定到服务

    提示:分步演示图片可在“Assets/Creating Self-signed Certificate And Binding to Service or Web site”文件夹下找到。

  3. 使用 IIS Express 为 Web API 项目启用 SSL

    • 选择您的 Web API 项目
    • 转到“属性”窗口

    • 将“SSL 已启用”属性设置为 True 将生成 SSL URL。请参见下图

导出证书

  • 打开证书管理器工具(Certmgr.msc)。开始 -> 运行 -> Certmgr.msc
  • 按照“Assets/Exporting Certificate”文件夹下的图片中显示的步骤操作。

注意:导出证书时,请务必不要包含私钥。

验证 HTTPS 请求 URL 和证书

在服务器端处理客户端请求之前,有多种验证方法。例如:

  1. 使用 DelegateHandler 验证证书
  2. 使用 AuthorizationFilter 验证 HTTPS 请求 URL
    1. 使用 DelegateHandler 验证证书

      Web API 服务请求可以根据客户端发送的证书进行验证。如果不成功,服务会向客户端发送适当的消息。将使用 Http Delegate Handler 在执行请求方法之前验证证书。该验证会检查随请求一起发送的证书指纹。一旦提供了一个证书指纹,它将被验证为有效证书。如果成功,它将允许执行所请求的服务方法。
      /// <summary>
      /// Certificate validator handler class
      /// </summary>
      public class CertificateValidationHandler : DelegatingHandler
      {
          /// <summary>
          /// Send request asynchronously
          /// </summary>
          /// </param name="request">HttpRequestMessage value</param>
          /// </param name="cancellationToken">CancellationToken value</param>
          /// <returns>HttpResponseMessage object</returns>
          protected override System.Threading.Tasks.Task<HttpResponseMessage>
                  SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
          {
              HttpResponseMessage response = ValidateCertificate(request);
              if (response.StatusCode == HttpStatusCode.OK)
                  return base.SendAsync(request, cancellationToken);
              else
                  return Task<HttpResponseMessage>.Factory.StartNew(() => response);
          }
          
         /// <summary>
         /// Validate Certificate
         /// </summary>
         /// </param name="request">HttpRequestMessage value</param>
         /// <returns>HttpResponseMessage object</returns>
         private HttpResponseMessage ValidateCertificate(HttpRequestMessage request)
         {
              IEnumerable<string> thumbPrints;
      
              if (!request.Headers.TryGetValues("Thumbprint", out thumbPrints) ||
                  thumbPrints == null ||
                  (thumbPrints != null && thumbPrints.Count() == 0))
              {
                  return request.CreateResponse(HttpStatusCode.NotAcceptable, new Message() 
                  { Content = "Thumbprint request header is not available !" });
              }
              try
              {
                  List<StoreLocation> locations = 
                          new List<StoreLocation> // Certificate location indicators
                  {    
                      StoreLocation.CurrentUser, 
                      StoreLocation.LocalMachine
                  };
      
                  bool? verified = null; // A flag used to check Certificate validation
      
                  OpenFlags openFlags = OpenFlags.OpenExistingOnly | 
                                        OpenFlags.ReadOnly;
      
                  List<string> thumbPrintCollection = new List<string>();
      
                  if (thumbPrints.FirstOrDefault().Contains(',')) // Has many thumbprints
                  {
                      thumbPrintCollection.AddRange(thumbPrints.FirstOrDefault().Split(','));
                  }
                  else
                  {
                      thumbPrintCollection.Add(thumbPrints.FirstOrDefault());
                  }
      
                  // .........................................
                  // Full code available in the source control
                  // .........................................            
              }
              catch (Exception exception)
              {
                  // Log error
                  return request.CreateResponse(HttpStatusCode.BadRequest, new Message() 
                  { Content = string.Concat
                  ("Exception happens while processing certificate ! \n", exception) });
              }
         }
      } 

      创建证书委托后,它将如下所示在 http 配置下注册

       public static class WebApiConfig
       {
          public static void Register(HttpConfiguration config)
          {
              // .........................................
              // Full code available in the source control
              // .........................................
              config.MessageHandlers.Add(new CertificateValidationHandler());
          }
       } 
    2. 使用 AuthorizationFilter 验证 HTTPS 请求 URL

      另一种验证方法是强制所有传入请求仅通过 HTTPS 协议传输。这可以通过将授权过滤器属性应用于所有服务方法或服务内暴露的特定方法来实现。以下代码显示了如何执行此操作

      /// <summary>
      /// Https URI validator class
      /// </summary>
      public class HttpsValidator : AuthorizationFilterAttribute
      {
          ///  <summary>
          ///  Validate request URI
          ///  </summary>
          ///  <param name="actionContext">HttpActionContext value</param>
          public override void OnAuthorization(HttpActionContext actionContext)
          {
              if (actionContext != null && actionContext.Request != null && 
              !actionContext.Request.RequestUri.Scheme.Equals(Uri.UriSchemeHttps))
              {
                  var controllerFilters = actionContext.ControllerContext.ControllerDescriptor.GetFilters();
                  var actionFilters = actionContext.ActionDescriptor.GetFilters();
      
                  if ((controllerFilters != null && controllerFilters.Select
                  (t => t.GetType() == typeof(HttpsValidator)).Count() > 0) ||
                      (actionFilters != null && actionFilters.Select(t => 
                      t.GetType() == typeof(HttpsValidator)).Count() > 0))
                  {
                          actionContext.Response = actionContext.Request.CreateResponse(HttpStatusCode.Forbidden, 
                              new Message() { Content = "Requested URI requires HTTPS" }, 
                              new MediaTypeHeaderValue("text/json"));
                  }
              }
          }
      } 

      创建 Https 授权过滤器后,与证书委托类似,它将如下所示在 http 配置下注册

      public static class WebApiConfig
      {
          public static void Register(HttpConfiguration config)
          {
              // .........................................
              // Full code available in the source control
              // .........................................
              config.Filters.Add(new HttpsValidator());
          }
      } 

      配置完成后,它将验证整个 Web API 控制器或特定方法,如下所示

      [HttpsValidator] // Enforce HTTPS request to the controller
      public class PhysicianController : PhysicianBaseController
      {              
          // .........................................
          // Full code available in the source control
          // .........................................
      } 

如何在客户端应用程序中使用

到目前为止,Web API 服务已准备好使用 HTTPS 协议。让我们看看如何在客户端应用程序中使用它。有几种方法可以消耗启用了 HTTPS 的 Web API 服务。为了演示,我将使用一个托管在 IIS 上的自签名 HTTPS 启用服务,以及一个使用 ASP.NET Self-Host 和 OWIN 的自托管 HTTPS 启用 Web API 服务。

  1. 使用 IIS 托管的 HTTPS 启用 Web API 服务

    正如我之前讨论过的,Web API 服务要求将证书指纹作为请求的一部分进行传递。为了发送请求,客户端必须拥有证书文件或证书的指纹值。一旦获得证书指纹,它将被附加到 http 请求头。

    提示:如果证书不可用,请参阅 导出证书 部分。

    这是发送带有证书的请求的客户端代码。

    /// <summary>
    /// Call IIS Https Service
    /// </summary>
    /// <returns>Awaitable task object</returns>
    private async static Task CallIISHttpsService()
    {
        Console.ForegroundColor = ConsoleColor.Green;
        Console.WriteLine("Start processing IIS Https service operation call");
    
        string actionURI = "physician/activephysicians";
    
        try
        {
            string uri = string.Concat(secureURL.AbsoluteUri, actionURI);
    
            Uri address = new Uri(uri);
    
            HttpRequestMessage request = new HttpRequestMessage
                    (HttpMethod.Get, address); // HttpMethod.Post is also works
    
            var cert = new X509Certificate2(File.ReadAllBytes(@"c:\MyCert.cer"));
            //var cert2 = new X509Certificate2(File.ReadAllBytes(@"c:\MyCert2.cer"));
    
            List<string> certs = new List<string>();
            certs.Add(cert.Thumbprint);
            //certs.Add(cert2.Thumbprint);
            request.Headers.Add("Thumbprint", certs);
    
            CancellationToken token = new CancellationToken();
    
            using (HttpClient httpClient = new HttpClient())
            {
                await httpClient.SendAsync(request, token).
                    ContinueWith((response)
                        =>
                        {
                            try
                            {
                                ProcessIISHttpsResponse(response);
                            }
                            catch (AggregateException aggregateException)
                            {
                                Console.ForegroundColor = ConsoleColor.Red;
                                Console.WriteLine
                                (string.Format("Exception {0}", aggregateException));
                            }
                        });
            }
        }
        catch (Exception exception)
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine(exception);
        }
    }
    
    /// <summary>
    /// Process IIS Https response object
    /// </summary>
    /// <param name="response">
    /// Awaitable HttpResponseMessage task value</param>
    private static void ProcessIISHttpsResponse(Task<HttpResponseMessage> response)
    {
        if (response.Result.StatusCode == HttpStatusCode.OK ||
            response.Result.StatusCode == HttpStatusCode.Created)
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine(string.Concat("Response message: \n",
                JsonConvert.SerializeObject
                (response.Result.Content.ReadAsAsync<List<PhysicianBase>>().TryResult(), 
                Formatting.Indented)));
        }
        else
        {
            ProcessFailResponse(response);
        }
    } 
  2. 使用 HTTPS 启用的自托管 Web API 服务

    ASP.NET Web API Self-Host 允许在不安装 IIS 的情况下使用 Web API 服务。一旦定义了 Web API 控制器,它将使用客户端应用程序中的 ASP.NET Self Host 库进行托管和使用。要使用该库
    • 将库安装到客户端应用程序
       PM > Install-Package Microsoft.AspNet.WebApi.SelfHost
    • 定义一个名为 PhysicianSelfHostController 的 Web API 控制器,其中有一个 CalculateSalaryRaise 方法
      /// <summary>
      /// Physician api controller class
      /// </summary>
      public class PhysicianSelfHostController : PhysicianBaseController
      {
          /// <summary>
          /// Calculate Salary raise
          /// </summary>
          /// <param name="request">HttpRequestMessage value</param>
          /// <returns>HttpResponseMessage object</returns>
          [AcceptVerbs("Post","Put")]
          public HttpResponseMessage CalculateSalaryRaise(HttpRequestMessage request)
          {
              try
              {
                  InternalPhysician physicianBase = 
                      request.Content.ReadAsAsync<InternalPhysician>().Result;
                  // Calculate fixed salary raise
                  physicianBase.Salary = physicianBase.CalculateSalaryRaise();
                  return Request.CreateResponse(HttpStatusCode.OK, 
                  physicianBase, new MediaTypeHeaderValue("text/json"));
              }
              catch (Exception exception)
              {
                  return Request.CreateErrorResponse
                      (HttpStatusCode.InternalServerError, exception);
              }
          }
      } 

      客户端代码将注册自托管 HTTPS 地址并尝试与 Web API 服务资源通信。由于我们通过 Https 进行通信,因此在访问请求的资源之前应执行证书验证。为此,我们使用 ServicePointManager 类通过调用 ServerCertificateValidationCallback 来验证证书,该回调调用一个返回 bool 的方法。

      /// <summary>
      /// Call SelfHost Https Service
      /// </summary>
      /// <returns>Awaitable task object</returns>
      private async static Task CallSelfHostHttpsService()
      {
          Console.ForegroundColor = ConsoleColor.Green;
          Console.WriteLine
              ("Start processing Self-Host Https service operation call");
      
          try
          {
              // Set up SelfHost configuration
              HttpSelfHostConfiguration config = 
                  new HttpSelfHostConfiguration(serverDefaultSecureURL);
      
              config.MapHttpAttributeRoutes();
      
              config.Routes.MapHttpRoute(
                  name: "SelfHostActionApi",
                  routeTemplate: "{controller}/{action}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
      
              config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = 
                            TypeNameHandling.Auto;
      
              // Create server
              using (HttpSelfHostServer server = new HttpSelfHostServer(config))
              {
                  // Open sever
                  server.OpenAsync().Wait();
                  Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
                  Console.WriteLine();
      
                  // In order to communicate with server, 
                  // need to validate remote server certificate 
                  ServicePointManager.ServerCertificateValidationCallback +=
                      new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
      
                  Uri address = new Uri
                  (serverDefaultSecureURL, "physicianselfhost/calculatesalaryraise");
                  await ProcessRaiseSalaryRequest(address);
      
                  server.CloseAsync().Wait();
              }
          }
          catch (Exception exception)
          {
              Console.ForegroundColor = ConsoleColor.Red;
              Console.WriteLine(exception);
          }
      }
      
      /// <summary>
      /// Process Raise Salary request object
      /// <param name="address">Request Uri value</param>
      /// </summary>
      /// <param name="response">Awaitable task object</param>
      private static async Task ProcessRaiseSalaryRequest(Uri address)
      {
          using (HttpClient httpClient = new HttpClient())
          {
              HttpRequestMessage request = new HttpRequestMessage();
              request.Method = HttpMethod.Post; // HttpMethod.Put is also works
              request.RequestUri = address;
      
              PhysicianBase physician = new InternalPhysician()
              {
                  FirstName = "Joe",
                  LastName = "Doe",
                  Salary = 120000
              };
      
              string requestobject = JsonConvert.SerializeObject(physician, Formatting.Indented);
              Console.ForegroundColor = ConsoleColor.Green;
              Console.WriteLine("Before Salary raised \n{0}", requestobject);
      
              request.Content = new ObjectContent(typeof(PhysicianBase), physician, 
              new JsonMediaTypeFormatter(), new MediaTypeHeaderValue("text/json"));
      
              await httpClient.SendAsync(request).
                  ContinueWith((response)
                  =>
                  {
                      try
                      {
                          ProcessRaiseSalaryResponse(response);
                      }
                      catch (AggregateException aggregateException)
                      {
                          Console.ForegroundColor = ConsoleColor.Red;
                          Console.WriteLine(string.Format
                              ("Exception {0}", aggregateException));
                      }
                  });
          }
      } 
  3. 使用 OWIN 使用 HTTPS 启用的 Web API 服务

    OWIN(Open Web Interface for .NET)是一个接口,有助于分离服务器和应用程序。与 Self-Host 类似,不需要安装 IIS 即可使用服务。要使用该库
    • 将库安装到客户端应用程序
       PM > Install-Package Install-Package Microsoft.AspNet.WebApi.Owin
    • 首先,让我们定义一个 ServerStartup 类,它有助于配置服务器
      /// <summary>
      /// OWIN Server StartUp class
      /// <summary>
      public class ServerStartup
      {
          public void Configuration(IAppBuilder appBuilder)
          {
              HttpListener listener = 
              (HttpListener)appBuilder.Properties["System.Net.HttpListener"];
      
              // For Https set as an Anonymous 
              listener.AuthenticationSchemes = AuthenticationSchemes.Anonymous;
      
              HttpConfiguration config = new HttpConfiguration();
      
              config.MapHttpAttributeRoutes();
      
              config.Routes.MapHttpRoute(
                  name: "OWINActionApi",
                  routeTemplate: "{controller}/{action}/{id}",
                  defaults: new { id = RouteParameter.Optional }
              );
      
              config.Formatters.JsonFormatter.SerializerSettings.TypeNameHandling = 
                                  TypeNameHandling.Auto;
              appBuilder.UseWebApi(config);
          }
      } 
    • 使用先前定义的 ApiControllerPhysicianSelfHostController
    • 定义一个与服务器通信的方法。
      /// <summary>
      /// Call OWIN Https Service
      /// </summary>
      /// <returns>Awaitable task object</returns>
      private async static Task CallOWINHttpsService()
      {
          Console.ForegroundColor = ConsoleColor.Green;
          Console.WriteLine("Start processing OWIN Https service operation call");
      
          try
          {
              // Create server
              using (WebApp.Start<ServerStartup>(serverDefaultSecureURL.AbsoluteUri))
              {
                  Console.WriteLine(string.Concat("Opening server ", serverDefaultSecureURL));
      
                  //In order to communicate with server,need to validate remote server certificate 
                  ServicePointManager.ServerCertificateValidationCallback +=
                      new RemoteCertificateValidationCallback(ValidateRemoteCertificate);
      
                  Uri address = new Uri(serverDefaultSecureURL, 
                      "physicianselfhost/calculatesalaryraise");
                  await ProcessRaiseSalaryRequest(address);
              }
          }
          catch (Exception exception)
          {
              Console.ForegroundColor = ConsoleColor.Red;
              Console.WriteLine(exception);
          }
      } 

下一步

下一节将介绍 扩展 Web API 文档

参考文献

历史和 GitHub 版本

© . All rights reserved.