熱點推薦:
您现在的位置: 電腦知識網 >> 編程 >> Java編程 >> Java核心技術 >> 正文

用J2SE1.4進行Internet安全編程

2022-06-13   來源: Java核心技術 
服務器端

  任何在計算機網絡或者 Internet 中傳輸的消息都可能被攔截其中不乏一些比較敏感的內容如信用卡號或者其它一些私人數據為了更好的在企業環境和電子商務中使用 Internet應用軟件必須使用加 密驗證和安全的通信協議來保護用戶的數據安全安全超文本傳輸協議 (secure Hypertext Transfer Protocol HTTPS) 是建立於安全套接層 (Secure Sockets Layer SSL) 上的 HTTP它已經成功的應用於電子商務

  Java 安全套接擴展 (Java Secure Socket Extension JSSE) 使 Internet 安全通信成為現實它是 % 純 Java 實現的 SSL 框架這個包讓 Java 開發人員能夠開發安全的網絡應用為基於 TCP/IP 的何應用協議如 HTTPFTPTelnet或者 NTTP在客戶端和服務器端之間建立安全的數據通道

  JSSE 已經整合在 Java SDK 標准版本 (JSE ) 中了這真是一個好消息這意味著只要你安裝了 JSE 不需要再下載其它的包就可以創建基於 SSL 的 Internet 應用程序了這個系列的文章共有 它是一本關於為今後的市場開發安全 Interent 應用的手冊這篇文章主要是講的服務器端而下一篇是講客戶端的這篇文章從概覽 SSL 開始然後告訴你如何進行下列內容


  使用 JSSE 的 API


  在你的 C/S 應用程序中結合 JSSE


  開發一個簡單的 HTTP 服務器


  讓 HTTP 服務器能夠處理 HTTPS 請求


  使用包含在 JSE 中的 keytool 產生自己的證書


  開發配置和運行一個安全的 HTTP 服務器

  概覽 SSL

  SSL 協議是 Netscape 在 年開發出來的以允許服務端 (典型的如浏覽器) 和 HTTP 服務器之間能通過安全的連接來通信它加密來源驗證數據完整性等支持以保護在不安全的公眾網絡上交換的數據SSL 有這樣一些版本SSL 有安全隱患現在已經幾本上不用了SSL 應用則比較廣泛最後由 SSL 改進而來的傳輸層加密 (Transport Layer Security TLS) 已經成為 Internet 標准並應用於幾乎所有新近的軟件中

  在數據傳播之前加密技術通過將數據轉變成看起來毫無意義的內容來保護數據不被非法使用其過程是數據在一端 (客戶端或者服務器端) 被加密傳輸再在另一端解密

  來源認證是驗證數據發送者身份的一種辦法浏覽器或者其它客戶端第一次嘗試與網頁服務器進行安全連接之上的通信時服務器會將一套信任信息以證書的形式呈現出來

  證書由權威認證機構 (CA)——值得信賴的授權者發行和驗證一個證書描述一個人的公鑰一個簽名的文檔會作出如下保證我證明文檔中的這個公鑰屬於在該文檔中命名的實體簽名(權威認證機構)目前知名的權威認證機構有 VerisignEntrust 和 Thawte 等注意現在使用的 SSL/TLS 證書是 X 證書

  數據完整性就是要確保數據在傳輸過程中沒有被改變

  SSL 和 TCP/IP 協議的層次

  SSL 是名符其實的安全套接層它的連接動作和 TCP 的連接類似因此你可以想象 SSL 連接就是安全的 TCP 連接因為在協議層次圖中 SSL 的位置正好在 TCP 之上而在應用層之下如圖 所示注意到這點很重要但是SSL 不支持某些 TCP 的特性比如頻帶外數據

  

SSL 和 TCP/IP 協議的的層次

  可交流的加密技術

  SSL 的特性之一是為電子商務的事務提供可交流的加密技術和驗證算法提供標准的方法SSL 的開發者認識到不是所有人都會使用同一個客戶端軟件從而不是所有客戶端都會包括任何詳細的加密算法對於服務器也是同樣位於連接兩端的的客戶端和服務器在初始化握手的時候需要交流加密和解密算法(密碼組)如果它們沒有足夠的公用算法連接嘗試將會失敗

  注意當 SSL 允許客戶端和服務器端相互驗證的時候典型的作法是只有服務器端在 SSL 層上進行驗證客戶端通常在應用層通過 SSL 保護通道傳送的密碼來進行驗證這個模式常用於銀行股份交易和其它的安全網絡應用中

  SSL 完全握手協議如圖 所示它展示了在 SSL 握手過程中的信息交換順序

  

SSL 握手協議

  這些消息的意思如下

   ClientHello發送信息到服務器的客戶端這些信息如 SSL 協議版本會話 ID 和密碼組信息如加密算法和能支持的密匙的大小

   ServerHello選擇最好密碼組的服務器並發送這個消息給客戶端密碼組包括客戶端和服務器支持

   Certificate服務器將包含其公鑰的證書發送給客戶端這個消息是可選的在服務器請求驗證的時候會需要它換句話說證書用於向客戶端確認服務器的身分

   Certificate Request: 這個消息僅在服務器請求客戶端驗證它自身的時候發送多數電子商務應用不需要客戶端對自身進行

   Server Key Exchange如果證書包含了服務器的公鑰不足以進行密匙交換則發送該消息

   ServerHelloDone這個消息通知客戶端服務器已經完成了交流過程的初始化

   Certificate僅當服務器請求客戶端對自己進行驗證的時候發送

   Client Key Exchage客戶端產生一個密匙與服務器共享如果使用 RivestShamirAdelman (RSA) 加密算法客戶端將使用服務器的公鑰將密匙加密之後再發送給服務器服務器使用自己的私鑰或者密鑰對消息進行解密以得到共享的密匙現在客戶端和服務器共享著一個已經安全分發的密匙

   Certificate Verify如果服務器請求驗證客戶端這個消息允許服務器完成驗證過程

   Change Cipher Spec客戶端要求服務器使用加密模式

   Finished客戶端告訴服務器它已經准備好安全通信了

   Change Cipher Spec服務器要求客戶端使用加密模式

   Finished服務器告訴客戶端它已經准備好安全通信了這是 SSL 握手結果的標志

   Encrypted Data客戶端和服務器現在可以開發在安全通信通道上進行加密信息的交流了

  JSSE

  Java 安全套接擴展 (JSSE) 提供一個框架及一個 % 純 Java 實現的 SSL 和 TLS 協議它提供了數據加密服務器驗證消息完成性和可選的客戶端驗證等機制JSSE 的引人之外就是將復雜的根本的加密算法抽象化了這樣就降低了受到敏感或者危險的安全性攻擊的風險另外由於它能將 SSL 無縫地結合在應用當然使安全應用的開發變得非常簡單JSSE 框架可以支撐許多不同的安全通信協議如 SSL 以及 TLS 但是 JSE v 只實現了 SSL 和 TLS

  JSSE 編程

  JSSE API 提供了擴充的網絡套接字類信用和密匙管理以及為簡化套接字創建而設計的套接字工廠框架以此擴充 javasecurity 和 兩個包這些類都包含在 和 ssl 包中

  SSLSocket 和 SSLServerSocket

  sslSSLSocket 是 Socket 的子類因此他支持所有標准 Socket 的方法和一些為安全套接字新增加的方法sslSSLServerSocket 類與 SSLSocket 類相似只是它用於創建服務器套接子而 SSLSocket 不是

  創建一個 SSLSocket 實例有如何兩種方法

   用 SSLSocketFactory 實例執行 createSocket 方法來創建

   通過 SSLServerSocket 的 accept 方法獲得

  SSLSocketFactory 和 SSLServerSocketFactory

  sslSSLSocketFactory 類是用於創建安全套接字的對象工廠sslSSLServerSocketFactory 也是這樣的工廠但它用於創建安全的服務器套接字

  可以通過如下方法獲得 SSLSocketFactory 實例

   執行 SSLSocketFactorygetDefault 方法獲得一個默認的工廠

   通過特定的配置行為構造一個新的工廠

  注意默認的工廠的配置只允許服務器驗證

  使現有的 Client/Server 應用變得安全

  在現有的 C/S 應用中整合 SSL 以使其變得安全比較簡單使用幾行 JSSE 代碼就可以做到為了使服務器變得安全下面的例子中加黑顯示的內容是必須的

  

  

  import javaio*;

  import ssl*;

  public class Server {

  int port = portNumber;

  SSLServerSocket server;

  try {

  SSLServerSocketFactory factory =

  (SSLServerSocketFactory) SSLServerSocketFactorygetDefault();

  server = (SSLServerSocket)

  factorycreateServerSocket(portNumber);

  SSLSocket client = (SSLSocket)

  serveraccept();

  // Create input and output streams as usual

  // send secure messages to client through the

  // output stream

  // receive secure messages from client through

  // the input stream

  } catch(Exception e) {

  }

  }

  為了使客戶端變得安全下面的例子中加黑顯示的內容是必須的

  

  

  import javaio*;

  import ssl*;

  public class Client {

  

  try {

  SSLSocketFactory factory = (SSLSocketFactory)

  SSLSocketFactorygetDefault();

  server = (SSLServerSocket)

  factorycreateServerSocket(portNumber);

  SSLSocket client = (SSLSOcket)

  factorycreateSocket(serverHost port);

  // Create input and output streams as usual

  // send secure messages to server through the

  // output stream receive secure

  // messages from server through the input stream

  } catch(Exception e) {

  }

  }

  SunJSSE 提供者

  JSE v 和一個 JSSE 提供者SunJSSE 一起發布SunJSSE 安裝並預登記了 Java 的加密體系請把 SunJSSE 作為一個實現的名字來考慮它提供了 SSL v 和 TLS v 的實現也提供了普通的 SSL 和 TLS 密碼組如果你想找到你的實現 (這裡是 SunJSSE) 所支持的密碼組列表可以調用 SSLSocket 的 getSupportedCipherSuites 方法然而不是所有這些密碼組都是可用的為了找出那些是可用的調用 getEnabledCipherSuites 方法這個列表可以用 setEnabledCipherSuites 方法來更改

  一個完整的例子

  我發現使用 JSSE 開發最復雜的事情關系到系統設置以及管理證書和密匙在這個例子中我演示了如何開發配置和運行一個完整的支持 GET 請求方法的 HTTP 服務器應用

  HTTP 概覽

  超文本傳輸協議 (Hypertext Transfer Protocol HTTP) 是一個請求回應的應用協議這個協議支持一套固定的方法如 GETPOSTPUTDELETE 等一般用 GET 方法向服務器請求資源這裡有兩個 GET 請求的例子

  GET / HTTP/

  GET /l HTTP/

  不安全的 HTTP 服務器

  為了開發一個 HTTP 服務器你得先搞明白 HTTP 協議是如何工作的這個服務器是一個只支持 GET 請求方法的簡單服務器代碼示例 是這個例子的實現這是一個多線程的 HTTP 服務器ProcessConnection 類用於執行不同線程中新的請求當服務器收到一個來自浏覽器的請求時它解析這個請求並找出需要的文檔如果被請求的文檔在服務器上可用那麼被請求的文檔會由 shipDocument 方法送到服務器如果被請求的文檔沒有打開那麼送到服務器的就是出錯消息

  代碼示例 HttpServerjava

  

  

  import javaio*;

  import *;

  import javautilStringTokenizer;

  /**

  * This class implements a multithreaded simple HTTP

  * server that supports the GET request method

  * It listens on port waits client requests and

  * serves documents

  */

  public class HttpServer {

  // The port number which the server

  // will be listening on

  public static final int HTTP_PORT = ;

  public ServerSocket getServer() throws Exception {

  return new ServerSocket(HTTP_PORT);

  }

  // multithreading create a new connection

  // for each request

  public void run() {

  ServerSocket listen;

  try {

  listen = getServer();

  while(true) {

  Socket client = listenaccept();

  ProcessConnection cc = new

  ProcessConnection(client);

  }

  } catch(Exception e) {

  Systemoutprintln(Exception:

  +egetMessage());

  }

  }

  // main program

  public static void main(String argv[]) throws

  Exception {

  HttpServer httpserver = new HttpServer();

  ();

  }

  }

  class ProcessConnection extends Thread {

  Socket client;

  BufferedReader is;

  DataOutputStream os;

  public ProcessConnection(Socket s) { // constructor client = s;

  try {

  is = new BufferedReader(new InputStreamReader

  (clientgetInputStream()));

  os = new DataOutputStream(clientgetOutputStream());

  } catch (IOException e) {

  Systemoutprintln(Exception: +egetMessage());

  }thisstart(); // Thread starts herethis start()

  will call run()

  }

  public void run() {

  try {

  // get a request and parse it

  String request = isreadLine();

  Systemoutprintln( Request: +request );

  StringTokenizer st = new StringTokenizer( request );

  if ( (untTokens() >= ) &&

  stnextToken()equals(GET) ) {

  if ( (request =

  stnextToken())startsWith(/) )

  request = requestsubstring( );

  if ( requestequals() )

  request = request + l;

  File f = new File(request);

  shipDocument(os f);

  } else {

  oswriteBytes( Bad Request );

  }

  clientclose();

  } catch (Exception e) {

  Systemoutprintln(Exception: +

  egetMessage());

  }

  }

  /**

  * Read the requested file and ships it

  * to the browser if found

  */

  public static void shipDocument(DataOutputStream out

  File f) throws Exception {

  try {

  DataInputStream in = new

  DataInputStream(new FileInputStream(f));

  int len = (int) flength();

  byte[] buf = new byte[len];

  inreadFully(buf);

  inclose();

  outwriteBytes(HTTP/ OK\r\n);

  outwriteBytes(ContentLength: + flength() +\r\n);

  outwriteBytes(ContentType: text/html\r\n\r\n);

  outwrite(buf);

  outflush();

  }

  catch (Exception e) {outwriteBytes(\r\n\r\n);

  outwriteBytes(HTTP/ + egetMessage() + \r\n);

  outwriteBytes(ContentType: text/html\r\n\r\n);

  outwriteBytes();

  outflush();

  } finally {

  outclose();

  }

  }

  }

  實驗一下 HttpServer 類

   將 HttpServer 的代碼保存在文件 HttpServerjava 中並選擇一個目錄把它存放在那裡

   使用 javac 編譯 HttpServerjava

   建立一些 HTML 文件作為例子要有一個l因為它是這個例子中默認的 HTML 文檔

   運行 HttpServer服務器運行時使用 端口

   打開網頁浏覽器並發出請求//localhost: 或者

  注意你能想到 HttpServer 可能接收到一些惡意的 URL 嗎?比如像 或者 等作為一個練習修改 HttpServer 以使其不允許這些 URL 的訪問提示寫你自己的 SecurityManager 或者使用 javalangSecurityManager你可以在 main 方法的第一行添加語句 SystemsetSecurityManager(new JavalangSecurityManager) 來安裝這個安全的管理器試試吧!

  擴展 HttpServer 使其能夠處理 https://URL

  現在我要們修改 HttpServer 類使它變得安全我希望 HTTP 服務器能處理 https://URL 請求我在前面就提到過JSSE 讓你可以很容易的把 SSL 整合到應用中去

  創建一個服務器證書

  就像我前面提到的那樣SSL 使用證書來進行驗證對於需要使用 SSL 來保證通信安全的客戶端和服務器都必須創建證書JSSE 使用的證書要用與 JSE 一起發布的 Java keytool 來創建用下列命令來為 HTTP 服務器創建一個 RSA 證書

  prompt> keytool genkey keystore serverkeys keyalg rsa alias qusay

  這個命令會產生一個由別名 qusay 引用的證書並將其保存在一個名為 serverkeys 的文件中產生證書的時候這個工具會提示我們一些信息如下面的信息其中加黑的內容是我寫的

  Enter keystore password: hellothere

  What is your first and last name?

  [Unknown]:

  What is the name of your organizational unit?

  [Unknown]: Training and Consulting

  What is the name of your organization?

  [Unknown]:

  What is the name of your City or Locality?

  [Unknown]: Toronto

  What is the name of your State or Province?

  [Unknown]: Ontario

  What is the twoletter country code for this unit?

  [Unknown]: CA

  Is CN=ultra OU=Training and Consulting

  O= L=Toronto ST=Ontario C=CA correct?

  [no]: yes

  Enter key password for

  (RETURN if same as keystore password): hiagain

  正如你所看到的keytool 提示為 keystore 輸入密碼那是因為讓服務器能訪問 keystore 就必須讓它知道密碼那工具也要求為別名輸入一個密碼如果你願意這些密碼信息能由 keytool 從命令行指定使用參數 storepass 和 keypass 就行了注意我使用了作為姓名這個名字是為我的機器假想的一個名字你應該輸入服務器的主機名或者 IP 地址

  在你運行 keytool 命令的時候它可能會花幾秒鐘的時間來產生你的密碼具體速度得看你機器的速度了

  既然我為服務器創建了證書現在可以修改 HttpServer 使其變得安全了如果你檢查 HttpServer 類你會注意到 getServer 方法用來返回一個服務器套接子也就是說只需要修改 getServer 方法讓它返回一個安全的服務器套接字就可以了在代碼示例 中加黑的部分就是所做的改變請注意我將端口號改成了 這是 https 默認的端口號還有一點非常值得注意 之間的端口號都是保留的如果你在不同的端口運行 HttpsServer那麼 URL 應該是https://localhost:portnumber但如果你在 端口運行 HttpsServer那麼 URL 應該是https://localhost

  示例代碼 HttpsServerjava

  

  

  import javaio*;

  import *;

  import *;

  import ssl*;

  import javasecurity*;

  import javautilStringTokenizer;

  /**

  * This class implements a multithreaded simple HTTPS

  * server that supports the GET request method

  * It listens on port waits client requests

  * and serves documents

  */

  public class HttpsServer {

  String keystore = serverkeys;

  char keystorepass[] = hellotheretoCharArray();

  char keypassword[] = hiagaintoCharArray();

  // The port number which the server will be listening on

  public static final int HTTPS_PORT = ;

  public ServerSocket getServer() throws Exception {

  KeyStore ks = KeyStoregetInstance(JKS);

  ksload(new FileInputStream(keystore) keystorepass);

  KeyManagerFactory kmf = KeyManagerFactorygetInstance(SunX);

  kmfinit(ks keypassword);

  SSLContext sslcontext = SSLContextgetInstance(SSLv);

  sslcontextinit(kmfgetKeyManagers() null null);

  ServerSocketFactory ssf = sslcontextgetServerSocketFactory();

  SSLServerSocket serversocket = (SSLServerSocket)

  ssfcreateServerSocket(HTTPS_PORT);

  return serversocket;

  }

  // multithreading create a new connection

  // for each request

  public void run() {

  ServerSocket listen;

  try {

  listen = getServer();

  while(true) {

  Socket client = listenaccept();

  ProcessConnection cc = new ProcessConnection(client);

  }

  } catch(Exception e) {

  Systemoutprintln(Exception: +egetMessage());

  }

  }

  // main program

  public static void main(String argv[]) throws Exception {

  HttpsServer https = new HttpsServer();

  ();

  }

  }

  這幾行String keystore = serverkeys;

  char keystorepass[] = hellotheretoCharArray();

  char keypassword[] = hiagaintoCharArray();

  指定了 keystore 的名字密碼和密匙密碼直接在代碼中寫出密碼文本是個糟糕的主意不過我們可以在運行服務器的時候在命令行指定密碼

  getServer 方法中的其它 JSSE 代碼


  它訪問 serverkeys keystoreJSK 是 Java KeyStore (一種由 keytool 產生的 keystore)


  用 KeyManagerFactory 為 keystore 創建 X 密匙管理


  SSLContext 是實現 JSSE 的環境用它來創建可以創建 SSLServerSocket 的 ServerSocketFactory雖然我們指定使用 SSL 但是返回來的實現常常支持其它協議版本如 TLS 舊的浏覽器中更多時候使用 SSL

  注意默認情況下不需要客戶端的驗證如果你想要服務器請求客戶端進行驗證使用

  serversocketsetNeedClientAuth(true)

  現在用 HttpsServer 類做個實驗

   將 HttpsServer 和 ProcessConnection 兩個類 (上面的代碼) 保存在文件 HttpsServerjava 中

   讓HttpsServerjava 與 keytool 創建的 serverkyes 文件在同一目錄

   使用 javac 編譯 HttpsServer

   運行 HttpsServer默認情況下它應該使用 端口不過如果你不能在這個端口上使用它請選擇另一個大於 的端口號

   打開網頁浏覽器並輸入請求https://localhost 或者 這是假譯服務器使用 端口的情況如果不是這個端口那麼使用use: https://localhost:port

  你在浏覽器中輸入 https://URL 的時候你會得到一個安全警告的彈出窗口就像圖 那樣這是因為 HTTP 服務器證書是自己產生的換句話說它由未知的 CA 創建在你的浏覽器保存的 CA 中沒有找到這個 CA有一個選項讓你顯示證書 (檢查它是不是正確的證書以及是誰簽的名) 和安裝該證書拒絕該證書或者接受該證書

  

由未知 CA 頒發的服務器證書

  注意在內部的私有系統中產生你自己的證書是個很好的主意但在公共系統中最好從知名的 CA 處獲得證書以避免浏覽器的安全警告

  如果你接受證書你就可以看到安全連接之後的頁面以後訪問同一個網站的時候浏覽器就不再會彈出安全警告了注意有許多網站使用 HTTPS而證書是自己產生或者由不知名的 CA 產生的例如如果你沒訪問過這個網頁你會看到一個像圖 一樣的安全警告

  注意你接受證書以後它只對當前的會話有效也就是說如果你完全退出浏覽器後它就失效了Netscape 和 Microsoft Internet Explorer (MSIE) 都允許你永久保證證書在 MSIE 中的作法是選擇圖 所示的View Certificate並在新開的窗口中選擇Install Certificate

  這篇文章談到了 SSL 並描述了 JSSE 框架及其實現文中的例子可以說明把 SSL 整合到你的 C/S 應用中是一件很容易的事情文中給出了一個安全 HTTP 服務器的例子你可以使用它來進行實驗文中還介紹了 JSSE API 以及可以發生 HTTPS 請求的網頁浏覽器

用JSE 進行Internet安全編程(下)

  客戶端

  Java 安全套接擴展 (Java Secure Socket Extension JSSE) 使 Internet 安全通信成為現實它是 SSL (Secure Socket Layer) 及 TLS (Transport Layer Security由 SSL 改善而來) 的框架和實現這個包讓 Java 開發人員能夠開發安全的網絡應用為基於 TCP/IP 的何應用協議如 HTTPFTPTelnet或者 NTTP在客戶端和服務器端之間建立安全的數據通道

  在這篇文章的第一部分 (服務器端)作者已經詳細說明了 SSL 和 JSSE並且說明了如何開發服務器端支持 SSL 應用程序那一部分中我們開發了一個 HTTPS 服務器這是一個非常有用的應用程序在這一部分中同樣會用到它

  在這篇文章涉及到客戶端的內容它首先簡述 JSSE然後會做這樣一些事情

  l在客戶端使用 JSSE API

  l一步步的開發一個支持 SSL 的客戶端應用程序

  l開發簡單的支持 SSL 的客戶端應用程序

  l從服務器端導出證書並在客戶端導入

  l開發一個支持 SSL 的網頁浏覽器

  JSSE

  Java 安全套接擴展 (JSSE) 提供了 SSL 和 TLS 協議的框架及實現JSSE 將復雜的根本的加密算法抽象化了這樣就降低了受到敏感或者危險的安全性攻擊的風險正如你在本文中看到的那樣由於它能將 SSL 無縫地結合在應用當然使安全應用的開發變得非常簡單JSSE 框架可以支撐許多不同的安全通信協議如 SSL 以及 TLS 但是 JSE v 只實現了 SSL 和 TLS

  用 JSSE 編寫客戶端應用程序

  JSSE API 提供了擴充的網絡套接字類信用和密匙管理以及為簡化套接字創建而設計的套接字工廠框架以此擴充 javasecurity 和 兩個包這些類都包含在 和 ssl 包中

  sllSSLSocketFactory 類是一個創建安全套接字的對象工廠可以通過下面兩種方法獲得 SSLSocketFactory 的實例

  調用 SSLSocketFactorygetDefault 來獲得默認的工廠默認的工廠被配置為只允許服務器端驗證 (不允許客戶端驗證)注意許多電子商務網站不需要客戶端驗證

  使用指定的配置來構造一個新的工廠 (這不在本文講述的范圍內)

  建立 SSLSocketFactory 實例之後你就可以通過 SSLSocketFactory 實例的 createSocket 方法創建 SSLSocket 對象了這裡有一個例子該例通過 SSL 端口 (這是 HTTPS 的默認端口) 創建套接字並連接到 Sun 的 WWW 服務器

  // Get a Socket factory

  SocketFactory factory = SSLSocketFactorygetDefault();

  // Get Socket from factory

  Socket socket = factorycreateSocket( );

  使用低層的 SSL 套接字

  現在讓我們看一個使用低層套接字在 HTTPS 服務器上打開一個 SSL 套接字連接的完整例子在這個例子中打開了一個到 HTTPS 服務器的 SSL 套接字連接並且讀入默認文檔的內容示例代碼 展示了這個應用程序其中用於打開 SSL 套接字的代碼已經加黑顯示了你將會看到應用程序中其余代碼就是常規的輸入/輸出流代碼

  代碼示例 ReadHttpsURL

  

  

  import *;

  import *;

  import ssl*;

  public class ReadHttpsURL {

  static final int HTTPS_PORT = ;

  public static void main(String argv[]) throws Exception {

  if (argvlength != ) {

  Systemoutprintln(Usage: java ReadHttpsURL );

  Systemexit();

  }

  // Get a Socket factory

  SocketFactory factory = SSLSocketFactorygetDefault();

  // Get Socket from factory

  Socket socket = factorycreateSocket(argv[] HTTPS_PORT);

  BufferedWriter out

  = new BufferedWriter(new OutputStreamWriter(

  socketgetOutputStream()));

  BufferedReader in

  = new BufferedReader(new InputStreamReader(socketgetInputStream()));

  outwrite(GET / HTTP/\n\n);

  outflush();

  String line;

  StringBuffer sb = new StringBuffer();

  while((line = inreadLine()) != null) {

  sbappend(line);

  }

  outclose();

  inclose();

  Systemoutprintln(sbtoString());

  }

  }

  用這個應用程序進行實驗

  拷貝 ReadHttpsURL 類的代碼並粘貼到一個新文件中將該文件改名為 ReadHttpsURLjava並保存在一個你指定的目錄下

  使用 javac 編譯 ReadHttpsURLjava

  運行 ReadHttpsURL 並提供一個域名作為參數

  Prompt> java ReadHttpsURL

  幾秒種後你會看到許多 HTML 代碼顯示在屏幕上注意即使我們提供的是域名 我們打開的連接也是 這是因為我們使用的端口號 是 HTTPS 的默認端口號

  再試試另一個例子

  Prompt> java ReadHttpsURL wwwjamca

  這次運行會拋出如下所示的異常你能猜到是為什麼嗎?

  Exception in thread main sslSSLHandshakeException: javasecuritycertCertificateException: Couldnt find trusted certificate at sslinternalsslBaseSSLSocketImpla(DashoA)

  緣於一個很好的理由它不能運行——因為遠端的服務器發送了一個客戶端不認識的證書我在本文的第一部分提到過當客戶端連接服務器的時候服務器發送它的證書到客戶端請求驗證這樣第一個例子中你進入了 服務器的確發送了證書但 Java 檢查了默認的證書庫並認出了這個證書是由可信任的 CA 產生的默認情況下Java 信任這個 CA第二個例子中你進入的是 wwwjamca那個網端的證書不是它自己產生的就是由一個 Java 不知道的 CA 產生的因此不受信任

  注意如果系統時鐘沒有設置正確那麼它的時間就可能在證書的有效期之外服務器會認為證書無效並拋出 CertificateException 異常

  為了讓示例正確運行你得從 wwwjamca 導入證收到 Java 信任的證書庫中

  導出和導入證書

  為了解釋清楚如何輸出和輸入證書我會使用我自己的 HTTPS 服務器這個服務器在第一部分中討論過然後跟著下面的內容開始

  運行 HTTPS 服務器像在第一部分中討論的那樣

  運行 ReadHttpsURLjava ReadHttpsURL localhost你同樣會得到上面所述的異常

  使用下面的 keytool 命令導出服務器證書

  o 從 serverkeys 文件中導出別名為 qusay 的證書

  o 將導出的證書保存在 servercert 文件中這個文件會由 keytool 創建

  如你看到的那樣我根據要求輸入了密碼成功輸入密碼之後服務器證書被成功的導出並保存在 servercert 中

  Prompt> keytool export keystore serverkeys alias qusay file servercert

  Enter keystore password: hellothere

  Certificate stored in file

  將文件 servercert 拷貝到 ReadHttpsURL 所在的目錄使用 keytool 創建一個新的 keystore 並將服務器的 servercert 證書導入其中這裡的命令示例

  Prompt> keytool import keystore trustedcerts alias qusay file servercert

  這個命令會產生下面那樣的輸出它要求輸入密碼這是一個新的密碼用於 trustedcerts 這個 keystore 的這個 keystore 由 keytool 創建在輸出信息的最後它詢問我是否願意相信這個證書我回答 yes

  Enter keystore password: clientpass

  Owner: CN=localhost OU=Training and Consulting O= L=Toronto ST=Ontario C=CA

  Issuer: CN=localhost OU=Training and Consulting O= L=Toronto ST=Ontario C=CA

  Serial number: dcfa

  Valid from: Mon Nov :: EST until: Sun Feb :: EST

  Certificate fingerprints:

  MD: ::D:A:B:E:B::A::B:FA:E:C:D:C

  SHA: CB:C::::A:::E:::C:D::::

  :F:B:B

  Trust this certificate? [no]: yes

  Certificate was added to keystore

  現在運行 ReadHttpsURL 並告訴它哪裡能找到證書使用下面的命令

  Prompt> java ssltrustStore=trustedcerts ReadHttpsURL localhost

  這將會與你的 HTTPS 服務器聯接校驗證書如果正確它會下載默認頁面 l

  注意信任管理器負責決定遠端的證書是否值得信任它使用下面的規則

  如果 slltrustStore 系統屬性指定了信任庫那麼信任管理器會使用提供的文件來檢查證書如果那個系統屬性存在但指定的文件不存在那麼就沒有使用任何信任庫會拋出一個 CertificateException 異常

  如果 slltrustStore 系統屬性沒有定義那麼它會去尋找默認的信任庫

  如果在你的 javahome 目錄的 lib/security 子目錄下存在名為 jssecacerts 的信任庫那麼使用的就是它

  如果 jssecacerts 不存在但是 cacerts 存在 (它隨 JSDK 一起發行含有數量有限的可信任的基本證書)使用的就是 cacerts

  在我的 Windows 客戶機中javahome 目錄是 c:\Program File\java\jre\lib\security在上例中如果你將 trustedcerts 更名為 jssecacerts 並將其移動到 lib/security 子目錄中那麼你以後就不需要在命令行指定 ssltrustStore 屬性了

  如果你不知道 javahome 在哪裡這裡有一小段代碼可以讓你找到它

  

  

  public class FindJavaHome {

  public static void main(String argv[]) {

  Systemoutprintln(SystemgetProperty(javahome));

  }

  }

  URL 類

  示例代碼 中的 ReadHttpsURL 使用低層的套接字打開到 SSL 服務器的連接這樣做有一個缺點如果不進行一番解析我們就不能在命令行清楚的寫出像 這樣的 URL這裡有一個更簡單的辦法在客戶端應用程序中使用 SSL 和 JSSE

  URL 類支持 HTTPS 地址例如下面的代碼段創建一個 HTTPS 地址並建立一個輸入流的讀入器

  URL url = new URL();

  BufferedReader in

  = new BufferedReader(new InputStreamReader(urlopenStream()));

  是不是很簡單?我希望當你學習 Java 的新東西時你能欣賞到它的美好之處

  示例代碼 中的 ReadHttpsURL 可以由下面使用了 URL 類的示例代碼 代替

  示例代碼 ReadHttpsURLjava

  

  

  import *;

  import javaio*;

  public class ReadHttpsURL {

  public static void main(String argv[]) throws Exception {

  if(argvlength != ) {

  Systemoutprintln(Usage: java ReadHttpsURL );

  Systemexit();

  }

  URL url = new URL(argv[]);

  BufferedReader in

  = new BufferedReader(new InputStreamReader(urlopenStream()));

  String line;

  StringBuffer sb = new StringBuffer();

  while ((line = inreadLine()) != null) {

  sbappend(line);

  }

  inclose();

  Systemoutprintln(sbtoString());

  }

  }

  如果你想試試 ReadHttpsURL執行它的命令和上面討論的類似注意無論如何既然我們使用 URL 類你就能在命令行指定 URL包括協議的名稱這裡是一個例子

  Prompt> java ReadHttpsURL https://localhost

開發一個支持 SSL 的網頁浏覽器

  我們開發一個支持 SSL 的網頁浏覽器作為一個完整的例子該浏覽器要做下面的工作

  用戶輸入 URL浏覽器能接收它

  浏覽器能打開到 URL 指定主機的連接

  浏覽器能發送 HTTP 命令

  浏覽器會等待 HTTP/HTTPS 服務器的回應

  浏覽器能接收 HTML 回應

  浏覽器能解析 HTML 並顯示出頁面

  我們創建的浏覽器要能處理任何 URL 如 HTTPHTTPSFTP 等注意我使用工具類 lHTMLEditorKit 來解析 HTML它提供了對 HTML 的支持

  示例代碼 中展示了這個浏覽器QBrowser的代碼注意 QBrowser 實現了 Runnable 接口我這樣做是因為這個浏覽器沒有提供停止按鈕

  示例代碼 QBrowserjava

  

  

  import javaio*;

  import *;

  import javaawt*;

  import javaawtevent*;

  import javaxswing*;

  public class QBrowser implements ActionListener Runnable {

  private JFrame frame;

  private JButton go;

  private JEditorPane content;

  private JTextField url;

  private JLabel statusLine;

  // default constructor

  public QBrowser () {

  buildBrowserInterface();

  }

  private void buildBrowserInterface() {

  frame = new JFrame(Qs Browser);

  // on close exit the application using Systemexit();

  framesetDefaultCloseOperation ();

  url = new JTextField( );

  go = new JButton(Go Get It);

  goaddActionListener(this);

  JPanel controls = new JPanel(new FlowLayout ());

  controlsadd(new JLabel(URL:));

  controlsadd(url);

  controlsadd(go);

  content = new JEditorPane();

  contentsetEditable(false);

  contentsetContentType(text/html);

  contentsetText(

Qs Browser

  

  Copyright (c) Qusay H Mahmoud

);

  statusLine = new JLabel(Initialization Complete);

  JPanel panel = new JPanel(new BorderLayout ( ));

  framesetContentPane(panel);

  paneladd(controls North);

  paneladd(new JScrollPane (content) Center);

  paneladd(statusLine South);

  framepack();

  framesetVisible(true);

  }

  /**

  * You cannot stop a download with QBrowser

  * The thread allows multiple downloads to start

  * concurrently in case a download freezes

  */

  public void actionPerformed (ActionEvent event) {

  Thread thread = new Thread(this);

  threadstart();

  }

  // this is the Threads run method

  public void run () {

  try {

  String str = urlgetText();

  URL url = new URL(str);

  readURL(url);

  } catch (IOException ioe) {

  statusLinesetText(Error: +ioegetMessage());

  showException(ioe);

  }

  }

  private void showException(Exception ex) {

  StringWriter trace = new StringWriter ();

  exprintStackTrace (new PrintWriter (trace));

  contentsetContentType (text/html);

  contentsetText ( + ex +

   + trace +

);

  }

  /**

  * The URL class is capable of // and https:// URLs

  */

  private void readURL(URL url) throws IOException {

  statusLinesetText(Opening + urltoExternalForm());

  URLConnection connection = urlopenConnection();

  StringBuffer buffer = new StringBuffer();

  BufferedReader in=null;

  try {

  in = new BufferedReader(new InputStreamReader

  (connectiongetInputStream()));

  String line;

  while ((line = inreadLine()) != null) {

  bufferappend(line)append(\n);

  statusLinesetText(Read + bufferlength () + bytes);

  }

  } finally {

  if(in != null) inclose();

  }

  String type = connectiongetContentType();

  if(type == null) type = text/plain;

  statusLinesetText(Content type + type);

  contentsetContentType(type);

  contentsetText(buffertoString());

  statusLinesetText(Done);

  }

  public static void main (String[] args) {

  QBrowser browser = new QBrowser();

  }

  }

  既然 QBrowser 使用 URL 類它就可以處理 HTTP 和 HTTPS 請求你可以使用 HTTP 和 HTTPS 地址測試 QBrowser這裡是一些測試

  請求 你會看到如圖 所示的內容

  

  請求 結果拋出了異常因為這個網頁服務器的證書不受信任並且不能在默認頁中找到所以它拋出如圖 所示的異常

  

  請求 https://localhost這裡運行著第一部分中寫的 HttpServer注意如果你使用命令 java QBrowser 來運行 QBrowser而服務器的證書導出後被導入默認文件 jssecacerts那麼應該將該文件拷貝到 javahome 目錄的 lib/security 子目錄中如果證書被導入了其它文件你可以使用 trustStore 選項java ssltrustStore=file QBrowser使用其實任何一種方法浏覽器都會工作並且你可以看到如圖 所示的默認頁面

  

https://localhost

  HttpsURLConnection 類

  這個類存在於 ssl 包中它擴展了 HttpURLConnection以支持 HTTPS 描述的一些特性它能夠通過 SSL/TLS 套接字建立安全通道來請求/獲取數據示例代碼 展示了一個小型客戶端它使用 HttpsURLConnection 類從 HTTPS 服務器下載文檔

  示例代碼 ReadHttpsURLjava

  

  

  import javaio*;

  import *;

  import ssl*;

  public class ReadHttpsURL {

  public static void main(String[] argv) throws Exception {

  URL url = new URL(argv[]);

  HttpsURLConnection connection = (HttpsURLConnection) urlopenConnection();

  connectionsetDoOutput(true);

  BufferedReader in

  = new BufferedReader(new InputStreamReader(connectiongetInputStream()));

  String line;

  while ((line = inreadLine()) != null) {

  Systemoutprintln(line);

  }

  inclose();

  }

  }

  現在試試 ReadHttpsURL完成上面討論的內容注意無論如何既然我們使用 URL 類你就能在命令行指定 URL包括協議的名稱這裡是一個例子

  Prompt> java ReadHttpsURL

  HttpsURLConnection 有一個非常有趣的特點一旦獲得了連接你就可以在網絡連接之前使用一些有用的參數對其進行配置如 HostnameVerifierHostnameVerifier 是一個接口它申明了方法public boolean verify (String hostname SSLSession session)而且它像下面所述的那樣工作

  如果 SSL/TLS 標准主機名校驗邏輯失敗執行過程中會調用回調類的 verify 方法回調類是實現了 HostnameVerifier 接口的類

  如果回調類檢查到主機名可以接受則允許連接否則連接會被終止

  回調類遵循的規則即可以是基本的驗證方法也可以依賴其它驗證方法這裡說明了如何實現

  

  

  public class MyVerified implements HostnameVerifier {

  public boolean verify(String hostname SSLSession session) {

  // pop up a dialog box

  //

  // return either true or false

  }

  }

  現在可以這樣使用它

  HttpsURLConnection connection = (HttpsURLConnection) urlopenConnection();

  connectionsetHostnameVerifier(new MyVerifier());

  信任管理器

  一個 SSL 客戶端如網頁浏覽器連接到 SSL 服務器 (如 HTTPS 服務器) 的時候HTTPS 服務器將自己的證書鏈交給客戶端驗證SSL 規范規定如果在證書鏈中發現有無效的證書客戶端應該立即終止連接一些網頁浏覽器如 Netscape Communicator 和 Microsoft Internet Explorer詢問用戶是否忽略無效的證書並繼續檢查證書鏈以確定是否有可能驗證通過 HTTPS 服務器使用 sllTrustManager 可以很好的消除這種矛盾它是 JSSE 信任管理器的基礎接口而這些信任管理器則是用來管理可信任的資料以及決定是否接受某個憑證的典型的信任管理器都支持基於 X 的證書它是 JDK 的 keytool 可以管理的一個普通的證書格式

  XTrustManager 接口

  sllXTrustManager 接口擴展了普通的 TrustManager 接口使用基於 X 公鑰證書驗證方案時信任管理器必須實現該接口實現 XTrustManager 可以創建信任管理器這裡有一個空實現

  

  

  public class MyTrustManager implements XTrustManager {

  MyTrustManager() { // constructor

  // create/load keystore

  }

  public void checkClientTrusted(

  XCertificate chain[] String authType)

  throws CertificatException {

  }

  public void checkServerTrusted(

  XCertificate chain[] String authType)

  throws CertificationException {

  // special handling such as poping dialog boxes

  }

  public XCertificate[] getAcceptedIssuers() {

  }

  }

  為了支持遠端套接字 X 證書實現了 XTrustManager 接口的類其實例要傳遞給 SSLContext 對象的 init 方法它作為 SSL 套接字工廠換句話說一旦創建了信任管理器且通過 init 方法將其分配給了一個 SSLSocket以後從 SSLContext 創建的 SocketFactories 在作信任決策時將使用新的信任管理器下面的代碼段就是個示例

  XTrustManager xtm = new MyTrustManager()

  TrustManager mytm[] = {xtm};

  SSLContext ctx = SSLContextgetInstance(SSL);

  ctxinit(nullmytm null );

  SSLSocketFactory sf = ctxgetSocketFactory();

  JSSE 調試工具

  Sun 的 JSSE 實現提供了動態調試跟蹤支持使用系統屬性 debug 即可JSSE 並不正式支持這個特性但它可以讓你看到在 SSL 通信過程中幕後在干什麼這個工具可以通過如下命令使用

  Prompt> java debug=option[debugSpecifiers] MySSLApp

  如果你使用了 help 參數它就會顯示調試選項列表JSE 選項如下

  

  

  all turn on all debugging

  ssl turn on ssl debugging

  The following can be used with ssl:

  record enable perrecord tracing

  handshake print each handshake message

  keygen print key generation data

  session print session activity

  defaultctx print default SSL initialization

  sslctx print SSLContext tracing

  sessioncache print session cache tracing

  keymanager print key manager tracing

  trustmanager print trust manager tracing

  handshake debugging can be widened with:

  data hex dump of each handshake message

  verbose verbose handshake message printing

  record debugging can be widened with:

  plaintext hex dump of record plaintext

  你必須指定參數 ssl 或者 all 中的一個緊跟 debug 符號可以使用一個或多個調試說明符使用:或者作為分隔符說明符不是必須的但可以增強可讀性這裡是一些例子

  Prompt> java debug=all MyApp

  Prompt> java debug=ssl MyApp

  Prompt> java debug=ssl:handshake:trustmanager MyApp

  這篇文章展示了如何使用 JSSE (SSL 協議的框架和實現) 開發安全的客戶端應用程序這篇文章中的例子展示了將 SSL 整合到 C/S 應用程序是多麼簡單的事情這篇文章中講到一個網頁浏覽器QBrowser可以處理 HTTP 和 HTTPS 請求

  QBrowser 中如果服務器上按輸入 HTTPS 的地址中不存在有效的證書則會拋出一個異常你也許想修改 QBrowser 使其能夠處理這個異常並且彈出一個窗口詢問用戶是否願意下載安裝證書那麼你可以把它做為一個練習x 的 Java 插件使用了 JSSE它有自己的的信任管理器如果它不能在信任庫裡找到證書而彈出窗口提示

  原文Secure Internet Programming with Java Standard Edition (JSE) (Part II: The Client Side)

  參閱Secure Internet Programming with Java Standard Edition (JSE) (Part I: The Server Side)


From:http://tw.wingwit.com/Article/program/Java/hx/201311/27135.html
    推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.