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

Java 編程語言中的口令屏蔽

2022-06-13   來源: Java核心技術 

  目前對於 Java 命令行基於文本的輸入/輸出 API 的批評之一就是它缺乏對命令行輸入口令屏蔽的支持如果借助 AWT/Swing這便不再成為問題因為 AWT/Swing 提供了可以提供屏蔽口令的方法

  我發表了本文的早期版本其後便不斷收到大量感謝信建設性的意見和在應用程序中使用源代碼的許可本文

  概述了口令屏蔽
  描述了用於口令屏蔽的
  AWT/Swing 實用程序
  為命令行輸入口令屏蔽問題
  提供獨立於平台的解決方案
  為口令屏蔽提供一個改進的解決方案(可靠而安全)

  口令屏蔽

  登錄屏幕和登錄對話框使用口令屏蔽技術這種技術要麼在輸入口令時隱藏口令要麼顯示一個字符(比如星號*)來代替用戶輸入的字符例如當您在一台 Windows 機器上進行登錄時一個登錄對話框將會呈現在您眼前其中的口令一欄使用星號作為屏蔽或回顯字符

  如果操作系統是 UNIX則登錄屏幕中的口令欄不顯示回顯字符它的做法很簡單就是什麼都不顯示

  AWT/Swing 中的口令屏蔽

  如果您希望為您的應用程序提供圖形化的登錄對話框您可以使用 AWT 的 TextField 類該類是一個文本組件允許編輯單行文本為了屏蔽口令欄要使用 setEchoChar 方法例如為了把回顯字符設置為星號您需要這樣做

  TextField password = new TextField();
passwordsetEchoChar(*);

  基於所使用字體的平均字符寬度數字指定了文本欄的寬度您可以把回顯字符設置為任何您喜歡的字符注意如果您把它設置為這意味著輸入將會被回顯而不會被屏蔽

  在 Swing 中您可以使用 JPasswordField它允許編輯單行文本視圖表明正在輸入內容但是不會顯示原始字符JPasswordField 類與和 setEchoChar 一起使用的 AWT 的 TextField 是源代碼兼容的如果您使用 JPasswordField 默認的回顯字符是星號 * 但是您可以將其修改為任何您選定的字符此外如果您把回顯字符設置為 這意味著字符將在輸入時顯示出來而不會被屏蔽 顯示了一個 Swing 登錄對話框其中的回顯字符被設置為 #使用的是下面的代碼片斷

  JPasswordField password = new JPasswordField();
passwordsetEchoChar(#);

  命令行輸入屏蔽

  和 AWT/Swing 不同在 Java 中沒有特殊的 API 可用於屏蔽命令行輸入這也是許多開發人員一直所要求的一項功能如果您希望為命令行基於文本的 Java 應用程序以及服務器端 Java 應用程序提供一個登錄屏幕它就很有用提供這種功能的一種方式就是使用 Java 本地接口(Java Native Interface JNI)對於不了解 C/C++ 或者希望堅持 % 純 Java 代碼的某些 Java 開發人員來說這可能有一定難度

  這裡我針對這個問題提出一個解決方案在本文的早期版本中所使用的是一個 UNIX 風格的登錄屏幕口令根本不在屏幕上回顯這樣做的具體方法是讓一個單獨的線程通過重寫和打印口令提示命令行嘗試擦除回顯到控制台的字符大家仍然可以從論壇下載該篇文章中專用的代碼和改進後的代碼

  然而大家最需要的功能之一是使用星號*替換回顯的字符因此本文從為口令屏蔽提供一個簡單的解決方案開始接著給出改進後的更加可靠和安全的代碼

  簡單的解決方案

  這個解決方案使用一個單獨的線程在輸入回顯字符的時候擦除它們然後使用星號代替它們這是使用 EraserThread 類來完成的如代碼示例 所示

  代碼示例 EraserThreadjava

  import javaio*;

  class EraserThread implements Runnable {
   private boolean stop;

  /**
    *@param The prompt displayed to the user
    */
   public EraserThread(String prompt) {
       Systemoutprint(prompt);
   }

  /**
    * Begin maskingdisplay asterisks (*)
    */
   public void run () {
      stop = true;
      while (stop) {
         Systemoutprint(\*);
         try {
            ThreadcurrentThread()sleep();
         } catch(InterruptedException ie) {
            ieprintStackTrace();
         }
      }
   }

  /**
    * Instruct the thread to stop masking
    */
   public void stopMasking() {
      thisstop = false;
   }
}

  注意 這個解決方案廣泛利用了線程然而如果機器負載很重就不能確保 MaskingThread 能夠足夠經常地運行請繼續閱讀本文的余下部分來了解代碼的改進版本

  PasswordField 類使用了 EraserThread 類這一點在代碼示例 中體現出來了這個類提示用戶輸入口令而且 EraserThread 的一個實例嘗試使用 * 屏蔽輸入注意一開始將顯示一個星號 (*)

  代碼示例 PasswordFieldjava

  public class PasswordField {

  /**
    *@param prompt The prompt to display to the user
    *@return The password as entered by the user
    */
   public static String readPassword (String prompt) {
      EraserThread et = new EraserThread(prompt);
      Thread mask = new Thread(et);
      maskstart();

  BufferedReader in = new BufferedReader(new InputStreamReader(Systemin));
      String password = ;

  try {
         password = inreadLine();
      } catch (IOException ioe) {
        ioeprintStackTrace();
      }
      // stop masking
      etstopMasking();
      // return the password entered by the user
      return password;
   }
}

  作為如何使用 PasswordField 類的一個例子考慮應用程序 TestApp如示例代碼 所示這個應用程序顯示一條提示並等待用戶輸入口令當然輸入被屏蔽為星號(*)

  代碼示例 TestAppjava

  class TestApp {
   public static void main(String argv[]) {
      String password = PasswordFieldreadPassword(Enter password: );
      Systemoutprintln(The password entered is: +password);
   }
}

  如果您在 WindowsMacOS 或 UNIX 操作系統上運行 TesApp 您將會發現其輸出與圖 類似此外還要注意當您運行該應用程序時會顯示一個初始的星號

  使代碼安全而可靠

  上述的簡單解決方案有一個主要缺陷不應該使用字符串來存儲諸如口令這類敏感信息!在本文的余下部分中將會給出一個經過改進的解決方案

  然而首先MaskingThread 類能夠從幾處改進中獲益

  為了確保跨線程的可見性尤其是在多 CPU 的機器上stop 字段應該被標記為 volatilevolatile 關鍵字指定同步線程使用該字段這樣編譯器就不會對它進行任何優化換句話說應該從內存讀取變量的值而不應該在堆棧中保存任何拷貝

  為了確保屏蔽能夠在系統高負荷運轉時也能夠出現在調用持續期間調用線程的優先權被設定為最大返回時再恢復其原始的優先權

  代碼示例 顯示了修訂後的 MaskingThread 類修改的地方均以粗體形式突出顯示

  import javaio*;

  /**
* This class attempts to erase characters echoed to the console
*/

  class MaskingThread extends Thread {
   private volatile boolean stop;
   private char echochar = *;

  /**
   *@param prompt The prompt displayed to the user
   */
   public MaskingThread(String prompt) {
      Systemoutprint(prompt);
   }

  /**
   * Begin masking until asked to stop
   */
   public void run() {

  int priority = ThreadcurrentThread()getPriority();
      ThreadcurrentThread()setPriority(ThreadMAX_PRIORITY);

  try {
         stop = true;
         while(stop) {
           Systemoutprint(\ + echochar);
           try {
              // attempt masking at this rate
              ThreadcurrentThread()sleep();
           }catch (InterruptedException iex) {
              ThreadcurrentThread()interrupt();
              return;
           }
         }
      } finally { // restore the original priority
         ThreadcurrentThread()setPriority(priority);
      }
   }

  /**
   * Instruct the thread to stop masking
   */
   public void stopMasking() {
      thisstop = false;
   }
}

  盡管使用 Strings 收集和存儲口令看起來似乎很合邏輯它們並不適合存儲諸如口令這樣的敏感信息這是因為 Strings 類型的對象是不可改變的——使用後不能重寫或修改字符串的內容應該使用一個 chars 數組作為代替修訂後的 PasswordField 如代碼示例 所示它是根據 Using PasswordBased Encryption 改寫而來

  代碼示例 PasswordFieldjava

  import javaio*;
import javautil*;

  /**
* This class prompts the user for a password and attempts to mask input with *
*/

  public class PasswordField {

  /**
   *@param input stream to be used (eg Systemin)
   *@param prompt The prompt to display to the user
   *@return The password as entered by the user
   */

  public static final char[] getPassword(InputStream in String prompt)
    throws IOException {

  MaskingThread maskingthread = new MaskingThread(prompt);
      Thread thread = new Thread(maskingthread);
      threadstart();

  char[] lineBuffer;
      char[] buf;
      int i;

  buf = lineBuffer = new char[];

  int room = buflength;
      int offset = ;
      int c;

  loop:   while (true) {
         switch (c = inread()) {
            case :
            case :
               break loop;

  case \r:
               int c = inread();
               if ((c != ) && (c != )) {
                  if (!(in instanceof PushbackInputStream)) {
                     in = new PushbackInputStream(in);
                  }
                  ((PushbackInputStream)in)unread(c);
                } else {
                  break loop;
                }

  default:
                   if (room < ) {
                      buf = new char[offset + ];
                      room = buflength offset ;
                      Systemarraycopy(lineBuffer buf offset);
                      Arraysfill(lineBuffer );
                      lineBuffer = buf;
                   }
                   buf[offset++] = (char) c;
                   break;
         }
      }
      maskingthreadstopMasking();
      if (offset == ) {
         return null;
      }
      char[] ret = new char[offset];
      Systemarraycopy(buf ret offset);
      Arraysfill(buf );
      return ret;
   }
}

  最後PasswordApp 類如代碼示例 所示它只是一個用於測試修訂後代碼的測試應用程序

  代碼示例 PasswordAppjava

  import javaio*;

  public class PasswordApp {
   public static void main(String argv[]) {
      char password[] = null;
      try {
         password = PasswordFieldgetPassword(Systemin Enter your password: );
      } catch(IOException ioe) {
         ioeprintStackTrace();
      }
      if(password == null ) {
         Systemoutprintln(No password entered);
      } else {
         Systemoutprintln(The password entered is: +StringvalueOf(password));
      }
   }
}


From:http://tw.wingwit.com/Article/program/Java/hx/201311/25934.html
  • 上一篇文章:

  • 下一篇文章:
  • 推薦文章
    Copyright © 2005-2022 電腦知識網 Computer Knowledge   All rights reserved.