使用WebService釋出soap介面,並實現客戶端的https驗證
什麼是https
HTTPS其實是有兩部分組成:HTTP + SSL / TLS,
也就是在HTTP上又加了一層處理加密資訊的模組,並且會進行身份的驗證。
如何進行身份驗證?
首先我們要明白什麼是對稱加密,什麼是非堆成加密
對稱加密
對稱加密就是隻有一個金鑰,客戶端雙方按照約定的金鑰對自己的明文進行加密。
但是這種方式有個很不好的情況。A和B通訊之前並不知道使用哪種金鑰加密。所以在第一次通訊的時候會事先溝通好。
A->B:嗨!在嗎?我們開始聊天吧。
B->A:好的,我們的金鑰是:xxxxx。
如果此時黑客擷取到了你的金鑰,那加密也無濟於事。
非對稱加密
非對稱加密就是在對稱加密上再套一層公鑰。所以有公鑰和私鑰兩種形式。
A->B:嗨!在嗎?我們開始聊天吧。
B->A:好的,我們的公鑰是:xx。
A->B(以公鑰:xx加密): 你好我是A,我們之後使用對稱加密的zz來通訊。
B:以xx的金鑰解密,得到金鑰zz,之後雙方通訊都通過對稱加密的zz來通訊。
公鑰是公開的,用公鑰加密的資訊只能使用金鑰解密。公鑰是解不開的,即便公鑰被非法路由或黑客攔截,也不能通過公鑰解密。這就是數學的魅力,有興趣的話可以瞭解一下:RSA演算法
這樣就安全了?答案是否定的,如果在B->A傳送公鑰 xx 的時候就已經被非法路由器或者黑客攔截,然後向A傳送偷天換日後的公鑰 yy。此時非法路由就有兩個公鑰 xx 和 yy 。並且擁有有 yy 的私鑰,因為 yy 是其自己生成的。
因此,當A收到非法路由傳送過來的偷天換日後的公鑰 yy ,A信以為真的認為這個就是B傳送過來的公鑰。當A向B傳送傳送一串由 YY 公鑰加密的資訊,非法路將其攔截並且用 yy 的金鑰解密後再通過公鑰 xx 加密後傳送給B。這樣你的訊息通訊依舊是不安全的。
證書體系
上面這個環節按理來說已經很安全了,為什麼還是會被黑客或者非法路由攻擊?思考上面的環節,問題出在傳送公鑰的時候,被黑客攔截,然後偷天換日的換了一個公鑰。那是不是隻要保證公鑰的不可更改,就能維護了呢?是的,證書的出現就是解決這個問題(也是為什麼你要去找證書籤發機構花錢購買證書的原因)!!
簡單來說,一個HTTPS網站響應給我們的並不是一個公鑰,而是證書。證書上包含了公鑰,還包含了域名、簽發機構、有效期、簽名等等。
那證書的安全性怎麼保證?為什麼中間人不能做一個假證書?
因為這套證書體系已經根植於每一個作業系統裡了。每一個作業系統裡,都內建了數十張根證書,每個根證書都對應一個非常權威的證書籤發機構。這些根證書上記錄了各個機構的公鑰。
那非法路由或者黑客能不能申請一個真的證書然後去做劫持呢?
通常來說,證書籤發機構的稽核非常嚴格,如果無法證明B這個域名屬於他,簽發機構是不會給他簽發一個知乎域名的證書的。而如果中間人用了其他域名的證書,瀏覽器會發現你請求的域名和返回的證書不一致,從而拒絕繼續請求。除非你一定要點下面的「仍然繼續」:
最後無奈地說,在破解了這麼多數學上的難題後,HTTPS的安全性仍然保證在『證書籤發機構一定都是很有良心的』這種脆弱的基礎上。(即便一些網站是通過證書籤發機構的,但是嘛依舊會被植入小廣告,咳咳....)。
實現客戶端驗證的WebService
我們瞭解了https和證書的作用。其實我們在系統之間內部通訊的時候就可以使用對稱加密的方式進行訪問即可。
因為是內部嘛,我們提前約定好使用何種證書就可以了。除非是很正式的專案,否則使用自己簽發的證書即可,因為官方生成證書是要花錢滴。
有兩種方式
第一種:服務端生成一個證書,每個客戶端都生成自己的證書,然後讓服務端與各個客戶端相互信任。
第二種:生成一個證書,將這個證書派發給各個客戶端,每個客戶端攜帶該證書就會被服務端認證(這種安全性稍微低一些,但是在大部分對安全性要求不是特別高的場景推薦使用。下面主要說明這一種方式,如果想實現第一種參考:https://blog.csdn.net/u011350541/article/details/71941536)
生成證書
首先保證伺服器有jdk,開啟CMD工具,進入到jdk的bin目錄下輸入一下資訊。如果配置了環境變數則直接在命令列中輸入即可。
keytool -genkey -alias tomcat -keypass 123456 -keyalg RSA -keysize 1024 -validity 365 -keystore D:/keys/server.jks -storepass 123456
上述引數的具體說明如下:
keytool
-genkey
-alias service(別名)
-keypass 123456(別名密碼)
-keyalg RSA(演算法)
-keysize 1024(金鑰長度)
-validity 365(有效期,天單位)
-keystore D:/keys/server.jks(指定生成證書的位置和證書名稱)
-storepass 123456(獲取jks資訊的密碼)
釋出的soap介面
/**
* @author Eric
* @Title: SoapService
* @date 2019/7/17 10:42
* @Description: 釋出的soap介面,注意註釋的使用
*/
@WebService
public interface SoapService {
void sayHello(@WebParam(name = "clientName") String clientName);
}
介面的具體實現類
/**
* @author Eric
* @Title: SoapServiceImpl
* @date 2019/7/17 10:42
* @Description: soap介面的具體實現,注意註釋的使用
*/
@javax.jws.WebService
public class SoapServiceImpl implements SoapService {
@Override
public void sayHello(String clientName) {
System.out.println(clientName + "呼叫介面打了招呼");
}
}
釋出soap介面
/**
* @author Eric
* @Title: testTest
* @date 2019/7/17 10:45
* @Description: 釋出soap介面
*/
public class Release {
public static void main(String[] args) throws Exception {
initSoap();
}
//初始化soap介面
public static void initSoap() {
try {
String host = "0.0.0.0";
int port = 8888;
String address = "https://" + host + ":" + port + "/logProcessor"; //釋出於https
JaxWsServerFactoryBean sf = new JaxWsServerFactoryBean();
sf.setServiceClass(SoapService.class); //釋出實現soap的介面類(你自己實現的介面,裡面的方法就是釋出的soap介面)
sf.setAddress(address);
SoapServiceImpl soapServiceImpl = new SoapServiceImpl(); //SoapService的實現類
sf.getServiceFactory().setInvoker(new BeanInvoker(soapServiceImpl));
sf = configureSSLOnTheServer(sf, port);
Server server = sf.create();
String endpoint = server.getEndpoint().getEndpointInfo().getAddress();
System.out.println("soap釋出的地址為:" + endpoint);
} catch (Exception e) {
System.out.println("Soap初始化失敗:"+e);
}
}
//https並且開啟客戶端認證。如果釋出的介面是http,則無需實現該方法。
private static JaxWsServerFactoryBean configureSSLOnTheServer(JaxWsServerFactoryBean sf, int port) throws Exception {
TLSServerParameters tlsParams = new TLSServerParameters();
ClientAuthentication clientAuthentication = new ClientAuthentication();
clientAuthentication.setRequired(true); //開啟客戶端認證
tlsParams.setClientAuthentication(clientAuthentication);
KeyStore keyStore = KeyStore.getInstance("JKS");
String password = "123456"; //生成證書時候定義的的密碼
File truststore = new File(Paths.get("D:\\keys", "server.jks").toString());
keyStore.load(new FileInputStream(truststore), password.toCharArray());
KeyManagerFactory keyFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyFactory.init(keyStore, password.toCharArray());
KeyManager[] km = keyFactory.getKeyManagers();
tlsParams.setKeyManagers(km);
truststore = new File(Paths.get("D:\\keys", "server.jks").toString());
keyStore.load(new FileInputStream(truststore), password.toCharArray());
TrustManagerFactory trustFactory = TrustManagerFactory
.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustFactory.init(keyStore);
TrustManager[] tm = trustFactory.getTrustManagers();
tlsParams.setTrustManagers(tm);
JettyHTTPServerEngineFactory factory = new JettyHTTPServerEngineFactory();
factory.setTLSServerParametersForPort(port, tlsParams);
}
}
使用soapUI模擬客戶端呼叫soap介面
通過上述程式碼,我們將介面釋出在了你自己定義的https://0.0.0.0:8888/logProcessor。如果釋出成功,我們想遠端呼叫soap介面,此時開啟soapUI
- 選擇左上角File然後選擇Preferences。
- 選擇SSL Settings,在KeyStore上匯入我們生成的證書。否則無法遠端呼叫(因為我們在程式碼中開啟了客戶端認證,如果沒有開啟可以跳過這一步)
- 然後返回主介面,建立soap,在彈出的介面中 ‘Project Name’可以自己定義。‘Initial WSDL’中輸入你釋出的地址加上‘?wsdl’如下:https://0.0.0.0:8888/logProcessor?wsdl
- 如果建立成功,你就能在左邊看到自己專案中釋出的介面。選擇我們釋出的介面sayHello,雙擊Request。
- 在彈出的介面中,將自己的引數輸入,然後點選綠色三角形開始呼叫,因為我們這個方法沒有引數返回,所有呼叫後沒有什麼資訊會提示。
- 開啟我們的ide,此時就能看到下面列印了一行話,說明方法呼叫成功
參考
https://zhuanlan.zhihu.com/p/37738632
https://blog.csdn.net/u011350541/article/details/71941536