此示例演示如何将自定义令牌实现添加到 Windows Communication Foundation (WCF) 应用程序中。 该示例使用 a CreditCardToken
将有关客户端信用卡的信息安全地传递给服务。 令牌在 WS-Security 消息标头中传递,并使用对称安全绑定元素以及消息正文和其他消息标头进行签名和加密。 在内置令牌不足的情况下,这非常有用。 此示例演示如何向服务提供自定义安全令牌,而不是使用内置令牌之一。 该服务实现定义请求-回复通信模式的协定。
注释
本示例的设置过程和生成说明位于本主题末尾。
概括而言,此示例演示了以下内容:
客户端如何将自定义安全令牌传递给服务。
服务如何使用和验证自定义安全令牌。
WCF 服务代码如何获取有关收到的安全令牌的信息,包括自定义安全令牌。
服务器的 X.509 证书如何用于保护用于消息加密和签名的对称密钥。
使用自定义安全令牌进行客户端身份验证
服务公开单个终结点,此终结点是使用 BindingHelper
和 EchoServiceHost
类以编程方式创建的。 终结点由地址、绑定和协定组成。 此绑定使用 SymmetricSecurityBindingElement
和 HttpTransportBindingElement
按照自定义绑定进行配置。 此示例设置 SymmetricSecurityBindingElement
使用服务的 X.509 证书在传输期间保护对称密钥,并将 WS-Security 消息标头中的自定义 CreditCardToken
作为已签名和加密的安全令牌传递。 该行为指定要用于客户端身份验证的服务凭据,以及有关服务 X.509 证书的信息。
public static class BindingHelper
{
public static Binding CreateCreditCardBinding()
{
var httpTransport = new HttpTransportBindingElement();
// The message security binding element will be configured to require a credit card.
// The token that is encrypted with the service's certificate.
var messageSecurity = new SymmetricSecurityBindingElement();
messageSecurity.EndpointSupportingTokenParameters.SignedEncrypted.Add(new CreditCardTokenParameters());
X509SecurityTokenParameters x509ProtectionParameters = new X509SecurityTokenParameters();
x509ProtectionParameters.InclusionMode = SecurityTokenInclusionMode.Never;
messageSecurity.ProtectionTokenParameters = x509ProtectionParameters;
return new CustomBinding(messageSecurity, httpTransport);
}
}
为了使用消息中的信用卡令牌,此示例使用自定义服务凭据来提供此功能。 服务凭据类位于该类中 CreditCardServiceCredentials
,并将其添加到方法中 EchoServiceHost.InitializeRuntime
服务主机的行为集合中。
class EchoServiceHost : ServiceHost
{
string creditCardFile;
public EchoServiceHost(parameters Uri[] addresses)
: base(typeof(EchoService), addresses)
{
creditCardFile = ConfigurationManager.AppSettings["creditCardFile"];
if (string.IsNullOrEmpty(creditCardFile))
{
throw new ConfigurationErrorsException("creditCardFile not specified in service config");
}
creditCardFile = String.Format("{0}\\{1}", System.Web.Hosting.HostingEnvironment.ApplicationPhysicalPath, creditCardFile);
}
override protected void InitializeRuntime()
{
// Create a credit card service credentials and add it to the behaviors.
CreditCardServiceCredentials serviceCredentials = new CreditCardServiceCredentials(this.creditCardFile);
serviceCredentials.ServiceCertificate.SetCertificate("CN=localhost", StoreLocation.LocalMachine, StoreName.My);
this.Description.Behaviors.Remove((typeof(ServiceCredentials)));
this.Description.Behaviors.Add(serviceCredentials);
// Register a credit card binding for the endpoint.
Binding creditCardBinding = BindingHelper.CreateCreditCardBinding();
this.AddServiceEndpoint(typeof(IEchoService), creditCardBinding, string.Empty);
base.InitializeRuntime();
}
}
客户端终结点的配置方式与服务终结点类似。 客户端使用相同的 BindingHelper
类来创建绑定。 剩余的设置位于 Client
类中。 客户端还通过向客户端终结点行为集合添加具有适当数据的 CreditCardToken
实例来设置要包含在 CreditCardClientCredentials
中的信息和有关在设置代码中的服务 X.509 证书的信息。 此实例使用将主题名称设置为 CN=localhost
的 X.509 证书作为服务证书。
Binding creditCardBinding = BindingHelper.CreateCreditCardBinding();
var serviceAddress = new EndpointAddress("http://localhost/servicemodelsamples/service.svc");
// Create a client with given client endpoint configuration.
channelFactory = new ChannelFactory<IEchoService>(creditCardBinding, serviceAddress);
// Configure the credit card credentials on the channel factory.
var credentials =
new CreditCardClientCredentials(
new CreditCardInfo(creditCardNumber, issuer, expirationTime));
// Configure the service certificate on the credentials.
credentials.ServiceCertificate.SetDefaultCertificate(
"CN=localhost", StoreLocation.LocalMachine, StoreName.My);
// Replace ClientCredentials with CreditCardClientCredentials.
channelFactory.Endpoint.Behaviors.Remove(typeof(ClientCredentials));
channelFactory.Endpoint.Behaviors.Add(credentials);
client = channelFactory.CreateChannel();
Console.WriteLine($"Echo service returned: {client.Echo()}");
((IChannel)client).Close();
channelFactory.Close();
自定义安全令牌实现
若要在 WCF 中启用自定义安全令牌,请创建自定义安全令牌的对象表示形式。 该示例在 CreditCardToken
类中具有此表示形式。 对象表示形式负责保存所有相关的安全令牌信息,并提供安全令牌中包含的安全密钥列表。 在这种情况下,信用卡安全令牌不包含任何安全密钥。
下一部分介绍必须执行哪些作业才能使自定义令牌通过线路传输并由 WCF 终结点使用。
class CreditCardToken : SecurityToken
{
CreditCardInfo cardInfo;
DateTime effectiveTime = DateTime.UtcNow;
string id;
ReadOnlyCollection<SecurityKey> securityKeys;
public CreditCardToken(CreditCardInfo cardInfo) : this(cardInfo, Guid.NewGuid().ToString()) { }
public CreditCardToken(CreditCardInfo cardInfo, string id)
{
if (cardInfo == null)
throw new ArgumentNullException(nameof(cardInfo));
if (id == null)
throw new ArgumentNullException(nameof(id));
this.cardInfo = cardInfo;
this.id = id;
// The credit card token is not capable of any cryptography.
this.securityKeys = new ReadOnlyCollection<SecurityKey>(new List<SecurityKey>());
}
public CreditCardInfo CardInfo { get { return this.cardInfo; } }
public override ReadOnlyCollection<SecurityKey> SecurityKeys { get { return this.securityKeys; } }
public override DateTime ValidFrom { get { return this.effectiveTime; } }
public override DateTime ValidTo { get { return this.cardInfo.ExpirationDate; } }
public override string Id { get { return this.id; } }
}
向消息中写入和从消息中获取自定义信用卡令牌
WCF 中的安全令牌序列化程序负责从消息中的 XML 创建安全令牌的对象表示形式,以及创建安全令牌的 XML 形式。 它们还负责其他功能,例如读取和写入指向安全令牌的密钥标识符,但此示例仅使用与安全令牌相关的功能。 若要启用自定义令牌,必须实现自己的安全令牌序列化程序。 此示例将 CreditCardSecurityTokenSerializer
类用于此目的。
在服务中,自定义序列化程序读取自定义令牌的 XML 形式,并从中创建自定义令牌对象表示形式。
在客户端上,类 CreditCardSecurityTokenSerializer
将安全令牌对象表示形式中包含的信息写入 XML 编写器。
public class CreditCardSecurityTokenSerializer : WSSecurityTokenSerializer
{
public CreditCardSecurityTokenSerializer(SecurityTokenVersion version) : base() { }
protected override bool CanReadTokenCore(XmlReader reader)
{
XmlDictionaryReader localReader = XmlDictionaryReader.CreateDictionaryReader(reader);
if (reader == null)
throw new ArgumentNullException(nameof(reader));
if (reader.IsStartElement(Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace))
return true;
return base.CanReadTokenCore(reader);
}
protected override SecurityToken ReadTokenCore(XmlReader reader, SecurityTokenResolver tokenResolver)
{
if (reader == null)
throw new ArgumentNullException(nameof(reader));
if (reader.IsStartElement(Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace))
{
string id = reader.GetAttribute(Constants.Id, Constants.WsUtilityNamespace);
reader.ReadStartElement();
// Read the credit card number.
string creditCardNumber = reader.ReadElementString(Constants.CreditCardNumberElementName, Constants.CreditCardTokenNamespace);
// Read the expiration date.
string expirationTimeString = reader.ReadElementString(Constants.CreditCardExpirationElementName, Constants.CreditCardTokenNamespace);
DateTime expirationTime = XmlConvert.ToDateTime(expirationTimeString, XmlDateTimeSerializationMode.Utc);
// Read the issuer of the credit card.
string creditCardIssuer = reader.ReadElementString(Constants.CreditCardIssuerElementName, Constants.CreditCardTokenNamespace);
reader.ReadEndElement();
var cardInfo = new CreditCardInfo(creditCardNumber, creditCardIssuer, expirationTime);
return new CreditCardToken(cardInfo, id);
}
else
{
return WSSecurityTokenSerializer.DefaultInstance.ReadToken(reader, tokenResolver);
}
}
protected override bool CanWriteTokenCore(SecurityToken token)
{
if (token is CreditCardToken)
return true;
return base.CanWriteTokenCore(token);
}
protected override void WriteTokenCore(XmlWriter writer, SecurityToken token)
{
if (writer == null)
throw new ArgumentNullException(nameof(writer));
if (token == null)
throw new ArgumentNullException(nameof(token));
CreditCardToken c = token as CreditCardToken;
if (c != null)
{
writer.WriteStartElement(Constants.CreditCardTokenPrefix, Constants.CreditCardTokenName, Constants.CreditCardTokenNamespace);
writer.WriteAttributeString(Constants.WsUtilityPrefix, Constants.Id, Constants.WsUtilityNamespace, token.Id);
writer.WriteElementString(Constants.CreditCardNumberElementName, Constants.CreditCardTokenNamespace, c.CardInfo.CardNumber);
writer.WriteElementString(Constants.CreditCardExpirationElementName, Constants.CreditCardTokenNamespace, XmlConvert.ToString(c.CardInfo.ExpirationDate, XmlDateTimeSerializationMode.Utc));
writer.WriteElementString(Constants.CreditCardIssuerElementName, Constants.CreditCardTokenNamespace, c.CardInfo.CardIssuer);
writer.WriteEndElement();
writer.Flush();
}
else
{
base.WriteTokenCore(writer, token);
}
}
}
如何创建令牌提供程序和令牌验证器类
客户端和服务凭据负责提供安全令牌管理器实例。 安全令牌管理器实例用于获取令牌提供程序、令牌验证器和令牌序列化程序。
令牌提供程序基于客户端或服务凭据中包含的信息创建令牌的对象表示形式。 然后,使用令牌序列化程序(在上一部分中讨论)将令牌对象表示形式写入消息。
令牌验证器用于验证消息中传入的令牌。 传入令牌对象表示形式由令牌序列化程序创建。 然后,此对象表示形式将传递给令牌验证器进行验证。 成功验证令牌后,令牌验证器将返回表示令牌中包含的信息的对象的集合 IAuthorizationPolicy
。 此信息稍后在消息处理期间用于执行授权决策并为应用程序提供声明。 在此示例中,信用卡令牌验证器 CreditCardTokenAuthorizationPolicy
用于此目的。
令牌序列化程序负责通过网络获取令牌的对象表示形式。 上一部分对此进行了讨论。
在此示例中,我们仅在客户端上使用令牌提供程序,并且仅在服务上使用令牌验证器,因为我们希望仅在客户端到服务方向传输信用卡令牌。
客户端上的功能位于类 CreditCardClientCredentials
、CreditCardClientCredentialsSecurityTokenManager
和 CreditCardTokenProvider
中。
在服务中,该功能驻留在类CreditCardServiceCredentials
、CreditCardServiceCredentialsSecurityTokenManager
、CreditCardTokenAuthenticator
和CreditCardTokenAuthorizationPolicy
中。
public class CreditCardClientCredentials : ClientCredentials
{
CreditCardInfo creditCardInfo;
public CreditCardClientCredentials(CreditCardInfo creditCardInfo)
: base()
{
if (creditCardInfo == null)
throw new ArgumentNullException(nameof(creditCardInfo));
this.creditCardInfo = creditCardInfo;
}
public CreditCardInfo CreditCardInfo
{
get { return this.creditCardInfo; }
}
protected override ClientCredentials CloneCore()
{
return new CreditCardClientCredentials(this.creditCardInfo);
}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CreditCardClientCredentialsSecurityTokenManager(this);
}
}
public class CreditCardClientCredentialsSecurityTokenManager : ClientCredentialsSecurityTokenManager
{
CreditCardClientCredentials creditCardClientCredentials;
public CreditCardClientCredentialsSecurityTokenManager(CreditCardClientCredentials creditCardClientCredentials)
: base (creditCardClientCredentials)
{
this.creditCardClientCredentials = creditCardClientCredentials;
}
public override SecurityTokenProvider CreateSecurityTokenProvider(SecurityTokenRequirement tokenRequirement)
{
// Handle this token for Custom.
if (tokenRequirement.TokenType == Constants.CreditCardTokenType)
return new CreditCardTokenProvider(this.creditCardClientCredentials.CreditCardInfo);
// Return server cert.
else if (tokenRequirement is InitiatorServiceModelSecurityTokenRequirement)
{
if (tokenRequirement.TokenType == SecurityTokenTypes.X509Certificate)
{
return new X509SecurityTokenProvider(creditCardClientCredentials.ServiceCertificate.DefaultCertificate);
}
}
return base.CreateSecurityTokenProvider(tokenRequirement);
}
public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
{
return new CreditCardSecurityTokenSerializer(version);
}
}
class CreditCardTokenProvider : SecurityTokenProvider
{
CreditCardInfo creditCardInfo;
public CreditCardTokenProvider(CreditCardInfo creditCardInfo) : base()
{
if (creditCardInfo == null)
throw new ArgumentNullException(nameof(creditCardInfo));
this.creditCardInfo = creditCardInfo;
}
protected override SecurityToken GetTokenCore(TimeSpan timeout)
{
SecurityToken result = new CreditCardToken(this.creditCardInfo);
return result;
}
}
public class CreditCardServiceCredentials : ServiceCredentials
{
string creditCardFile;
public CreditCardServiceCredentials(string creditCardFile)
: base()
{
if (creditCardFile == null)
throw new ArgumentNullException(nameof(creditCardFile));
this.creditCardFile = creditCardFile;
}
public string CreditCardDataFile
{
get { return this.creditCardFile; }
}
protected override ServiceCredentials CloneCore()
{
return new CreditCardServiceCredentials(this.creditCardFile);
}
public override SecurityTokenManager CreateSecurityTokenManager()
{
return new CreditCardServiceCredentialsSecurityTokenManager(this);
}
}
public class CreditCardServiceCredentialsSecurityTokenManager : ServiceCredentialsSecurityTokenManager
{
CreditCardServiceCredentials creditCardServiceCredentials;
public CreditCardServiceCredentialsSecurityTokenManager(CreditCardServiceCredentials creditCardServiceCredentials)
: base(creditCardServiceCredentials)
{
this.creditCardServiceCredentials = creditCardServiceCredentials;
}
public override SecurityTokenAuthenticator CreateSecurityTokenAuthenticator(SecurityTokenRequirement tokenRequirement, out SecurityTokenResolver outOfBandTokenResolver)
{
if (tokenRequirement.TokenType == Constants.CreditCardTokenType)
{
outOfBandTokenResolver = null;
return new CreditCardTokenAuthenticator(creditCardServiceCredentials.CreditCardDataFile);
}
return base.CreateSecurityTokenAuthenticator(tokenRequirement, out outOfBandTokenResolver);
}
public override SecurityTokenSerializer CreateSecurityTokenSerializer(SecurityTokenVersion version)
{
return new CreditCardSecurityTokenSerializer(version);
}
}
class CreditCardTokenAuthenticator : SecurityTokenAuthenticator
{
string creditCardsFile;
public CreditCardTokenAuthenticator(string creditCardsFile)
{
this.creditCardsFile = creditCardsFile;
}
protected override bool CanValidateTokenCore(SecurityToken token)
{
return (token is CreditCardToken);
}
protected override ReadOnlyCollection<IAuthorizationPolicy> ValidateTokenCore(SecurityToken token)
{
CreditCardToken creditCardToken = token as CreditCardToken;
if (creditCardToken.CardInfo.ExpirationDate < DateTime.UtcNow)
throw new SecurityTokenValidationException("The credit card has expired.");
if (!IsCardNumberAndExpirationValid(creditCardToken.CardInfo))
throw new SecurityTokenValidationException("Unknown or invalid credit card.");
// the credit card token has only 1 claim - the card number. The issuer for the claim is the
// credit card issuer
var cardIssuerClaimSet = new DefaultClaimSet(new Claim(ClaimTypes.Name, creditCardToken.CardInfo.CardIssuer, Rights.PossessProperty));
var cardClaimSet = new DefaultClaimSet(cardIssuerClaimSet, new Claim(Constants.CreditCardNumberClaim, creditCardToken.CardInfo.CardNumber, Rights.PossessProperty));
var policies = new List<IAuthorizationPolicy>(1);
policies.Add(new CreditCardTokenAuthorizationPolicy(cardClaimSet));
return policies.AsReadOnly();
}
/// <summary>
/// Helper method to check if a given credit card entry is present in the User DB
/// </summary>
private bool IsCardNumberAndExpirationValid(CreditCardInfo cardInfo)
{
try
{
using (var myStreamReader = new StreamReader(this.creditCardsFile))
{
string line = "";
while ((line = myStreamReader.ReadLine()) != null)
{
string[] splitEntry = line.Split('#');
if (splitEntry[0] == cardInfo.CardNumber)
{
string expirationDateString = splitEntry[1].Trim();
DateTime expirationDateOnFile = DateTime.Parse(expirationDateString, System.Globalization.DateTimeFormatInfo.InvariantInfo, System.Globalization.DateTimeStyles.AdjustToUniversal);
if (cardInfo.ExpirationDate == expirationDateOnFile)
{
string issuer = splitEntry[2];
return issuer.Equals(cardInfo.CardIssuer, StringComparison.InvariantCultureIgnoreCase);
}
else
{
return false;
}
}
}
return false;
}
}
catch (Exception e)
{
throw new Exception("BookStoreService: Error while retrieving credit card information from User DB " + e.ToString());
}
}
}
public class CreditCardTokenAuthorizationPolicy : IAuthorizationPolicy
{
string id;
ClaimSet issuer;
IEnumerable<ClaimSet> issuedClaimSets;
public CreditCardTokenAuthorizationPolicy(ClaimSet issuedClaims)
{
if (issuedClaims == null)
throw new ArgumentNullException(nameof(issuedClaims));
this.issuer = issuedClaims.Issuer;
this.issuedClaimSets = new ClaimSet[] { issuedClaims };
this.id = Guid.NewGuid().ToString();
}
public ClaimSet Issuer { get { return this.issuer; } }
public string Id { get { return this.id; } }
public bool Evaluate(EvaluationContext context, ref object state)
{
foreach (ClaimSet issuance in this.issuedClaimSets)
{
context.AddClaimSet(this, issuance);
}
return true;
}
}
显示调用方的信息
若要显示调用方的信息,请使用 ServiceSecurityContext.Current.AuthorizationContext.ClaimSets
以下示例代码中所示。
ServiceSecurityContext.Current.AuthorizationContext.ClaimSets
包含与当前调用方相关的授权声明。 声明由 CreditCardToken
类通过其 AuthorizationPolicies
集合提供。
bool TryGetStringClaimValue(ClaimSet claimSet, string claimType, out string claimValue)
{
claimValue = null;
IEnumerable<Claim> matchingClaims = claimSet.FindClaims(claimType, Rights.PossessProperty);
if (matchingClaims == null)
return false;
IEnumerator<Claim> enumerator = matchingClaims.GetEnumerator();
enumerator.MoveNext();
claimValue = (enumerator.Current.Resource == null) ? null :
enumerator.Current.Resource.ToString();
return true;
}
string GetCallerCreditCardNumber()
{
foreach (ClaimSet claimSet in
ServiceSecurityContext.Current.AuthorizationContext.ClaimSets)
{
string creditCardNumber = null;
if (TryGetStringClaimValue(claimSet,
Constants.CreditCardNumberClaim, out creditCardNumber))
{
string issuer;
if (!TryGetStringClaimValue(claimSet.Issuer,
ClaimTypes.Name, out issuer))
{
issuer = "Unknown";
}
return $"Credit card '{creditCardNumber}' issued by '{issuer}'";
}
}
return "Credit card is not known";
}
运行示例时,操作请求和响应将显示在客户端控制台窗口中。 在客户端窗口中按 Enter 关闭客户端。
设置批处理文件
此示例中包含的 Setup.bat 批处理文件允许你配置具有相关证书的服务器,以运行需要基于服务器证书的安全性的 IIS 托管应用程序。 必须修改此批处理文件,以便跨计算机或在非承载情况下工作。
下面提供了批处理文件不同节的简要概述,以便可以修改批处理文件从而在相应的配置中运行。
创建服务器证书:
批处理文件中的
Setup.bat
以下行创建要使用的服务器证书。%SERVER_NAME%
变量指定服务器名称。 更改此变量可以指定您自己的服务器名称。 此批处理文件中的默认值为 localhost。 如果更改%SERVER_NAME%
变量,则必须浏览 Client.cs 和 Service.cs 文件并用 Setup.bat 脚本中使用的服务器名来替换 localhost 的所有实例。证书存储在
LocalMachine
存储位置下的“我的(个人)”存储区中。 证书存储在 IIS 托管服务的 LocalMachine 存储中。 对于自承载服务,应将批处理文件修改为将客户端证书存储在 CurrentUser 存储位置,方法是将字符串 LocalMachine 替换为 CurrentUser。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颁发的证书),则不需要使用服务器证书填充客户端证书存储区。
echo ************ echo copying server cert to client's TrustedPeople store echo ************ certmgr.exe -add -r LocalMachine -s My -c -n %SERVER_NAME% -r CurrentUser -s TrustedPeople
若要从 IIS 托管服务启用对证书私钥的访问权限,必须向运行 IIS 托管进程的用户帐户授予私钥的适当权限。 这将由 Setup.bat 脚本中的最后步骤来完成。
echo ************ echo setting privileges on server certificates echo ************ for /F "delims=" %%i in ('"%ProgramFiles%\ServiceModelSampleTools\FindPrivateKey.exe" My LocalMachine -n CN^=%SERVER_NAME% -a') do set PRIVATE_KEY_FILE=%%i set WP_ACCOUNT=NT AUTHORITY\NETWORK SERVICE (ver | findstr /C:"5.1") && set WP_ACCOUNT=%COMPUTERNAME%\ASPNET echo Y|cacls.exe "%PRIVATE_KEY_FILE%" /E /G "%WP_ACCOUNT%":R iisreset
注释
Setup.bat 批处理文件设计为通过 Visual Studio 命令提示符运行。 在 Visual Studio 命令提示中设置的 PATH 环境变量指向包含 Setup.bat 脚本所需的可执行文件的目录。
设置和生成示例
确保已为 Windows Communication Foundation 示例 执行One-Time 安装过程。
要生成解决方案,请按照生成 Windows Communication Foundation 示例中的说明进行操作。
在同一计算机上运行示例
- 使用管理员权限打开 Visual Studio 命令提示符窗口,并从示例安装文件夹运行 Setup.bat。 这会安装运行示例所需的所有证书。请确保路径包含 Makecert.exe 所在的文件夹。
注释
完成示例后,请务必运行 Cleanup.bat 来删除证书。 其他安全示例使用相同的证书。
跨计算机运行示例
在服务计算机上为服务二进制文件创建目录。
将服务程序文件复制到服务计算机上的服务目录中。 不要忘记复制 CreditCardFile.txt;否则,信用卡验证器无法验证从客户端发送的信用卡信息。 此外,将 Setup.bat 和 Cleanup.bat 文件复制到服务计算机。
必须具有一个其主题名称中包含计算机的完全限定域名的服务器证书。 如果您将
%SERVER_NAME%
变量更改为承载服务的计算机的完全限定的名称,您可以使用 Setup.bat 来创建一个这样的证书。 请注意,Setup.bat 文件必须在使用管理员权限打开的 Visual Studio 开发人员命令提示符下运行。将服务器证书复制到客户端上的 CurrentUser-TrustedPeople 存储中。 仅当服务器证书未由受信任的颁发者颁发时,才必须执行此作。
在 EchoServiceHost.cs 文件中,更改证书主题名称的值以指定一个完全限定的计算机名,而不是 localhost。
将 \client\bin\ 文件夹中的客户端程序文件(在特定于语言的文件夹下)复制到客户端计算机。
在Client.cs文件中,更改终结点的地址值以匹配服务的新地址。
在 Client.cs 文件中,更改服务 X.509 证书的主题名称以与远程主机的完全限定的计算机名(而不是 localhost)相匹配。
在客户端计算机上,从命令提示符窗口启动 Client.exe。
运行示例后进行清理
- 运行完示例后,在示例文件夹中运行 Cleanup.bat。