通向架構師的道路(第十三天)Axis2 Web Service安全初步

引用 :http://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出來後,和數據庫一匹配,驗證。

 

第二種:

<Request xmlns="http://10.225.106.35">

    <username>驗證個頭啊</username>

    <password>不要老是你個頭你個頭</password>

    <BusinessData>2007-01-01</BusinessData>

</ Response >

服務端拿到後把這個soap request body中的<username>和<password>拿出來後和數據庫一匹配,又驗證了!

這兩種做法,無疑是掩耳盜鈴!!!(不要和我說業務實現是最主要的,等你的數據哪天沒了,廠長經理的工資被篡改了,如果你願意被客戶做成東方不敗,那你儘管去這樣做就好了。

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文件的內容

<service name="SimpleAuthService">

         <Description>

                   Please Type your service description here

         </Description>

         <parameter name="ServiceClass" locked="false">org.sky.axis2.security.SimpleAuthService

         </parameter>

         <messageReceivers>

                   <messageReceiver mep="http://www.w3.org/2004/08/wsdl/in-out"

                            class="org.apache.axis2.rpc.receivers.RPCMessageReceiver" />

         </messageReceivers>

         <actionMapping>urn:getTax</actionMapping>

</service>

最重要的來了

修改web.xml文件,增加以下的內容

<security-constraint>

                   <web-resource-collection>

                            <web-resource-name>Simple Authenticate Web service</web-resource-name>

                            <url-pattern>/services/SimpleAuthService</url-pattern>

                   </web-resource-collection>

                   <auth-constraint>

                            <role-name>bank_member</role-name>

                   </auth-constraint>

         </security-constraint>

         <login-config>

                   <auth-method>BASIC</auth-method>

                   <realm-name>Axis Basic Authentication Area</realm-name>

         </login-config>

         <security-role>

                   <role-name>bank_member</role-name>

         </security-role>

我們可以看到:

l   只有在服務端屬於bank_member角色(組)中的人員才被允許訪問該web service即:SimplAuthService。

l   而且,該該訪問採用“BASIC”模式,即需要用戶名和密碼來進行訪問。

隨後,我們打開tomcat所在目錄下的conf目錄下的tomcat-users.xml如我的是“D:\tomcat\conf\tomcat-users.xml”。

在文件中加入如下內容(注意紅色加粗的部分):

<?xml version='1.0' encoding='utf-8'?>

<tomcat-users>

  <role rolename="manager"/>

  <role rolename="bank_member"/>

  <role rolename="admin"/>

  <role rolename="sales"/>

  <role rolename="participant"/>

  <user username="xxx" password="xxx" roles="admin,manager,participant,sales"/>

  <user username="Wright" password="abcdefg" roles="bank_member"/>

</tomcat-users>

然後我們來佈署我們的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文件。

<Connector executor="tomcatThreadPool"

               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的鏈接所需要的步驟,現在我們這樣來想:

l   我們現在將要運行的是一個Java應用程序;

l   該應用程序將通過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<String> auth = new ArrayList<String>();

                   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<String> auth = new ArrayList<String>();

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的認證。

結束今天教程!!!

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