JAX-WS Consuming web service with WS-Security and WS-Addressing

醉酒当歌 提交于 2019-11-30 13:12:43
Jared Knipp

I did end up figuring this issue out but I went in another direction to do so. My solution was to use CXF 2.1 and its JAX-WS implementation, combining the power of CXF with the existing Spring infrastructure I already had in place. I was skeptical at first because of the numerous jars required by CXF, but in the end it provided the best and simplest solution.

Adapting an example from the CXF website for client configuration, I used the custom CXF JAXWS namespace within spring and used an Out Interceptor for Username Token Authentication (Password digest, nonces and timestamp) and timestamp verification. The only other step to make this work was creating my own Password Callback handler that is executed for each outbound SOAP request.

For SSL configuration, I again turned to CXF and its SSL support via conduits, although I could never make SSL work with a specific http:conduit name, I had to use the general purpose one that is not recommended for production environments.

Below is an example of my config file.

Spring config file

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:jaxws="http://cxf.apache.org/jaxws"
    xmlns:sec="http://cxf.apache.org/configuration/security"
    xmlns:http="http://cxf.apache.org/transports/http/configuration"
    xmlns:context="http://www.springframework.org/schema/context"
    xmlns:cxf="http://cxf.apache.org/core"
    xsi:schemaLocation="http://www.springframework.org/schema/beans
    http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
    http://cxf.apache.org/configuration/security http://cxf.apache.org/schemas/configuration/security.xsd
    http://cxf.apache.org/transports/http/configuration http://cxf.apache.org/schemas/configuration/http-conf.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-2.5.xsd
    http://cxf.apache.org/jaxws http://cxf.apache.org/schemas/jaxws.xsd
    http://cxf.apache.org/core http://cxf.apache.org/schemas/core.xsd">

    <context:property-placeholder location="meta/my.properties" />
    <context:component-scan base-package="com.foo" />

    <import resource="remoting.xml" />
    <jaxws:client id="myWebService" address="${my.endpointAddress}"
                  serviceClass="com.foo.my.ServicePortType">

<!-- Testing only, adds logging of entire message in and out -->
<jaxws:outInterceptors>
    <ref bean="TimestampUsernameToken_Request" />
    <ref bean="logOutbound" />
</jaxws:outInterceptors>
<jaxws:inInterceptors>
        <ref bean="logInbound" />
    </jaxws:inInterceptors>
    <jaxws:inFaultInterceptors>
        <ref bean="logOutbound" />
    </jaxws:inFaultInterceptors>

<!-- Production settings -->
<!--
    <jaxws:outInterceptors> <ref bean="TimestampUsernameToken_Request" />
    </jaxws:outInterceptors>
    -->
</jaxws:client >



<!--
    CXF Interceptors for Inbound and Outbound messages
    Used for logging and adding Username token / Timestamp Security Header to SOAP message
-->
<bean id="logInbound" class="org.apache.cxf.interceptor.LoggingInInterceptor" />
<bean id="logOutbound" class="org.apache.cxf.interceptor.LoggingOutInterceptor" />

<bean id="TimestampUsernameToken_Request" class="org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor">
    <constructor-arg>
        <map>
            <entry key="action" value="UsernameToken Timestamp" />
            <entry key="user" value="${my.group}.${my.userId}" />
            <entry key="passwordType" value="PasswordDigest" />
            <entry key="passwordCallbackClass" value="com.foo.my.ClientPasswordHandler" />
        </map>
    </constructor-arg>
</bean>

<!--
    http:conduit namespace is used to configure SSL using keystores, etc
    *.http-conduit works but CXF says its only supposed to be for temporary use (not production),
    well until the correct way works, we're going to use it.
-->
<http:conduit name="*.http-conduit">
    <http:tlsClientParameters   
                  secureSocketProtocol="SSL">
                  <!--
          <sec:trustManagers>
        <sec:keyStore type="JKS"
                         password="${my.truststore.password}"
                         file="${my.truststore.file}" />
                  </sec:trustManagers>
                  -->
                  <sec:keyManagers keyPassword="${my.keystore.password}">
                    <sec:keyStore type="JKS"
                         password="${my.keystore.password}"
                         file="${my.keystore.file}" />
                  </sec:keyManagers>

                  <!-- Cipher suites filters specify the cipher suite to allow/disallow in SSL communcation  -->
                  <sec:cipherSuitesFilter>
                    <sec:include>.*_WITH_3DES_.*</sec:include>
                    <sec:include>.*_EXPORT_.*</sec:include>
                    <sec:include>.*_EXPORT1024_.*</sec:include
                    <sec:include>.*_WITH_DES_.*</sec:include
                    <sec:exclude>.*_WITH_NULL_.*</sec:exclude
                    <sec:exclude>.*_DH_anon_.*</sec:exclude>
                  </sec:cipherSuitesFilter>
    </http:tlsClientParameters>
</http:conduit>
</beans>

Java Client Password Handler:

import java.io.IOException;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;

import org.apache.log4j.Logger;
import org.apache.ws.security.WSPasswordCallback;


/**
 * <p>
 * Provides a callback handler for use processing outbound/inbound SOAP messages.
 * ClientPasswordHandler sets the password used in the WS-Security UsernameToken 
 * SOAP header.
 * 
 * </p>
 * 
 * Created: Apr 1, 2009
 * @author Jared Knipp
 * 
 */
public final class ClientPasswordHandler implements CallbackHandler {
    protected static Logger log = Logger.getLogger(ClientPasswordHandler.class);

    private static final PropertyManager PROPS = PropertyManager.getInstance();
    private static String PASSWORD = PROPS.getPassword();
    private static boolean IS_PASSWORD_CLEAR = PROPS.getIsClearPassword();

    /**
     * Client password handler call back.  This method is used to provide
     * additional outbound (or could be inbound also) message processing.
     * 
     * Here the method sets the password used in the UsernameToken SOAP security header
     * element in the SOAP header of the outbound message.  For our purposes the clear 
     * text password is SHA1 hashed first before it is hashed again along with the nonce and 
     * current timestamp in the security header.
     */
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        if(log.isDebugEnabled()) { log.debug("Setting password for UsernameToken"); }
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];


        // Check to see if the password is already Hashed via SHA1, if not then hash it first
        if(IS_PASSWORD_CLEAR) {
            synchronized(this) {
                PASSWORD = PasswordDigestUtil.doPasswordDigest(PASSWORD);
                IS_PASSWORD_CLEAR = false;
                PROPS.setIsClearPassword(IS_PASSWORD_CLEAR);
                PROPS.setPassword(PASSWORD);
                PROPS.saveProperties();
            }
        }

        pc.setPassword(PASSWORD);
    }
}

If the information isn't in the WSDL, are you sure it's in the service described by the WSDL? The WSDL is meant to provide all the information necessary to describe the service, including the security policies necessary to use the service.

What platform did the WSDL come from? Is it possible that the WSDL is not the complete description? For instance, it might be a WSDL that is included in another WSDL that does provide the security information.

There is a post here explaining how to configure a Client and a Server in CXF with WS-Security: JAX-WS Web Services with Spring and CXF

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!