2015年7月11日星期六

Fuse_010:Fuse 快速上手之十:secure-soap

环境:JBoss Fuse 6.2.0

1. 学习重点
(1)开发 JAX-WS Web Service。
(2)使用 CXF 的 JaxWsProxyFactoryBean 创建 Client 端 proxy,调用远程 Web Service。
(3)在配置文件中为 CXF JAX-WS Web service 设置 WS-Security。

2. blueprint.xml 
<?xml version="1.0" encoding="UTF-8"?>
<!--
    JBoss, Home of Professional Open Source
    Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
    contributors by the @authors tag. See the copyright.txt in the
    distribution for a full listing of individual contributors.

    Licensed under the Apache License, Version 2.0 (the "License");
    you may not use this file except in compliance with the License.
    You may obtain a copy of the License at
    http://www.apache.org/licenses/LICENSE-2.0
    Unless required by applicable law or agreed to in writing, software
    distributed under the License is distributed on an "AS IS" BASIS,
    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    See the License for the specific language governing permissions and
    limitations under the License.
-->
<!--
   This is the OSGi Blueprint XML file defining the CXF JAX-WS beans.  Because the file is in the
   OSGI-INF/blueprint directory inside our JAR, it will be automatically activated as soon as the artifact is installed.

   The root element for any OSGi Blueprint file is 'blueprint' - you also see the namespace definitions for both the Blueprint
   and the CXF JAX-WS namespaces.
-->
<blueprint
        xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0"
        xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xmlns:jaxws="http://cxf.apache.org/blueprint/jaxws"
        xsi:schemaLocation="http://www.osgi.org/xmlns/blueprint/v1.0.0 http://www.osgi.org/xmlns/blueprint/v1.0.0/blueprint.xsd
                        http://cxf.apache.org/blueprint/jaxws http://cxf.apache.org/schemas/blueprint/jaxws.xsd">

    <!--
      Using the <jaxws:endpoint/>, we're configuring the actual JAX-WS endpoint, referring to our web service implementation class
      and the URI address we want to assign to our service.  The address is relative to the CXF servlet URI,
      with the default configuration in place, this endpoint will be available at 'http://localhost:8181/cxf/HelloWorld'.
    -->
    <jaxws:endpoint id="helloWorld"
                    implementor="io.fabric8.quickstarts.soap.secure.HelloWorldImpl"
                    address="/HelloWorldSecurity">


        <!--
          We will be adding two interceptors to the inbound interceptor chain:
          - the CXF WSS4J interceptor to support WS-Security for passing along the credentials
          - a reference to the the JAAS authentication interceptor defined as a separate bean later on
            this will ensure that the credentials are being authenticated in the JAAS realm defined there ('karaf')
        -->
        <jaxws:inInterceptors>

            <bean class="org.apache.cxf.ws.security.wss4j.WSS4JInInterceptor">
                <property name="properties">
                    <map>
                        <entry key="action" value="UsernameToken"/>
                        <entry key="passwordType" value="PasswordText"/>
                    </map>
                </property>
            </bean>
            <ref component-id="authenticationInterceptor"/>
        </jaxws:inInterceptors>
        <jaxws:outInterceptors>
            <bean class="io.fabric8.quickstarts.soap.secure.EnableCORSInterceptor"/>
        </jaxws:outInterceptors>

        <!--
          Disable the WSS4JInInterceptor validation check fo the password callback, as we don't provide it here.
        -->
        <jaxws:properties>
            <entry key="ws-security.validate.token" value="false"/>
        </jaxws:properties>
    </jaxws:endpoint>

    <!--
      We are using the OSGi Blueprint XML syntax to define a bean that we referred to in our JAX-WS endpoint setup.
      This bean is a CXF interceptor that ensures that a request has been authenticated before allowing it to pass. For
      performing the authentication, this interceptor will delegate to JAAS, using the realm name 'karaf'.  This will allow
      it to reuse the same authentication mechanism that is being used to secure other ESB facilities, such as the remote
      SSH shell and the webconsole.
    -->
    <bean id="authenticationInterceptor" class="org.apache.cxf.interceptor.security.JAASLoginInterceptor">
        <property name="contextName" value="karaf"/>
    </bean>

</blueprint>

3. EnableCORSInterceptor.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.soap.secure;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.transport.http.Headers;

import java.util.Arrays;
import java.util.List;
import java.util.Map;

public class EnableCORSInterceptor extends AbstractPhaseInterceptor {

    public EnableCORSInterceptor() {
        super(Phase.PRE_PROTOCOL);
    }

    @Override
    public void handleMessage(Message message) throws Fault {
        Map> headers = Headers.getSetProtocolHeaders(message);
        try {
            //Access-Control-Allow-Origin:* Access-Control-Allow-Methods:POST,GET
            headers.put("Access-Control-Allow-Origin", Arrays.asList("*"));
            headers.put("Access-Control-Allow-Methods", Arrays.asList("POST", "GET"));
        } catch (Exception ce) {
            throw new Fault(ce);
        }
    }
}


4. SecureSoapTest.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.soap.secure;

import org.apache.cxf.frontend.ClientProxy;
import org.apache.cxf.jaxws.JaxWsProxyFactoryBean;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.HashMap;
import java.util.Map;

import static org.junit.Assert.*;

/**
 * A Java client application that uses CXF's JaxWsProxyFactoryBean to create a web service client proxy to invoke
 * the remote web service.
 */
public class SecureSoapTest {

    private static final Logger LOG = LoggerFactory.getLogger(SecureSoapTest.class);

    public static void main(String[] args) {
        try {
            new SecureSoapTest().sendRequest();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void sendRequest() throws Exception {
        /*
         * Set up the JaxWsFactoryBean to access our client:
         * - the Java interface defining the service
         * - the HTTP address for the service
         */
        JaxWsProxyFactoryBean factory = new JaxWsProxyFactoryBean();
        factory.setServiceClass(HelloWorld.class);
        factory.setAddress("http://localhost:8181/cxf/HelloWorldSecurity");

        /*
         * Obtain a proxy, implementing the service interface, to access the remote interface.
         * It will allow you to easily perform the HTTP SOAP request from Java code.
         */
        HelloWorld client = (HelloWorld) factory.create();

        /*
         * Add the extra configuration and interceptors required for the authentication
         */
        Map outProps = new HashMap();
        outProps.put("action", "UsernameToken");
        ClientProxy.getClient(client).getOutInterceptors().add(new CustomSecurityInterceptor());
        ClientProxy.getClient(client).getOutInterceptors().add(new WSS4JOutInterceptor());

        /*
         * Calling sayHi() on on the client object will actually perform an HTTP SOAP request instead behind the scenes
         * and returns the resulting response.
         */
        String ret = client.sayHi("World");
        LOG.info("result: " + ret);

        assertEquals("Hello World", ret);
    }

}


5. CustomSecurityInterceptor.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.soap.secure;

import org.apache.cxf.interceptor.Fault;
import org.apache.cxf.interceptor.Interceptor;
import org.apache.cxf.message.Message;
import org.apache.cxf.phase.AbstractPhaseInterceptor;
import org.apache.cxf.phase.Phase;
import org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor;

import java.util.HashMap;
import java.util.Map;

/**
 * CXF Interceptors are a very powerful and flexible mechanism to add custom logic to the default CXF processing,
 * both when using CXF on the client side and on the server side.
 *
 * With this custom security interceptor, we will configure the default WSS4J interceptor in the client to provide the required
 * credentials to perform our web service invocation.
 */
public class CustomSecurityInterceptor extends AbstractPhaseInterceptor {

    /**
     * Configuring the interceptor to be used in the 'setup' phase.
     */
    public CustomSecurityInterceptor() {
        super(Phase.SETUP);
    }

    /**
     * This is the actual implementation for our interceptor - we define the necessary properties for doing the authentication
     * and then iterate over the rest of the interceptor chain to find the WSS4J interceptor and configure it properly.
     */
    public void handleMessage(Message message) throws Fault {
        /*
         * Define the configuration properties
         */
        Map outProps = new HashMap();
        outProps.put("action", "UsernameToken");
        outProps.put("passwordType", "PasswordText");

        /*
         * The username ('admin') is provided as a literal, the corresponding password will be determined by the client
         * password callback object.
         */
        outProps.put("user", "admin");
        outProps.put("passwordCallbackClass", ClientPasswordCallback.class.getName());

        /*
         * Find the WSS4J interceptor in the interceptor chain and set the configuration properties
         */
        for (Interceptor interceptor : message.getInterceptorChain()) {
            //set properties for WSS4JOutInterceptor
            if (interceptor.getClass().getName().equals("org.apache.cxf.ws.security.wss4j.WSS4JOutInterceptor")) {
                ((WSS4JOutInterceptor) interceptor).setProperties(outProps);
            }
        }
    }

}


6. ClientPasswordCallback.java
/*
 * JBoss, Home of Professional Open Source
 * Copyright 2014, Red Hat, Inc. and/or its affiliates, and individual
 * contributors by the @authors tag. See the copyright.txt in the
 * distribution for a full listing of individual contributors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * http://www.apache.org/licenses/LICENSE-2.0
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package io.fabric8.quickstarts.soap.secure;

import org.apache.wss4j.common.ext.WSPasswordCallback;

import javax.security.auth.callback.Callback;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.callback.UnsupportedCallbackException;
import java.io.IOException;

/**
 * This is a JAAS CallbackHandler implementation that will provide the password for our custom security interceptor.
 */
public class ClientPasswordCallback implements CallbackHandler {

    /*
     * Handle the authentication callback by checking the identifier and just hard-coded returning the correct password.
     */
    public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
        WSPasswordCallback pc = (WSPasswordCallback) callbacks[0];

        /*
         * User is 'admin', password is 'admin'
         */
        if (pc.getIdentifier().equals("admin")) {
            pc.setPassword("admin");
        }
    }

}

7. 编译、部署、卸载
(1)cd /Users/maping/Redhat/fuse/jboss-fuse-6.2.0.redhat-133/quickstarts/cxf/secure-soap
(2)mvn clean install
(3)./fuse (确认 etc/user.properties 文件中 admin 用户已经打开注释)
(4)features:install cxf-ws-security
(5)osgi:install -s mvn:org.jboss.quickstarts.fuse/cxf-secure-soap/6.2.0.redhat-133
(6)osgi:list
(7)http://localhost:8181/cxf/ 
(8)http://localhost:8181/cxf/HelloWorldSecurity?wsdl
(9)cxf:list-endpoints
(10)mvn -Ptest
(11)osgi:uninstall <id>

没有评论: