共用方式為


令牌驗證器

此範例示範如何實作自定義令牌驗證器。 Windows Communication Foundation (WCF) 中的令牌驗證器可用來驗證與訊息搭配使用的令牌、驗證其自我一致,以及驗證與令牌相關聯的身分識別。

自訂令牌驗證器在各種情況下很有用,例如:

  • 當您想要覆寫與令牌相關聯的預設身份驗證機制時。

  • 當您建置自定義令牌時。

此範例示範下列各項:

  • 用戶端如何使用使用者名稱/密碼組進行驗證。

  • 伺服器如何使用自定義令牌驗證器來驗證客戶端認證。

  • WCF 服務程式代碼如何與自定義令牌驗證器系結。

  • 如何使用伺服器的 X.509 憑證來驗證伺服器。

此範例也會示範在自定義令牌驗證程序之後,呼叫端的身分識別如何從WCF存取。

服務會公開單一端點,以便與服務通訊,並使用組態檔 App.config 定義。 端點是由位址、系結和合約所組成。 系結使用標準 wsHttpBinding 設定,安全模式設置為訊息 - 這是 wsHttpBinding 的預設模式。 此範例會設定使用用戶端用戶名稱驗證的標準 wsHttpBinding 。 服務也會使用 serviceCredentials 行為來設定服務憑證。 行為 securityCredentials 可讓您指定服務憑證。 用戶端會使用服務憑證來驗證服務並提供訊息保護。 下列組態會參考範例安裝期間所安裝的localhost憑證,如下列安裝指示所述。

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

    <bindings>
      <wsHttpBinding>
        <binding name="Binding1">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>

    <behaviors>
      <serviceBehaviors>
        <behavior name="CalculatorServiceBehavior">
          <serviceDebug includeExceptionDetailInFaults="False" />
          <!--
          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.
....        -->
          <serviceCredentials>
            <serviceCertificate findValue="localhost" storeLocation="LocalMachine" storeName="My" x509FindType="FindBySubjectName" />
          </serviceCredentials>
        </behavior>
      </serviceBehaviors>
    </behaviors>

  </system.serviceModel>

用戶端端點組態包含組態名稱、服務端點的絕對位址、系結和合約。 用戶端系結是使用適當的 ModeclientCredentialType來設定。

<system.serviceModel>
    <client>
      <endpoint name=""
                address="http://localhost:8000/servicemodelsamples/service"
                binding="wsHttpBinding"
                bindingConfiguration="Binding1"
                contract="Microsoft.ServiceModel.Samples.ICalculator">
      </endpoint>
    </client>

    <bindings>
      <wsHttpBinding>
        <binding name="Binding1">
          <security mode="Message">
            <message clientCredentialType="UserName" />
          </security>
        </binding>
      </wsHttpBinding>
    </bindings>
  </system.serviceModel>

用戶端實作會設定要使用的使用者名稱和密碼。

static void Main()
{
     ...
     client.ClientCredentials.UserNamePassword.UserName = username;
     client.ClientCredentials.UserNamePassword.Password = password;
     ...
}

自定義令牌驗證器

使用下列步驟來建立自訂權杖驗證器:

  1. 撰寫自定義令牌驗證器。

    此範例會實作自定義令牌驗證器,以驗證用戶名稱是否具有有效的電子郵件格式。 它會生成 UserNameSecurityTokenAuthenticator。 這個類別 ValidateUserNamePasswordCore(String, String)中最重要的方法是 。 在此方法中,驗證器會驗證用戶名稱的格式,而且主機名不是來自流氓網域。 如果符合這兩個條件,則會傳回 IAuthorizationPolicy 實例的唯讀集合,以提供代表儲存在用戶名稱令牌內的資訊的宣告。

    protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateUserNamePasswordCore(string userName, string password)
    {
        if (!ValidateUserNameFormat(userName))
            throw new SecurityTokenValidationException("Incorrect UserName format");
    
        ClaimSet claimSet = new DefaultClaimSet(ClaimSet.System, new Claim(ClaimTypes.Name, userName, Rights.PossessProperty));
        List<IIdentity> identities = new List<IIdentity>(1);
        identities.Add(new GenericIdentity(userName));
        List<IAuthorizationPolicy> policies = new List<IAuthorizationPolicy>(1);
        policies.Add(new UnconditionalPolicy(ClaimSet.System, claimSet, DateTime.MaxValue.ToUniversalTime(), identities));
        return policies.AsReadOnly();
    }
    
  2. 提供自定義令牌驗證器所傳回的授權原則。

    這個範例提供一個名為IAuthorizationPolicy的實UnconditionalPolicy作,傳回在其建構函式中傳遞給它的宣告和身分識別集。

    class UnconditionalPolicy : IAuthorizationPolicy
    {
        String id = Guid.NewGuid().ToString();
        ClaimSet issuer;
        ClaimSet issuance;
        DateTime expirationTime;
        IList<IIdentity> identities;
    
        public UnconditionalPolicy(ClaimSet issuer, ClaimSet issuance, DateTime expirationTime, IList<IIdentity> identities)
        {
            if (issuer == null)
                throw new ArgumentNullException("issuer");
            if (issuance == null)
                throw new ArgumentNullException("issuance");
    
            this.issuer = issuer;
            this.issuance = issuance;
            this.identities = identities;
            this.expirationTime = expirationTime;
        }
    
        public string Id
        {
            get { return this.id; }
        }
    
        public ClaimSet Issuer
        {
            get { return this.issuer; }
        }
    
        public DateTime ExpirationTime
        {
            get { return this.expirationTime; }
        }
    
        public bool Evaluate(EvaluationContext evaluationContext, ref object state)
        {
            evaluationContext.AddToTarget(this, this.issuance);
    
            if (this.identities != null)
            {
                object value;
                IList<IIdentity> contextIdentities;
                if (!evaluationContext.Properties.TryGetValue("Identities", out value))
                {
                    contextIdentities = new List<IIdentity>(this.identities.Count);
                    evaluationContext.Properties.Add("Identities", contextIdentities);
                }
                else
                {
                    contextIdentities = value as IList<IIdentity>;
                }
                foreach (IIdentity identity in this.identities)
                {
                    contextIdentities.Add(identity);
                }
            }
    
            evaluationContext.RecordExpirationTime(this.expirationTime);
            return true;
        }
    }
    
  3. 撰寫自定義安全性令牌管理員。

    SecurityTokenManager 用來在方法 SecurityTokenAuthenticator 中為傳遞給它的特定 SecurityTokenRequirement 物件建立 CreateSecurityTokenAuthenticator 。 安全性令牌管理員也可用來建立令牌提供者和令牌串行化程式,但此範例並未涵蓋這些令牌管理員。 在此範例中,自定義安全性令牌管理員繼承自 ServiceCredentialsSecurityTokenManager 類別,並覆寫 CreateSecurityTokenAuthenticator 方法,以在傳遞的令牌需求指出要求使用者名稱驗證器時傳回自定義用戶名稱令牌驗證器。

    public class MySecurityTokenManager : ServiceCredentialsSecurityTokenManager
    {
        MyUserNameCredential myUserNameCredential;
    
        public MySecurityTokenManager(MyUserNameCredential myUserNameCredential)
            : base(myUserNameCredential)
        {
            this.myUserNameCredential = myUserNameCredential;
        }
    
        public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
        {
            if (tokenRequirement.TokenType ==  SecurityTokenTypes.UserName)
            {
                outOfBandTokenResolver = null;
                return new MyTokenAuthenticator();
            }
            else
            {
                return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
            }
        }
    }
    
  4. 撰寫自定義服務認證。

    服務認證類別用來表示為服務設定的認證,並建立安全性令牌管理員,用來取得令牌驗證器、令牌提供者和令牌串行化程式。

    public class MyUserNameCredential : ServiceCredentials
    {
    
        public MyUserNameCredential()
            : base()
        {
        }
    
        protected override ServiceCredentials CloneCore()
        {
            return new MyUserNameCredential();
        }
    
        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            return new MySecurityTokenManager(this);
        }
    
    }
    
  5. 將服務設定為使用自訂服務認證。

    為了讓服務使用自定義服務認證,我們會在擷取已在預設服務認證中預先設定的服務憑證之後,刪除預設服務認證類別,並將新的服務認證實例設定為使用預先設定的服務憑證,並將這個新的服務認證實例新增至服務行為。

    ServiceCredentials sc = serviceHost.Credentials;
    X509Certificate2 cert = sc.ServiceCertificate.Certificate;
    MyUserNameCredential serviceCredential = new MyUserNameCredential();
    serviceCredential.ServiceCertificate.Certificate = cert;
    serviceHost.Description.Behaviors.Remove((typeof(ServiceCredentials)));
    serviceHost.Description.Behaviors.Add(serviceCredential);
    

若要顯示呼叫端的資訊,您可以使用 , PrimaryIdentity 如下列程式代碼所示。 Current包含目前呼叫端的權限資訊。

static void DisplayIdentityInformation()
{
    Console.WriteLine("\t\tSecurity context identity  :  {0}",
            ServiceSecurityContext.Current.PrimaryIdentity.Name);
     return;
}

當您執行範例時,作業要求和回應會顯示在用戶端控制台視窗中。 在客戶端視窗中按 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
    

    備註

    安裝程式批處理文件的設計目的是要從 Windows SDK 命令提示字元執行。 它要求 MSSDK 環境變數指向安裝 SDK 的目錄。 此環境變數會在 Windows SDK 命令提示字元內自動設定。

若要設定和建置範例

  1. 請確定您已針對 Windows Communication Foundation 範例 執行One-Time 安裝程式。

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

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

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

    備註

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

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

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

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

在多台電腦上執行範例

  1. 在服務電腦上為服務的二進位檔案建立目錄。

  2. 將服務程式檔案複製到服務電腦上的服務目錄。 同時將 Setup.bat 和 Cleanup.bat 檔案複製到服務計算機。

  3. 您必須擁有包含電腦完整域名的主體名稱的伺服器證書。 服務 App.config 檔案必須更新,才能反映這個新的憑證名稱。 您可以透過使用 Setup.bat 建立一個,前提是您將 %SERVER_NAME% 變數設定為服務將執行所在電腦的完整合格的主機名稱。 請注意,setup.bat 檔案必須從以系統管理員許可權開啟的Visual Studio開發人員命令提示字元執行。

  4. 將伺服器證書複製到用戶端 CurrentUser-TrustedPeople 存放區。 除了由用戶端信任的簽發者簽發伺服器證書時,您不需要這麼做。

  5. 在服務計算機上的 App.config 檔案中,變更基位址的值,以指定完整計算機名稱,而不是localhost。

  6. 在服務計算機上,從命令提示字元執行 service.exe。

  7. 將用戶端程式檔案從語言特定資料夾下的 \client\bin\ 資料夾,複製到用戶端電腦。

  8. 在用戶端電腦上的 Client.exe.config 檔案中,變更端點的位址值,以符合您服務的新位址。

  9. 在用戶端電腦上,從命令提示符啟動 "Client.exe"。

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

在範例之後清除

  1. 在您完成執行範例之後,請在samples資料夾中執行 Cleanup.bat。