2017-02-09 10 views
12

데스크톱 응용 프로그램에서 C#을 사용하여 Java Web Service를 사용하려고합니다. My first attempt
는 WebServicesClientProtocol를 사용했다,하지만 난이 구조가 요청을 만들 필요가 WSSE Username and Token Security Spec 1.1
ClientMessageInspector가 BinarySecurityToken 및 서명을 추가합니다.

에서 요구하는 필요한 속성을 추가 할 수 없습니다 해요 : 내가 만들고 관리했습니다

<soap:Envelope xmlns:dz="http://dom.query.api.com" xmlns:soap="http://www.w3.org/2003/05/soap-envelope" xmlns:xsd="http://dz.api.swd.zbp.pl/xsd"> 
    <soap:Header> 
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
      <wsse:UsernameToken wsu:Id="UsernameToken-E94CEB6F4708FB7C23148611494797612"> 
       <wsse:Username>my_login</wsse:Username> 
       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">XqEwZ/CxaBfFvh487TjvN8qD63c=</wsse:Password> 
       <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">JzURe0CxvzRjmEcH/ndldw==</wsse:Nonce> 
       <wsu:Created>2017-02-09T09:42:27.976Z</wsu:Created> 
      </wsse:UsernameToken> 
      <wsse:BinarySecurityToken EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="X509-E94CEB6F4708FB7C2314861149479517">MIIKnDCCB.........nmIngeg6d6TNI=</wsse:BinarySecurityToken> 
      <ds:Signature Id="SIG-E94CEB6F4708FB7C23148611494795311" xmlns:ds="http://www.w3.org/2000/09/xmldsig#"> 
       <ds:SignedInfo> 
        <ds:CanonicalizationMethod Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> 
         <ec:InclusiveNamespaces PrefixList="dz soap xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/> 
        </ds:CanonicalizationMethod> 
        <ds:SignatureMethod Algorithm="http://www.w3.org/2000/09/xmldsig#rsa-sha1"/> 
        <ds:Reference URI="#id-E94CEB6F4708FB7C23148611494795310"> 
         <ds:Transforms> 
          <ds:Transform Algorithm="http://www.w3.org/2001/10/xml-exc-c14n#"> 
           <ec:InclusiveNamespaces PrefixList="dz xsd" xmlns:ec="http://www.w3.org/2001/10/xml-exc-c14n#"/> 
          </ds:Transform> 
         </ds:Transforms> 
         <ds:DigestMethod Algorithm="http://www.w3.org/2000/09/xmldsig#sha1"/> 
         <ds:DigestValue>mlABQuNUFOmLqsDswxXxQ6XnjpQ=</ds:DigestValue> 
        </ds:Reference> 
       </ds:SignedInfo> 
       <ds:SignatureValue>lYhBHSQ/L...XL1HEbMQjJ/Q2Rvg==</ds:SignatureValue> 
       <ds:KeyInfo Id="KI-E94CEB6F4708FB7C2314861149479518"> 
        <wsse:SecurityTokenReference wsse11:TokenType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1" wsu:Id="STR-E94CEB6F4708FB7C2314861149479519" xmlns:wsse11="http://docs.oasis-open.org/wss/oasis-wss-wssecurity-secext-1.1.xsd"> 
         <wsse:Reference URI="#X509-E94CEB6F4708FB7C2314861149479517" ValueType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-x509-token-profile-1.0#X509PKIPathv1"/> 
        </wsse:SecurityTokenReference> 
       </ds:KeyInfo> 
      </ds:Signature> 
     </wsse:Security> 
    </soap:Header> 
    <soap:Body wsu:Id="id-E94CEB6F4708FB7C23148611494795310" xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
     <dz:query> 
      <dz:param> 
       <xsd:userQueryId>27467</xsd:userQueryId> 
      </dz:param> 
     </dz:query> 
    </soap:Body> 
</soap:Envelope> 

을 사용자 정의 클래스는 IEndpointBehavior 및 IClientMessageInspector를 사용하지만, 그들과 내가 함께 UsernameToken

public class InspectorBehavior : IEndpointBehavior 
{ 
    /// <summary> 
    /// Gets or sets the custom ClientInspector. 
    /// </summary> 
    public ClientInspector ClientInspector { get; set; } 

    /// <summary> 
    /// Constructs a new InspectorBehavior 
    /// </summary> 
    /// <param name="clientInspector"><see cref="ClientInspector"/></param> 
    public InspectorBehavior(ClientInspector clientInspector) 
    { 
     ClientInspector = clientInspector; 
    } 

    /// <summary> 
    /// Implement to confirm that the endpoint meets some intended criteria. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    public void Validate(ServiceEndpoint endpoint) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implement to pass data at runtime to bindings to support custom behavior. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    /// <param name="bindingParameters"><see cref="BindingParameterCollection"/></param> 
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implements a modification or extension of the service across an endpoint. 
    /// </summary> 
    /// <param name="endponit"><see cref="ServiceEndpoint"/></param> 
    /// <param name="endpointDispatcher"><see cref="EndpointDispatcher"/></param> 
    public void ApplyDispatchBehavior(ServiceEndpoint endponit, EndpointDispatcher endpointDispatcher) 
    { 
     // not calling the base implementation 
    } 

    /// <summary> 
    /// Implements the custom modification of the WCF client across an endpoint. 
    /// </summary> 
    /// <param name="endpoint"><see cref="ServiceEndpoint"/></param> 
    /// <param name="clientRuntime"><see cref="ClientRuntime"/></param> 
    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime) 
    { 
     if (this.ClientInspector == null) 
      throw new InvalidOperationException("Caller must supply ClientInspector."); 

     clientRuntime.ClientMessageInspectors.Add(ClientInspector); 
    } 
} 

public class ClientInspector : IClientMessageInspector 
{ 
    /// <summary> 
    /// Gets or sets the custom MessageHeader. 
    /// </summary> 
    public MessageHeader[] Headers 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Constructs a new ClientInspector 
    /// </summary> 
    /// <param name="headers"><see cref="MessageHeader"/></param> 
    public ClientInspector(params MessageHeader[] headers) 
    { 
     Headers = headers; 
    } 

    /// <summary> 
    /// Enables inspection or modification of a message before a request message is sent to a service. 
    /// </summary> 
    /// <param name="request"><see cref="Message"/></param> 
    /// <param name="channel"><see cref="IClientChannel"/></param> 
    /// <returns></returns> 
    public object BeforeSendRequest(ref Message request, IClientChannel channel) 
    { 
     if (Headers != null) 
     { 
      for (int i = Headers.Length - 1; i >= 0; i--) 
       request.Headers.Insert(0, Headers[i]); 
     } 

     return request; 
    } 

    /// <summary> 
    /// Enables inspection or modification of a message after a reply message is received but 
    /// prior to passing it back to the client. 
    /// </summary> 
    /// <param name="reply"><see cref="Message"/></param> 
    /// <param name="correlationState">object</param> 
    public void AfterReceiveReply(ref Message reply, object correlationState) 
    { 
     // not calling the base implementation 
    } 
} 

public class SecurityHeader : MessageHeader 
{ 
    private readonly APIConfig config; 

    /// <summary> 
    /// Constructors a new SecurityHeader 
    /// </summary> 
    /// <param name="config"><see cref="APIConfig"/></param> 
    public SecurityHeader(APIConfig config) 
    { 
     this.config = config; 
    } 

    /// <summary> 
    /// Gets or sets a value that indicates whether the header must be understood, according to SOAP 1.1/1.2 specification. 
    /// </summary> 
    public override bool MustUnderstand 
    { 
     get 
     { 
      return true; 
     } 
    } 

    /// <summary> 
    /// Gets the name of the message header. 
    /// </summary> 
    public override string Name 
    { 
     get 
     { 
      return "Security"; 
     } 
    } 

    /// <summary> 
    /// Gets the namespace of the message header. 
    /// </summary> 
    public override string Namespace 
    { 
     get 
     { 
      return "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"; 
     } 
    } 

    protected override void OnWriteStartHeader(XmlDictionaryWriter writer, MessageVersion messageVersion) 
    { 
     writer.WriteStartElement("wsse", Name, Namespace); 
     writer.WriteXmlnsAttribute("wsse", Namespace); 
    } 

    /// <summary> 
    /// Called when the header content is serialized using the specified XML writer. 
    /// </summary> 
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param> 
    /// <param name="messageVersion"><see cref="MessageVersion"/></param> 
    protected override void OnWriteHeaderContents(XmlDictionaryWriter writer, MessageVersion messageVersion) 
    { 
     WriteHeader(writer); 
    } 

    /// <summary> 
    /// Overwrites the default SOAP Security Header values generated by WCF with 
    /// those required by the UserService which implements WSE 2.0. This is required 
    /// for interoperability between a WCF Client and a WSE 2.0 Service. 
    /// </summary> 
    /// <param name="writer"><see cref="XmlDictionaryWriter"/></param> 
    private void WriteHeader(XmlDictionaryWriter writer) 
    { 
     // Create the Nonce 
     byte[] nonce = GenerateNonce(); 

     // Create the Created Date 
     string created = DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffZ"); 

     // Create the WSSE Security Header, starting with the Username Element 
     writer.WriteStartElement("wsse", "UsernameToken", Namespace); 
     writer.WriteXmlnsAttribute("wsu", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"); 
     writer.WriteStartElement("wsse", "Username", null); 
     writer.WriteString(config.Username); 
     writer.WriteEndElement(); 

     // Add the Password Element 
     writer.WriteStartElement("wsse", "Password", null); 
     writer.WriteAttributeString("Type", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest"); 
     writer.WriteString(GeneratePasswordDigest(nonce, created, config.Password)); 
     writer.WriteEndElement(); 

     // Add the Nonce Element 
     writer.WriteStartElement("wsse", "Nonce", null); 
     writer.WriteAttributeString("EncodingType", "http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary"); 
     writer.WriteBase64(nonce, 0, nonce.Length); 
     writer.WriteEndElement(); 

     // Lastly, add the Created Element 
     writer.WriteStartElement("wsu", "Created", null); 
     writer.WriteString(created); 
     writer.WriteEndElement(); 
     writer.WriteEndElement(); 
     writer.Flush(); 
    } 

    /// <summary> 
    /// Generates a random Nonce for encryption purposes 
    /// </summary> 
    /// <returns>byte[]</returns> 
    private byte[] GenerateNonce() 
    { 
     RNGCryptoServiceProvider rand = new RNGCryptoServiceProvider(); 
     byte[] buf = new byte[0x10]; 
     rand.GetBytes(buf); 
     return buf; 
    } 

    /// <summary> 
    /// Generates the PasswordDigest using a SHA1 Hash 
    /// </summary> 
    /// <param name="nonceBytes">byte[]</param> 
    /// <param name="created">string</param> 
    /// <param name="password">string</param> 
    /// <returns>string</returns> 
    private string GeneratePasswordDigest(byte[] nonceBytes, string created, string password) 
    { 
     // Convert the values to be hashed to bytes 
     byte[] createdBytes = Encoding.UTF8.GetBytes(created); 
     byte[] passwordBytes = Encoding.UTF8.GetBytes(password); 
     byte[] msgBytes = new byte[nonceBytes.Length + createdBytes.Length + passwordBytes.Length]; 

     // Combine the values into one byte array 
     Array.Copy(nonceBytes, msgBytes, nonceBytes.Length); 
     Array.Copy(createdBytes, 0, msgBytes, nonceBytes.Length, createdBytes.Length); 
     Array.Copy(passwordBytes, 0, msgBytes, (nonceBytes.Length + createdBytes.Length), passwordBytes.Length); 

     // Generate the hash 
     SHA1CryptoServiceProvider sha1 = new SHA1CryptoServiceProvider(); 
     byte[] hashBytes = sha1.ComputeHash(msgBytes); 
     return Convert.ToBase64String(hashBytes); 
    } 
} 

public class APIConfig 
{ 
    /// <summary> 
    /// Gets or Sets the Password property 
    /// </summary> 
    public string Password 
    { 
     get; 
     set; 
    } 

    /// <summary> 
    /// Gets or Sets the Username property 
    /// </summary> 
    public string Username 
    { 
     get; 
     set; 
    } 
} 

을 추가 할 경우에만 수 있어요

<s:Envelope xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"> 
    <s:Header> 
     <wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"> 
      <wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"> 
       <wsse:Username>Demo</wsse:Username> 
       <wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">1TiCoKWfNF3EdEH3qdU4inKklaw=</wsse:Password> 
       <wsse:Nonce EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">mAyz3SywR8sR9IkhDGJRIw==</wsse:Nonce> 
       <wsu:Created>2017-02-09T23:29:14.371Z</wsu:Created> 
      </wsse:UsernameToken> 
     </wsse:Security> 
    </s:Header> 
    <s:Body xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> 
     <query xmlns="http://dom.query.api.com"> 
      <param> 
       <userQueryId xsi:nil="true" xmlns="http://dom.query.api.com/xsd"/> 
      </param> 
     </query> 
    </s:Body> 
</s:Envelope> 

당신은 내 Security 요소 BinarySecurityTokenSignature 요소가 누락 볼 수 있듯이 : 코드 위에 나는이 요청을 만들 수 있어요. Microsoft.Web.Services3을 사용해 보았지만 행운은 없었습니다.
예를 들어 BinarySecurityToken의 생성자가 보호됩니다.

고객 인증서를 내 인증서 저장소에 가져 왔습니다. 내 요청의 시신에만 서명해야합니다.

Security 요소에 Header의 요소를 어떻게 추가 할 수 있습니까? 내가 Microsoft.Web.Services3을 사용해야한다는 것을 알고 있지만 어떻게해야할지 모르겠다.

내가 비슷한 질문을 인터넷을 통해 검색 한하지만 내가 찾은 모든 사용자 이름과 암호를 추가하는 방법에 대한 자습서이었다 SignatureBinarySecurityToken을 추가하는 방법에 대한 질문에 답이 남아있다 - How to sign xml with X509 cert, add digest value and signature to xml template

답변

4

이 코딩 비슷한을 생산한다 바인딩 메시지 :

var b = new CustomBinding(); 

      var sec = (AsymmetricSecurityBindingElement)SecurityBindingElement.CreateMutualCertificateBindingElement(MessageSecurityVersion.WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10); 
      sec.EndpointSupportingTokenParameters.Signed.Add(new UserNameSecurityTokenParameters()); 
      sec.MessageSecurityVersion = 
       MessageSecurityVersion. 
        WSSecurity10WSTrust13WSSecureConversation13WSSecurityPolicy12BasicSecurityProfile10; 
      sec.IncludeTimestamp = false; 
      sec.MessageProtectionOrder = System.ServiceModel.Security.MessageProtectionOrder.EncryptBeforeSign; 

      b.Elements.Add(sec); 
      b.Elements.Add(new TextMessageEncodingBindingElement(MessageVersion.Soap11, Encoding.UTF8)); 
      b.Elements.Add(new HttpsTransportBindingElement()); 


      var c = 
       new ServiceReference1.SimpleServiceSoapClient(b, new EndpointAddress(new Uri("https://www.bankhapoalim.co.il/"), new DnsEndpointIdentity("WSE2QuickStartServer"), new AddressHeaderCollection())); 

      c.ClientCredentials.UserName.UserName = "yaron"; 
      c.ClientCredentials.UserName.Password = "1234"; 

      c.ClientCredentials.ServiceCertificate.Authentication.CertificateValidationMode = 
       System.ServiceModel.Security.X509CertificateValidationMode.None; 
      c.ClientCredentials.ServiceCertificate.DefaultCertificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Server Public.cer"); 

      c.ClientCredentials.ClientCertificate.Certificate = new X509Certificate2(@"C:\Program Files\Microsoft WSE\v2.0\Samples\Sample Test Certificates\Client Private.pfx", "wse2qs"); 

      c.Endpoint.Contract.ProtectionLevel = System.Net.Security.ProtectionLevel.Sign; 

선택한 경로는 혼자서 메시지 서명을 구현해야합니다.

+0

답장을 보내 주셔서 감사합니다. 귀하의 코드로 생성 된 요청은 거의 정상적으로 보입니다. 한가지 빠진 것은 UsernameToken의 'Nonce'입니다. 커스텀'WSSecurityTokenSerializer'를 사용해야합니까? 여기에 표시된 것처럼 https://weblog.west-wind.com/posts/2012/nov/24/wcf-wssecurity-and-wse-nonce-authentication. 보안 헤더에 'Nonce'와 서명 된 본문이 모두 필요합니다. – Misiu

+0

링크가 작동하는지 확실하지 않으면 시도해야합니다. 다른 방법으로는 https://msdn.microsoft.com/en-us/library/ms731872(v=vs.110).aspx와 같은 wcf에서 사용자 지정 사용자 이름 토큰을 만드는 다른 방법이 있어야합니다. 또한 메시지 관리자에서 사용자 이름을 직접 작성하는 경우에도 (이 경우 코드 구성에서 제거하는 것) 여전히 좋을 것입니다. 또 다른 옵션은 사용자 이름에 nonce를 추가하는 사용자 정의 인코더를 구현하는 것입니다 (https://gist.github.com/yaronn/44bb89cd14c54b24240d152e64ab37bb를 기본으로 사용). 그러나 공백을주의하여 서명을 무효화하지 않도록주의하십시오. –

+0

나는'MessageInspector'를 사용해 보았습니다 만's : Header'에'o : Security' 태그를 두 개 만듭니다. 하나는'Nonce'가있는'o : UsernameToken'과'Nonce'가 있지만 둘째'BinarySecurityToken'과 'Signature' – Misiu