WCF加密操作(包括證書和證書+帳號密碼)


  WCF作為.net三大組件之一,偉大之處不用多說,但是其加密配置對於我這樣的萌新來說還是頗有難度,因此將幾天來的研究成果共享出來,與各位共勉~

  首先聲明我的開發環境,Win10創意者更新 + Visual Studio 2015 update3 + .Net 4.5 + iis10

  一、創建X.509證書

    1、創建證書

    可通過PowerShell或者makecert工具兩種方式,個人建議使用參考資料更多后者,但最新的Windows和VS都不帶makecert,所以需要的話可以到文章結尾處下載

    使用CMD運行: 

makecert -sr CurrentUser -ss My -n CN=HelloServiceClient -sky exchange -pe -r

    提示Succeded即創建完成。

    此時將在當前用戶下的個人項目中看到這個證書,圖中MMC管理單元的使用可以參考這里

    

    2、設置為信任

    由於創建的證書在個人域,且不在信任鏈中,wcf和iis目前不能使用這個證書,一次需要將其設置為信任。

    首先先將其導出到磁盤:證書上右鍵--所有任務--導出--選擇導出私鑰--設置私鑰密碼,完成后將得到一個pfx文件。

    然后進入上圖的本地計算機,在個人域導入剛才那個pfx文件,完成后雙擊證書,在“證書路徑”標簽中提示“由於CA 根證書不在“受信任的根證書頒發機構”存儲區中,所以它不受信任。”,此時證書仍然不能被使用,我的做法是在本地計算機的“受信任的根證書頒發機構”重復導入一次。此時兩個證書都變成可信,即使將第二次導入的刪除也沒關系。

    以上做完沒問題的話,雙擊證書后的狀態應該是這樣的:

    

  二、通過證書加密的項目

    1、創建wcf服務

      VS中新建“WCF服務應用程序”的項目,命名為WCF_HelloService,此時不用任何修改,已經是可運行的wcf服務,然后將其部署到iis,在瀏覽器中可使用http訪問到服務信息:

      並重寫Service1.scv.cs中的GetData()方法:

        public string GetData(int value)
        {
            if (ServiceSecurityContext.Current != null)
            {
                if (!ServiceSecurityContext.Current.IsAnonymous)
                {
                    return "Hello:" + ServiceSecurityContext.Current.PrimaryIdentity.Name + ";type=" + ServiceSecurityContext.Current.PrimaryIdentity.AuthenticationType;
                }
                return "Hello,你輸入的是:" + value;
            }
            return "Hello ||未檢測到證書:" + value;
        }
View Code

      下面是重點,編輯服務的Web.config文件,使其訪問證書,這里尤其注意要注意用於各項配置互調的名稱設置,如behaviorConfiguration和bindingConfiguration等:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
    <httpModules>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/>
    </httpModules>
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="ApplicationInsightsWebTracking"/>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
        preCondition="managedHandler"/>
    </modules>
    <!--
        若要在調試過程中瀏覽 Web 應用程序根目錄,請將下面的值設置為 True。
        在部署之前將該值設置為 False 可避免泄露 Web 應用程序文件夾信息。
      -->
    <directoryBrowse enabled="true"/>
    <validation validateIntegratedModeConfiguration="false"/>
  </system.webServer>
  
  <system.serviceModel>
    <services>
      <service name="WCF_HelloService.HelloService" behaviorConfiguration="CustomBehavior">

        <endpoint
    binding="mexHttpBinding"
    contract="IMetadataExchange"
    address="mex" />
        <endpoint address="" binding="wsHttpBinding" contract="WCF_HelloService.IHelloService" bindingConfiguration="CustomBinding"/>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="CustomBehavior">
          <!-- 為避免泄漏元數據信息,請在部署前將以下值設置為 false 並刪除上面的元數據終結點 -->
          <serviceMetadata httpGetEnabled="true"/>
          <!-- 要接收故障異常詳細信息以進行調試,請將以下值設置為 true。在部署前設置為 false 以避免泄漏異常信息 -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
          
          <!--add by Lbh-->
          <serviceCredentials>
            <!-- 服務端采用證書詳細配置    findValue :創建證書名稱   storeName:證書儲存詳細位於哪    storeLocation :證書儲存位於當前本機用戶  X509FindType : x509查找證書主題名-->
            <serviceCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
            <!--客戶端驗證方式-->
            <clientCertificate>
              <authentication certificateValidationMode="None"/>
            </clientCertificate>
          </serviceCredentials>

        </behavior>
      </serviceBehaviors>
    </behaviors>
    <serviceHostingEnvironment multipleSiteBindingsEnabled="true" />
    
    <!--add by Lbh-->
    <bindings>
      <wsHttpBinding>
        <binding name="CustomBinding">
          <!--驗證方式-->
          <security mode="Message">
            <message clientCredentialType="Certificate"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

  </system.serviceModel>
</configuration>
View Code

      添加add by 注釋是添加的內容,注意serviceCertificate節點,這里定義了目的證書的信息,請務必使其指向我們剛才配置好的證書,其他諸如命名空間、接口、類名等也應與項目對應。

      配置完成后如無問題,刷新剛才的web頁面,我們仍然能看到服務啟動成功的頁面。

      2、配置客戶端

      隨便添加個winform程序,首先引用上面的服務,然后修改其app.config,同樣需要注意behaviorConfiguration設置:

      

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IHelloService">
                    <security mode="Message">
                        <transport clientCredentialType="Windows" />
                        <message clientCredentialType="Certificate" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
      <!--add by Lau-->
      <behaviors>
        <endpointBehaviors>
          <behavior name="CustomBehavior">
            <clientCredentials>
              <clientCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
              <serviceCertificate>
                <authentication certificateValidationMode="None"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>
      
      <client>
        <endpoint address="http://localhost:8096/HelloService.svc" behaviorConfiguration="CustomBehavior"
          binding="wsHttpBinding" bindingConfiguration="WSHttpBinding_IHelloService"
          contract="HelloService.IHelloService" name="WSHttpBinding_IHelloService">
          <identity>
            <certificate encodedValue="AwAAAAEAAAAUAAAAmIXXyLpHnm+H6oDaCP03aIn03SsgAAAAAQAAABUCAAAwggIRMIIBeqADAgECAhC1V8uCAl/avEkX078G+PlRMA0GCSqGSIb3DQEBBAUAMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDAeFw0xNzA1MDgwNzE1NDBaFw0zOTEyMzEyMzU5NTlaMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1+nEnhCxXtfAFxOGgFgzBjcPeO2WmxQI5SC14e6S4yEz+ymJtfKBcEnRSCX7onQDRE5H9dPl9CqoNjI/nkU5OKZ789f5Jh7ISfDK0jfHPa2EYwKK3FwOwGFmx5YY2/7Eb/nmyq6gbroronBIioFU6mcZjkFmTQTDa2WnZJMIsikCAwEAAaNSMFAwTgYDVR0BBEcwRYAQhYkF0TiSQwHAV/0wgMmvE6EfMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudIIQtVfLggJf2rxJF9O/Bvj5UTANBgkqhkiG9w0BAQQFAAOBgQA0LvNliWDaWtU4YkqXI8JU9/2mIHO2PK4EVUmUYJu0oxFNEeRcX8ZpAAAA26gRYN+J4IjC1F33NjRG/tzkGJeaTBdOl2SkJo8LqD2D7YfOcMaXfrAsAOcEP5e4z2Z4aZlZp1tOjf0X5SZ6QL4FbPiiJog+1UbF/z5J097peDU7Bw==" />
          </identity>
        </endpoint>
      </client>
    </system.serviceModel>
</configuration>
View Code

      與服務端類似地,clientCertificate節點定義了客戶端證書,本例中使用了服務端相同的證書,也可以創建另一個專供客戶端使用。certificate節點的內容來自服務端,引用WCF服務操作完成后會自動生成,如果沒有,請檢查WCF的web.config中是否定義為baseHttpBinding而不是wsHttpBinding(正確的是后者)。

      最后在winform加上基本的button和txtResult,並在button按鈕事件寫入代碼:

      

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                HelloService.HelloServiceClient client = new HelloService.HelloServiceClient();
                string result = client.GetData(DateTime.Now.Second);
                txtResult.Text = result;
            }
            catch (Exception ex)
            {
                this.txtResult.Text = ex.ToString();
            }
        }
View Code

      運行程序,得到正常結果如圖:

      

      並且通過http攔截到的都是密文:

      

 

      至此,第一個證書項目完成,demo請到文章結尾處下載

  三、通過證書+帳號密碼加密的項目

    1、創建WCF服務

      按上面步驟創建好服務,首先添加IdentityModel庫的引用:

      

      然后創建用於校驗的CustomUserPassword類,代碼如下:

using System.IdentityModel.Selectors;
using System.ServiceModel;

namespace TestUserPassService
{
    public class CustomUserPassword : UserNamePasswordValidator
    {
        public override void Validate(string userName, string password)
        {
            if (userName != "admin" || password != "admin")
            {
                //throw new SecurityNegotiationException("驗證用戶名和密碼時,未通過檢測");// 此異常可能無法被客戶端捕獲
                throw new FaultException("用戶名或者密碼錯誤!");
            }
        }
    }
}
View Code

       最后修改web.config文件,可以看到增加了userNameAuthentication節點,定義的正是自定義的校驗類:

<?xml version="1.0" encoding="utf-8"?>
<configuration>

  <appSettings>
    <add key="aspnet:UseTaskFriendlySynchronizationContext" value="true"/>
  </appSettings>
  <system.web>
    <compilation debug="true" targetFramework="4.5.2"/>
    <httpRuntime targetFramework="4.5.2"/>
    <httpModules>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"/>
    </httpModules>
  </system.web>
  <system.webServer>
    <modules runAllManagedModulesForAllRequests="true">
      <remove name="ApplicationInsightsWebTracking"/>
      <add name="ApplicationInsightsWebTracking" type="Microsoft.ApplicationInsights.Web.ApplicationInsightsHttpModule, Microsoft.AI.Web"
        preCondition="managedHandler"/>
    </modules>
    <!--
        若要在調試過程中瀏覽 Web 應用程序根目錄,請將下面的值設置為 True。
        在部署之前將該值設置為 False 可避免泄露 Web 應用程序文件夾信息。
      -->
    <directoryBrowse enabled="true"/>
    <validation validateIntegratedModeConfiguration="false"/>
  </system.webServer>
  <system.serviceModel>
    <services>
      <service name="TestUserPassService.Service1" behaviorConfiguration="CustomBehavior">

        <endpoint
    binding="mexHttpBinding"
    contract="IMetadataExchange"
    address="mex" />
        <endpoint address="" binding="wsHttpBinding" contract="TestUserPassService.IService1" bindingConfiguration="CustomBinding"/>
      </service>
    </services>
    
    <!--add by Lbh-->
    <behaviors>
      <serviceBehaviors>
        <behavior name="CustomBehavior">
          <!-- 為避免泄漏元數據信息,請在部署前將以下值設置為 false -->
          <serviceMetadata httpGetEnabled="true" httpsGetEnabled="true"/>
          <!-- 要接收故障異常詳細信息以進行調試,請將以下值設置為 true。在部署前設置為 false 以避免泄漏異常信息 -->
          <serviceDebug includeExceptionDetailInFaults="false"/>
          <serviceCredentials>
            <!-- 服務端采用證書詳細配置    findValue :創建證書名稱   storeName:證書儲存詳細位於哪    storeLocation :證書儲存位於當前本機用戶  X509FindType : x509查找證書主題名-->
            <serviceCertificate findValue="HelloServiceClient" storeName="My" storeLocation="LocalMachine" x509FindType="FindBySubjectName"/>
            <!--客戶端驗證方式-->
            <clientCertificate>
              <authentication certificateValidationMode="None"/>
            </clientCertificate>
            <userNameAuthentication  customUserNamePasswordValidatorType="TestUserPassService.CustomUserPassword,TestUserPassService" userNamePasswordValidationMode="Custom"/>
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <protocolMapping>
        <add binding="basicHttpsBinding" scheme="https"/>
    </protocolMapping>    
    <serviceHostingEnvironment aspNetCompatibilityEnabled="true" multipleSiteBindingsEnabled="true"/>
  
    <!--add by Lbh-->
    <bindings>
      <wsHttpBinding>
        <binding name="CustomBinding">
          <security mode="Message">
            <transport clientCredentialType="Windows"/>
            <message clientCredentialType="UserName"/>
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>

</configuration>
View Code

      注意clientCredentialType節點,這里采用映射到Windows賬戶的方式,這是頗為常用和可靠的方式。

      部署到iis,沒問題的話,我們仍然可以使用瀏覽器通過http訪問到服務。

    2、創建測試客戶端

      新建winform客戶端,首先添加引用,修改后的app.config如下:

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
    <startup> 
        <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.2" />
    </startup>
    <system.serviceModel>
        <bindings>
            <wsHttpBinding>
                <binding name="WSHttpBinding_IService1">
                  
                  <!--add by Lbh-->
                    <security mode="Message">
                        <transport clientCredentialType="Windows" />
                        <message clientCredentialType="UserName" />
                    </security>
                </binding>
            </wsHttpBinding>
        </bindings>
        <client>
            <endpoint address="http://localhost:8095/Service1.svc" binding="wsHttpBinding"
                bindingConfiguration="WSHttpBinding_IService1" contract="Service1.IService1"
                name="WSHttpBinding_IService1">
                <identity>
                    <certificate encodedValue="AwAAAAEAAAAUAAAAmIXXyLpHnm+H6oDaCP03aIn03SsgAAAAAQAAABUCAAAwggIRMIIBeqADAgECAhC1V8uCAl/avEkX078G+PlRMA0GCSqGSIb3DQEBBAUAMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDAeFw0xNzA1MDgwNzE1NDBaFw0zOTEyMzEyMzU5NTlaMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudDCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA1+nEnhCxXtfAFxOGgFgzBjcPeO2WmxQI5SC14e6S4yEz+ymJtfKBcEnRSCX7onQDRE5H9dPl9CqoNjI/nkU5OKZ789f5Jh7ISfDK0jfHPa2EYwKK3FwOwGFmx5YY2/7Eb/nmyq6gbroronBIioFU6mcZjkFmTQTDa2WnZJMIsikCAwEAAaNSMFAwTgYDVR0BBEcwRYAQhYkF0TiSQwHAV/0wgMmvE6EfMB0xGzAZBgNVBAMTEkhlbGxvU2VydmljZUNsaWVudIIQtVfLggJf2rxJF9O/Bvj5UTANBgkqhkiG9w0BAQQFAAOBgQA0LvNliWDaWtU4YkqXI8JU9/2mIHO2PK4EVUmUYJu0oxFNEeRcX8ZpAAAA26gRYN+J4IjC1F33NjRG/tzkGJeaTBdOl2SkJo8LqD2D7YfOcMaXfrAsAOcEP5e4z2Z4aZlZp1tOjf0X5SZ6QL4FbPiiJog+1UbF/z5J097peDU7Bw==" />
                </identity>
            </endpoint>
        </client>
    </system.serviceModel>
</configuration>
View Code

      可以看到,配置相比上一個項目簡單許多,因為這里的客戶端無需調用證書,只需定義加密類型。

      添加兩個textbox一個button和一個textResult,定義按鈕事件代碼:

using System;
using System.Windows.Forms;

namespace TestUserPassService_Client
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private void textBox1_TextChanged(object sender, EventArgs e)
        {

        }

        private void textBox2_TextChanged(object sender, EventArgs e)
        {

        }

        private void button1_Click(object sender, EventArgs e)
        {
            try
            {
                Service1.Service1Client client = new Service1.Service1Client();
                // 傳入帳號密碼
                client.ClientCredentials.UserName.UserName = this.textBox1.Text;
                client.ClientCredentials.UserName.Password = this.textBox2.Text;
                string result = client.GetData(DateTime.Now.Second);
                txtResult.Text = result;
            }
            catch (Exception ex)
            {
                this.txtResult.Text = ex.ToString();
            }
        }
    }
}
View Code

      運行客戶端,正確的結果如圖:

      

      假若修改傳入的帳號密碼,結果如下:

      

      查看http傳輸內容,同樣是密文:

      

      至此,本項目完成,demo可在文章結尾處下載

  四、總結

    其實wcf加密操作沒有太高深的內容(或者說暫且不用理會里面高深的內容),繁瑣的部分在於web.config和app.config的配置,尤其bindingConfiguration這類名稱命名上,由於網上教程眾多,東拉一塊西扯一塊拼起來是用不了的。比如我這樣的萌新調通兩個項目就花了2天時間,因此這篇文章也盡可能將容易踩到的雷點暴露出來,供后來者們借鑒。當然篇幅和能力有限不能面面俱到,也請各位諒解,有問題可以在下面回復或者請教谷歌。

  五、demo下載

  

  證書demo

  

    證書+帳號密碼demo

 

--------------------------------------------------------------------------------更新01------------------------------------------------------------------------------------------------------

  如果web訪問配置好的服務提示“密鑰集不存在”的問題,請按一下方法處理:

  進入路徑:C:\ProgramData\Microsoft\Crypto\RSA\MachineKeys(vista之后可用)

  找到剛才創建的證書文件,如果你不確定,可以參考這里

  然后右鍵-屬性-安全,保證IIS_IUSRS用戶有讀取該文件的權限(本機測試時IIS是由這個用戶運行的,其他電腦可能會有不同。)即可。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM