Search code examples
apacheweb-servicescxfws-securitywss4j

CallbackHandler in CXF 3.X & WSS4J 2.X


I'm trying to upgrade our current application to CXF 3 and WSS4J 2. This is causing me quite a headache.

The current application code for the client:

private void secureWebService( Client client, final Credentials credentials ) {

  // set some WS-Security information
  Map<String,Object> outProps = new HashMap<String,Object>();
  outProps.put( WSHandlerConstants.ACTION, WSHandlerConstants.USERNAME_TOKEN );
  outProps.put( WSHandlerConstants.USER, credentials.getUsername() );
  outProps.put( WSHandlerConstants.PASSWORD_TYPE, WSConstants.PW_TEXT );

  // Callback used to retrieve password for given user.
  outProps.put( WSHandlerConstants.PW_CALLBACK_REF, new CallbackHandler() {
     @Override
     public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];
        pc.setPassword( credentials.getPassword() );
     }
  });

  WSS4JOutInterceptor wssOut = new WSS4JOutInterceptor( outProps );
  client.getOutInterceptors().clear();
  client.getOutInterceptors().add( wssOut );
}

On the Server side...

public class ServerPasswordCallback implements CallbackHandler {
    public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback)callbacks[0];
        boolean result = false;
        try {
            LoginContext lc = new LoginContext( container, new CallbackHandler() {
                public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
                    NameCallback nc = (NameCallback)callbacks[0];
                    nc.setName( myGetName() );

                    PasswordCallback pc2 = (PasswordCallback)callbacks[1];
                    String clientPasssword = pc.getPassword(); //Used to contain the password but is now NULL
                    pc2.setPassword( clientPasssword.toCharArray() );
                }
            } );
            lc.login();
            result = true;
        } catch( LoginException le ) {
            le.printStackTrace(); //current stack trace is a NULLPointerException since "clientPassword" is NULL
            // We haven't authenticated, so false will be returned
        } catch( SecurityException se ) {
            throw new IOException( "Cannot create LoginContext. " + se.getMessage() );
        }
        return result;
    }
}

My JAX-WS Endpoint Config:

<bean id="wss4jPasswordCallback" class="com.mycompany.webservice.security.ServerPasswordCallback"/>

<jaxws:endpoint id="customerEndpoint" implementor="#customerWebService" address="/Customer">
    <jaxws:inInterceptors>
        <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
            <constructor-arg>
                <map>
                    <entry key="action" value="UsernameToken"/>
                    <entry key="passwordType" value="PlainText"/>
                    <entry key="passwordCallbackRef">
                        <ref bean="wss4jPasswordCallback"/>
                    </entry>
                </map>
            </constructor-arg>
        </bean>
        <bean class="com.mycompany.webservice.security.Wss4jPrincipalInjectorInterceptor"/>
    </jaxws:inInterceptors>
    <jaxws:outInterceptors>
        <bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
    </jaxws:outInterceptors>    
    <jaxws:outFaultInterceptors>
        <bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
    </jaxws:outFaultInterceptors>   
</jaxws:endpoint>

Specifically, the WSPasswordCallback object is now passing NULL rather than the password as it used to. From my reading, CXF just chose to stop doing this with insufficient documentation regarding what I would do for an upgrade path. What is an upgrade path for this?

Also, I've noticed that WSS4J is changing where it lives. It has moved from "org.apache.ws.security" to "org.apache.wss4j.common.ext". I have also updated all my constants to "org.apache.wss4j.dom.WSConstants" & "org.apache.wss4j.dom.handler.WSHandlerConstants" to get things to compile. This also has drastically changed the old "org.apache.ws.security.validate.Validator" class in "org.apache.commons.validator.Validator". The classes are quite different now. Maybe "org.apache.wss4j.dom.validate.KerberosTokenValidator" is the new replacement? Again, I could find no documentation for this fact.

Please note: This is all working code until moving to the new CXF and WSS4J version!


Solution

  • Due to the significant time I spent on this issue, I wanted to make sure I provided my solution. This may not be for everyone, but if your code looks like my question, this should get you on the right track.

    First, what was the Validator class is now an interface after CXF 3. What I have working is the org.apache.wss4j.dom.validate.UsernameTokenValidator in place of what was org.apache.ws.security.validate.Validator. This critical piece of info was absent in my searches.

    Therefore, if you are using CallbackHandler for doing custom authentication, you need to switch to the UsernameTokenValidator. Here is what my code now looks like.

    JAX-WS Config:

    <!-- Bean for custom authentication of web service -->
    <bean id="UsernameTokenLDAPValidator" class="com.mycompany.webservice.security.UsernameTokenLDAPValidator"/>
    
    <jaxws:endpoint id="customerEndpoint" implementor="#customerWebService" address="/Customer">
        <jaxws:inInterceptors>
            <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
                <constructor-arg>
                    <map>
                        <entry key="action" value="UsernameToken"/>
                        <entry key="passwordType" value="PasswordText"/>
                    </map>
                </constructor-arg>
            </bean>
            <bean class="com.mycompany.webservice.security.Wss4jPrincipalInjectorInterceptor"/>
        </jaxws:inInterceptors>
        <jaxws:outInterceptors>
            <bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
        </jaxws:outInterceptors>    
        <jaxws:outFaultInterceptors>
            <bean class="com.mycompany.webservice.security.Wss4jPrincipalRemoverInterceptor"/>
        </jaxws:outFaultInterceptors>   
        <jaxws:properties>
            <entry key="ws-security.enable.nonce.cache" value="false" />
            <entry key="ws-security.enable.timestamp.cache" value="false" />
            <entry key="ws-security.ut.validator" value-ref="UsernameTokenLDAPValidator"/>
        </jaxws:properties>
    </jaxws:endpoint>
    

    NEW UsernameTokenLDAPValidator class

    public class UsernameTokenLDAPValidator extends UsernameTokenValidator {
    
    
        public Credential validate( Credential credential, RequestData request ) throws WSSecurityException {
            UsernameToken userToken = credential.getUsernametoken();
            final String userId = userToken.getName();
            final String password = userToken.getPassword();
            String securityDomainName = "SecurityDomainNameNameOfJBOSSConfig"; //<login-module>
    
            LoginContext lc;
            try {
                lc = new LoginContext( securityDomainName, new CallbackHandler() {
                    public void handle( Callback[] callbacks ) throws IOException, UnsupportedCallbackException {
                        NameCallback nc = (NameCallback)callbacks[0];
                        nc.setName( userId );
    
                        PasswordCallback pc2 = (PasswordCallback)callbacks[1];
                        pc2.setPassword( password.toCharArray() );
                    }
                } );
                lc.login();
            } catch( LoginException e ) {
                throw new WSSecurityException( ErrorCode.FAILED_AUTHENTICATION, e );
            }
    
            return credential;
        }
    
    }
    

    Notes:

    • I removed my old CallbackHandler class (ServerPasswordCallback) (in question)
    • I did not make any changes to the application code for the client (in question)