Axis2+Rampart(WSS4J)實現UsernameToken認證方式的WS-Security(基於SOAP的Web安全呼叫機制)
最近一直研究SOAP訊息的安全通訊方式,沒在網上搜到啥靠譜的,就一步一步摸索,終於成功了,把過程整理出來,供大家參考。
廢話不多說,關於怎麼用Axis2釋出Web Service,WS-Security是啥,什麼是Rampart、和WSS4J又是什麼關係,SOAP訊息等等的問題不是本文的重點,這裡推薦一篇文章,是用Axis2+Rampart實現證書認證方式的WS-Security,寫的比較好,對相關的概念也有部分介紹,必須得贊一個,一系列的文章也都寫的非常好,值得細細品讀。
1、 服務端
最終建好的工程截圖如下:
a) 建立Axis2的工程
b) 新增相關的Jar包,Axis2、Rampart的jar包都需要有,由於本專案中還用到了資料庫,所以也添加了jdbc的jar包。
c) 之後在WEB-INF/modules目錄下新增rampart模組,將rampart-1.6.2.mar和rahas-1.6.2.mar兩個檔案直接拷過來即可。
d) 之後就是在配置檔案services.xml中新增rampart的配置,內容如下:
<module ref="rampart" /> <!-- <parameter name="OutflowSecurity"> <action> <items>UsernameToken</items> <user>administrator</user> <passwordCallbackClass>com.rampart.client.ClientAuthHandler </passwordCallbackClass> </action> </parameter> --> <parameter name="InflowSecurity"> <action> <items>UsernameToken</items> <passwordCallbackClass>com.rampart.WsServiceAuthHandler</passwordCallbackClass> </action> </parameter>
因為服務端只是對訪問請求進行驗證,所以對OutflowSecurity引數不做設定。InflowSecurity引數項配置裡面的passwordCallbackClass需要配置自己寫的回撥函式的類名,程式碼如下:
package com.rampart; 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; public class WsServiceAuthHandler implements CallbackHandler { private final static String USERNAME = "administrator"; private final static String PASSWORD = "123456"; private Logger log = Logger.getLogger(WsServiceAuthHandler.class); /** * 〈一句話功能簡述〉 〈功能詳細描述〉 * * @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[]) * @param callbacks * @throws IOException * @throws UnsupportedCallbackException */ @Override public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException { WSPasswordCallback pCallback = (WSPasswordCallback) callbacks[0]; // 識別符號 String id = pCallback.getIdentifier(); // 此處獲取到的password為null,但是並不代表服務端沒有拿到該屬性。 // 這是因為客戶端提交過來的密碼在SOAP 訊息中已經被加密為MD5 // 的字串,如果我們要在回撥方法中作比較,那麼第一步要做的就是把服務端準備好的密碼加密為MD5 字串,由於MD5 // 演算法引數不同結果也會有差別,另外,這樣的工作由框架替我們完成不是更簡單嗎? String password = pCallback.getPassword(); System.out.println("接收到WebService請求,userName[" + id + "],password[" + password + "]......"); if (null == USERNAME) { System.out.println("驗證使用者失敗,原因:您沒有許可權訪問,使用者名稱為空!"); throw new UnsupportedCallbackException(pCallback, "您沒有許可權訪問,使用者名稱為空!"); } else if (!USERNAME.equals(id)) { System.out.println("驗證使用者失敗,原因:您沒有許可權訪問,使用者名稱錯誤!"); throw new UnsupportedCallbackException(pCallback, "您沒有許可權訪問,使用者名稱錯誤!"); } else { /** * 此處應該這樣做: * 1. 查詢資料庫,得到資料庫中該使用者名稱對應密碼 * 2. 設定密碼,wss4j會自動將你設定的密碼與客戶端傳遞的密碼進行匹配 * 3. 如果相同,則放行,否則返回許可權不足資訊 */ pCallback.setPassword(PASSWORD); /* * if (!PASSWORD.equals(password)) { * System.out.println("驗證使用者失敗,原因:您沒有許可權訪問,密碼錯誤!"); throw new * UnsupportedCallbackException(pCallback, "您沒有許可權訪問,密碼錯誤!"); } */ } pCallback.setIdentifier("service"); } }
具體什麼什麼的看註釋就行了,可以說是綜合了網上能找的所有資料裡面感覺有道理的說明的總和了。
e) 再然後就釋出服務就行了。
2、 客戶端
客戶端的配置相對來說就比較麻煩了,一步一步來吧。
客戶端只要建立普通的Java工程就行了,最終工程截圖如下:
a) 新建Java工程
b) 同樣新增相關的Jar包,Axis2、Rampart的jar包都需要有。
c) 這裡就在src下面新建個repository的目錄,將axis2.xml檔案從Axis2的conf目錄下拷過來,然後再將modules目錄全部拷過來。
d) 之後是新建類,編寫呼叫的客戶端程式碼,其中的關鍵程式碼如下:
package com.client;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMFactory;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.om.OMNode;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.ConfigurationContext;
import org.apache.axis2.context.ConfigurationContextFactory;
import org.apache.axis2.databinding.utils.BeanUtil;
import org.apache.axis2.engine.DefaultObjectSupplier;
import com.bean.PeopleStatisticsInfo;
public class DocumentClient
{
private static EndpointReference targetEPR = new EndpointReference(
"http://localhost:1235/WSS4JServer/services/ZhiliDspServices");
private String getAxis2ConfPath()
{
StringBuilder confPath = new StringBuilder();
confPath.append(this.getClass().getResource("/").getPath());
confPath.append("repository");
return confPath.toString();
}
private String getAxis2ConfFilePath()
{
String confFilePath = "";
confFilePath = getAxis2ConfPath() + "/axis2.xml";
return confFilePath;
}
public void invokeRampartService()
{
System.out
.println("****** Invoking function: invokeRampartService ******");
Options options = new Options();
options.setTo(targetEPR);
options.setAction("urn:getAllPeopleStatistics");
ServiceClient sender = null;
try
{
String confPath = getAxis2ConfPath();
String confFilePath = getAxis2ConfFilePath();
System.out.println("confPath ====== " + confPath);
System.out.println("confFilePath ==== " + confFilePath);
ConfigurationContext configContext = ConfigurationContextFactory
.createConfigurationContextFromFileSystem(confPath,
confFilePath);
sender = new ServiceClient(configContext, null);
sender.setOptions(options);
OMFactory fac = OMAbstractFactory.getOMFactory();
OMNamespace omNs = fac.createOMNamespace(
"http://ws.apache.org/axis2", "");
OMElement callMethod = fac.createOMElement(
"getAllPeopleStatistics", omNs);
OMElement response = sender.sendReceive(callMethod);
System.out.println("response ====>" + response);
System.out.println(response.getFirstElement().getText());
}
catch (Exception e)
{
e.printStackTrace();
}
finally
{
if (sender != null)
sender.disengageModule("addressing");
try
{
sender.cleanup();
}
catch (Exception e)
{
}
}
}
public static void main(String[] args)
{
DocumentClient documentClient = new DocumentClient();
documentClient.invokeRampartService();
}
}
e) 這樣就能呼叫了嗎?還沒有完,還要配置?對,就是配置,不過這裡配置的是拷過來axis2.xml檔案,新增內容如下:
<module ref="rampart" />
<parameter name="OutflowSecurity">
<action>
<items>UsernameToken</items>
<user>administrator</user>
<passwordCallbackClass>com.rampart.client.ClientAuthHandler
</passwordCallbackClass>
</action>
</parameter>
<!--
<parameter name="InflowSecurity">
<action>
<items>UsernameToken</items>
<passwordCallbackClass>com.rampart.client.ServiceAuthHandler
</passwordCallbackClass>
</action>
</parameter>
-->
因為是呼叫的程式碼,只需要配置OutflowSecurity就行了,不需要的InflowSecurity給註釋掉。OutflowSecurity中設定的passwordCallbackClass多對應的回撥函式的程式碼也給貼出來了,如下:
package com.rampart.client;
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;
public class ClientAuthHandler implements CallbackHandler
{
private final static String USERNAME = "administrator";
private final static String PASSWORD = "123456";
private Logger log = Logger.getLogger(ClientAuthHandler.class);
/**
* 〈一句話功能簡述〉 〈功能詳細描述〉
*
* @see javax.security.auth.callback.CallbackHandler#handle(javax.security.auth.callback.Callback[])
* @param callbacks
* @throws IOException
* @throws UnsupportedCallbackException
*/
@Override
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException
{
System.out.println("客戶端 wss4j內容加密併發送到服務端......");
for (int i = 0; i < callbacks.length; i++)
{
WSPasswordCallback pCallback = (WSPasswordCallback) callbacks[i];
String id = pCallback.getIdentifier();
if (USERNAME.equals(id))
{
pCallback.setPassword(PASSWORD);
}
// pCallback.setPassword(PASSWORD);
// pCallback.setIdentifier(USERNAME);
}
}
}
f) 好了,可以呼叫了,是不是很興奮啊!
3、測試
這麼多的配置到底是為了什麼呢?監聽下SOAP請求資訊,看到request的資訊如下:
<?xml version="1.0" encoding="http://schemas.xmlsoap.org/soap/envelope/" standalone="no"?>
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<soapenv:Header xmlns:wsa="http://www.w3.org/2005/08/addressing">
<wsse:Security
xmlns:wsse="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd"
xmlns:wsu="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-utility-1.0.xsd"
soapenv:mustUnderstand="1">
<wsse:UsernameToken wsu:Id="UsernameToken-1">
<wsse:Username>administrator</wsse:Username>
<wsse:Password
Type="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-username-token-profile-1.0#PasswordDigest">1Rjr1XAOJQMeRGzxq5uNVRuoux8=</wsse:Password>
<wsse:Nonce
EncodingType="http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-soap-message-security-1.0#Base64Binary">aHlI/3CcTUy4xlGS8caxuQ==</wsse:Nonce>
<wsu:Created>2014-02-25T00:53:15.832Z</wsu:Created>
</wsse:UsernameToken>
</wsse:Security>
<wsa:To>http://localhost:1235/WSS4JServer/services/ZhiliDspServices
</wsa:To>
<wsa:MessageID>urn:uuid:43409459-de4c-4e92-9a11-d67a42afce31
</wsa:MessageID>
<wsa:Action>urn:getAllPeopleStatistics</wsa:Action>
</soapenv:Header>
<soapenv:Body>
<getAllPeopleStatistics xmlns="http://ws.apache.org/axis2" />
</soapenv:Body>
</soapenv:Envelope>
可以看到,這裡面把密碼給加密了,然後還有一堆亂七八糟看不懂的東西,安全性算是有一定程度的保障吧。
由於程式碼有很多的涉及業務範圍的東西,就不貼出了,有問題的話可以留言或者發訊息。
寫的有點倉促,很多東西都很淺顯,敬請諒解!