令牌供应商

此示例演示如何实现自定义令牌提供程序。 Windows Communication Foundation(WCF)中的令牌提供程序用于向安全基础结构提供凭据。 令牌提供程序通常检查目标并颁发适当的凭据,以便安全基础结构可以保护消息。 WCF 随附有默认凭据管理器令牌提供程序。 WCF 还附带 CardSpace 令牌提供程序。 自定义令牌提供程序在以下情况下非常有用:

  • 存在不能由这些令牌提供程序操作的凭据存储。

  • 想要提供自己的自定义传输机制,以便从用户提供详细信息这一刻起到 WCF 客户端框架使用凭据时转换凭据。

  • 要生成一个自定义令牌。

此示例演示如何生成自定义令牌提供程序,该提供程序将用户输入转换为其他格式。

概括而言,此示例演示了以下内容:

  • 客户端如何使用用户名/密码对进行身份验证。

  • 如何使用自定义令牌提供程序对客户端进行配置。

  • 服务器如何通过使用密码和自定义 UserNamePasswordValidator 来验证客户端凭据,以确保用户名和密码匹配。

  • 客户端如何通过服务器的 X.509 证书对其进行身份验证。

此示例还演示了在自定义令牌身份验证过程后如何可以访问调用者的身份。

该服务公开一个终结点,用于与服务通信,该终结点使用 App.config 配置文件定义。 终结点由地址、绑定和协定组成。 绑定是使用标准 wsHttpBinding 配置的,该元素在默认情况下使用消息安全性。 此示例设置使用客户端用户名身份验证的标准 wsHttpBinding 。 该服务还通过 serviceCredentials 行为来配置服务证书。 serviceCredentials 特性允许你配置服务证书。 客户端使用服务证书对服务进行身份验证并提供消息保护。 以下配置引用在示例安装过程中安装的 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>

客户端终结点配置包括配置名称、服务终结点的绝对地址、绑定和协定。 该客户端绑定是使用适当的 Mode 和消息 clientCredentialType 配置的。

<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>

以下步骤演示如何开发自定义令牌提供程序并将其与 WCF 安全框架集成:

  1. 编写自定义令牌提供程序。

    此示例实现获取用户名和密码的自定义令牌提供程序。 密码必须与此用户名匹配。 此自定义令牌提供程序仅用于演示目的,不建议用于实际部署。

    为了执行此任务,自定义令牌提供程序派生了 SecurityTokenProvider 类,并重写了 GetTokenCore(TimeSpan) 方法。 此方法创建并返回一个新的 UserNameSecurityToken

    protected override SecurityToken GetTokenCore(TimeSpan timeout)
    {
        // obtain username and password from the user using console window
        string username = GetUserName();
        string password = GetPassword();
        Console.WriteLine("username: {0}", username);
    
        // return new UserNameSecurityToken containing information obtained from user
        return new UserNameSecurityToken(username, password);
    }
    
  2. 编写自定义安全令牌管理器。

    使用 SecurityTokenManager,可以为在 SecurityTokenProvider 方法中传入该管理器的特定 SecurityTokenRequirement 创建 CreateSecurityTokenProvider。 安全令牌管理器还用于创建令牌验证器和令牌序列化程序,但此示例未涵盖这些令牌。 在此示例中,自定义安全令牌管理器继承自 ClientCredentialsSecurityTokenManager 类并重写 CreateSecurityTokenProvider 方法,这样,当所传递令牌的要求指示需要用户名提供程序时,将返回自定义用户名令牌提供程序。

    public class MyUserNameSecurityTokenManager : ClientCredentialsSecurityTokenManager
    {
        MyUserNameClientCredentials myUserNameClientCredentials;
    
        public MyUserNameSecurityTokenManager(MyUserNameClientCredentials myUserNameClientCredentials)
            : base(myUserNameClientCredentials)
        {
            this.myUserNameClientCredentials = myUserNameClientCredentials;
        }
    
        public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
        {
            // if token requirement matches username token return custom username token provider
            // otherwise use base implementation
            if (tokenRequirement.TokenType == SecurityTokenTypes.UserName)
            {
                return new MyUserNameTokenProvider();
            }
            else
            {
                return base.CreateSecurityTokenProvider(tokenRequirement);
            }
        }
    }
    
  3. 编写自定义客户端凭据。

    客户端凭据类用于表示为客户端代理配置的凭据,并创建用于获取令牌验证器、令牌提供程序和令牌序列化程序的安全令牌管理器。

    public class MyUserNameClientCredentials : ClientCredentials
    {
        public MyUserNameClientCredentials()
            : base()
        {
        }
    
        protected override ClientCredentials CloneCore()
        {
            return new MyUserNameClientCredentials();
        }
    
        public override SecurityTokenManager CreateSecurityTokenManager()
        {
            // return custom security token manager
            return new MyUserNameSecurityTokenManager(this);
        }
    }
    
  4. 将客户端配置为使用自定义客户端凭据。

    为了使客户端使用自定义客户端凭据,该示例将删除默认客户端凭据类并提供新的客户端凭据类。

    static void Main()
    {
        // ...
           // Create a client with given client endpoint configuration
          CalculatorClient client = new CalculatorClient();
    
          // set new credentials
           client.ChannelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
         client.ChannelFactory.Endpoint.Behaviors.Add(new MyUserNameClientCredentials());
       // ...
    }
    

在服务中,若要显示调用方的信息,请使用 PrimaryIdentity 以下代码示例所示。 Current 包含有关当前调用方的声明信息。

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

运行示例时,操作请求和响应将显示在客户端控制台窗口中。 在客户端窗口中按 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 批处理文件旨在从 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. 在用户名提示符下,键入用户名。

  5. 在密码提示符下,使用为用户名提示键入的同一字符串。

  6. 如果客户端和服务无法通信,请参阅 WCF 示例 故障排除提示。

跨计算机运行示例

  1. 在服务计算机上为服务二进制文件创建目录。

  2. 将服务程序文件复制到服务计算机上的服务目录。 此外,将 Setup.bat 和 Cleanup.bat 文件复制到服务计算机。

  3. 必须具有一个其主题名称中包含计算机的完全限定域名的服务器证书。 必须更新 Service.exe.config 文件以反映此新证书名称。 可以通过修改 Setup.bat 批处理文件来创建服务器证书。 请注意,必须使用管理员权限从 Visual Studio 的开发人员命令提示符下运行 setup.bat 文件。 您必须将%SERVER_NAME%变量设置为用于托管服务的计算机的完全限定主机名。

  4. 将服务器证书复制到客户端 CurrentUser-TrustedPeople 存储中。 当服务器证书由客户端受信任的颁发者颁发时,无需执行此作。

  5. 在服务计算机的 Service.exe.config 文件中,更改基址的值以指定一个完全限定的计算机名称,而不是 localhost。

  6. 在服务计算机上,从命令提示符运行 service.exe。

  7. 将 \client\bin\ 文件夹中的客户端程序文件(在特定于语言的文件夹下)复制到客户端计算机。

  8. 在客户端计算机上的 Client.exe.config 文件中,更改终结点的地址值以匹配服务的新地址。

  9. 在客户端计算机上,从命令提示符窗口启动 Client.exe

  10. 如果客户端和服务无法通信,请参阅 WCF 示例 故障排除提示。

运行示例后进行清理

  1. 运行完示例后,在示例文件夹中运行 Cleanup.bat。