授權原則

此範例示範如何實作自定義宣告授權原則和相關聯的自定義服務授權管理員。 當服務對服務作業進行宣告型存取檢查,以及在存取檢查之前,授與呼叫端特定許可權時,這非常有用。 此範例會顯示新增宣告的程式,以及針對完成的宣告集執行存取檢查的程式。 用戶端與伺服器之間的所有應用程式訊息都會經過簽署和加密。 預設情況下,wsHttpBinding 綁定會使用用戶端提供的使用者名稱和密碼來登入有效的 Windows 帳戶。 此範例示範如何使用自定義 UserNamePasswordValidator 來驗證用戶端。 此外,此範例會顯示使用 X.509 憑證向服務進行驗證的用戶端。 此範例展示IAuthorizationPolicy和ServiceAuthorizationManager的實作,它們之間提供了特定用戶對服務中特定方法的存取權。 此範例以訊息安全性使用者名稱為基礎,並示範如何在ServiceAuthorizationManager被呼叫之前執行宣告轉換。

備註

此範例的安裝程式和建置指示位於本主題結尾。

總而言之,此範例示範如何:

  • 用戶端可以使用使用者名稱密碼進行驗證。

  • 用戶端可以使用 X.509 憑證進行驗證。

  • 伺服器會針對自定義 UsernamePassword 驗證程式驗證客戶端認證。

  • 伺服器是使用伺服器的 X.509 憑證進行驗證。

  • 伺服器可用來 ServiceAuthorizationManager 控制服務中特定方法的存取。

  • 如何實作 IAuthorizationPolicy。

服務會公開兩個端點來與服務通訊,此端點是使用組態檔 App.config所定義。每個端點都包含位址、系結和合約。 一個繫結已配置為使用標準的 wsHttpBinding 繫結,採用 WS-Security 和用戶名稱驗證。 另一個繫結是以使用 WS-Security 和客戶端憑證驗證的標準 wsHttpBinding 繫結來設定。 行為<>會指定使用者認證要用於服務驗證。 伺服器證書必須包含與 SubjectName 中findValue屬性相同的<屬性值。

<system.serviceModel>
  <services>
    <service name="Microsoft.ServiceModel.Samples.CalculatorService"
             behaviorConfiguration="CalculatorServiceBehavior">
      <host>
        <baseAddresses>
          <!-- configure base address provided by host -->
          <add baseAddress ="http://localhost:8001/servicemodelsamples/service"/>
        </baseAddresses>
      </host>
      <!-- use base address provided by host, provide two endpoints -->
      <endpoint address="username"
                binding="wsHttpBinding"
                bindingConfiguration="Binding1"
                contract="Microsoft.ServiceModel.Samples.ICalculator" />
      <endpoint address="certificate"
                binding="wsHttpBinding"
                bindingConfiguration="Binding2"
                contract="Microsoft.ServiceModel.Samples.ICalculator" />
    </service>
  </services>

  <bindings>
    <wsHttpBinding>
      <!-- Username binding -->
      <binding name="Binding1">
        <security mode="Message">
    <message clientCredentialType="UserName" />
        </security>
      </binding>
      <!-- X509 certificate binding -->
      <binding name="Binding2">
        <security mode="Message">
          <message clientCredentialType="Certificate" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>

  <behaviors>
    <serviceBehaviors>
      <behavior name="CalculatorServiceBehavior" >
        <serviceDebug includeExceptionDetailInFaults ="true" />
        <serviceCredentials>
          <!--
          The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.
          -->
          <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.ServiceModel.Samples.MyCustomUserNameValidator, service" />
          <!--
          The serviceCredentials behavior allows one to specify authentication constraints on client certificates.
          -->
          <clientCertificate>
            <!--
            Setting the certificateValidationMode to PeerOrChainTrust means that if the certificate
            is in the user's Trusted People store, then it will be trusted without performing a
            validation of the certificate's issuer chain. This setting is used here for convenience so that the
            sample can be run without having to have certificates issued by a certification authority (CA).
            This setting is less secure than the default, ChainTrust. The security implications of this
            setting should be carefully considered before using PeerOrChainTrust in production code.
            -->
            <authentication certificateValidationMode="PeerOrChainTrust" />
          </clientCertificate>
          <!--
          The serviceCredentials behavior allows one to define a service certificate.
          A service certificate is used by a client to authenticate the service and provide message protection.
          This configuration references the "localhost" certificate installed during the setup instructions.
          -->
          <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
        </serviceCredentials>
        <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Samples.MyServiceAuthorizationManager, service">
          <!--
          The serviceAuthorization behavior allows one to specify custom authorization policies.
          -->
          <authorizationPolicies>
            <add policyType="Microsoft.ServiceModel.Samples.CustomAuthorizationPolicy.MyAuthorizationPolicy, PolicyLibrary" />
          </authorizationPolicies>
        </serviceAuthorization>
      </behavior>
    </serviceBehaviors>
  </behaviors>

</system.serviceModel>

每個用戶端端點組態都包含組態名稱、服務端點的絕對位址、系結和合約。 在本案例中,用戶端系結依據<安全性>以及依據clientCredentialType所指定的適當安全性模式進行設定。

<system.serviceModel>

    <client>
      <!-- Username based endpoint -->
      <endpoint name="Username"
            address="http://localhost:8001/servicemodelsamples/service/username"
    binding="wsHttpBinding"
    bindingConfiguration="Binding1"
                behaviorConfiguration="ClientCertificateBehavior"
                contract="Microsoft.ServiceModel.Samples.ICalculator" >
      </endpoint>
      <!-- X509 certificate based endpoint -->
      <endpoint name="Certificate"
                        address="http://localhost:8001/servicemodelsamples/service/certificate"
                binding="wsHttpBinding"
            bindingConfiguration="Binding2"
                behaviorConfiguration="ClientCertificateBehavior"
                contract="Microsoft.ServiceModel.Samples.ICalculator">
      </endpoint>
    </client>

    <bindings>
      <wsHttpBinding>
        <!-- Username binding -->
      <binding name="Binding1">
        <security mode="Message">
          <message clientCredentialType="UserName" />
        </security>
      </binding>
        <!-- X509 certificate binding -->
        <binding name="Binding2">
          <security mode="Message">
            <message clientCredentialType="Certificate" />
          </security>
        </binding>
    </wsHttpBinding>
    </bindings>

    <behaviors>
      <behavior name="ClientCertificateBehavior">
        <clientCredentials>
          <serviceCertificate>
            <!--
            Setting the certificateValidationMode to PeerOrChainTrust
            means that if the certificate
            is in the user's Trusted People store, then it will be
            trusted without performing a
            validation of the certificate's issuer chain. This setting
            is used here for convenience so that the
            sample can be run without having to have certificates
            issued by a certification authority (CA).
            This setting is less secure than the default, ChainTrust.
            The security implications of this
            setting should be carefully considered before using
            PeerOrChainTrust in production code.
            -->
            <authentication certificateValidationMode = "PeerOrChainTrust" />
          </serviceCertificate>
        </clientCredentials>
      </behavior>
    </behaviors>

  </system.serviceModel>

針對使用者名稱型端點,用戶端實作會設定要使用的使用者名稱和密碼。

// Create a client with Username endpoint configuration
CalculatorClient client1 = new CalculatorClient("Username");

client1.ClientCredentials.UserName.UserName = "test1";
client1.ClientCredentials.UserName.Password = "1tset";

try
{
    // Call the Add service operation.
    double value1 = 100.00D;
    double value2 = 15.99D;
    double result = client1.Add(value1, value2);
    Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
    ...
}
catch (Exception e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
}

client1.Close();

針對憑證型端點,用戶端實作會設定要使用的用戶端憑證。

// Create a client with Certificate endpoint configuration
CalculatorClient client2 = new CalculatorClient("Certificate");

client2.ClientCredentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindBySubjectName, "test1");

try
{
    // Call the Add service operation.
    double value1 = 100.00D;
    double value2 = 15.99D;
    double result = client2.Add(value1, value2);
    Console.WriteLine("Add({0},{1}) = {2}", value1, value2, result);
    ...
}
catch (Exception e)
{
    Console.WriteLine("Call failed : {0}", e.Message);
}

client2.Close();

此範例會使用自定義 UserNamePasswordValidator 來驗證使用者名稱和密碼。 範例中實作了從MyCustomUserNamePasswordValidator衍生出的UserNamePasswordValidator。 如需詳細資訊,請參閱相關 UserNamePasswordValidator 文件。 為了示範與 UserNamePasswordValidator的整合,這個自定義驗證程式範例會實 Validate 作 方法來接受使用者名稱/密碼組,其中用戶名稱符合密碼,如下列程式代碼所示。

public class MyCustomUserNamePasswordValidator : UserNamePasswordValidator
{
  // This method validates users. It allows in two users,
  // test1 and test2 with passwords 1tset and 2tset respectively.
  // This code is for illustration purposes only and
  // MUST NOT be used in a production environment because it
  // is NOT secure.
  public override void Validate(string userName, string password)
  {
    if (null == userName || null == password)
    {
      throw new ArgumentNullException();
    }

    if (!(userName == "test1" && password == "1tset") && !(userName == "test2" && password == "2tset"))
    {
      throw new SecurityTokenException("Unknown Username or Password");
    }
  }
}

在服務程式代碼中實作驗證程序之後,必須通知服務主機要使用的驗證程序實例。 這是使用下列程式代碼完成的:

Servicehost.Credentials.UserNameAuthentication.UserNamePasswordValidationMode = UserNamePasswordValidationMode.Custom;
serviceHost.Credentials.UserNameAuthentication.CustomUserNamePasswordValidator = new MyCustomUserNamePasswordValidatorProvider();

或者,您可以在設定中執行相同的動作:

<behavior>
    <serviceCredentials>
      <!--
      The serviceCredentials behavior allows one to specify a custom validator for username/password combinations.
      -->
      <userNameAuthentication userNamePasswordValidationMode="Custom" customUserNamePasswordValidatorType="Microsoft.ServiceModel.Samples.MyCustomUserNameValidator, service" />
    ...
    </serviceCredentials>
</behavior>

Windows Communication Foundation (WCF) 提供豐富的宣告型模型來執行存取檢查。 對象 ServiceAuthorizationManager 可用來執行存取檢查,並判斷與客戶端相關聯的宣告是否符合存取服務方法所需的需求。

為了展示的目的,這個範例中展示的 ServiceAuthorizationManager 實作會實現 CheckAccessCore 方法,以允許使用者根據類型為 http://example.com/claims/allowedoperation 的宣告來存取方法,而其值是允許呼叫的操作的 Action URI。

public class MyServiceAuthorizationManager : ServiceAuthorizationManager
{
  protected override bool CheckAccessCore(OperationContext operationContext)
  {
    string action = operationContext.RequestContext.RequestMessage.Headers.Action;
    Console.WriteLine("action: {0}", action);
    foreach(ClaimSet cs in operationContext.ServiceSecurityContext.AuthorizationContext.ClaimSets)
    {
      if ( cs.Issuer == ClaimSet.System )
      {
        foreach (Claim c in cs.FindClaims("http://example.com/claims/allowedoperation", Rights.PossessProperty))
        {
          Console.WriteLine("resource: {0}", c.Resource.ToString());
          if (action == c.Resource.ToString())
            return true;
        }
      }
    }
    return false;
  }
}

實作自訂 ServiceAuthorizationManager 之後,必須通知服務主機所要使用的 ServiceAuthorizationManager。 這會如下列程式代碼所示完成。

<behavior>
    ...
    <serviceAuthorization serviceAuthorizationManagerType="Microsoft.ServiceModel.Samples.MyServiceAuthorizationManager, service">
        ...
    </serviceAuthorization>
</behavior>

主要要實作的IAuthorizationPolicy方法是Evaluate(EvaluationContext, Object)方法。

public class MyAuthorizationPolicy : IAuthorizationPolicy
{
    string id;

    public MyAuthorizationPolicy()
    {
    id =  Guid.NewGuid().ToString();
    }

    public bool Evaluate(EvaluationContext evaluationContext,
                                            ref object state)
    {
        bool bRet = false;
        CustomAuthState customstate = null;

        if (state == null)
        {
            customstate = new CustomAuthState();
            state = customstate;
        }
        else
            customstate = (CustomAuthState)state;
        Console.WriteLine("In Evaluate");
        if (!customstate.ClaimsAdded)
        {
           IList<Claim> claims = new List<Claim>();

           foreach (ClaimSet cs in evaluationContext.ClaimSets)
              foreach (Claim c in cs.FindClaims(ClaimTypes.Name,
                                         Rights.PossessProperty))
                  foreach (string s in
                        GetAllowedOpList(c.Resource.ToString()))
                  {
                       claims.Add(new
               Claim("http://example.com/claims/allowedoperation",
                                    s, Rights.PossessProperty));
                            Console.WriteLine("Claim added {0}", s);
                      }
                   evaluationContext.AddClaimSet(this,
                           new DefaultClaimSet(this.Issuer,claims));
                   customstate.ClaimsAdded = true;
                   bRet = true;
                }
         else
         {
              bRet = true;
         }
         return bRet;
     }
...
}

上述程式代碼展示了方法如何透過 Evaluate(EvaluationContext, Object) 檢查是否已新增任何會影響處理的新宣告,並添加特定的宣告。 允許的宣告是從GetAllowedOpList 方法取得的,它的實作會傳回一個使用者被允許執行的特定操作清單。 授權政策會為特定操作新增存取許可的聲明。 這稍後會由 ServiceAuthorizationManager 用來執行存取檢查決策。

實作自定義 IAuthorizationPolicy 之後,必須通知服務主機要使用的授權原則。

<serviceAuthorization>
       <authorizationPolicies>
            <add policyType='Microsoft.ServiceModel.Samples.CustomAuthorizationPolicy.MyAuthorizationPolicy, PolicyLibrary' />
       </authorizationPolicies>
</serviceAuthorization>

當您執行範例時,作業要求和回應會顯示在用戶端控制台視窗中。 用戶端成功呼叫 Add、Subtract 和 Multiple 方法,並在嘗試呼叫 Divide 方法時取得「拒絕存取」訊息。 在客戶端視窗中按 ENTER 鍵以關閉用戶端。

設定批次處理檔

此範例隨附的 Setup.bat 批處理檔可讓您設定具有相關憑證的伺服器,以執行需要伺服器證書式安全性的自我裝載應用程式。

下列提供批處理檔不同區段的簡短概觀,以便修改它們以在適當的組態中執行:

  • 建立伺服器證書。

    Setup.bat 批處理檔中的下列幾行會建立要使用的伺服器證書。 %SERVER_NAME% 變數會指定伺服器名稱。 變更此變數以指定您自己的伺服器名稱。 預設值為localhost。

    echo ************
    echo Server cert setup starting
    echo %SERVER_NAME%
    echo ************
    echo making server cert
    echo ************
    makecert.exe -sr LocalMachine -ss MY -a sha1 -n CN=%SERVER_NAME% -sky exchange -pe
    
  • 將伺服器證書安裝到用戶端的受信任證書存儲中。

    Setup.bat 批處理檔中的下列幾行會將伺服器證書複製到用戶端信任的人員存放區。 此步驟是必要的,因為客戶端系統不會隱含信任 Makecert.exe 所產生的憑證。 如果您已經有根植於用戶端受信任的根證書的憑證,例如,Microsoft 發行的憑證,則不需要執行使用伺服器證書填入用戶端證書存儲的此步驟。

    certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
    
  • 建立客戶端憑證。

    Setup.bat 批處理檔中的下列幾行會建立要使用的用戶端憑證。 %USER_NAME% 變數會指定伺服器名稱。 此值設定為 「test1」 因為這是所 IAuthorizationPolicy 尋找的名稱。 如果您變更 %USER_NAME% 的值,則必須變更 方法中的 IAuthorizationPolicy.Evaluate 對應值。

    憑證會儲存在 CurrentUser 存放區位置下的 My (Personal) 存放區中。

    echo ************
    echo making client cert
    echo ************
    makecert.exe -sr CurrentUser -ss MY -a sha1 -n CN=%CLIENT_NAME% -sky exchange -pe
    
  • 將客戶端憑證安裝到伺服器的受信任證書存儲中。

    Setup.bat 批處理檔中的下列幾行會將客戶端憑證複製到受信任的人員存放區。 此步驟是必要的,因為伺服器系統不會隱含信任 Makecert.exe 所產生的憑證。 如果您已經擁有一個包含在受信任的根憑證中的憑證,例如Microsoft核發的憑證,則不需要執行將用戶端憑證填入伺服器憑證存儲區的這個步驟。

    certmgr.exe -add -r CurrentUser -s My -c -n %CLIENT_NAME% -r LocalMachine -s TrustedPeople
    

若要設定和建置範例

  1. 若要建置解決方案,請遵循 建置 Windows Communication Foundation 範例中的指示。

  2. 若要在單一或跨計算機組態中執行範例,請使用下列指示。

備註

如果您使用 Svcutil.exe 重新產生此範例的組態,請務必修改用戶端組態中的端點名稱,以符合客戶端程序代碼。

在同一部電腦上執行這個範例

  1. 使用系統管理員許可權開啟 Visual Studio 的開發人員命令提示字元,然後從範例安裝資料夾執行 Setup.bat 。 這會安裝執行範例所需的所有憑證。

    備註

    Setup.bat 批處理文件的設計目的是要從 Visual Studio 的開發人員命令提示字元執行。 Visual Studio 開發人員命令提示字元內設定的PATH環境變數會指向包含 Setup.bat 腳本所需可執行檔案的目錄。

  2. 從 service\bin 啟動 Service.exe。

  3. 從 \client\bin 啟動 Client.exe。 用戶端活動會顯示在用戶端主控台應用程式上。

如果客戶端和服務無法通訊,請參閱 WCF 範例 的疑難解答秘訣。

在多台電腦上執行範例

  1. 在服務電腦上建立目錄。

  2. 將服務程序檔從 \service\bin 複製到服務計算機上的目錄。 同時將 Setup.bat、Cleanup.bat、GetComputerName.vbs 和 ImportClientCert.bat 檔案複製到服務計算機。

  3. 在用戶端電腦上為用戶端二進位檔建立目錄。

  4. 將用戶端程式檔案複製到用戶端電腦上的客戶端目錄。 同時將 Setup.bat、Cleanup.bat和 ImportServiceCert.bat 檔案複製到用戶端。

  5. 在伺服器上,以系統管理員許可權開啟的 Visual Studio 開發人員命令提示字元中執行 setup.bat service。

    使用setup.bat參數執行service會建立具有電腦完整功能變數名稱的服務憑證,並將服務憑證匯出至名為Service.cer的檔案。

  6. 編輯 Service.exe.config 以反映新的憑證名稱(在 findValueserviceCertificate< 中的屬性 >),該名稱與電腦的完整域名稱相同。 此外,將 service/<baseAddresses> 元素中的 <> 從 localhost 變更為服務電腦的完整名稱。

  7. 將 Service.cer 檔案從服務目錄複製到用戶端電腦上的客戶端目錄。

  8. 在客戶端電腦上,於以系統管理員權限開啟的 Visual Studio 開發人員命令提示字元中執行 setup.bat client。

    使用 自變數執行setup.batclient會建立名為test1的客戶端憑證,並將客戶端憑證匯出至名為 Client.cer的檔案。

  9. 在用戶端電腦上的 Client.exe.config 檔案中,變更端點的位址值,以符合您服務的新位址。 將 localhost 取代為伺服器的完整功能變數名稱來執行此動作。

  10. 將Client.cer檔案從客戶端目錄複製到伺服器上的服務目錄。

  11. 在用戶端上,以系統管理員許可權開啟的Visual Studio開發人員命令提示字元中執行 ImportServiceCert.bat 。

    這會將服務憑證從Service.cer檔案匯入 CurrentUser - TrustedPeople 存放區。

  12. 在伺服器上,以系統管理員許可權開啟的Visual Studio開發人員命令提示字元中執行 ImportClientCert.bat 。

    這會將客戶端憑證從 Client.cer 檔案匯入 LocalMachine - TrustedPeople 存放區。

  13. 在伺服器電腦上,從命令提示字元窗口啟動 Service.exe。

  14. 在用戶端電腦上,從命令提示字元窗口啟動 Client.exe。

    如果客戶端和服務無法通訊,請參閱 WCF 範例 的疑難解答秘訣。

在範例之後清除

若要在範例之後清除,請在完成範例執行時,在samples資料夾中執行 Cleanup.bat 。 這會從證書存儲中移除伺服器和客戶端憑證。

備註

在跨電腦執行此範例時,此腳本不會移除用戶端上的服務憑證。 如果您已執行跨計算機使用憑證的 WCF 範例,請務必清除已在 CurrentUser - TrustedPeople 存放區中安裝的服務憑證。 若要這樣做,請使用下列命令:certmgr -del -r CurrentUser -s TrustedPeople -c -n <Fully Qualified Server Machine Name> 例如:certmgr -del -r CurrentUser -s TrustedPeople -c -n server1.contoso.com。