我有一个WCF服务设置自定义绑定和自定义证书验证程序。
证书验证器定义如下。它将在稍后扩展,但目前只是进行基本验证。
public class MyX509CertificateValidator : X509CertificateValidator
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));
public MyX509CertificateValidator()
{
Logger.Info("certval - Constructor ");
}
public override void Validate(X509Certificate2 certificate)
{
Logger.Info("certval - Validate(). Calling Cert.validate()");
bool verifyResult = certificate.Verify();
Logger.Info("verify result: " + verifyResult);
if (!verifyResult)
{
throw new SecurityTokenValidationException("cert had some bad juju");
}
}
}
我的web.config设置如下。目标是使用传输安全性并使用会话。我希望在创建会话时验证cert一次。但是,我可以通过登录cert验证器看到,当使用现有的开放式WCF客户端代理时,验证将发生在客户端进行的每个服务调用上。
我已经验证了每个会话创建了一次我的WCF服务实例(每个会话都会调用一次构建函数的登录)。但是,每个服务调用都会调用cert验证器。如何才能在会话开始时调用cert验证器?
鉴于它似乎正在使用会话,我认为证书验证将是sesssion-full,并且每个会话只调用一次。我已经仔细阅读了MSDN上的WCF配置文档,并没有看到进一步定制reliableSession标记的方法,也没有看到与安全相关的任何内容来做我想做的事情。
这是web.config和服务定义
[ServiceBehavior(AutomaticSessionShutdown = true,
InstanceContextMode = InstanceContextMode.PerSession, ConcurrencyMode = ConcurrencyMode.Multiple)]
public class WcfBasicService : IWcfBasicService
{
...
<system.serviceModel>
<bindings>
<customBinding>
<binding name="reliableSessionOverHttps">
<reliableSession/>
<security authenticationMode="CertificateOverTransport"/>
<httpsTransport />
</binding>
</customBinding>
</bindings>
<services>
<service name="WcfServiceLibrary1.WcfBasicService">
<endpoint address="" binding="customBinding" contract="WcfServiceLibrary1.IWcfBasicService" name="mainEndpoint"
bindingConfiguration="reliableSessionOverHttps">
<identity>
<dns value="localhost" />
</identity>
</endpoint>
<endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
</service>
</services>
<behaviors>
<serviceBehaviors>
<behavior>
<serviceCredentials>
<clientCertificate>
<authentication certificateValidationMode="Custom" customCertificateValidatorType="WcfServiceLibrary1.MyX509CertificateValidator, WcfServiceLibrary1" />
</clientCertificate>
</serviceCredentials>
<!-- To avoid disclosing metadata information,
set the values below to false before deployment -->
<serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" />
<!-- To receive exception details in faults for debugging purposes,
set the value below to true. Set to false before deployment
to avoid disclosing exception information -->
<serviceDebug includeExceptionDetailInFaults="True" />
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
AFAIK没有任何好的WCF配置解决方案,但你可以在Validate
方法中实现某种缓存,使用证书的Thumbprint
属性(Thumbprint
实际上是证书体的哈希):
public class MyX509CertificateValidator : X509CertificateValidator
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));
private string lastValidCertTumbprint = null;
public MyX509CertificateValidator()
{
Logger.Info("certval - Constructor ");
}
public override void Validate(X509Certificate2 certificate)
{
if ((lastValidCertTumbprint != null) && (certificate.Tumbprint == lastValidCertTumbprint))
{
return; // Fast track
}
Logger.Info("certval - Validate(). Calling Cert.validate()");
bool verifyResult = certificate.Verify();
Logger.Info("verify result: " + verifyResult);
if (!verifyResult)
{
throw new SecurityTokenValidationException("cert had some bad juju");
}
// The cert valid, save this fact into fast track cache
lastValidCertTumbprint = certificate.Tumbprint;
}
}
我假设会话持续时间远远小于证书生命周期,如果证书被撤销,您还有其他方法可以终止会话:)
为了使事情变得更好,您可以添加最后一次验证调用的某种时间戳,并在合理的超时(例如,30分钟)到期时重新验证证书:
public class MyX509CertificateValidator : X509CertificateValidator
{
private static readonly ILog Logger = LogManager.GetLogger(typeof(MyX509CertificateValidator));
private string lastValidCertTumbprint = null;
private Stopwatch lastValidCertTimeMarker = new Stopwatch();
private const int VALIDATION_CACHE_LIFETIME = 30*60*1000; // in ms // 30min
public MyX509CertificateValidator()
{
Logger.Info("certval - Constructor ");
}
public override void Validate(X509Certificate2 certificate)
{
if ((lastValidCertTumbprint != null)
&& (certificate.Tumbprint == lastValidCertTumbprint)
&& (lastValidCertTimeMarker.ElapsedMilliseconds < VALIDATION_CACHE_LIFETIME))
{
return; // Fast track
}
lastValidCertTumbprint = null;
Logger.Info("certval - Validate(). Calling Cert.validate()");
bool verifyResult = certificate.Verify();
Logger.Info("verify result: " + verifyResult);
if (!verifyResult)
{
throw new SecurityTokenValidationException("cert had some bad juju");
}
// The cert valid, save this fact into fast track cache and save timestamp
lastValidCertTumbprint = certificate.Tumbprint;
lastValidCertTimeMarker.Reset();
lastValidCertTimeMarker.Start();
}
}