• Abstraction:

    本文描述了Java语言的嵌入类、内部类的具体分类和行为表现。

    1         概述

    我们知道,Java是一种完全的面向对象的语言,作为对象的灵魂,类的种类是多种多样的。类大致可以分外部类和内部类两种,外部类就是我们通常使用的类,而内部类的使用要比外部类少的多,最常见的是GUI事件侦听器。内部类的应用虽然不多,但是如果能够有效的使用内部类,能达到事半功倍的效果。废话不多说,下面开始:

    2         内部类和嵌套类

    要讨论内部类和嵌套类,首先要分清他们两者的区别于联系。

    首先,内部类(Inner Classes)和嵌套类(Nested Classes)是指在一个类里面定义的另一个类。其次无论是内部类还是嵌套类,在编译时都被当作一个独立的整体。对于访问他们的其他对象来说(假如他们对这个类来说是可见的)他们的使用和我们通常用的类是一样的。

    但是,内部类和嵌套类的区别在于:

    1.         嵌套类是静态的,而内部类不是,也就是说嵌套类的实例化不需要外部类的实例。但是内部类是需要这个实例的。

    2.         嵌套类可以任意声明静态成员,内部类不允许声明除了编译时常量以外的任何静态成员。这一限制也适用于静态初始化函数。

    3.         嵌套类都是命名的,匿名的类声明不能声明运行时静态成员(不管声明是不是静态的)。

    注意:类内部声明的接口都属于嵌入类。

    下面我们来分别讨论一下他们的具体行为表现。

    3         内部类

    内部类是指在一个类里面以非静态形式声明的另一个类。内部类的实例化需要外部实例的存在。一个类可以拥有多个内部类,一个外部实例可以拥有多个内部类的实例。但是内部类有且只有一个外部实例,并且在实例化时就已经指定,无法更改。

    3.1       内部类的分类

    内部类根据访问权限不同可以分为以下一种类型:普通内部类局部内部类

    普通内部类和局部内部类主要的区别在于作用域和访问权限的不同,普通内部类可以被所有人访问(只要访问控制符允许),而局部内部类的作用域更像一个变量,只能在定义它的函数内部被使用,其他人是无法使用这个类的。而且局部类可以访问定义它的函数中的final变量。

    根据声明方式不同又可以分为:命名内部类匿名内部类。命名内部类和匿名内部类的区别在于:

    首先,命名类可以抽象的。匿名类不能为抽象,事实上,匿名内部类在编译时被隐形的声明为最终的(final)

    其次,命名类声明可以继承一个父类并实现多个接口,匿名类只能有一个父类(或者接口)。

    再次,命名类可以被多次实例化,而匿名类只能在定义时被实例化一次。

    最后,命名类可以声明多个构造函数并控制访问哪一个父类的构造函数,匿名类无法声明构造函数。

    下面分别描述这些区别:

    3.2       普通内部类

    普通内部类是指在类成员定义中定义的类,这些类可以拥有访问控制符(public, private)。如果访问控释符允许,则这些类可以被外面直接应用。普通命名内部类可以声明为一个接口,或者是抽象的。嵌入类其实是特殊的普通内部类,但由于其特殊性,故在下面独立讨论。

    3.2.1       声明

    3.2.1.1       命名类

    class OuterClass{

    //Outer class deflation
          class InnerNamedClass{
                   //Inner class definition

    }
    }

    3.2.1.2       匿名类

    声明匿名类时,可以使用一个类或者接口作为他的父类。

    class OuterClass{
        //Outer class deflation
        Object unnamedObject new Object(){
            //Inner class definition
        }
    }

    3.2.2       实例化

    3.2.2.1       命名类

    外部或静态方法

    OuterClass outerObject new OuterClass();
    OuterClass.InnerNamedClass innerObject outerObject.new InnerNamedClass();

    内部

    InnerNamedClass innerObject new InnerNamedClass();

    3.2.2.2       匿名类

    定义时即完成实例化

    3.2.3       访问权限:

    3.2.3.1       内部实例对外部实例的访问权限为:

    外部类定义或继承的所有字段

    3.2.3.2       外部实例对内部实例的访问权限为:

    内部类定义或继承的所有字段

    3.2.3.3       其他对象对内部实例的访问权限为:

    若内部类不可见,则只能访问其超类定义的字段

    若内部类可见,则可访问内部实例的非私有字段,具体情况与通常的类类似

    3.2.4       备注:

    内部类的外部实例是在构造时作为参数传给构造函数的,在SUN JDK中,保存外部实例的字段通常被声明为:

    final OutterClass this$0;

    3.3       局部内部类

    局部内部类,是指在函数体内声明的类,这种类是局域性的,只在函数内声明后有效,他最大的特点是:可以访问定义它的函数中的final变量。

    3.3.1       声明:

    3.3.1.1      命名类:

    class OuterClass{
        //Outer class deflation
        void aMethod(){
            class InnerNamedClass{
                //Inner class definition
            }
        }
    }

    3.3.1.2      匿名类:

    class OuterClass{
        //Outer class deflation
        void aMethod(){
            Object unnamedObject new Object(){
                //Inner class definition
            }
        }

    3.3.2       实例化:

    3.3.2.1      命名类:

    局部类的作用域仅限定义该类的方法中,无法在外部实例化或访问,实例化方法与通常类相同

    3.3.2.2      匿名类:

    定义时即完成实例化

    3.3.3       访问权限:

    3.3.3.1      内部实例对外部实例的访问权限为:

    外部类定义或继承的所有字段

    定义该内部类的方法在定义类之前所定义的所有final变量

    3.3.3.2      外部实例对内部实例的访问权限为:

    在定义该内部类的方法中可以访问内部类定义或继承的所有字段

    在外部实例的其他字段中只能访问其超类定义的字段

    3.3.3.3      其他对象对内部实例的访问权限为:

    只能访问其超类定义的字段

    3.3.4       备注:

    当试图访问一个final变量时,编译器实际上把该变量的值赋值给内部类的一个隐含字段中。编译器会自动的在构造函数中添加相应的参数。在SUN JDK中,这个隐含字段通常被命名为:val$varname

    3.4       使用内部类的注意事项:

    本节包括了一些使用内部类时需要注意的事项,这些事项对所有内部类都适用。

    3.4.1       静态成员

    首先需要注意的是,内部类不能非常量声明静态成员,任何静态成员的声明都会被当成编译错误,例如:

    InnerClasses.java:12: 内部类不能有静态声明

                    static String a;

                                   ^

    1 错误

             先要了解更加详细的信息,请参考附录A

    3.4.2       访问外部实例

    要在一个内部实例中访问外部实例,请使用:

    OuterClass.this

    3.4.3       字段覆盖

    在内部类中声明的与外部类同签名的字段将覆盖外部字段,要访问外部字段请使用外部实例对象前缀。

    OuterClass.this.field

    OuterClass.this.Method();

    4         嵌套类

    嵌套类(Nested Classes)其实是普通内部类的一种特殊形式,首先它的声明是静态的,这就表示了这个类不需要外部实例。也表示了他不能访问外部类的实例字段。但是相应的,嵌套类可以拥有非常量静态成员。事实上,JDK通常把嵌套类当成一个具有特殊名字的独立类。

    另外,嵌套类还拥有一种特殊形式:匿名嵌套类。前面说过,匿名类都是内部类,但是匿名嵌套类是一个特例,从理论上讲,他既不属于嵌套类,也不属于内部类。匿名嵌套类 不允许拥有非常量静态成员,但是他也没有外部实例供访问。

    4.1       声明:

    4.1.1       命名类:

    class OuterClass{

    //Outer class deflation
          static class StaticInnerNamedClass{
                   //Inner class definition

    }
    }

    4.1.2       匿名类:

    class OuterClass{
        //Outer class deflation
        static Object unnamedObject new Object(){
            //Inner class definition
        }
    }

    4.2       实例化

    4.2.1       内部

    StaticInnerNamedClass staticInnerNamedObject new StaticInnerNamedClass();

    4.2.2       外部

    OuterClass.StaticInnerNamedClass staticInnerNamedObject new OuterClass.StaticInnerNamedClass();

  • package com.geotools.test;

    /**
     *
     * CopyRight (C)    All rights reserved.<p>
     *
     * WuHan Inpoint Information Technology Development,Inc.<p>
     *
     * Author sinoly<p> Project Name: PostGeo
     *
     * @version 1.0    2006-11-13
     *
     * <p>Base on : JDK1.5<p>
     *
     */

    public class GeoUtils {
        public enum GaussSphere{
            Beijing54,
            Xian80,
            WGS84,
        }
        private static double Rad(double d){
            return d * Math.PI / 180.0;
        }
     public double DistanceOfTwoPoints(double lng1,double lat1,double lng2,double lat2,
       GaussSphere gs){
            double radLat1 = Rad(lat1);
            double radLat2 = Rad(lat2);
            double a = radLat1 - radLat2;
            double b = Rad(lng1) - Rad(lng2);
            double s = 2 * Math.asin(Math.sqrt(Math.pow(Math.sin(a/2),2) +
             Math.cos(radLat1) * Math.cos(radLat2) * Math.pow(Math.sin(b/2),2)));
            s = s * (gs == GaussSphere.WGS84 ? 6378137.0 : (gs == GaussSphere.Xian80 ? 6378140.0 : 6378245.0));
            s = Math.round(s * 10000) / 10000;
            return s;
     }
    }

  • http://birdshome.cnblogs.com/archive/2004/12/30/83996.aspx

    http://www.cnblogs.com/birdshome/archive/2004/12/31/84495.html

    这段时间写脚本的机会比较多,也就对这些工具有个比较。我们都知道,工欲善其事,必先利其器嘛。有的人觉得脚本嘛有什么好调试的,一眼就看过来了,当然过于依赖于调试器很多时候也会让我们变得懒惰和不仔细,不过大多数的时候好的开发环境还是让我们事半功倍的。

        其实回想起接触脚本的时间算起来还真的是有些念头了,从第一次做个人网页算起,00年吧,都要来5个年头了。那个时候学C语言也没多久,对TC2和TC++1.0那两个IDE喜欢的不得了,结果在遇到了没有集成开发环境的JavaScript后,真是郁闷的要命,当然也就没有好好学习了。直到一年前我对JS的认识都是以拿来主义为主,一般不会自己从头写个什么功能的。

        计划不如变化,今年下半年的工作却主要集中在了JS的开发上,于是开始恶补JavaScript知识,同时自己也搜寻好的IDE。不过以JavaScript作为一个解释性语言的原理来看,其实因该可以有好的调试环境的,因为想当初学习机上的BASIC都可以单步执行也。可是为什么JavaScript确实又没有一个象样的调试环境呢?这个和JavaScript这个语言的使用环境有关,你看Visual JScript .NET不一样是可编译可调试的吗?

        我们这里说这个JavaScript都是指在网页中使用的,如果我们抛开了DHTML和DOM模型,JS存在还真是想不出有什么大的意义了。正是DHTML需要Browser这个支持环境,一下就让 JavaScript的调试变得郁闷起来了。

        在我记忆里微软那个脚本调试器,是和Visual Studio一起的一个东东,而且是挺早以前就有了的,可是只是偶尔网页出错会跑出来,但也不知道它到底什么时候出来,也没有深究(后面会就究的:)。其它的一些JavaScript IDE大多都是用IE作为运行环境,当然可以也可以使用IE的COM组件接口来执行一些JS语句和函数,但都不是真正意义上的IDE,因为我们需要的是Step Into & Step Over! 前者的代表是一个叫做Antechinus JavaScript Editor的东东,看看图片,还不错吧:)


       这个程序看着挺专业自己还是只叫做Editor,还比较的谦虚。它的代码着色和IntelliSence都做得都还可以了,不过它还有一个不错的地方是提供了一个系列的示例代码库,就是上图右下window里的Solutions,用TreeView组织起来的。这类工具还有Developer's JavaScript Editor、jsEditor、MyEclipse JavaScript Editor,而且他们无疑例外都叫做Editor了。

        当然除了一穷二白的Notepad可以写脚本,还有DreamWeaver、FontPage什么的,都可以写,而且后者也有highlighting和有限的IntelliSence。我们就不去多说这些了,接下来给大家推荐的真正的JavaScirpt开发的IDE就是... VS.NET 2003 + VS.NET 2005,是不是觉得既然都提到2005了,还要2003干什么啊?! 后面会说到为什么会这么郁闷。

        居然杀鸡用牛刀,可是这个牛刀就是有很好的杀鸡能力哦。为什么不直接使用VS.NET 2005而还要使用2003,由于2005我手里在用的只是个beta1,有一个功能不知道是没有improve好,还是什么别的原因,完全赶不上2003,而且还没法完全替代:( 到底是什么功能呢?下面再具体的说,去吃个猕猴桃先。

       不管我们是怎么调试JavaScript,他总是在browser里运行,下面我都说在IE里的情况。目前我使用IE6.0 sp1,要启动脚本调试,需要在IE的Tools->Internet Options的Advanced里uncheck 'Disabled Script Debugging.',否则脚本出错时我只能在browser的status bar上看到一个错误提示图标。这样一来,当browser里出现脚本错误的时候,就会弹出一个这样的warning dialog:

    点击Yes后,将出现如下窗口:  

       这个debugger dialog里列出的‘Possible Debuggers’就是我机器里已安装的Script调试器。其中"Microsoft Script Editor"就是上次说的Visual Studio里带的调试器了,... 2003、... 2005就不用说了,就是VS.NET了。这种情况的调试是最普通的调试,可以直接把断点定位到脚本错误的行上,然后通过watch窗口看local变量的值来确定错误的所在。

        那么脚本要是不明显的出语法错误怎么设断点呢?这里有两个办法,一个是使用IE View菜单里的Script Debugger子菜单(这个菜单是在IE的Tools->Internet Options->Advanced里设置过才会出来的):  

        "Open"就和上面说到到的点"Yes"后出来的窗口一样,点"Break at Next Statement"比较的有意思,它将会在你的Browser执行下一条脚本的时候让你选择调试器来调试,这一点十分便于我们分析别人的拥有复杂JavaScript的页面。比如gmail里面的管理页面,如果我想看看点了star之后它是怎么处理的,就可以使用这个"Break at Next Statement",让它break在next statement上。不过使用这个feature有个技巧:有的页面由于捕获了onmousemove事件,如果你通过鼠标去点击菜单里的"Break at Next Statement"条目,你的next statement总是被onmousemove给捕获了,而得不到你想要的onclick的语句的中断,怎么办呢?! 那就是使用快捷键啦,比如你希望激活调试gmail里的mail list上的star(如下图)被点击时执行的脚本,你就把鼠标放在star上面,先按快捷键"Alt+V"、"u"、"b",再点击就行了。


        到这里就可以说一下为什么现在还要用VS.NET 2003,而不只用VS.NET 2005来调试脚本了。比如上面的case,如果我在"Possible Debuggers"里选"New Instance of Visual Studio Whidbey",我们将会得到一个Msg Box:

        用我test team一个哥们的话来说:真是伤感哦!

        这时我们使用VS.NET 2003就可以顺利的跟踪了。


        确实郁闷吧?不过,我相信whidbey正式release的时候,这个问题因该是能解决的了,那时2003就可以完全下岗了。  
      
        当然让调试器whidbey跟踪<script src="..."></script>这种情况不是完全没有可能,这里有个小hack可以使用的。就是在调试前把会被引用到的script文件预先就打开在whidbey里,然后就能trace到该文件中去,这个办法显然只能解决本地调试,对于服务器端引用的意义不大,因为我们没法预先把服务器上的脚本文件打开:(。  _fcksavedurl=""..."></script>这种情况不是完全没有可能,这里有个小hack可以使用的。就是在调试前把会被引用到的script文件预先就打开在whidbey里,然后就能trace到该文件中去,这个办法显然只能解决本地调试,对于服务器端引用的意义不大,因为我们没法预先把服务器上的脚本文件打开:(。 "


    我们简单的使用VS.NET打开一个包含脚本的html文件,我们发现我们是不能调试它的,虽然我们可以按F9来设置一个端点,但是这个断点却是形同虚设:(。这是因为我们的browser还没有进入可调试状态,使用上一篇文章中的在代码出错时选择Debugger的方法可以让browser进入调试状态,更普通的办法是把Browser的线程附加到调试器上去。下图就是调试Test.html的示例:


        附加线程到运行Test.html的Browser进程上,就是Processes List里的"IEXPLORE.EXE"。  

        这样一来再在脚本代码上按F9设置断点,断点的状态就会从下图的第一个unavailable状态变为下图的第二个available状态。

        这样一来我们的代码执行到断点处就会停止下来,这个好像废话哈。下面就是VS.NET 2005比VS.NET 2003强的地方登场了。我们如果用过VS.NET编译型程序的调试,都知道把鼠标放在变量名上,会很快有个Tooltip显示变量的值,或对象的类型和默认ToString()的值。这个功能在2003和2005里同样拥有,只是对于对象Object,在2003里就显示一个{...},如果要看{...}是啥?需要使用Add Watch或QuickWatch来看;在whidbey里面,这个察看功能有了很大的提升,我们将会看到一个可展开的节点来显示对象的值,我们可以在弹出窗口里浏览整个对象树,如下图:
        

        这个东西看起来是不是对调试非常方便呢?当然要是我们觉得弹出窗口太大遮挡了代码,而且这时我们又浏览了很多几级对象树了,屏幕上有一大堆展开的弹出窗口,我们不希望它们消失后,看清了代码又再把它们重新弄出来,怎么办呢?我们这时可以按Ctrl键或者鼠标中键(就是滚轮)让这些弹出窗口透明化~~~


        很酷很贴心的功能吧?  

        调试脚本中可能遇到的问题和对策(我们都默认IE的允许脚本调试的选项是开启的):

        1、本来我们打开IE的脚本调试后,程序出现脚本错误时就会弹出一个MsgBox来提示是否调试,而不只是在IE左下角显示一个warning icon。但是有的时候经过长时间的调试,IE在脚本错误的时候,会不再弹出那个调试提示框,而是又变为在左下角显示错误图标。解决这个问题很简单,关掉browser重新打开来调试就好了。

        2、在脚本正常运行时,我们使用IE的View->Script Debugger的Open和Break at Next Statement选项会强行调出脚本调试器选择窗口。但是如果我们在脚本中开启过popup窗口,这两个调试选项会失效,就是点选后没有任何反应,这个可能是IE的bug,解决办法一:重新起动IE来调试;解决办法二:在希望调试的脚本语句前写一个错误的脚本调用,比如a.a;,这样程序运行在a.a语句时就会被break并弹出错误调试提示MsgBox,我们可以在这时选择调试器,进入调试状态后使用鼠标改变脚本执行顺序,跳过错误的语句就行了。

        关于脚本调试环境先写到这里,以后有更好的调试方法我会更新进来,欢迎大家提出更好的脚本调试意见和建议。

  • 恐怕没有什么软件工程主题的重要性比应用程序安全性更紧迫了。无论是来自内部或外部的攻击都会带来巨大损失,某些攻击会使软件公司对造成的损失承担赔偿责任。随着计算机(尤其是因特网)技术的发展,安全性攻击正在变得越来越成熟和频繁。利用各种最新技术和工具是应用程序安全性的一个关键;另一个关键是由经过考验的技术(如数据加密、认证和授权)构筑的牢固的基础。
    Java 平台的基本语言和库扩展都提供了用于编写安全应用程序的极佳基础。本教程讨论了密码术基础知识与如何用 Java 编程语言实现密码术,并提供了样本代码来说明这些概念。
    在这个两部分教程的第一部分中,我们讨论库扩展(现在是 JDK 1.4 库的一部分)中的内容,这些库扩展被称为 Java 密码术扩展(Java Cryptography Extension(JCE))和 Java 安全套接字扩展(Java Secure Sockets Extension (JSSE))。此外,本教程还介绍了 CertPath API,这是 JDK 1.4 中新增加的特性。在本教程第二部分(请参阅参考资料)中,我们将把讨论范围扩大到由 Java 平台中 Java 认证和授权服务(Java Authentication and Authorization Service (JAAS))管理的访问控制。
    这是一篇中级教程;它假设您知道如何阅读和编写基本的 Java 程序,包括应用程序和 applet
    如果您已经是 Java 程序员并且对密码术(关于诸如私钥和公钥加密、RSASSL、证书之类的主题)以及支持它们的 Java 库(JCEJSSE)感到好奇,那么本教程就是为您准备的。本教程不要求您已经具有任何密码术、JCE JSSE 等方面的知识背景。
    本教程介绍了基本密码构件概念。每个概念都附有 Java 实现考虑事项、代码示例和示例执行的结果。
    您将需要以下几项以完成本教程中的编程练习:
    您可以使用 JDK 1.3.x,但必须自行安装 JCE JSSE
    代码示例直接将已加密的数据显示到屏幕上。大多数情况下,这会产生奇形怪状的控制字符,其中的一些偶尔可能会引起屏幕格式化问题。这不是良好的编程实践(将它们转换成可显示的ASCII 字符或十进制表示会更好),但我们这样做是为了保持代码示例及其输出的简洁。
    在示例执行章节的大多数情况下,我们修改了实际字符串以便与本教程中的字符集需求兼容。还有,我们在大多数示例中查询和显示了用于给定算法的实际安全性提供程序库。这样做是为了让用户更好地了解哪个功能是调用哪个库实现的。为什么这样做呢?因为,在大多数安装中都安装了许多这样的提供程序。
    Java 平台是如何使安全编程更方便的
    Java 编程语言和环境有许多特性使安全编程更方便:
    • 无指针,这意味着 Java 程序不能对地址空间中的任意内存位置寻址。
    • 字节码验证器,在编译成 .class 文件之后运行,在执行之前检查安全性问题。例如,访问超出数组大小的数组元素的尝试将被拒绝。因为缓冲区溢出攻击是造成大多数系统漏洞的主要原因,所以这是一种重要的安全性特性。
    • 对资源访问的细颗粒度控制,用于 applet 和应用程序。例如,可以限制 applet 对磁盘空间的读或写,或者可以授权它仅从特定目录读数据。可以根据对代码签名的人(请参阅代码签名的概念)以及代码来源处的 http 地址来进行授权。这些设置都出现在 java.policy 文件中。
    • 大量库函数,这些函数用于主要密码构件和SSL(本教程的主题)以及认证和授权(在本系列的第二篇教程中讨论)。此外,有众多的第三方库可用于额外的算法。
    什么是安全编程技术?
    简单地说,有多种编程风格和技术可以帮助确保应用程序更安全。考虑下列两个一般示例:
    • 存储/删除密码。如果密码是存储在 Java String对象中的,则直到对它进行垃圾收集或进程终止之前,密码会一直驻留在内存中。即使进行了垃圾收集,它仍会存在于空闲内存堆中,直到重用该内存空间为止。密码String在内存中驻留得越久,遭到窃听的危险性就越大。

      更糟的是,如果实际内存减少,则操作系统会将这个密码String换页调度到磁盘的交换空间,因此容易遭受磁盘块窃听攻击。

      为了将这种泄密的可能性降至最低(但不是消除),您应该将密码存储在char数组中,并在使用后对其置零。(String是不可变的,所以无法对其置零。)
    • 智能序列化。当为存储器或传输任何私有字段而序列化对象时,缺省情况下,这些对象都呈现在流中。因此,敏感数据很容易被窃听。可以使用transient关键字来标记属性,这样在流中将忽略该属性。
    当在本教程中需要用到这些技术以及其它技术时,我们将更详细地讨论它们。
    JDK 1.4 之前,许多安全性功能必须作为扩展添加到基本 Java 代码分发版中。严格的美国出口限制要求这种功能的分离。
    现在,新的宽松法规使安全性特性和基本语言更紧密的集成成为可能。下列软件包(在 1.4 发行版之前作为扩展使用)现在集成到了 JDK 1.4 中:
    • JCEJava 密码术扩展)
    • JSSEJava 安全套接字扩展)
    • JAASJava 认证和授权服务)
    JDK 1.4 还引入了两种新功能:
    • JGSSJava 通用安全性服务 (Java General Security Service)
    • CertPath APIJava 证书路径 API (Java Certification Path API)
    JCEJSSE CertPath API 是本教程讨论的主题。在本系列的下一篇教程中,我们将重点介绍 JAAS。这两篇教程都不讨论JGSS(它提供一般框架以在两个应用程序之间安全地交换消息)。
    我们可以用第三方库(也称为提供程序)来增强当前 Java 语言中已经很丰富的功能集。提供程序添加了额外的安全性算法。
    作为库示例,我们将使用 Bouncy Castle 提供程序(请参阅参考资料)。Bouncy Castle 库提供了其它密码算法,包括本教程什么是公钥密码术?什么是数字签名?中讨论的流行的 RSA 算法。
    尽管您的目录名和java.security文件可能有一点不同,但仍可用以下模板安装 Bouncy Castle 提供程序。要安装这个库,请下载 bcprov-jdk14-112.jar 文件并将它放到 j2sdk1.4.0\jre\lib\ext Program Files\Java\J2re1.4.0\lib\ext 目录中。在两个 java.security 文件(他们位于上述相同的目录下,但位于“security”子目录而不是“ext”)中,将下面的行:security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider
    添加至这些行的末尾:
    security.provider.1=sun.security.provider.Sun
    security.provider.2=com.sun.net.ssl.internal.ssl.Provider
    security.provider.3=com.sun.rsajca.Provider
    security.provider.4=com.sun.crypto.provider.SunJCE
    security.provider.5=sun.security.jgss.SunProvider
    security.provider.6=org.bouncycastle.jce.provider.BouncyCastleProvider
    (注:笔者学习本教程时,java1.5早已经Release,并且Bound Castle也已经Release了针对jdk1.5版本的软件库,并且java.security文件的内容也与本教程有些不同,jdk1.5.0_09java.security文件的内容如下:
    #
    # List of providers and their preference orders (see above):
    #
    security.provider.1=sun.security.provider.Sun
    security.provider.2=sun.security.rsa.SunRsaSign
    security.provider.3=com.sun.net.ssl.internal.ssl.Provider
    security.provider.4=com.sun.crypto.provider.SunJCE
    security.provider.5=sun.security.jgss.SunProvider
    security.provider.6=com.sun.security.sasl.Provider
    也就是说,sun已经可以提供有关RSA加密的算法。)
    在本章中,我们已经介绍了 Java 语言提供的有助于确保编程保持安全的特性(无论是完全集成的还是基于扩展的)。我们提供了一些安全编程技术的通用示例以帮助您熟悉这个概念。我们介绍了过去是作为扩展的但现在集成到版本1.4 发行版中的安全性技术;我们也注意到两种新的安全性技术。我们还说明了了第三方库通过提供新技术能够增强安全性程序。
    在本教程余下的部分,我们将让您熟悉这些旨在提供安全的消息传递的概念(因为它们应用于 Java 编程):
    • 消息摘要。这是一种与消息认证码结合使用以确保消息完整性的技术。
    • 私钥加密。被设计用来确保消息机密性的技术。
    • 公钥加密。允许通信双方不必事先协商秘钥即可共享秘密消息的技术。
    • 数字签名。证明另一方的消息确定来自正确通信方的位模式。
    • 数字证书。通过让第三方认证机构认证消息,向数字签名添加另一级别安全性的技术。
    • 代码签名。由可信的实体将签名嵌入被传递的代码中的概念。
    • SSL/TLS在客户机和服务器之间建立安全通信通道的协议。传输层安全性(Transport Layer Security (TLS))是安全套接字层(Secure Sockets Layer (SSL))的替代品。
    当我们讨论上述每个主题时,都将提供示例和样本代码。
    确保消息的完整性
    我们将在本章中了解消息摘要,它获取消息中的数据并生成一个被设计用来表示该消息“指纹”的位块。我们还将讨论 JDK 1.4 支持的与消息摘要相关的算法、类和方法,并为消息摘要和消息认证特性提供代码示例和样本的执行代码。
    什么是消息摘要?
    消息摘要是一种确保消息完整性的功能。消息摘要获取消息作为输入并生成位块(通常是几百位长),该位块表示消息的指纹。消息中很小的更改(比如说,由闯入者和窃听者造成的更改)将引起指纹发生显著更改。
    消息摘要函数是单向函数。从消息生成指纹是很简单的事情,但生成与给定指纹匹配的消息却很难。
    消息摘要可强可弱。校验和(消息的所有字节异或运算的结果)是弱消息摘要函数的一个示例。很容易修改一个字节以生成任何期望的校验和指纹。大多数强函数使用散列法。消息中1 位更改将引起指纹中巨大的更改(理想的比例是更改指纹中 50% 的位)。
    JDK 1.4 支持下列消息摘要算法:
    • MD2MD5,它们都是 128 位算法
    • SHA-1 160 位算法
    • SHA-256SHA-383SHA-512提供更长的指纹,大小分别是 256 位、383 位和512
    MD5 SHA-1 是最常用的算法。
    MessageDigest类操作消息摘要。消息摘要代码示例中使用下列方法:
    • MessageDigest.getInstance("MD5"):创建消息摘要。
    • .update(plaintext):用明文字符串计算消息摘要。
    • .digest():读取消息摘要。
    如果密钥被用作消息摘要生成过程的一部分,则将该算法称为消息认证码JDK 1.4 支持 HMAC/SHA-1 HMAC/MD5 消息认证码算法。
    Mac类使用由KeyGenerator类产生的密钥操作消息认证码。消息认证码示例中使用了下列方法:
    • KeyGenerator.getInstance("HmacMD5").generateKey():生成密钥。
    • Mac.getInstance("HmacMD5"):创建MAC对象。
    • .init(MD5key):初始化MAC对象。
    • .update(plaintext).doFinal():用明文字符串计算MAC对象。
    import java.io.UnsupportedEncodingException;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    public class MessageDigestExample {
                       /**
                        * @param args
                        * @throws UnsupportedEncodingException
                        * @throws NoSuchAlgorithmException
                        */
                       public static void main(String[] args) throws
    UnsupportedEncodingException,
                                                             NoSuchAlgorithmException {
                                          if (args == null || args.length == 0) {
                                                             System.out.println("Usage:\n\tjava MDE text");
                                                             System.exit(1);
                                          }
                                          byte[] plainText = args[0].getBytes("UTF-8");
                                          System.out.println("Original Text:" + args[0]);
                                          MessageDigest messageDigest = MessageDigest.getInstance("MD5");
                                          System.out.println(messageDigest.getProvider().getInfo());
                                          messageDigest.update(plainText);
                                          byte[] digest = messageDigest.digest();
                                          System.out.println("Digest:" + new String(digest, "UTF-8"));
                       }
    }
    执行输出结果:
    Original Text:HelloWorld
    SUN (DSA key/parameter generation; DSA signing; SHA-1, MD5 digests; SecureRandom; X.509 certificates; JKS keystore; PKIX CertPathValidator; PKIX CertPathBuilder; LDAP, Collection CertStores)
    Digest:h?                   ??_?*_?\?'??
    消息认证码示例
    import java.io.UnsupportedEncodingException;
    import java.security.InvalidKeyException;
    import java.security.NoSuchAlgorithmException;
    import javax.crypto.KeyGenerator;
    import javax.crypto.Mac;
    import javax.crypto.SecretKey;
    public class MessageAuthenticationExample {
                       /**
                        * @param args
                        * @throws UnsupportedEncodingException
                        * @throws NoSuchAlgorithmException
                        * @throws InvalidKeyException
                        */
                       public static void main(String[] args) throws
    UnsupportedEncodingException,
                                                             NoSuchAlgorithmException, InvalidKeyException {
                                          if (args == null || args.length == 0) {
                                                             System.out.println("Usage:");
                                                             System.out.println("java MAE text");
                                          }
                                          byte[] plainText = args[0].getBytes("UTF-8");
                                          System.out.println("Original Text:"+args[0]);
                                          KeyGenerator keyGen = KeyGenerator.getInstance("HmacMD5");
                                          SecretKey md5Key = keyGen.generateKey();
                                          System.out.println("HmacMD5 Provider:");
                                          System.out.println(keyGen.getProvider().getInfo());
                                          Mac mac = Mac.getInstance("HmacMD5");
                                          mac.init(md5Key);
                                          mac.update(plainText);
                                          System.out.println("\n" + mac.getProvider().getInfo());
                                          System.out.println("\nMAC: ");
                                          System.out.println(new String(mac.doFinal(), "UTF-8"));
                       }
    }
    执行代码结果:
    Original Text:HelloWorld
    HmacMD5 Provider:
    SunJCE Provider (implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, Diffie-Hellman, HMAC)
    SunJCE Provider (implements RSA, DES, Triple DES, AES, Blowfish, ARCFOUR, RC2, PBE, Diffie-Hellman, HMAC)
    MAC:
    F~??u_?:_?_?3f%?
    在生成密钥时,执行程序过程中可以看到在控制台停滞了几秒钟。
    注:因为代码用线程行为定时来生成优质的伪随机数,所以密钥生成很费时。一旦生成了第一个数,其它数的生成就快得多了。
    还请注意,与消息摘要不同的是,消息认证码使用密码提供程序。(有关提供程序的更多信息,请参阅第三方库充实了安全性。)
    保持消息的机密性
    在本节中,我们将研究私钥加密的使用并主要讨论诸如密码块、填充、序列密码和密码方式之类的概念。我们将迅速地讨论密码算法、类和方法的细节并用代码示例和样本执行来说明这个概念。
    什么是私钥密码术?
    消息摘要可以确保消息的完整性,但不能用来确保消息的机密性。要确保机密性,我们需要使用私钥密码术来交换私有消息。
    Alice Bob 各有一个只有他们两人知道的共享密钥,并且约定使用一种公用密码算法或密码。也就是说,他们保持密钥的私有性。当 Alice 想给Bob 发送消息时,她加密原始消息(称为明文)以创建密文,然后将密文发送给 BobBob 接收了来自 Alice 的密文并用自己的私钥对它解密,以重新创建原始明文消息。如果窃听者 Eve 侦听该通信,她仅得到密文,因此消息的机密性得以保持。
    您可以加密单一位或位块(称为块)。块(称为密码块)通常是 64 位大小。如果消息大小不是 64 位的整数倍,那么必须填充短块(关于填充的更多信息请参阅什么是填充?)。单一位加密在硬件实现中更常见。单一位的密码被称为序列密码
    私钥加密的强度取决于密码算法和密钥的长度。如果算法比较好,那么攻击它的唯一方法就是使用尝试每个可能密钥的蛮力攻击,它平均要尝试(1/2)*2*n次,其中n是密钥的位数。
    在美国出口法规很严时,只允许出口 40 位密钥。这种密钥长度相当弱。美国官方标准(DES 算法)使用 56 位密钥,但随着处理器速度的增加,它变得越来越弱。现在,通常首选 128 位密钥。如果每秒可以尝试一百万个密钥,那么,使用 128 位密钥,找到密钥所需的平均时间是宇宙年龄的许多倍!
    什么是填充?
    如前所述,如果使用分组密码而消息长度不是块长度的整数倍,那么,必须用字节填充最后一个块以凑成完整的块大小。有许多方法填充块,譬如全用零或一。在本教程中,我们将对私钥加密使用PKCS5 填充,而对公钥加密使用 PKCS1
    使用 PKCS5 时,由一个其值表示剩余字节数的重复字节来填充短块。我们不会在本教程中更深入地讨论填充算法,但您需要了解的信息是,JDK 1.4 支持下列填充技术:
    • 无填充
    • PKCS5
    • OAEP
    • SSL3
    BouncyCastle 库(请参阅第三方库充实了安全性参考资料)支持其它填充技术。
    方式:确定加密是如何工作的
    可以以各种方式使用给定密码。方式允许您确定加密是如何工作的。
    例如,您可以允许一个块的加密依赖于前一个块的加密,也可以使块的加密独立于任何其它块的加密。
    根据您的需求选择方式,并且您必须考虑各方面的权衡(安全性、并行处理能力以及明文和密文的容错等)。方式的选择超出了本教程的讨论范围(请参阅参考资料以进行更深入的阅读),但再次指出,您要了解的信息是,Java 平台支持下列方法:
    • ECB(电子密码本 (Electronic Code Book)
    • CBC(密码块链接 (Cipher Block Chaining)
    • CFB(密码反馈方式 (Cipher Feedback Mode)
    • OFB(输出反馈方式 (Output Feedback Mode)
    • PCBC(填充密码块链接 (Propagating Cipher Block Chaining)
    JDK 1.4 支持下列私钥算法:
    • DESDES(数据加密标准)是由 IBM 于上世纪 70 年代发明的,美国政府将其采纳为标准。它是一种 56 位分组密码。
    • TripleDES该算法被用来解决使用 DES 技术的 56 位时密钥日益减弱的强度,其方法是:使用两个密钥对明文运行 DES 算法三次,从而得到 112 位有效密钥强度。TripleDES 有时称为 DESede(表示加密、解密和加密这三个阶段)。
    • AESAES(高级加密标准)取代 DES 成为美国标准。它是由 Joan Daemen Vincent Rijmen 发明的,也被称为 Rinjdael 算法。它是 128 位分组密码,密钥长度为 128 位、192 位或 256 位。
    • RC2RC4RC5这些算法来自领先的加密安全性公司 RSA Security
    • Blowfish这种算法是由 Bruce Schneier 开发的,它是一种具有从 32 位到 448 位(都是 8 的整数倍)可变密钥长度的分组密码,被设计用于在软件中有效实现微处理器。
    • PBE
  • 2007-11-22

    JAVA安全教程 - JAAS - [JAVA]

    在这个两部分的教程中,我们学习了 Java 平台的安全性特性。第 1 部分为初学者介绍了 Java 密码术。在这第 2 部分中,我们将详细讨论访问控制,在 Java 平台中由“Java 认证与授权服务(Java Authentication and Authorization Service (JAAS)”管理访问控制。
    JAAS 本来是 Java 2 平台标准版的扩展。然而,最近已将它添加到版本 1.4 中。要完成本教程,需要下列各项:
    • JDK 1.4,标准版
    • 教程源代码和类,以便您可以跟上我们的进度并理解示例。
    • 支持 Java 1.4 插件的浏览器。
    可以使用 JDK 1.3.x,但您必须自行安装 JCE JSSE
    认证与授权
    认证是用户或计算设备用来验证身份的过程。授权是根据请求用户的身份允许访问和操作一段敏感软件的过程。这两个概念密不可分。没有授权,就无需知道用户的身份。没能认证,就不可能区分可信和不可信用户,更不可能安全地授权访问许多系统部分。
    不一定要标识或认证个别实体;在某些情况下,可以通过分组,对给定组中的所有实体授予某种权限来进行认证。在某些情况下,个别认证是系统安全性必不可少的环节。
    认证与授权的另一个有趣方面是,一个实体在系统中可以有几个角色。例如,用户可以同时是公司职工(表示他需要对公司的电子邮件有访问权)和该公司的会计师(表示他需要对公司财务系统有访问权)。
    认证元素
    认证基于以下一个或多个元素:
    • 您知道什么。该类别包括个别人知道而其它人一般不知道的信息。示例包括 PIN、密码和个人信息(如母亲的婚前姓)。
    • 您有些什么。该类别包括使个人能够访问资源的物理项。示例包括 ATM 卡、Secure ID 令牌和信用卡。
    • 您是谁。该类别包括如指纹、视网膜剖面和面部照片等生物测定信息。
    通常,对于授权只使用一种类别是不够的。例如,ATM 卡通常与 PIN 结合在一起使用。即使物理卡丢失,用户和系统也能够安然无恙,因为小偷还必须知道 PIN 才能访问任何资源。
    授权元素
    有两种控制访问敏感代码的基本方法:
    • 声明性授权可以由系统管理员执行,他配置系统的访问权(即,声明谁可以访问系统中的哪些应用程序)。通过声明性授权,可以添加、更改或取消用户访问特权,而不影响底层应用程序代码。
    • 程序性授权使用 Java 应用程序代码来做授权决定。当授权决定需要更复杂的逻辑和决定(超出了声明性授权的能力范围)时,程序性授权是必需的。因为程序性授权被构建到应用程序代码中,所以更改程序性授权时要求重写应用程序的部分代码。
    您将在本教程中学习声明性和程序性授权技术。
    保护用户和代码
    根据用户在代码中的可信度,Java 平台允许对计算资源(如磁盘文件和网络连接)进行细颗粒度的访问控制。Java 平台的大多数基本安全性特性都是为保护用户免受潜在的恶意代码破坏而设计的。例如,第三方证书支持的数字签名代码确保代码来源的身份。根据用户对代码来源的了解,他可以选择授予或拒绝对该代码的执行权。同样,用户可以根据给定代码来源的下载URL 授予或拒绝访问权。
    基于 Java 的系统上的访问控制是通过策略文件实现的,该文件包含的语句如下:
     
    grant signedBy "Brad", codeBase "http://www.bradrubin.com" {
     
           permission java.io.FilePermission "/tmp/abc", "read";
    };
     
    该语句允许由“Brad”签署并从 http://www.bradrubin.com 装入的代码读取/tmp/abc目录。
    其它 Java 平台特性(如缺少指针)进一步保护用户免受潜在的恶意代码破坏。JAAS 的认证和授权服务一起工作,提供了补充功能:它们防止敏感的 Java 应用程序代码遭到潜在的恶意用户破坏。
    以下内容是%JAVA_HOME%\jre\lib\security中java.policy文件的内容:
    // Standard extensions get all permissions by default
    grant codeBase "file:${{java.ext.dirs}}/*" {
                  permission java.security.AllPermission;
    };
    // default permissions granted to all domains
    grant {
                  // Allows any thread to stop itself using the java.lang.Thread.stop()
                  // method that takes no argument.
                  // Note that this permission is granted by default only to remain
                  // backwards compatible.
                  // It is strongly recommended that you either remove this permission
                  // from this policy file or further restrict it to code sources
                  // that you specify, because Thread.stop() is potentially unsafe.
                  // See "http://java.sun.com/notes" for more information.
                  permission java.lang.RuntimePermission "stopThread";
                  // allows anyone to listen on un-privileged ports
                  permission java.net.SocketPermission "localhost:1024-", "listen";
                  // "standard" properies that can be read by anyone
                  permission java.util.PropertyPermission "java.version", "read";
                  permission java.util.PropertyPermission "java.vendor", "read";
                  permission java.util.PropertyPermission "java.vendor.url", "read";
                  permission java.util.PropertyPermission "java.class.version", "read";
                  permission java.util.PropertyPermission "os.name", "read";
                  permission java.util.PropertyPermission "os.version", "read";
                  permission java.util.PropertyPermission "os.arch", "read";
                  permission java.util.PropertyPermission "file.separator", "read";
                  permission java.util.PropertyPermission "path.separator", "read";
                  permission java.util.PropertyPermission "line.separator", "read";
                  permission java.util.PropertyPermission "java.specification.version", "read";
                  permission java.util.PropertyPermission "java.specification.vendor", "read";
                  permission java.util.PropertyPermission "java.specification.name", "read";
                  permission java.util.PropertyPermission "java.vm.specification.version", "read";
                  permission java.util.PropertyPermission "java.vm.specification.vendor", "read";
                  permission java.util.PropertyPermission "java.vm.specification.name", "read";
                  permission java.util.PropertyPermission "java.vm.version", "read";
                  permission java.util.PropertyPermission "java.vm.vendor", "read";
                  permission java.util.PropertyPermission "java.vm.name", "read";
    };
    可插入认证模块
    JAAS 实现“可插入认证模块(Pluggable Authentication Module(PAM))”框架的 Java 版本。Sun Microsystems 为其 Solaris 操作系统创建了 PAM;通过 JAAS,现在可以以独立于平台的形式使用 PAM
    PAM 的主要用途是允许应用程序开发人员在开发时写入标准认证接口,并将使用哪些认证技术(以及如何使用它们)的决策留给系统管理员。认证技术是在登录模块中实现的,这些登录模块是在编写了应用程序之后部署的,并且在称为登录配置文件(本教程中名为login.config)的文本文件中指定。login.config 文件不仅可以指定要调用哪些模块,而且还可以指定总体认证成功的条件。
    PAM 使新的认证技术或技巧能更方便地添加到现有应用程序中。同样,可以通过更新 login.config 文件来更改认证策略,而不是重写整个应用程序。
    JDK 1.4 是与下列 PAM 模块一起提供的。稍后,我们将在本教程中使用其中一个模块,并还要练习编写我们自己的两个模块:
    • com.sun.security.auth.module.NTLoginModule
    • com.sun.security.auth.module.NTSystem
    • com.sun.security.auth.module.JndiLoginModule
    • com.sun.security.auth.module.KeyStoreLoginModule
    • com.sun.security.auth.module.Krb5LoginModule
    • com.sun.security.auth.module.SolarisSystem
    • com.sun.security.auth.module.UnixLoginModule
    • com.sun.security.auth.module.UnixSystem
    JAAS 示例和图
    在本教程中,我们将逐一研究 JAAS 示例应用程序的代码。为了对总体情况有所了解,下图显示了所有这些代码是如何组合在一起的。正在运行的示例(主程序JAASExample)先使用两种技术(即两个登录模块)来认证用户,然后根据认证步骤的结果允许或禁止(或授权)访问两段敏感代码。
    下面是 JAASExample 程序的图。下一页将描述操作流。
    JAASExample 操作流
    下面是由 JAASExample 图说明的总体认证与授权流的简要描述。以下每个步骤将在本教程的其它地方进行更为详细的描述。
    我们从认证的第一步开始,就是要创建登录环境并试图登录。LoginContext是一个 Java 类,它使用login.config 文件中的信息来决定要调用哪些登录模块以及将使用什么标准来确定是否成功。对本示例,有两个登录模块。第一个登录模块是AlwaysLoginModule,它不需要密码,所以它总是成功的(这是不切实际的,但它足以说明 JAAS 是如何工作的)。该模块用关键字required标记,表示它是成功所必需的(它总是成功)。第二个登录模块是PasswordLoginModule,它需要密码,但该模块的成功与否是可选的,因为它用关键字optional标记。这表示即使PasswordLoginModule失败,但总体登录仍可成功。
    初始化之后,选择的登录模块经历由LoginContext控制的两阶段提交过程。作为该过程的一部分,调用UsernamePasswordCallbackHandler以获取个人(用Subject对象表示)的用户名和密码。如果认证成功,则Principal被添加到Subject中。Subject可以有许多Principal(在该示例中,是“Brad”和“joeuser”),每个Principal都授予用户对系统的不同级别的访问权。这样就完成了认证步骤。
    一旦认证完成,通过使用程序认证技术和doAs方法,用Subject来尝试执行一些敏感的工资单操作代码。JAAS 检查是否授予Subject访问权。如果Subject有一个授权访问工资单代码的Principal,那么允许继续执行。否则,将拒绝执行。
    接下来,我们尝试使用声明性授权技术和doAsPrivilaged方法来执行一些敏感的职员信息操作代码。这次,JAAS 部署用户定义的特权(PersonnelPermission)、Java 策略文件(jaas.policy)和 Java 访问控制器(AccessController)用来决定是否可以继续执行。
    JAAS中的认证
    本章中,我们将集中讨论 JAAS 中的认证元素。我们将从描述简单的登录和认证过程开始,它将为您提供JAAS 认证体系结构的高级别视图。接着,我们将详细讨论体系结构的每一部分。本章结束时,您将有机会仔细地研究两个登录模块的代码。
    Subject Principal
    Subject是一种 Java 对象,它表示单个实体,如个人。一个Subject可以有许多个相关身份,每个身份都由一个Principal对象表示。那么,比方说一个Subject表示要求访问电子邮件系统和财务系统的雇员。该Subject将有两个Principal,一个与用于电子邮件访问的雇员的用户标识关联,另一个与用于财务系统访问的用户标识关联。
    Principal不是持久性的,所以每次用户登录时都必须将它们添加到SubjectPrincipal作为成功认证过程的一部分被添加到Subject。同样,如果认证失败,则从Subject中除去Principal。不管认证成功与否,当应用程序执行注销时,将除去所有Principal
    除了包含一组Principal外,Subject还可以包含两组凭证:公用和专用。credential是密码、密钥和令牌等。对公用和专用凭证集的访问是由 Java 特权控制的,稍后,我们将在本教程中讨论它。对凭证的完整讨论超出了本教程的范围。
    Subject 的方法
    Subject对象有几个方法,其中一些方法如下:
    • subject.getPrincipals() 返回一组 Principal 对象。因为结果是 Set,所以适用操作 remove()add()contains()
    • subject.getPublicCredentials() 返回一组与 Subject 相关的公用可访问凭证。
    • subject.getPrivateCredentials() 返回一组与 Subject 相关的专用可访问凭证。
    Principal 接口
    Principal是一个 Java 接口。程序员编写的PrincipalImpl对象与Serializable接口、名称字符串、返回该字符串的getName()方法以及其它支持方法(如hashCode()toString()equals())一起实现Principal接口。
    在登录过程期间,Principal被添加到Subject。正如我们稍后将看到的那样,声明性授权基于策略文件中的项。进行授权请求时,将系统的授权策略与包含在Subject中的Principal进行比较。如果Subject有一个满足策略文件中安全性需求的Principal,则授权;否则拒绝。
    PrincipalImpl
    这里是我们将在本教程中使用的PrincipalImpl
    import java.io.Serializable;
    import java.security.Principal;
    //
    // This class defines the principle object, which is just an encapsulated
    // String name
    public class PrincipalImpl implements Principal, Serializable {
                  private static final long serialVersionUID = 1L;
                  private String name;
                  public PrincipalImpl(String n) {
                                name = n;
                  }
                  public boolean equals(Object obj) {
                                if (!(obj instanceof PrincipalImpl)) {
                                              return false;
                                }
                                PrincipalImpl pobj = (PrincipalImpl) obj;
                                if (name.equals(pobj.getName())) {
                                              return true;
                                }
                                return false;
                  }
                  public String getName() {
                                return name;
                  }
                  public int hashCode() {
                                return name.hashCode();
                  }
                  public String toString() {
                                return getName();
                  }
    }
    登录配置
    JAAS 允许在以下几个方面有极大的灵活性:Subject需要的认证过程种类、它们的执行顺序以及在Subject被认为是已认证的之前要求的认证成功或失败的组合。
    JAAS 使用 login.config 文件来指定每个登录模块的认证项。login.config文件是在 Java 执行命令行上用特性-Djava.security.auth.login.config==login.config指定的。Java 有缺省登录配置文件,所以双等于号(==)替换系统登录配置文件。如果使用一个等于号,login.config 文件将被添加到(而不是替换)系统登录配置文件。因为我们不知道您的系统文件中可能会有什么,所以我们这样做来确保对于各种各样的教程用户都可以得到可靠的结果。
    login.config 文件包含LoginContext构造器中引用的文本字符串和登录过程列表。几个参数用于指定一个给定的登录过程的成功或失败对总体认证过程的影响。有如下参数:
    • required 表示登录模块必须成功。即使它不成功,还将调用其它登录模块。
    • optional 表示登录模块可以失败,但如果另一个登录模块成功,总体登录仍可以成功。如果所有登录模块都是可选的,那么要使整个认证成功至少必须有一个模块是成功的。
    • requisite 表示登录模块必须成功,而且如果它失败,将不调用其它登录模块。
    • sufficient 表示如果登录模块成功,则总体登录将成功,同时假设没有其它必需或必不可少的登录模块失败。
    示例 login.config 文件
    我们将在本教程中使用的 login.config 文件如下:
     
    JAASExample {
          AlwaysLoginModule required;
          PasswordLoginModule optional;
    };
     
    正如您看到的那样,AlwaysLoginModule必须成功,而PasswordLoginModule可以成功也可以失败。这不是一种现实的情形,稍后我们将修改这些参数来查看不同的配置如何更改代码行为。
    对于这项登录配置技术,应该认识到它将所有主要决定(如所需的认证类型和认证成功或失败的特定标准)都留到建立部署时决定,这很重要。成功的登录将导致新的Subject添加到LoginContext,同时将所有成功认证的Principal添加到该Subject
    登录环境
    LoginContext是一种用于设置登录过程的 Java 类,它进行实际的登录,如果登录成功,获取Subject。它有如下四种主要方法:
    • LoginContext("JAASExample", newUsernamePasswoerdCallbackHandler()) 是构造器。它把 login.config 文件中使用的字符串作为其第一个参数,把执行实际任务的回调处理程序作为其第二个参数。(接下来,我们将讨论回调处理程序。)
    • login(),它根据 login.config 文件中指定的规则实际尝试登录。
    • getSubject(),如果登录总体成功,它返回经认证的 Subject
    • logout(),它向 LoginContext 注销 Subject
    回调处理程序
    登录使用回调处理程序来获取用户的认证信息。CallbackHandler是在LoginContext对象的构造函数中指定的。在本教程中,回调处理程序使用几个提示来获取用户的用户名和密码信息。从登录模块调用的处理程序的handle()方法将Callback数组对象作为其参数。在登录期间,处理程序遍历Callback数组。handle()方法检查Callback对象的类型并执行适当的用户操作。Callback类型如下:
    • NameCallback
    • PasswordCallback
    • TextInputCallback
    • TextOutputCallback
    • LanguageCallback
    • ChoiceCallback
    • ConfirmationCallback
    在某些应用程序中,因为 JAAS 将用于与操作系统的认证机制相互操作,所以不需要任何用户交互。在这种情况下,LoginContext对象中的CallbackHandler参数将是空的。
    回调处理程序代码
    下面是本教程中使用的UsernamePasswordCallbackHandler的代码。它由AlwaysLoginModule调用一次(仅一次回调以获取用户标识),由PasswordLoginModule调用一次(两次回调以获取用户标识和密码)。
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import javax.security.auth.callback.Callback;
    import javax.security.auth.callback.CallbackHandler;
    import javax.security.auth.callback.NameCallback;
    import javax.security.auth.callback.PasswordCallback;
    import javax.security.auth.callback.UnsupportedCallbackException;
    // This class implements a username/password callback handler that gets
    // information from the user public class
    public class UsernamePasswordCallbackHandler implements CallbackHandler {
                  // The handle method does all the work and iterates through the array
                  // of callbacks, examines the type, and takes the appropriate user
                  // interaction action.
                  public void handle(Callback[] callbacks)
                                              throws UnsupportedCallbackException, IOException {
                                for (int i = 0; i < callbacks.length; i++) {
                                              Callback cb = callbacks[i];
                                              //
                                              // Handle username aquisition
                                              if (cb instanceof NameCallback) {
                                                            NameCallback nameCallback = (NameCallback) cb;
                                                            System.out.print(nameCallback.getPrompt() + "? ");
                                                            System.out.flush();
                                                            String username = new BufferedReader(new InputStreamReader(
                                                                                        System.in)).readLine();
                                                            nameCallback.setName(username);
                                                            // Handle password aquisition
                                              } else if (cb instanceof PasswordCallback) {
                                                            PasswordCallback passwordCallback = (PasswordCallback) cb;
                                                            System.out.print(passwordCallback.getPrompt() + "? ");
                                                            System.out.flush();
                                                            String password = new BufferedReader(new InputStreamReader(
                                                                                        System.in)).readLine();
                                                            passwordCallback.setPassword(passwo