I am working on a spring integration component where I posting data to external third-party URL. and Its is working fine with the below code.
<!-- language: lang-xml -->
<int:chain id="channe.id"
input-channel="request.in"
output-channel="reply.channel">
<int-ws:header-enricher>
<int-ws:soap-action
value="${service.soapaction}" />
</int-ws:header-enricher>
<int-ws:outbound-gateway
id="invoker.ws.outbound.gateway"
ignore-empty-responses="true" message-sender="message.sender"
interceptors="${SecurityInterceptor}"
message-factory="${mmessageFactory}"
uri="${protocol}://${host}:${port}/{endpoint}">
<int-ws:uri-variable name="endpoint"
expression="headers.endpoint" />
<int-ws:request-handler-advice-chain>
<ref bean="commonRetryAdviceBean" />
</int-ws:request-handler-advice-chain>
</int-ws:outbound-gateway>
</int:chain>
Below is payload received by third part api.
<MessageLogTraceRecord>
<HttpRequest xmlns="http://schemas.microsoft.com/2004/06/ServiceModel/Management/MessageTrace">
<Method>POST</Method>
<QueryString></QueryString>
<WebHeaders>
<Content-Length>9381</Content-Length>
<Content-Type>text/xml; charset=UTF-8</Content-Type>
<Accept>text/xml</Accept>
<Accept-Encoding>gzip</Accept-Encoding>
<Host>myhost</Host>
<User-Agent>Jakarta Commons-HttpClient/3.1</User-Agent>
<SOAPAction>"http://www.mysoap.com/action/update"</SOAPAction>
</WebHeaders>
Now, I have to add an additional security feature and send the API key in the HTTP header or soap header. SO I modified my code as below. Now I can see API key is sent as soap header but some how SOAPAction is going empty, not sure why.
below is the modified code to send api ket as part of soap header.
<int:chain id="channe.id"
input-channel="request.in"
output-channel="reply.channel">
<int-ws:header-enricher>
<int-ws:soap-action
value="${service.soapaction}" />
</int-ws:header-enricher>
<int-ws:outbound-gateway
id="invoker.ws.outbound.gateway"
ignore-empty-responses="true" message-sender="message.sender"
interceptors="${SecurityInterceptor}"
message-factory="${mmessageFactory}"
mapped-request-headers="soapHeaderMapper"
uri="${protocol}://${host}:${port}/{endpoint}">
<int-ws:uri-variable name="endpoint"
expression="headers.endpoint" />
<int-ws:request-handler-advice-chain>
<ref bean="commonRetryAdviceBean" />
</int-ws:request-handler-advice-chain>
</int-ws:outbound-gateway>
</int:chain>
<bean id="soapHeaderMapper"
class="org.springframework.integration.ws.DefaultSoapHeaderMapper">
<property name="requestHeaderNames">
<list>
<value>api-key</value>
</list>
</property>
</bean>
After adding mapped-request-headers now I am getting
org.springframework.messaging.MessagingException: The message could not be processed because the action '' is invalid or unrecognized.; nested exception is org.springframework.ws.soap.client.SoapFaultClientException: The message could not be processed because the action '' is invalid or unrecognized., failedMessage=GenericMessage
when I checked the payload received by thirdparty api I can see SOAP action is empty I am not sure why.
Please help me.
Thanks.
<QueryString></QueryString>
<WebHeaders>
<Content-Length>9463</Content-Length>
<Content-Type>text/xml; charset=UTF-8</Content-Type>
<Accept>text/xml</Accept>
<Accept-Encoding>gzip</Accept-Encoding>
<Host>myhost</Host>
<User-Agent>Jakarta Commons-HttpClient/3.1</User-Agent>
<SOAPAction>""</SOAPAction>
</WebHeaders>
</HttpRequest>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header api-key="dummy-123455555uuuuuuuuuuuqwert">
The mapped-request-headers="soapHeaderMapper" is wrong configuration. It is exactly about names, but in your case you try to make a reference to the DefaultSoapHeaderMapper bean definition.
Consider to use:
<xsd:attribute name="header-mapper">
<xsd:annotation>
<xsd:documentation>
Reference to a SoapHeaderMapper implementation
that this gateway will use to map between Spring Integration
MessageHeaders and the SoapHeader.
</xsd:documentation>
<xsd:appinfo>
<tool:annotation kind="ref">
<tool:expected-type type="org.springframework.integration.ws.SoapHeaderMapper"/>
</tool:annotation>
</xsd:appinfo>
</xsd:annotation>
</xsd:attribute>
instead.
Also there is the with your header names to map: when you configure some custom header names, all the standard headers are missed. So, alongside with the api-key, you need to consider include the mentioned ws_soapAction name as well.
Related
I am updating the client of an existing SOAP webservice since the service provider has made some changes to thier security headers in the request.
The requirement is to digitally sign the Timestamp which should be present in the request header and the body should not be digitally signed. I am using XML config to create my SOAP request header and digitally sign the Timestamp.
I am basically using org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor as an interceptor. The problem is that Timestamp gets created in the request header however the SignatureValue and DigestValue tags are empty
I have referred https://docs.spring.io/spring-ws/site/reference/html/security.html#security-wss4j-digital-signatures
Versions:
Spring-ws-core --> 2.0.0.RELEASE
spring-ws-security --> 2.0.0.RELEASE
<bean id="wsClientSecurityInterceptor" class="org.springframework.ws.soap.security.wss4j.Wss4jSecurityInterceptor">
<property name="securementActions" value="Timestamp Signature"/>
<property name="securementSignatureKeyIdentifier" value="DirectReference" />
<property name="securementUsername" value="username" />
<property name="securementPassword" value="keystorepassword" />
<property name="securementSignatureCrypto" ref="clientCrypto"/>
<property name="securementSignatureUser" value="username" />
<property name="securementSignatureParts" value="{}{http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd}Timestamp"/>
</bean>
<bean id="clientCrypto" class="org.springframework.ws.soap.security.wss4j.support.CryptoFactoryBean">
<property name="keyStorePassword" value="keystorepassword" />
<property name="keyStoreLocation" value="file:${key.store.location}"/>
<property name="keyStoreType" value="jks" />
<property name="keyStoreProvider" value="IBMJCE" />
</bean>
Though the timestamp gets added to the wsse:Security element in the header, the DigestValue and SignatureValue elements belonging to the xmlns:ds="http://www.w3.org/2000/09/xmldsig#" namespace are always empty
This does not happen if I only sign the Body
I also tried using another Interceptor XwsSecurityInterceptor but that does not work without the Wss4jSecurityInterceptor and gives me the same result when used with Wss4jSecurityInterceptor
<bean id="xwsSecurityInterceptor" class="org.springframework.ws.soap.security.xwss.XwsSecurityInterceptor">
<property name="policyConfiguration" value="classpath:securityPolicy.xml"/>
<property name="callbackHandlers">
<list>
<ref bean="keyStoreHandler"/>
</list>
</property>
</bean>
<bean id="keyStoreHandler" class="org.springframework.ws.soap.security.xwss.callback.KeyStoreCallbackHandler">
<property name="keyStore" ref="keyStore"/>
<property name="privateKeyPassword" value="keystorepassword"/>
</bean>
<bean id="keyStore" class="org.springframework.ws.soap.security.support.KeyStoreFactoryBean">
<property name="location" value="file:${key.store.location}"/>
<property name="password" value="keystorepassword"/>
</bean>
After referring a number of IBM support posts, I have finally come to the solution and have received a QA signoff too on the implementation. I had to replace all my spring mvc config for creating SSL context with configurations on the WAS server itself.You can configure all this using either scripts or using the WAS console manually.
Steps:
Add an entry to ur applications web.xml.Please refer this post https://www.ibm.com/support/knowledgecenter/en/SSAW57_8.5.5/com.ibm.websphere.nd.multiplatform.doc/ae/twbs_jaxwsclientdd.html
The link below will give you a step by step guide as to how to configure the request payload to have a signed timestamp on WAS server.
http://pglezen.github.io/was-config/html/signts.html#sec.signts.keystores.consumer
It basically focusses on :
Policy Set Creation:This will Specify the element or elements to be signed or encrypted in this message part.In our case its the timestamp in the header
Client Policy Set Bindings. If you are the provider you have to refer the provider Policy set bindings section.This involves creation of truststore and keystore required to configure your SSL context. Also has configurations for any proxy settings that you might require.
Finally take a JNDI reference of the service whereever you need to call a particular operation in the service.Refer https://www.ibm.com/support/knowledgecenter/SSAW57_9.0.5/com.ibm.websphere.nd.multiplatform.doc/ae/twbs_jaxwsclientdd.html
Once this done, Simply navigate to Services--> Service Clients and you should have your service refs visible there. You should now attach the Policy set and the bindings created above to the Service client references created in WAS due to point 1 above.You might have to write a script to attach the polict set and binding since after deployment you dont want to do this step manually
Please note: this solution came into picture sicne the version of WAS 8.5.5.16,spring -3.0.5.RELEASE and spring-ws-2.0.0.RELEASE did not support signing of timestamp in the request payload of the SOAP service request. Hope this helps someone !!
I found that when I want to make a REST call using Spring-Integration, it automatically appends 'x' in case its a custom header.
For example in Spring-integration while sending custom request headers such as API-KEY, the actual request header name in the API call becomes X-API-KEY and so it fails.
It seems like Spring is standardizing by enforcing the Custom request headers to start with X, is there a work around?
<int:channel id="requestChannel"/>
<int:channel id="httpHeaderEnricherChannel"/>
<int-http:outbound-gateway request-channel="requestChannel"
url="http://localhost:9090/balance"
http-method="GET"
mapped-request-headers="Api-Key"
expected-response-type="java.lang.String"/>
<int:header-enricher input-channel="httpHeaderEnricherChannel"
output-channel="requestChannel">
<int:header name="Api-Key" value="pass"/>
</int:header-enricher>
You should declare DefaultHttpHeaderMapper.outboundMapper() bean with the setUserDefinedHeaderPrefix(null) and including that your custom Api-Key header mapping. After that you should replace mapped-request-headers attribute with the header-mapper reference.
We have revised the feature and decided to remove "X-" default prefix in the next version.
For more info please, see here Custom HTTP headers : naming conventions and here https://jira.spring.io/browse/INT-3903.
Thanks to #Artem for clarifying, and Gary's post here Spring Integration Http Outbound Gateway Header Mapper
I was able to solve the issue
<int:channel id="requestChannel"/>
<int:gateway id="requestGateway"
service-interface="org.springframework.integration.samples.http.RequestGateway"
default-request-channel="requestChannel">
<int:default-header name="Api-Key" value="pass" />
</int:gateway>
<int-http:outbound-gateway request-channel="requestChannel"
header-mapper="headerMapper"
url="http://localhost:9090/balance"
http-method="GET"
expected-response-type="java.lang.String"/>
<beans:bean id="headerBean"
class="org.springframework.integration.samples.http.HeaderBean" />
<bean id="headerMapper"
class="org.springframework.integration.http.support.DefaultHttpHeaderMapper">
<property name="inboundHeaderNames" value="*" />
<property name="outboundHeaderNames" value="HTTP_REQUEST_HEADERS, Api-Key" />
<property name="userDefinedHeaderPrefix" value="" />
</bean>
I have http outbound gateway that post json message to rest service, now the rest will response with http error status with json message type in case if there is any error which should captured by our application. in happy scenario which post json message to that rest service and got http success status and json message also should captured with our application.
Now what I achieved through spring integration I was able to send message and got success response and capture it but in error status the spring behavior it throw an exception how can I change the behavior?
also how can make retry if there is any timeout ?
below the http outbound gateway configuration
<int-http:outbound-gateway request-channel="aggregatorChannel" reply-channel="responseChannel" charset="UTF-8"
url="http://localhost:8090/receiveGateway" http-method="POST" reply-timeout="180000">
<int-http:request-handler-advice-chain >
<int:retry-advice max-attempts="5" recovery-channel="aggregatorChannel" >
<int:exponential-back-off initial="1000" multiplier="5.0" maximum="600000" />
</int:retry-advice>
</int-http:request-handler-advice-chain>
</int-http:outbound-gateway>
the above configuration will retry on the error message which I don't want retry on that, I want to retry if there is only timeout.
What do you want to change the behavior to (when there's an error)?
If you wire up the RetryTemplate for the retry interceptor manually, as a <bean/>, you can specify in the RetryPolicy which exceptions are retryable.
EDIT:
It's not entirely clear what you are trying to do, but maybe this will point you in the right direction...
<int-http:outbound-gateway request-channel="requestChannel"
url="http://localhost:8080/http/receiveGateway"
http-method="POST"
request-factory="requestFactory"
expected-response-type="java.lang.String">
<int-http:request-handler-advice-chain >
<int:retry-advice max-attempts="5" recovery-channel="foo">
<int:exponential-back-off initial="1000" multiplier="1.1" maximum="600000" />
</int:retry-advice>
<bean class="foo.MyExceptionAdvice" />
</int-http:request-handler-advice-chain>
</int-http:outbound-gateway>
<bean id="requestFactory" class="org.springframework.http.client.SimpleClientHttpRequestFactory">
<property name="connectTimeout" value="5000"/>
<property name="readTimeout" value="5000"/>
</bean>
If the custom exception advice throws an exception, the message will be retried and eventually sent to he recovery channel. If the custom advice consumes the error, it can send a return value. I have put a simple on this gist.
I have a web service client implemented in Spring-ws, using a Wss4jSecurityInterceptor for ws-security.
Calling the endpoint works, data is encrypted, signed and sent, but when the reply is received it is not decrypted. Instead JAXB's unmarshaller is called, resulting in an error like :
Error : org.springframework.oxm.jaxb.JaxbUnmarshallingFailureException:
JAXB unmarshalling exception: unexpected element
(uri:"http://www.w3.org/2001/04/xmlenc#", local:"EncryptedData").
Expected elements are...
Expected elements then goes on to list every data type in the xdd.
This is what my Wss4jSecurityIntercepter is configured with :
<!-- username / password for signing -->
<property name="enableSignatureConfirmation" value="false" />
<property name="securementUsername" value="${securementUsername}" />
<property name="securementSignatureKeyIdentifier" value="DirectReference" />
<property name="securementPassword" value="${keystore.password}" />
<property name="securementSignatureCrypto" ref="crypto" />
<!-- username (certificate) and keystore for encryption -->
<property name="securementEncryptionUser" value="${securementEncryptionUsername}" />
<property name="securementEncryptionKeyIdentifier" value="SKIKeyIdentifier" />
<property name="securementEncryptionCrypto" ref="crypto" />
<!-- validate incoming message signature and decrypt -->
<property name="validationActions" value="Signature Encrypt Timestamp" />
<property name="validationDecryptionCrypto" ref="crypto" />
<property name="validationSignatureCrypto" ref="crypto" />
<property name="validationCallbackHandler">
<bean
class="org.springframework.ws.soap.security.wss4j.callback.KeyStoreCallbackHandler">
<property name="privateKeyPassword" value="${keystore.password}" />
</bean>
</property>
Any idea what goes wrong ?
Thanks.
EDIT: This was caused by a ClientInterceptor that returned false on handleResponse, and was located before the wss4j interceptor, causing all Interceptor processing to stop.
Your root cause is probably related to the order in which the securement of the message was made from the other side:
The order of the actions is significant and is enforced by the
interceptor. The interceptor will reject an incoming SOAP message if
its security actions were performed in a different order than the one
specified by validationActions.
I would recommend that you increase your log level (Add log4j if you are not already using it to see why the interceptor was not able to decrypt the message.
Last but not least, you should implement validator to prevent your process to go further if the message was not decrypted.
Caused by misconfiguration of the Interceptors. (see EDIT in original question)
I'm trying to develop a web service that uses WS-Security using Websphere 7 and JAX-WS. Looking through some guides, it appears that I MUST create a application server user registry and maintain username/passwords inside of that server. Is there anyway to avoid having to create usernames in the server itself and somehow capture the header and do validation based upon another a custom security configuration like a single sign-on?
I'm able to create a handler to get the header, but when mustUnderstands is set to 1 in the request (which is mandatory), it gets rejected before my handler sees the message.
I'm only looking to use the UsernameToken part of WS-Security.
Any help is appreciated.
An example of my request
<?xml version="1.0" encoding="UTF-8"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header>
<wsse:Security xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd" soapenv:mustUnderstand="1">
<wsse:UsernameToken xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd" Id="unt_20">
<wsse:Username>some_username</wsse:Username>
<wsse:Password Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordText">some_password</wsse:Password>
</wsse:UsernameToken>
</wsse:Security>
</soapenv:Header>
<soapenv:Body>
...body...
</soapenv:Body>
</soapenv:Envelope>
Is it possible to create a custom security implementation so I can use my existing user validation scheme?
It would appear that I can implement a custom user registry that can interact with the security implementation of my choice. A link to the IBM article:
http://publib.boulder.ibm.com/infocenter/wasinfo/v6r0/index.jsp?topic=/com.ibm.websphere.base.doc/info/aes/ae/tsec_useregistry.html
Another possible answer is to create a Trust Association Interceptor (TAI). This basically extends your current security.
Here is a useful link to get started:
http://www.ibm.com/developerworks/websphere/techjournal/0508_benantar/0508_benantar.html
You can use the out-of-box WS-Security runtime with policy/bindings to achieve this, but you must write custom code to override the default behavior of checking the user registry for UsernameTokens.
See this article for using your own authentication mechanism when consuming the UsernameToken:
Configuring a UsernameToken caller configuration with no registry interaction
See this article if you want to also create WebSphere credentials based on the user name in that token:
Replacing the authentication method of the UsernameToken consumer using a stacked JAAS login module
Can you elaborate on what you want to achieve?
The WAS Server needs to validate the username and password that comes in the header against its user registry (which could an LDAP, File based registry etc).
LTPA tokens (which are used by WebSphere and related products for SSO) can be used too.
If you spell out your requirements, folks here will be able to help you out.
HTH
Manglu
JAX-WS should allow you to have a custom interceptor.
Take a look at this spring config to see how I have added an interceptor to the service endpoint.
<jaxws:endpoint id="pqdws"
implementor="#Atypon"
address="/pqdws"
publishedEndpointUrl="#ws_webapp_url_ext#">
<jaxws:properties>
<entry key="exceptionMessageCauseEnabled" value="true"/>
<entry key="Content-length"
</jaxws:properties>
<jaxws:inInterceptors>
<bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
<constructor-arg>
<map>
<entry key="action" value="UsernameToken"/>
<entry key="passwordType" value="PasswordText"/>
<entry key="passwordCallbackRef">
<ref bean="passwordCallback"/>
</entry>
</map>
</constructor-arg>
</bean>
</jaxws:inInterceptors>
</jaxws:endpoint>
<bean id="passwordCallback"
class="access.ws.ServerPasswordCallback">
<property name="username" value="#ws_sec_username#"/>
<property name="password" value="#ws_sec_password#"/>
</bean>
The interceptor can then do whatever you wish including calling out to an external service for authentication.