一段时间后,带有客户端证书的 WCF 应用程序失败?

Posted

技术标签:

【中文标题】一段时间后,带有客户端证书的 WCF 应用程序失败?【英文标题】:WCF application with client certificate fails after a while? 【发布时间】:2017-08-24 02:49:12 【问题描述】:

我有一个如下所示的解决方案:

Winform WCF(客户端) IdentityService4 自托管在 Windows 服务中 WCF HTTPS 服务自托管在 Windows 服务中

IdentitySERvice4 和 WCF 服务都在使用函数证书。客户端正在使用从 Windows 应用商店加载的客户端证书。客户端服务通过第三方软件从智能卡安装到 Windows 应用商店。如果卡被移除,则证书将从存储中移除。

流程如下所示:

1 客户端从存储中加载证书并将其绑定到 takeenhandler,如下所示:

    public override TokenClient GetTokenClient(string host, string port)
            
                var certificate = SmartCardHandler.GetInstance().Result.CurrentCertificate();
                if (certificate == null)
                    throw new Exception("Certificate is missing");

                var handler = new WebRequestHandler();
                handler.ServerCertificateValidationCallback = PinPublicKey;
                var url = $"https://host:port/connect/token";

                handler.ClientCertificates.Add(certificate);

                return new TokenClient(url, ClientTypes.Siths, "secret", handler);
            

token = await tokenClient.RequestCustomGrantAsync(ClientTypes.Siths, "MyApp.wcf offline_access");

2. 客户端将请求发送到 IdentityServices,后者读取客户端证书并使用它进行身份验证并生成返回的客户端令牌。

3. 客户端使用客户端证书创建到服务的 WCF 通道,令牌也像这样附加到此通道

private async Task<ChannelFactory<T>> CreateChannelFactory(LoginTypeBase loginType, MyAppToken token)
        
            var service = await _ConsulService.GetServiceBlocking(loginType.MyAppServicesToUse, forceRefresh: true, token: new CancellationTokenSource(TimeSpan.FromSeconds(30)).Token);

            if (service == null)
                throw new MyAppServiceCommunicationException();

            var cert = loginType.ClientCertificate;
            var uri = loginType.GetMyAppClientServiceURL(service.Address, service.Port);

            var header = AddressHeader.CreateAddressHeader(nameof(MyAppToken), nameof(MyAppToken), token);
            var endpointAddress = new EndpointAddress(uri, header);

            ServiceEndpoint serviceEndpoint = null;
            if (loginType.LoginType == LoginType.SmartCard || loginType.LoginType == LoginType.UsernamePasswordSLL)
            
                var binding = new NetHttpsBinding("netHttpsBinding");
                binding.Security.Mode = BasicHttpsSecurityMode.Transport;
                if (loginType.LoginType == LoginType.SmartCard)
                    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.Certificate;
                else
                    binding.Security.Transport.ClientCredentialType = HttpClientCredentialType.None;

                serviceEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(T)), binding, endpointAddress);
            
            else
            
                var binding = new NetHttpBinding("netHttpBinding");
                serviceEndpoint = new ServiceEndpoint(ContractDescription.GetContract(typeof(T)), binding, endpointAddress);
            

            serviceEndpoint.EndpointBehaviors.Add(new ProtoEndpointBehavior());
            serviceEndpoint.EndpointBehaviors.Add(new CustomMessageInspectorBehavior());


            var v = new ChannelFactory<T>(serviceEndpoint);
            if (loginType.LoginType == LoginType.SmartCard)
            
                v.Credentials.ClientCertificate.Certificate = cert;
                //v.Credentials.ClientCertificate.SetCertificate(StoreLocation.CurrentUser, StoreName.My, X509FindType.FindByThumbprint, cert.Thumbprint);
            
            return v;
        
    

4. 客户端将第一条消息发送到 WCF 服务。第三方软件将做出反应并要求客户端证书的 pin 被授予后,消息被转发到 WCF 服务,在该服务中根据 IdentityService4 验证令牌。

真正的代理用于在需要时切换到另一个服务。在这种情况下,它只有一个在线 WCF 服务的 URL。

 public override IMessage Invoke(IMessage msg)
        
            var methodCall = (IMethodCallMessage)msg;
            var method = (MethodInfo)methodCall.MethodBase;
            var channel = GetChannel(false);
            var retryCount = 3;
            do
            
                try
                
                    var result = method.Invoke(channel, methodCall.InArgs);
                    var returnmessage = new ReturnMessage(result, null, 0, methodCall.LogicalCallContext, methodCall);
                    return returnmessage;
                
                catch (Exception e)
                
                    if (e is TargetInvocationException && e.InnerException != null)
                    
                        if (e.InnerException is FaultException)
                            return new ReturnMessage(ErrorHandler.Instance.UnwrapAgentException(e.InnerException), msg as IMethodCallMessage);

                        if (e.InnerException is EndpointNotFoundException || e.InnerException is TimeoutException)
                            channel = GetChannel(true);
                    
                    retryCount--;
                
             while (retryCount > 0);

            throw new Exception("Retrycount reached maximum. Customproxy Invoke");
        

5客户端令牌每30分钟刷新一次

private async Task RefreshToken(LoginTypeBase loginType)
        
            if (_MyAppToken == null)
                return;
            var tokenClient = await GetTokenClient(loginType);
            var result = !string.IsNullOrEmpty(_refreshToken) ? await tokenClient.RequestRefreshTokenAsync(_refreshToken, _cancelToken.Token) : await tokenClient.RequestCustomGrantAsync("siths", cancellationToken: _cancelToken.Token);
            if (string.IsNullOrEmpty(result.AccessToken))
                throw new Exception($"Accesstoken har blivit null försökte refresha med tokenClient.ClientId _refreshToken DateTime.Now");
            _MyAppToken.Token = result.AccessToken;
            _refreshToken = result.RefreshToken;
        

这一切在开始时都很好用,但在随机数量的调用之后它会失败,有时是它无法联系的 WCF 服务,此时 IdentityService 可能仍然能够与之通信。上面第 4 点所示的代码中抛出了异常,如下所示:

e.ToString() "System.Reflection.TargetInvocationException: 异常 已被调用的目标抛出。 ---> System.ServiceModel.Security.SecurityNegotiationException:不能 为具有权限的 SSL/TLS 建立安全通道 '139.107.245.141:44310'。 ---> System.Net.WebException:请求是 中止: 无法创建 SSL/TLS 安全通道。\r\n at System.Net.HttpWebRequest.GetResponse()\r\n 在 System.ServiceModel.Channels.HttpChannelFactory1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan timeout)\r\n --- End of inner exception stack trace ---\r\n\r\nServer stack trace: \r\n at System.ServiceModel.Channels.HttpChannelUtilities.ProcessGetResponseWebException(WebException webException, HttpWebRequest request, HttpAbortReason abortReason)\r\n at System.ServiceModel.Channels.HttpChannelFactory1.HttpRequestChannel.HttpChannelRequest.WaitForReply(TimeSpan 超时)\r\n 在 System.ServiceModel.Channels.RequestChannel.Request(消息消息, TimeSpan 超时)\r\n 在 System.ServiceModel.Dispatcher.RequestChannelBinder.Request(消息 消息,TimeSpan 超时)\r\n 在 System.ServiceModel.Channels.ServiceChannel.Call(字符串动作, Boolean oneway, ProxyOperationRuntime 操作, Object[] ins, Object[] 输出,TimeSpan 超时)\r\n at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall、ProxyOperationRuntime 操作)\r\n at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage 消息)\r\n\r\n在 [0] 处重新抛出异常:\r\n at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)\r\n at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(消息数据& msgData,Int32 类型)\r\n at myapp.ServiceContracts.ImyappClientService.LogData(GeneralFault generalFault)\r\n --- 内部异常堆栈跟踪结束 ---\r\n 在 System.RuntimeMethodHandle.InvokeMethod(对象目标,对象 [] 参数、签名 sig、布尔构造函数)\r\n at System.Reflection.RuntimeMethodInfo.UnsafeInvokeInternal(对象 obj, Object[] 参数,Object[] 参数)\r\n at System.Reflection.RuntimeMethodInfo.Invoke(对象 obj,BindingFlags invokeAttr、Binder binder、Object[] 参数、CultureInfo 文化)\r\n 在 System.Reflection.MethodBase.Invoke(对象 obj, 对象[] 参数)\r\n 在 myapp.Client.Main.Classes.Service_Management.CustomProxy`1.Invoke(IMessage 味精)在 C:\myapp\Produkter\myapp Utveckling\Solution\myapp.Client.Main\Classes\Service Management\CustomProxy.cs:line 74" 字符串

有时它会首先使 WCF 服务失败,然后再次工作,然后再次失败(完全相同的 vall),而当 IdentityService4 连接开始失败时,它每次都会失败,并出现此异常:

token.Exception.ToString() "System.Net.Http.HttpRequestException: 一个 发送请求时发生错误。 ---> System.Net.WebException:请求被中止:无法创建 SSL/TLS 安全通道。\r\n 在 System.Net.HttpWebRequest.EndGetRequestStream(IAsyncResult asyncResult、TransportContext& 上下文)\r\n at System.Net.Http.HttpClientHandler.GetRequestStreamCallback(IAsyncResult ar)\r\n --- 内部异常堆栈跟踪结束 ---\r\n at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务 任务)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 System.Net.Http.HttpClient.d__58.MoveNext()\r\n--- 从先前抛出异常的位置结束堆栈跟踪 ---\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(任务 任务)\r\n 在 System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(任务 任务)\r\n 在 IdentityModel.Client.TokenClient.d__26.MoveNext()" 字符串

此代码用于在客户端测试 IdentityService 连接

这是令牌。将持有上述异常的异常。

公共静态异步任务 LoginSiths() 尝试 var 主机 = "139.107.245.141"; 变量端口 = 44312; var url = $"https://host:port/connect/token";

            var handler = new WebRequestHandler();

            handler.ServerCertificateValidationCallback = PinPublicKey;
            handler.ClientCertificates.Add(SmartCardHandler.GetInstance().Result.CurrentCertificate());

            var client = new TokenClient(url, ClientTypes.Siths, "secret.ro101.orbit", handler);
            TokenResponse token = await client.RequestCustomGrantAsync(ClientTypes.Siths, "orbit.wcf offline_access");

            if (token.IsError)
                MessageBox.Show("Failed!");
            else
                MessageBox.Show("Success!");
        
        catch (Exception ex)
        
            MessageBox.Show("Exception : " + ex.ToString());
        
    

唯一的出路是重启客户端(不需要重启任何服务)。客户端重新启动后,可以再次登录并进行调用。

如果我将它添加到客户端的开头:

 ServicePointManager.MaxServicePointIdleTime = 1;

然后我会在登录后几乎立即遇到问题(此时变化缓慢)。

有什么方法可以知道为什么会出现 SSL 异常?

注意,这篇文章已于 2017-04-07 01:00 更新,这是为了更全面地了解正在发生的事情。问题实际上包括 两个问题,一个仍然处于活动状态并在上面进行了解释,另一个 是由于bug in IdentityService4 我们找到了解决方法 为。

【问题讨论】:

.NET 不使用 Wininet(它可以使用它的一些配置,但不能使用它的代码)。你得到的错误的堆栈帧是什么? 啊哈?嗯,那么我不明白是什么阻止了通信。我已在 Edit 3 中附加了堆栈跟踪。 您可以尝试先将 ServicePointManager.MaxServicePointIdleTime 设置为 1,然后看看是否有什么作用?注意:在上面的代码中,sp.Certificate 是服务器证书,而不是客户端证书。 查看我的编辑 4. 客户端变慢,当打开一个与服务进行大量通信的函数时,我得到一个异常。我试图移除并更换卡,但到目前为止它仍然有效。但是,我的小测试通常可以工作,但是一旦我的应用程序无法传达我的小测试代码填充也会失败。这个小测试代码只调用了 IndentityService4,所以只有几行代码。 您是否拥有与生产分开的完整系统副本?您是否能够访问此应用程序中涉及的每个系统的每个级别(您内部应用程序的所有源代码和所有生产控制)?如果没有,赏金是明智之举。没有人提出答案这一事实意味着没有人知道。获得这个答案的唯一途径是您的研发。您需要能够拆除所有东西并将其重新组合在一起,并使用特殊程序来模拟可能导致问题的系统的响应。如果没有什么能阻止您这样做,那么您现在已经找到了答案。 【参考方案1】:

如果您的问题出在客户端,我建议您对ServicePointManager 使用AppDomain 隔离,而不是一些反射黑客(它伤害了我的眼睛:) - 类似于此.NET https requests with different security protocols across threads

IE:

public class Client : MarshalByRefObject 
   public string GetResponse(string address) 
     // you can get crazy with ServicePointManager settings here
     // + actually do the request
   


public sealed class Isolated<T> : IDisposable where T : MarshalByRefObject 
   private AppDomain _domain;

   public Isolated() 
     _domain = AppDomain.CreateDomain("Isolated:" + Guid.NewGuid(), null,
       AppDomain.CurrentDomain.SetupInformation);
     var type = typeof(T);
     Value = (T)_domain.CreateInstanceAndUnwrap(type.Assembly.FullName, type.FullName);
   

   public T Value  get; private set; 

   public void Dispose() 
     if (_domain == null) return;
     AppDomain.Unload(_domain);
    _domain = null;
   
    

并将通信包含在以下内容中:

using (var isolated = new Isolated<Client>()) 
   string response = isolated.Value.GetResponse(url);

【讨论】:

以上是关于一段时间后,带有客户端证书的 WCF 应用程序失败?的主要内容,如果未能解决你的问题,请参考以下文章

WCF 错误:“X.509 证书 CN=localhost 链构建失败......”

WCF 客户端。客户证书。第一次调用成功,第二次调用失败

收到的 WCF 服务器证书的部分链验证失败

使用ssl和客户端证书的Wcf:请求svc succes wcf调用返回403.16

除非客户端和服务器使用相同的 Windows 身份,否则带有 sspi 的 WCF Net.tcp 会失败

带有证书的 WebRequest 在第一次调用时失败,并在所有后续调用中成功