(點選上方公眾號,可快速關註)
來源:袁鳴凱 ,
blog.csdn.net/lifetragedy/article/details/7823435
一、WSSecurity簡述
安全的Web服務是Web服務成功的必要保證。但大家都知道,Web服務使用XML來進行資料交換,而XML在預設情況下是明文編碼的;同時,大部分Web服務使用HTTP協議作為傳輸協議,同樣,HTTP也是使用明文方式來傳輸資料的。這就造成了在不加密的傳輸協議上傳輸不加密的資訊,從而使資訊傳輸的保密性受到威脅。作為企業級的應用,以上的方式不能滿足安全性基本要求:
-
資料在internet上傳輸的時侯是不應該被第三方能夠看到的;
-
雙方必須能夠驗定彼此間的來源;
-
雙方必須能夠確定被傳送的資料沒有被在中途中遭到駭客的修改。
透過使用SSL協議我們可以解決第一個問題即:”不應該被第三方看到”;使用數字簽名和數字證書可以解決後面的兩個問題。當使用數字證書方法時,Web 服務請求者必須有一個由可信認證中心簽署的數字證書。請求者使用這個證書來表明它們的身份,並對 SOAP 訊息進行數字簽名。對方系統接收到訊息後,就可對訊息做時間戳記併進行日誌記錄。此時,數字簽名會得到驗證。驗證過程將確保訊息來自傳送方,並且還要驗證訊息內容在傳輸過程中沒有被篡改。
IBM、Microsoft 和 Verisign 於2002年十二月份聯合釋出了一個關於 Web 服務安全性(Web Services Security,WS-Security)的規範,該規範描述如何向 SOAP 訊息附加簽名和加密報頭;另外,它還描述如何向訊息附加安全性令牌(包括二進位制安全性令牌,如 X.509 證書),提供了一套幫助 Web 服務開發者保護 SOAP 訊息交換的機制。
根據應用的對安全要求的級別不同,可以採用不同的方式來實現安全性,以下是目前最常用的一些實現方式(從低到高排列):
-
J2EE Web應用預設的訪問控制(資料是明文的);
-
使用axis的Handler進行訪問控制(資料是明文的);
-
使用Servlet過濾器(Filter)進行訪問控制(資料是明文的);
-
使用SSL/HTTPS協議來傳輸(加密的資料傳輸協議);
-
使用WS-Security規範對資訊進行加密與身份認證(資料被加密傳輸)。
前三種方式對於安全級別要求不高的應用是可行的,它能夠使用Web應用訪問認證機制來進行許可權驗證,從而保護對資源的訪問。但需要註意的是,雖然它們進行了身份驗證,但資訊的傳遞還是以明文的方式進行的,不能保證資訊在傳輸過程中不被竊取。SSL是一個安全的傳輸協議,使用它傳輸Web服務能保證資訊不被第三方竊取。但它有個缺點就是對系統資源消耗大。採用最後一種方式,資訊被簽名後再加密,然後把加密後的資訊網路上傳播,這樣,即使第三方獲得加密後的傳輸資訊,也不能解密。對於安全級別要求高的系統,應該採用WS-Security規範來作為Web服務安全性解決方案。
二、基於https通訊並且使用使用者名稱密碼來驗證的WS
在一般的應用中,我們可以透過https來保護我們傳輸的明文資料。
關鍵在於我們需要來驗證這個客戶端過來的請求,即需要具有基本的使用者名稱,密碼才能訪問我的Web Service,我們稱之為Basic Auth。
2.1 錯誤做法
在很多專案中,有些開發隊伍為了圖省事,客戶對環境的掌控也不好,為了驗證一個webservice,我們往往會採用以下這樣的驗證手法:
第一種:
http://xxxx.xxx.xxx/abc.wsdl?username=驗證個頭&password;=驗證個頭
服務端拿到這個url把username,password用request.getParameter出來後,和資料庫一匹配,驗證。
第二種:
驗證個頭啊
不要老是你個頭你個頭
2007-01-01
服務端拿到後把這個soap request body中的
這兩種做法,無疑是掩耳盜鈴!!!(不要和我說業務實現是最主要的,等你的資料哪天沒了,廠長經理的工資被篡改了,如果你願意被客戶做成東方不敗,那你儘管去這樣做就好了。)
2.2 正確的做法
透過上圖我們可以看到,如果你的使用者名稱和密碼和服務端預設的使用者名稱密碼如果不匹配,你的“呼叫”,根本到達不了具體的Web Service,直接在Web Server端已經被打回來了,即你連wsdl都到達不了。
三、實際例子
3.1 Service端
我們編寫一個Service端
org.sky.axis2.security.SimpleAuthService
package org.sky.axis2.security;
public class SimpleAuthService {
public double getTax(double salary) {
// System.out.println(“input salary=====” + salary);
if (salary > 10000) {
return 2000;
} else if (salary > 1000 && salary <= 10000) {
return 200;
} else {
return 0;
}
}
}
service.xml檔案的內容
Please Type your service description here
class=”org.apache.axis2.rpc.receivers.RPCMessageReceiver” />
最重要的來了
修改web.xml檔案,增加以下的內容
Simple Authenticate Web service
/services/SimpleAuthService
我們可以看到:
-
只有在服務端屬於bank_member角色(組)中的人員才被允許訪問該web service即:SimplAuthService。
-
而且,該該訪問採用“BASIC”樣式,即需要使用者名稱和密碼來進行訪問。
隨後,我們開啟tomcat所在目錄下的conf目錄下的tomcat-users.xml如我的是“D:\tomcat\conf\tomcat-users.xml”。
在檔案中加入如下內容(註意紅色加粗的部分):
然後我們來佈署我們的web service。
佈署後我們啟動我們的tomcat,訪問:http://localhost:8080/Axis2Service/services/listServices
我們來點這個SimpleauthService,然後我們可以看到我們的瀏覽器彈出一個對話方塊
我們輸入剛才在tomcat的tomcat-users.xml中定義的Wright這個使用者,即
使用者名稱:Wright
密碼: abcdefg
註意大小寫要區分啊!
然後得到wsdl的輸出:
如果我們在使用者名稱和密碼處輸錯一個字元,我們將會被迫停留在此對話方塊上,而得不到我們相要的wsdl的正確輸出:
然後我們試著點[取消]按鈕,我們將得到
3.2 製作client端前的準備
我們前面說過了,我們需要使用https來保護我們傳輸的使用者名稱和密碼,即Wright/abcdefg這個認證。
因此,我們需要實現一個單向的https。
還記得我們在第二天“apache tomcat https應用”中所說的那個服務端與客戶端與CA之間的互信關係是如何構成的嗎?我們來重溫一下,看下麵這個圖:
1) 由於服務端是我由我們的CA即RootCA簽發的;
2) 而我們的客戶端的根信任域內裝著我們的RootCA這張證書;
3) 因此當我們的客戶端去訪問我們的服務端時,客戶端client和服務端之間搭成了“互信”;
我們來重溫一下這個環境搭建的過程。
3.2.1 製作CA
1)先產生KEY
openssl genrsa -des3 -out shnlap93.key 1024
2)透過key產生ca證書
我們生成了一個有效日期為3650天(10年)的RootCA。
註意:
如果不加這個-days引數,預設產生的證書檔案的有效期為30天,即一個月的有效期。
3.2.2 製作tomcat的證書即jks格式檔案
1)產生shnlap93.jks檔案
keytool -genkey -alias shnlap93X509 -keyalg RSA -keysize 1024 -dname “CN=shnlap93, OU=insurance, O=CTS, L=SH, S=SH, C=CN” -keypass aaaaaa -keystore shnlap93.jks -storepass aaaaaa
2)透過JKS產生csr(證書請求)檔案
3)使用我們的RootCA簽名該csr檔案並生成crt(證書檔案)
4) 將RootCA作為“信任域”即trustcacerts匯入原來的jks檔案
5) 將被簽名後的證書檔案匯入原來的jks檔案
這樣,我們勾成了上述的“三角形”互信關係,就可以把這個shnlap93.jks檔案扔給tomcat,然後修改tomcat的conf目錄下的server.xml檔案。
port=”8443″ protocol=”HTTP/1.1″
connectionTimeout=”20000″
secure=”true” SSLEnabled=”true”
clientAuth=”false” sslProtocol=”TLS”
keystoreFile=”d:/tomcat/conf/shnlap93.jks” keystorePass=”aaaaaa”
/>
隨後我們啟動tomcat。
如果得到上面截圖中白色方框標出的輸出的話則代表我們的tomcat已經以https方式在運行了。
3.2.3 將RootCA匯入IE的根證書串列中去
按[匯入],選擇我們的ca.crt檔案。
然後我們在IE中打入:https://shnlap93:8443/Axis2Service/services/SimpleAuthService?wsdl
由於是https系結證書檔案中的CN(Common Name),因此此時我們必須使用“主機名”來代替原來的IP地址來訪問。
在彈出的需要輸入使用者名稱和密碼的對話方塊中輸入”Wright/abcdefg”,我們即可得到如下的輸出:
我們可以看到,該證書是有效證書,而不會彈出一個“你是否信任”一類的對話方塊再次讓你確認是否相信該https連線了。其原因就在於
因為我們在我們的IE瀏覽器中已經建立起下述這樣的一個三角互信關係來了:
1) 由於服務端是我由我們的CA即RootCA簽發的;
2) 而我們的客戶端的根信任域內裝著我們的RootCA這張證書;
3) 因此當我們的客戶端去訪問我們的服務端時,客戶端client和服務端之間搭成了“互信”;
3.3 需要為我們的Axis2的呼叫客戶端也建立起https中的互信
上面是我們開啟一個IE後訪問https的連結所需要的步驟,現在我們這樣來想:
-
我們現在將要執行的是一個Java應用程式;
-
該應用程式將透過https這樣的連結來訪問我們的tomcat中的webservice;
而不再是一個IE來訪問我們的web service嘍!!!
那麼,我們應該為我們的Java應用程式,也配置一個上述這樣的三角信任關係,對不對?
先來看下麵這個示例圖,和IE端配置信任關係稍稍有點不一樣
由於我們的是一個Java應用程式,因此我們的應用程式需要帶著一個jks檔案,我們把它稱為client.jks吧。
這個client.jks檔案的trustcacerts域裡,只要帶有RootCA的資訊,是不是這個client就可以在訪問我們的伺服器時,和我們的伺服器也建立起一個三角互信關係了?
我們來動手吧,只需要兩步即可。
1) 建立客戶端所需的JKS檔案
這邊的密碼,我用的是六個b,和服務端的證書的密碼區分開來。
1) 將RootCA作為“信任域”即trustcacerts匯入客戶端的jks檔案
keytool -import -alias rootca -trustcacerts -file ca.crt -keystore shnlap93client.jks -storepass bbbbbb
這樣,這個客戶端的jks檔案我們就可以給我們的axis2的客戶端用了。
3.4 呼叫客戶端
先將shnlap93client.jks放置在我們工程的src目錄,使得這個jks檔案會被自動編譯到classpath下。
org.sky.axis2.security.SimpleAuthClient
package org.sky.axis2.security;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import javax.xml.namespace.QName;
import org.apache.axiom.om.OMAbstractFactory;
import org.apache.axiom.om.OMElement;
import org.apache.axiom.om.OMNamespace;
import org.apache.axiom.soap.SOAPBody;
import org.apache.axiom.soap.SOAPEnvelope;
import org.apache.axiom.soap.SOAPFactory;
import org.apache.axis2.addressing.EndpointReference;
import org.apache.axis2.client.OperationClient;
import org.apache.axis2.client.Options;
import org.apache.axis2.client.ServiceClient;
import org.apache.axis2.context.MessageContext;
import org.apache.axis2.transport.http.HTTPConstants;
import org.apache.axis2.transport.http.HttpTransportProperties.Authenticator;
import org.apache.axis2.wsdl.WSDLConstants;
public class SimpleAuthClient {
private static EndpointReference targetEPR = new EndpointReference(
“https://shnlap93:8443/Axis2Service/services/SimpleAuthService”);
private final static String usr = “Wright”;
private final static String pwd = “abcdefg”;
private final static String jksFile = “shnlap93client.jks”;
private final static String jksFilePWD = “bbbbbb”;
private String getJskFilePath() {
String filePath = “”;
ClassLoader classLoader = Thread.currentThread()
.getContextClassLoader();
URL url = classLoader.getResource(jksFile);
if (url == null) {
classLoader = ClassLoader.getSystemClassLoader();
url = classLoader.getResource(jksFile);
}
filePath = url.getPath();
System.out.println(filePath);
return filePath;
}
public void getTax() {
System.setProperty(“javax.net.ssl.trustStore”, getJskFilePath());// //
System.setProperty(“javax.net.ssl.trustStorePassword”, jksFilePWD);
ServiceClient sender = null;
Authenticator authenticator = new Authenticator();
List
auth = new ArrayList ();
auth.add(Authenticator.BASIC);
authenticator.setAuthSchemes(auth);
authenticator.setUsername(usr);
authenticator.setPassword(pwd);
authenticator.setPreemptiveAuthentication(true);
Options options = new Options();
options.setTo(targetEPR);
options.setAction(“urn:getTax”);
options.setProperty(HTTPConstants.AUTHENTICATE, authenticator);
try {
sender = new ServiceClient();
sender.setOptions(options);
OperationClient mepClient = sender
.createClient(ServiceClient.ANON_OUT_IN_OP);
MessageContext mc = new MessageContext();
SOAPFactory fac = OMAbstractFactory.getSOAP11Factory();
SOAPEnvelope env = fac.getDefaultEnvelope();
OMNamespace omNs = fac.createOMNamespace(
“http://security.axis2.sky.org”, “”);
OMElement getTax = fac.createOMElement(“getTax”, omNs);
OMElement salaryEle = fac.createOMElement(“salary”, omNs);
salaryEle.setText(“2100”);
getTax.addChild(salaryEle);
env.getBody().addChild(getTax);
mc.setEnvelope(env);
mepClient.addMessageContext(mc);
System.out.println(“message====” + env);
mepClient.execute(true);
MessageContext response = mepClient
.getMessageContext(WSDLConstants.MESSAGE_LABEL_IN_VALUE);
SOAPBody body = response.getEnvelope().getBody();
OMElement element = body.getFirstElement().getFirstChildWithName(
new QName(“http://security.axis2.sky.org”, “return”));
System.out.println(element.getText());
} catch (Exception e) {
e.printStackTrace();
} finally {
if (sender != null)
try {
sender.cleanup();
} catch (Exception e) {
}
}
}
public static void main(String[] args) {
SimpleAuthClient client = new SimpleAuthClient();
client.getTax();
}
}
註意紅色標粗的部分,由其是:
System.setProperty(“javax.net.ssl.trustStore”, getJskFilePath());// //
System.setProperty(“javax.net.ssl.trustStorePassword”, jksFilePWD);
由於https需要透過client的jks與server的jks建立起三角互信關係,因此我們需要透過程式去訪問這個jks檔案,訪問這個jks檔案我們需要知道:
1) 該jks所在路徑;
2) 該jks的密碼(六個b);
對吧?
Authenticator authenticator = new Authenticator();
List
auth = new ArrayList (); auth.add(Authenticator.BASIC);
authenticator.setAuthSchemes(auth);
authenticator.setUsername(usr);
authenticator.setPassword(pwd);
authenticator.setPreemptiveAuthentication(true);
options.setProperty(HTTPConstants.AUTHENTICATE, authenticator);
上述程式碼做的事就是把“Wright/abcdefg”這對使用者名稱及密碼,set到一個Authenticator物件中去,然後將該物件與需要訪問的soap request進行系結。
然後我們來看執行結果:
我們把:
privatefinal static String usr = “Wright”;
privatefinal static String pwd = “abcdefg”;
中的任何一個值改動一下比如說我們把usr改成”abc”,然後再來看執行結果。
註意到這個org.apache.axis2.AxisFault: Transport error: 401 Error: Unauthorized了嗎?
再來比較我們在瀏覽器中的當彈出要求輸入的使用者名稱和密碼的對話方塊時,我們點[取消]按鈕得到的輸出:
明白了吧?
這就是基於使用者名稱密碼且透過https來訪問web service的詳細過程。
但是這種方法的缺點是,使用者名稱和密碼還是以明文方式傳輸,且使用者名稱和密碼是預先配置在web server端的。
以後我們會將使用者名稱密碼以加密形式傳輸且是動態從資料庫中獲取的web service的認證。
系列
看完本文有收穫?請轉發分享給更多人
關註「ImportNew」,提升Java技能