2012-05-14 14 views
10

He estado en todas partes de la red para esto. Lo he estado haciendo diablos, y el proveedor cuyo servicio web estoy tratando de consumir se niega a dar soporte oficialmente a WCF como método de consumo.(Intentando) migrar de WSE 3.0 a WCF para el código de cliente

No soy un experto en servicios web, por lo que haré todo lo posible para documentar y explicar esta publicación inicial, pero, por supuesto, solicito más información si la necesita, y espero que pueda proporcionarla. lo que sea necesario

El servicio

En mi empresa, se utiliza una aplicación de un proveedor que expone un servicio. La aplicación está escrita en Java, y parece que el wsdl se creó con Apache Axis 1.2.

El código

Mi código heredado utiliza WSE 3.0. En particular, utiliza las clases proxy que tienen "WSE" tachado automáticamente al final. Esto me permite usar un esquema de autenticación mucho más simple (la única forma en que podría hacerlo funcionar). No necesito usar certificados. Yo uso un derivado de SecurityPolicyAssertion, y lo envuelvo en un objeto Policy que se pasa al método SetPolicy de la clase de cliente. Esto es todo lo que necesito hacer para crear una instancia de trabajo del cliente:

MyWebServiceWse api = new MyWebServiceWse(); 
api.Url = myUrl; 
api.SetPolicy(new Policy(new MyDerivedSecurityAssertion(user, pass))); 

Mi defecto, fuera de la caja de código para WCF (generado con una referencia de servicio) no acepta las credenciales, por lo que sé hay un problema desde el principio. He leído varias cosas en línea sobre el uso de diferentes security o configuraciones de enlace en mi app.config, pero nada ha funcionado por completo. Mi error más común después de retoques copiosos es WSDoAllReceiver: Request does not contain required Security header.

Aquí está la aplicación.config. Tal vez podríamos empezar diciéndome qué debería cambiar aquí para facilitar el paso de las credenciales; una vez más, he visto diversas opiniones en línea.

<?xml version="1.0" encoding="utf-8" ?> 
<configuration> 
    <system.serviceModel> 
     <bindings> 
      <basicHttpBinding> 
       <binding name="MySoapBinding" closeTimeout="00:01:00" 
        openTimeout="00:01:00" receiveTimeout="00:10:00" sendTimeout="00:01:00" 
        allowCookies="false" bypassProxyOnLocal="false" hostNameComparisonMode="StrongWildcard" 
        maxBufferSize="65536" maxBufferPoolSize="524288" maxReceivedMessageSize="65536" 
        messageEncoding="Text" textEncoding="utf-8" transferMode="Buffered" 
        useDefaultWebProxy="true"> 
        <readerQuotas maxDepth="32" maxStringContentLength="8192" maxArrayLength="16384" 
         maxBytesPerRead="4096" maxNameTableCharCount="16384" /> 
        <security mode="None"> 
         <transport clientCredentialType="None" proxyCredentialType="None" 
          realm="" /> 
         <message clientCredentialType="UserName" algorithmSuite="Default" /> 
        </security> 
       </binding> 
      </basicHttpBinding> 
     </bindings> 
     <client> 
      <endpoint address="http://xyz:12345/services/MyService" 
       binding="basicHttpBinding" bindingConfiguration="MySoapBinding" 
       contract="MyNS.MyService" name="MyService" /> 
     </client> 
    </system.serviceModel> 
</configuration> 

He cambiado algunos de los atributos para ocultar el servicio específico que estamos utilizando (política de la compañía y todo eso).

Y aquí es el código de ejemplo de C# hasta el momento (prueba en una aplicación de consola):

MyClient client = new MyClient(); 
client.listMethod(); 

ACTUALIZACIÓN

Leer este SO mensaje: wcf security . . ..

He actualizado mi app.config en consecuencia, y ahora estoy pasando el nombre de usuario y pwd en el código. Todavía estoy recibiendo el mismo error:

WSDoAllReceiver: Request does not contain required Security header 

20120517 ACTUALIZACIÓN

Una solicitud exitosa (de WSE3):

<soap:Header> 
    <wsa:Action> 
    </wsa:Action> 
    <wsa:MessageID>urn:uuid:cb739422-c077-4eec-8cb2-686837b76878</wsa:MessageID> 
    <wsa:ReplyTo> 
     <wsa:Address>http://schemas.xmlsoap.org/ws/2004/08/addressing/role/anonymous</wsa:Address> 
    </wsa:ReplyTo> 
    <wsa:To>http://removed-for-security</wsa:To> 
    <wsse:Security soap:mustUnderstand="1"> 
     <wsu:Timestamp wsu:Id="Timestamp-e13feaf9-33d9-47bf-ab5b-60b4611eb81a"> 
     <wsu:Created>2012-05-17T11:25:41Z</wsu:Created> 
     <wsu:Expires>2012-05-17T11:30:41Z</wsu:Expires> 
     </wsu:Timestamp> 
     <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" wsu:Id="SecurityToken-00c26e1a-3b3b-400f-a99a-3aa54cf8c8ff"> 
     <wsse:Username>change-to-protect-the-innocent</wsse:Username> 
     <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">nice-try</wsse:Password> 
     <wsse:Nonce>KJMvUuWF2eO2uIJCuxJC4A==</wsse:Nonce> 
     <wsu:Created>2012-05-17T11:25:41Z</wsu:Created> 
     </wsse:UsernameToken> 
    </wsse:Security> 
    </soap:Header> 
    <soap:Body> 
    <listChannels xmlns="http://removed-for-security"> 
     <rowfrom>0</rowfrom> 
     <rowto>10</rowto> 
    </listChannels> 
    </soap:Body> 
</soap:Envelope> 

trabajando en conseguir la trazabilidad de WCF -Me agregaré en breve.

20120517 ACTUALIZACIÓN 2

Y aquí está el sobre de WCF:

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 
    <s:Header> 
     <Action s:mustUnderstand="1" xmlns="http://schemas.microsoft.com/ws/2005/05/addressing/none"></Action> 
    </s:Header> 
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
     <listChannels xmlns="http://removed-for-security"> 
     <rowfrom>1</rowfrom> 
     <rowto>2147483647</rowto> 
     </listChannels> 
    </s:Body> 
    </s:Envelope> 

20120518 ACTUALIZACIÓN He tratado de implementar la solución en el post que Mike Miller une a en los comentarios. Ahora recibo el siguiente error (sin mensaje termina siendo enviado porque barfing de algo en el esquema):

The provided URI scheme 'http' is invalid; expected 'https'. 

Y por si alguien quiere preguntar, sí, tengo que enviar a través de http, y sí, me m consciente de que las credenciales se envían como cadenas sin encriptar :-)

+0

Lo primero que compararía son las solicitudes de jabón generadas por los clientes de WSE y WCF. Dado que el cliente WCF está configurado para 'security mode = 'None' 'probablemente no se está creando un encabezado de seguridad soap, pero las solicitudes capturadas lo mostrarán. Creo que el basicHttpBinding no se puede configurar para admitir la seguridad del nivel de mensajes. Es probable que necesite el wsHttpBinding para esa configuración. Por último, observe lo que Microsoft ha hecho en el proyecto [WCF Express Interop] (http://wcf.codeplex.com/wikipage?title=WCF%20Express%20Interop%20Bindings), sus asistentes también se pueden aplicar a los clientes de WCF. –

+0

Gracias, Sixto: ¿cuál es la mejor manera de capturar la solicitud que está enviando WCF? –

+0

WCF tiene una característica incorporada [seguimiento de mensajes] (http://msdn.microsoft.com/en-us/library/ms730064.aspx) pero también puede usar [Fiddler] (http: //www.fiddler2. com/fiddler2 /) para capturar el tráfico de los clientes WSE y WCF. Para ampliar mi comentario anterior, debe enfocarse en crear su nuevo cliente WCF desde el punto de vista de la interfaz con un servicio Apache Axis en lugar de hacerlo desde un cliente WSE heredado. A pesar de la promesa de "interoperabilidad" de los estándares WS- *, es realmente doloroso conseguir que WCF trabaje con cualquier servicio o cliente que no esté basado en WCF. –

Respuesta

10

Lo que necesita es enviar un token de nombre de usuario a través del transporte http que no es compatible con wcf ootb. Además, tu token usa nonce/created, que tampoco es ootb. tienes 2 opciones:

  1. este oss project añade nonce/creado para el token de nombre de usuario. este oss project agrega la capacidad de enviar nombre de usuario a través de http. necesitarías combinar ambos proyectos juntos.

  2. ws-security generalmente se considera complejo, pero lo usa en su forma más simple (nombre de usuario). ¡lo más fácil sería descartar cualquier configuración de seguridad wcf y crear todo el encabezado de seguridad en un message inspector! Como puede ver, la mayoría de los encabezados son simplemente nodos xml estáticos, y la mayoría de los valores son bastante claros (ya conoce el nombre de usuario). los únicos dos difíciles son el nonce y las marcas de tiempo que podría ver cómo hacer en este oss project (una línea cada uno). Hay una variante de esta opción que puede ser más fácil: use CUB después de todo e implemente un custom encoder que presione el timestmpa/nonce. Yo iría por este último, pero soy parcial ya he desarrollado CUB ...

Hay también las cabeceras WS-Addressing que puede configurar en su codificación personalizado "messageVersion" propiedad. No puedo decir el valor exacto ya que omitió el encabezado del sobre con la definición del prefijo wsa.

Si quiere ayuda de forma privada (ya que parece que tiene restricciones de seguridad) por todos los medios envíeme un correo electrónico desde my blog.

EDITAR: Lo he implementado para usted. siga estos pasos:

  1. descarga cub y que se familiarice con ella (no la parte interna, tal como usarlo de acuerdo con la publicación de blog)

  2. añadir una referencia a System.Runtime.Serialization.dll al proyecto ClearUsernameBinding

  3. agregue un nuevo archivo a ese proyecto: UsernameExEncoder.cs. Pega este contenido:

    using System; 
    using System.Collections.Generic; 
    using System.Linq; 
    using System.Text; 
    using System.ServiceModel.Channels; 
    using System.IO; 
    using System.Xml; 
    using System.Security.Cryptography; 
    
    namespace Webservices20.BindingExtensions 
        { 
        class UsernameExEncoderBindingElement : MessageEncodingBindingElement 
        { 
        MessageEncodingBindingElement inner;   
    
        public UsernameExEncoderBindingElement(MessageEncodingBindingElement inner) 
        { 
         this.inner = inner;    
        } 
    
        public override IChannelFactory<TChannel> BuildChannelFactory<TChannel>(BindingContext context) 
        { 
         context.BindingParameters.Add(this); 
         var res = base.BuildChannelFactory<TChannel>(context); 
         return res; 
        } 
    
        public override bool CanBuildChannelFactory<TChannel>(BindingContext context) 
        { 
         var res = base.CanBuildChannelFactory<TChannel>(context); 
         return res; 
        } 
    
        public override MessageEncoderFactory CreateMessageEncoderFactory() 
        { 
         return new UsernameExEncoderFactory(this.inner.CreateMessageEncoderFactory()); 
        }  
    
        public override MessageVersion MessageVersion 
        { 
         get 
         { 
          return this.inner.MessageVersion; 
         } 
         set 
         { 
          this.inner.MessageVersion = value; 
         } 
        } 
    
        public override BindingElement Clone() 
        { 
         var c = (MessageEncodingBindingElement)this.inner.Clone(); 
         var res = new UsernameExEncoderBindingElement(c); 
         return res; 
        } 
    
        public override T GetProperty<T>(BindingContext context) 
        { 
         var res = this.inner.GetProperty<T>(context); 
         return res; 
        } 
    } 
    
    class UsernameExEncoderFactory : MessageEncoderFactory 
    { 
        MessageEncoderFactory inner;   
    
        public UsernameExEncoderFactory(MessageEncoderFactory inner) 
        { 
         this.inner = inner;    
        } 
    
        public override MessageEncoder Encoder 
        { 
         get { return new UsernameExEncoder(inner.Encoder); } 
        } 
    
        public override MessageVersion MessageVersion 
        { 
         get { return this.inner.MessageVersion; } 
        } 
    
    } 
    
    class UsernameExEncoder : MessageEncoder 
    { 
        MessageEncoder inner; 
    
        public override T GetProperty<T>() 
        { 
         return inner.GetProperty<T>(); 
        } 
    
        public UsernameExEncoder(MessageEncoder inner) 
        { 
         this.inner = inner; 
        } 
    
        public override string ContentType 
        { 
         get { return this.inner.ContentType; } 
        } 
    
        public override string MediaType 
        { 
         get { return this.inner.MediaType; } 
        } 
    
        public override MessageVersion MessageVersion 
        { 
         get { return this.inner.MessageVersion; } 
        } 
    
        public override bool IsContentTypeSupported(string contentType) 
        { 
         return this.inner.IsContentTypeSupported(contentType); 
        } 
    
        public override Message ReadMessage(ArraySegment<byte> buffer, BufferManager bufferManager, string contentType) 
        { 
         return this.inner.ReadMessage(buffer, bufferManager, contentType); 
        } 
    
        public override Message ReadMessage(System.IO.Stream stream, int maxSizeOfHeaders, string contentType) 
        { 
         return this.inner.ReadMessage(stream, maxSizeOfHeaders, contentType); 
        } 
    
        public override ArraySegment<byte> WriteMessage(Message message, int maxMessageSize, BufferManager bufferManager, int messageOffset) 
        { 
         //load the message to dom 
         var mem = new MemoryStream(); 
         var x = XmlWriter.Create(mem); 
         message.WriteMessage(x); 
         x.Flush(); 
         mem.Flush(); 
         mem.Position = 0; 
         XmlDocument doc = new XmlDocument(); 
         doc.Load(mem); 
    
         //add the missing elements 
         var token = doc.SelectSingleNode("//*[local-name(.)='UsernameToken']"); 
         var created = doc.CreateElement("Created", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); 
         var nonce = doc.CreateElement("Nonce", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"); 
         token.AppendChild(created); 
         token.AppendChild(nonce); 
    
         //set nonce value 
         byte[] nonce_bytes = new byte[16]; 
         RandomNumberGenerator rndGenerator = new RNGCryptoServiceProvider(); 
         rndGenerator.GetBytes(nonce_bytes); 
         nonce.InnerText = Convert.ToBase64String(nonce_bytes); 
    
         //set create value 
         created.InnerText = XmlConvert.ToString(DateTime.Now.ToUniversalTime(), "yyyy-MM-ddTHH:mm:ssZ"); 
    
         //create a new message 
         var r = XmlReader.Create(new StringReader(doc.OuterXml)); 
         var newMsg = Message.CreateMessage(message.Version, message.Headers.Action, r); 
    
         return this.inner.WriteMessage(newMsg, maxMessageSize, bufferManager, messageOffset); 
        } 
    
    
    
    
        public override void WriteMessage(Message message, System.IO.Stream stream) 
        { 
         this.inner.WriteMessage(message, stream); 
        } 
    } 
    } 
    
  4. En los archivos ClearUsernameBinding.cs sustituir este:

    res.Add (nueva TextMessageEncodingBindingElement() {= MessageVersion esto.messageVersion});

    con esto:

    var textEncoder = new TextMessageEncodingBindingElement() {MessageVersion = this.messageVersion}; res.Add (new UsernameExEncoderBindingElement (textEncoder));

  5. En el proyecto TestClient en app.config, hay una propiedad messageVersion en el elemento de enlace. No ha publicado la raíz de su sobre, así que no puedo estar seguro, pero probablemente deba configurarlo para Soap11WSAddressingAugust2004 o Soap11WSAddressing10 (o uno de estos con Soap12 en su lugar).

¡Buena suerte!

+0

Yaron - Estoy obteniendo una violación de proxy aquí en la red de trabajo tratando de obtener el código CUB (porque es @blogspot). ¿Tiene alguna otra ubicación donde el código esté disponible? –

+0

el código está en google code http://code.google.com/p/wcf-clear-username-binding/ el blog solo contiene la explicación –

Cuestiones relacionadas