命令行输入屏蔽

--------------------------------------------------------------------------------

和 AWT/Swing 不同,在 Java 中没有特殊的 API 可用于屏蔽命令行输入。这也是许多开发人员一直所要求的一项功能。如果您希望为命令行基于文本的 Java 应用程序以及服务器端 Java 应用程序提供一个登录屏幕,它就很有用。提供这种功能的一种方式就是使用 Java 本地接口(Java Native Interface ,JNI)。对于不了解 C/C++ 或者希望坚持 100% 纯 Java 代码的某些 Java 开发人员来说,这可能有一定难度。

这里我针对这个问题提出一个解决方案。在本文的早期版本中,所使用的是一个 UNIX 风格的登录屏幕,口令根本不在屏幕上回显。这样做的具体方法是,让一个单独的线程通过重写和打印口令提示命令行,尝试擦除回显到控制台的字符。大家仍然可以从论坛下载该篇文章中专用的代码和改进后的代码。

然而,大家最需要的功能之一是使用星号"*"替换回显的字符。因此,本文从为口令屏蔽提供一个简单的解决方案开始,接着给出改进后的、更加可靠和安全的代码。

简单的解决方案

--------------------------------------------------------------------------------

这个解决方案使用一个单独的线程,在输入回显字符的时候擦除它们,然后使用星号代替它们。这是使用 EraserThread 类来完成的,如代码示例 1 所示。

代码示例 1:EraserThread.java

import java.io.*;

class EraserThread implements Runnable {
   private boolean stop;

   /**
    *@param The prompt displayed to the user
    */
   public EraserThread(String prompt) {
       System.out.print(prompt);
   }

   /**
    * Begin masking...display asterisks (*)
    */
   public void run () {
      stop = true;
      while (stop) {
         System.out.print("\010*");
	 try {
	    Thread.currentThread().sleep(1);
         } catch(InterruptedException ie) {
            ie.printStackTrace();
         }
      }
   }

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




注意: 这个解决方案广泛利用了线程,然而,如果机器负载很重,就不能确保 MaskingThread 能够足够经常地运行。请继续阅读本文的余下部分来了解代码的改进版本。 

PasswordField 类使用了 EraserThread 类,这一点在代码示例 2 中体现出来了。这个类提示用户输入口令,而且 EraserThread 的一个实例尝试使用 "*" 屏蔽输入。注意,一开始将显示一个星号 (*)。

代码示例 2:PasswordField.java

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);
      mask.start();

      BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
      String password = "";

      try {
         password = in.readLine();
      } catch (IOException ioe) {
        ioe.printStackTrace();
      }
      // stop masking
      et.stopMasking();
      // return the password entered by the user
      return password;
   }
}




作为如何使用 PasswordField 类的一个例子,考虑应用程序 TestApp,如示例代码 3 所示。这个应用程序显示一条提示,并等待用户输入口令。当然,输入被屏蔽为星号(*)。

代码示例 3:TestApp.java

class TestApp {
   public static void main(String argv[]) {
      String password = PasswordField.readPassword("Enter password: ");
      System.out.println("The password entered is: "+password);
   }
}





图 3:TestApp 示例输出




如果您在 Windows、MacOS 或 UNIX 操作系统上运行 TesApp, 您将会发现其输出与图 3 类似。此外还要注意,当您运行该应用程序时,会显示一个初始的星号。