网站首页
JSP空间
动态资讯
开源项目
技术文档
资源下载
J2EE资源
客户论坛
在线支付
 
  技术文档>>JAVA>>新手入门>>基础入门>查看文档  
  java 编程语言中的口令屏蔽     
  文章作者:未知  文章来源:水木森林  
  查看:73次  录入:管理员--2007-11-17  
 

  目前,对于 java 命令行基于文本的输入/输出 api 的批评之一就是它缺乏对命令行输入口令屏蔽的支持。如果借助 awt/swing,这便不再成为问题,因为 awt/swing 提供了可以提供屏蔽口令的方法。

  2002 年 9 月,我发表了本文的早期版本,其后便不断收到大量感谢信、建设性的意见和在应用程序中使用源代码的许可。本文:

  概述了口令屏蔽
  描述了用于口令屏蔽的
  awt/swing 实用程序
  为命令行输入口令屏蔽问题
  提供独立于平台的解决方案
  为口令屏蔽提供一个改进的解决方案(可靠而安全)

  口令屏蔽

  登录屏幕和登录对话框使用口令屏蔽技术,这种技术要么在输入口令时隐藏口令,要么显示一个字符(比如星号'*')来代替用户输入的字符。例如,当您在一台 windows 机器上进行登录时,一个登录对话框将会呈现在您眼前,其中的口令一栏使用星号作为屏蔽或回显字符。

  如果操作系统是 unix,则登录屏幕中的口令栏不显示回显字符。它的做法很简单,就是什么都不显示,

  awt/swing 中的口令屏蔽

  如果您希望为您的应用程序提供图形化的登录对话框,您可以使用 awt 的 textfield 类,该类是一个文本组件,允许编辑单行文本。为了屏蔽口令栏,要使用 setechochar 方法。例如,为了把回显字符设置为星号,您需要这样做:

textfield password = new textfield(8);
password.setechochar('*');

  基于所使用字体的平均字符宽度,数字8指定了文本栏的宽度。您可以把回显字符设置为任何您喜欢的字符。注意,如果您把它设置为0,这意味着输入将会被回显,而不会被屏蔽。

  在 swing 中,您可以使用 jpasswordfield,它允许编辑单行文本,视图表明正在输入内容,但是不会显示原始字符。jpasswordfield 类与和 setechochar 一起使用的 awt 的 textfield 是源代码兼容的。如果您使用 jpasswordfield, 默认的回显字符是星号 '*', 但是您可以将其修改为任何您选定的字符。此外,如果您把回显字符设置为 0,这意味着字符将在输入时显示出来,而不会被屏蔽。图 2 显示了一个 swing 登录对话框,其中的回显字符被设置为 #,使用的是下面的代码片断:

jpasswordfield password = new jpasswordfield(8);
password.setechochar('#');

  命令行输入屏蔽

  和 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);
   }
}

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


 

  使代码安全而可靠

  上述的简单解决方案有一个主要缺陷:不应该使用字符串来存储诸如口令这类敏感信息!在本文的余下部分中,将会给出一个经过改进的解决方案。

  然而,首先,maskingthread 类能够从几处改进中获益:

  为了确保跨线程的可见性,尤其是在多 cpu 的机器上,stop 字段应该被标记为 volatile.volatile 关键字指定同步线程使用该字段,这样编译器就不会对它进行任何优化;换句话说,应该从内存读取变量的值,而不应该在堆栈中保存任何拷贝。

  为了确保屏蔽能够在系统高负荷运转时也能够出现,在调用持续期间,调用线程的优先权被设定为最大。返回时再恢复其原始的优先权。

  代码示例 4 显示了修订后的 maskingthread 类,修改的地方均以粗体形式突出显示。

import java.io.*;

/**
* 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) {
      system.out.print(prompt);
   }

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

      int priority = thread.currentthread().getpriority();
      thread.currentthread().setpriority(thread.max_priority);

      try {
         stop = true;
         while(stop) {
           system.out.print("/010" + echochar);
           try {
              // attempt masking at this rate
              thread.currentthread().sleep(1);
           }catch (interruptedexception iex) {
              thread.currentthread().interrupt();
              return;
           }
         }
      } finally { // restore the original priority
         thread.currentthread().setpriority(priority);
      }
   }

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


 

  尽管使用 strings 收集和存储口令看起来似乎很合逻辑,它们并不适合存储诸如口令这样的敏感信息。这是因为 strings 类型的对象是不可改变的——使用后不能重写或修改字符串的内容。应该使用一个 chars 数组作为代替。修订后的 passwordfield 如代码示例 5 所示,它是根据 using password-based encryption 改写而来。

  代码示例 5:passwordfield.java

import java.io.*;
import java.util.*;

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

public class passwordfield {

  /**
   *@param input stream to be used (e.g. system.in)
   *@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);
      thread.start();

      char[] linebuffer;
      char[] buf;
      int i;

      buf = linebuffer = new char[128];

      int room = buf.length;
      int offset = 0;
      int c;

      loop:   while (true) {
         switch (c = in.read()) {
            case -1:
            case '':
               break loop;

            case '/r':
               int c2 = in.read();
               if ((c2 != '') && (c2 != -1)) {
                  if (!(in instanceof pushbackinputstream)) {
                     in = new pushbackinputstream(in);
                  }
                  ((pushbackinputstream)in).unread(c2);
                } else {
                  break loop;
                }

                default:
                   if (--room < 0) {
                      buf = new char[offset + 128];
                      room = buf.length - offset - 1;
                      system.arraycopy(linebuffer, 0, buf, 0, offset);
                      arrays.fill(linebuffer, ' ');
                      linebuffer = buf;
                   }
                   buf[offset++] = (char) c;
                   break;
         }
      }
      maskingthread.stopmasking();
      if (offset == 0) {
         return null;
      }
      char[] ret = new char[offset];
      system.arraycopy(buf, 0, ret, 0, offset);
      arrays.fill(buf, ' ');
      return ret;
   }
}

  最后,passwordapp 类如代码示例 6 所示,它只是一个用于测试修订后代码的测试应用程序。

  代码示例 6:passwordapp.java

import java.io.*;

public class passwordapp {
   public static void main(string argv[]) {
      char password[] = null;
      try {
         password = passwordfield.getpassword(system.in, "enter your password: ");
      } catch(ioexception ioe) {
         ioe.printstacktrace();
      }
      if(password == null ) {
         system.out.println("no password entered");
      } else {
         system.out.println("the password entered is: "+string.valueof(password));
      }
   }
}

 
 
上一篇: struts controller    下一篇: javacard开发教程之规范
  相关文档
java规则 中级篇 11-17
sun,ibm策划java portal标准 11-17
使用java开发计算器(二) 11-17
敏捷开发技巧-消除代码异味 11-17
jsf:初学者进入到java web世界的跳板 11-16
java中删除数据库中重复数据的几个方法 11-17
在eclipse 3.1体验j2se 5.0新特性六 11-17
您的 java 代码安全吗? 11-17
jdk-1_5_0_04-nb-4_1-linux简体中文版 11-17
jbuilder class文件的 module引用 11-17
抽象packages增加系统灵活性 11-17
java 继承的一个实例 11-17
java(jvm)虚拟机结构基础 11-16
让你知道:java 文件的缩进排版 11-17
web services&xml:使用xml的五种场合 02-28
开发 web 应用程序的快捷途径介绍 11-17
在java中连接sybase数据库的例子 11-17
java学习:j2me中的内存泄漏 11-16
webwork的validation校验框架实例详解 11-16
java安全 11-17
返回首页 | 关于我们 | J网章程 | JSP空间合租 | 客服中心 | 免责声明 | 常见问题 | 参观机房
本站主机空间代理至厦门市华众网络科技有限公司
《中华人民共和国增值电信业务经营许可证》
编号:闽B2-20050079
@2005-2008福建JSP技术网 版权所有 闽ICP备05000928号
技术电话:13616026886
邮箱:admin@fjjsp.com 站长QQ,点击这里给我发消息