常見的 SSL 單向認證僅驗證伺服器身分,確保用戶連線至真實網站。然而企業級應用常需雙向認證:伺服器與客戶端互相驗證身分,雙方各持有唯一、非公開的數位憑證,確保通訊雙方均受信任,並透過 SSL 協定加密傳輸內容,防止網路嗅探工具竊取資料。
採用 JSSE(Java Security Socket Extension)實作 SSL/TLS 協定。雙方皆需兩組 Keystore:
建議使用 JDK 內建 keytool 指令生成,便於後續 Java 程式整合。OpenSSL 雖跨平台但以 C 撰寫,整合較繁瑣。
# 1. 產生私鑰並存入 kserver.keystore
keytool -genkey -alias serverkey -keystore kserver.keystore
# 密碼設為 123456,私鑰密碼直接按 Enter 沿用 keystore 密碼(關鍵細節)
# 2. 匯出伺服器憑證
keytool -export -alias serverkey -keystore kserver.keystore -file server.crt
# 3. 將伺服器憑證匯入客戶端 Trust Store
keytool -import -alias serverkey -file server.crt -keystore tclient.keystore
keytool -genkey -alias clientkey -keystore kclient.keystore
keytool -export -alias clientkey -keystore kclient.keystore -file client.crt
keytool -import -alias clientkey -file client.crt -keystore tserver.keystore
最終檔案分佈:
kserver.keystore、tserver.keystorekclient.keystore、tclient.keystorepublic class SSLClient {
private static final String HOST = "127.0.0.1";
private static final int PORT = 7777;
private static final String PWD = "123456";
public static void main(String[] args) {
new SSLClient().init().process();
}
public SSLClient init() {
try {
SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("E:/kclient.keystore"), PWD.toCharArray());
tks.load(new FileInputStream("E:/tclient.keystore"), PWD.toCharArray());
kmf.init(ks, PWD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
sslSocket = (SSLSocket) ctx.getSocketFactory().createSocket(HOST, PORT);
} catch (Exception e) { e.printStackTrace(); }
return this;
}
public void process() {
// 發送 "Client Message" 並接收回應...
}
}
public class SSLServer {
private static final int PORT = 7777;
private static final String PWD = "123456";
public static void main(String[] args) {
new SSLServer().init().start();
}
public SSLServer init() {
try {
SSLContext ctx = SSLContext.getInstance("SSL");
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
KeyStore tks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("E:/kserver.keystore"), PWD.toCharArray());
tks.load(new FileInputStream("E:/tserver.keystore"), PWD.toCharArray());
kmf.init(ks, PWD.toCharArray());
tmf.init(tks);
ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
serverSocket = (SSLServerSocket) ctx.getServerSocketFactory().createServerSocket(PORT);
serverSocket.setNeedClientAuth(true); // 強制雙向認證
} catch (Exception e) { e.printStackTrace(); }
return this;
}
public void start() {
// 單執行緒迴圈 accept、讀取、回覆 "Server Echo"...
}
}
serverSocket.setNeedClientAuth(true) 是啟用雙向認證的關鍵。