在SpringSide實現XFire Webservice認證

本人在做xfire認證時,在網上找一篇文件介紹springside實現的認證,覺得不錯,所以摘錄了。多謝david.turing

XFire官方網站提供的基於Webservice認證的例子有問題,在新版本的XFire1.1.2中編譯不通過,不過這也是小Case,我後來折騰了一下,爲SpringSide提供了一個簡單的Webservice認證功能。
XFire跟Spring的天然融合,讓我們可以少努力10年就能簡單地在Spring中使用Webservice的強大魅力,我從AXIS專向XFire有一些衝動,也吃了不少虧,但受REST一族的強力吹捧,感覺還是值得嘗試的,因此,在公司的系統中也把Axis徹底換了XFire。

回到SpringSide,我大概介紹一下如何配置一個真正實用的XFire驗證服務。
SpringSide中的XFire配置文件放在:
SpringSide-bookstore/src/org/springside/bookstore/plugins/webservice/applicationContext-webservice-server.xml
我們在裏面定義各個Webservice,該文件其實對應於XFire官方的XFire-Servlet.xml
看看下面的BookService,這是一個典型的Webservice服務,紅色的inHandlers是我掛上去的。它的意思是所有訪問BookService的請求都會被先送到authenticationHandler去處理,我們的驗證邏輯可以在裏面進行。
    <!--Web Service 在SpringMVC中的URL 路徑映射-->
    <bean class="org.springframework.web.servlet.handler.SimpleUrlHandlerMapping">
        <property name="mappings">
            <value>/BookService=bookWebService</value>
        </property>
        <property name="inHandlers">
            <ref bean="authenticationHandler"/>
        </property>
    </bean>

我們接着看看authenticationHandler的代碼:
我們在SpringSide中通過header方式向服務器提供驗證信息(另外一種更簡單的方式是創建一個Login的webservice服務,然後在XFire Session中建立Token信息)。
package org.springside.bookstore.plugins.webservice.authentication;

import org.apache.log4j.Logger;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.exchange.InMessage;
import org.codehaus.xfire.fault.XFireFault;
import org.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
import org.jdom.Namespace;


/**
* XFire的回調的Handler,在XFire配置文件中配置
* Server端的認證模塊,回調處理模塊
*
* ClientAuthHandler跟AuthenticationHandler要一起用,或者都不用
*
* @author  david.turing
* @blog  openssl.blogjava.net
*
*/
public class AuthenticationHandler extends AbstractHandler {
    private static final Logger log = Logger.getLogger(AuthenticationHandler.class);
   
    public void invoke(MessageContext context) throws Exception {
       
        log.info("#AuthenticationHandler is invoked");
        InMessage message=context.getInMessage();
       
        final Namespace TOKEN_NS = Namespace.getNamespace("SpringSide","http://service.webservice.plugins.bookstore.springside.org"); 
       
        if(message.getHeader()==null)
        {
            throw new XFireFault("GetRelation Service Should be Authenticated",
                    XFireFault.SENDER);
        }
       
        Element token = message.getHeader().getChild("AuthenticationToken", TOKEN_NS);
        if (token == null)
        {
            throw new XFireFault("Request must include authentication token.",
                                 XFireFault.SENDER);
        }

        String username = token.getChild("Username", TOKEN_NS).getValue();
        String password = token.getChild("Password", TOKEN_NS).getValue();

        System.out.println("username="+username);       
        System.out.println("password="+password);
       
        if(username==null||password==null)
            throw new XFireFault("Supplied Username and Password Please",
                    XFireFault.SENDER);
       
        /**
         * 檢查用戶名密碼是否正確
         */
        PasswordAuthenticationManager pamanager=new PasswordAuthenticationManager();
        if(!pamanager.authenticate(username,password))
            throw new XFireFault("Authentication Fail! Check username/password",
                    XFireFault.SENDER);

       
    }
}
注意,XFireFault異常是往客戶端拋的,Webservice Client應該學會catch XFireFault.

服務器端就是這麼簡單,看看客戶端的TestCase
package org.springside.bookstore.plugins.webservice.service;

import java.lang.reflect.Proxy;
import java.net.MalformedURLException;
import java.util.List;

import org.codehaus.xfire.client.Client;
import org.codehaus.xfire.client.XFireProxy;
import org.codehaus.xfire.client.XFireProxyFactory;
import org.codehaus.xfire.service.Service;
import org.codehaus.xfire.service.binding.ObjectServiceFactory;
import org.springside.bookstore.commons.domain.Book;
import org.springside.bookstore.plugins.webservice.authentication.ClientAuthHandler;

import junit.framework.TestCase;

public class BookServiceWithAuthenticationTestCase extends TestCase {

    protected void setUp() throws Exception {
        super.setUp();
    }

    protected void tearDown() throws Exception {
        super.tearDown();
    }
   
    public void getBookFromWebservice() throws Exception{
   
          Service serviceModel = new ObjectServiceFactory()
                .create(BookService.class);
        BookService service = null;
       
        try {
            service=(BookService) new XFireProxyFactory().create(
                    serviceModel,
                    "http://localhost:8080/springside/service/BookService");
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
       
        Client client = ((XFireProxy) Proxy.getInvocationHandler(service)).getClient();
        //掛上ClientAuthHandler,提供認證
        client.addOutHandler(new ClientAuthHandler());
        List list = service.findBooksByCategory(null);
        assertNotNull(list);
        for(int i=0;i<list.size();i++)
            System.out.println(((Book)list.get(i)).getName());
    }

}

你應該看到上面的client.addOutHandler(new ClientAuthHandler());
沒錯,它跟服務器端的AuthenticationHandler是一對,一起使用的!
也就是,每個被送往WebService服務的請求都被ClientAuthHandler處理過了。
看看ClientAuthHandler做了些什麼:
package org.springside.bookstore.plugins.webservice.authentication;

import org.apache.log4j.Logger;
import org.codehaus.xfire.MessageContext;
import org.codehaus.xfire.handler.AbstractHandler;
import org.jdom.Element;
import org.jdom.Namespace;

/**
* 客戶端端的認證模塊,回調處理模塊
* 每個需要認證的WebService方法都可以掛這個Handler
*
* 僅用於Demo,從解耦和易用性出發,
* 沒有跟Acegi結合,你可以任意擴展
* 默認用戶名/密碼是admin/admin
*
* ClientAuthHandler跟AuthenticationHandler要一起用,或者都不用
*
* @author  david.turing
*
* @blog openssl.blogjava.net
*/   
public class ClientAuthHandler extends AbstractHandler {
        private static final Logger log = Logger.getLogger(ClientAuthHandler.class);
       
        //客戶端自己配置用戶名密碼或者更安全的KeyStore方式
        private String username = "admin";
        private String password = "admin";
       
        public ClientAuthHandler() {
        }
       
        public ClientAuthHandler(String username,String password) {
            this.username = username;
            this.password = password;
        }
       
        public void setUsername(String username) {
            this.username = username;
        }
       
        public void setPassword(String password) {
            this.password = password;
        }
       
        public void invoke(MessageContext context) throws Exception {
                       
            /*******************************************
             * Soap Header方式
             * 從Soap Header中獲取用戶名密碼
             *******************************************/
            final Namespace ns = Namespace.getNamespace("SpringSide","http://service.webservice.plugins.bookstore.springside.org"); 
            Element el = new Element("header",ns);

            Element auth = new Element("AuthenticationToken", ns);
            Element username_el = new Element("Username",ns);
            username_el.addContent(username);
            Element password_el = new Element("Password",ns);
            password_el.addContent(password);
            auth.addContent(username_el);
            auth.addContent(password_el);
            el.addContent(auth);           
            context.getCurrentMessage().setHeader(el);           
            log.info("ClientAuthHandler done!");
        }
    }

不就是往header裏面注入username,password!

在SpringSide中,所有的Spring配置文件都被小白分散到各個Module中去了,Wuyu原先是在Plugin中提供Webservice功能,因此,我仍然在Plugin中創建XFire接口。
SpringSide的Spring配置文件放在:
SpringSide-bookstore/webapp/WEB-INF/springmvc-servlet.xml
該文件定義了Plugin的xml:
AuthenticationHandler這個Bean需要先定義在Plugins-servlet.xml中,其它很簡單,大家去Try一下就知道了。

 
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章