48_3  使用JAAS用户认证

    48_3_1  JAAS简介

    Java安全框架最初集中在保护用户运行潜在的不可信任代码,是基于代码的来源(URL)和谁创建的代码(certificate)来给移动代码进行授权。Java 2 SDK 1.3引入了JAAS( Java Authentication and Authorization Service),增加了基于用户的访问控制能力,即根据谁在运行代码来进行授权。JAAS已经整合进了Java 2 SDK 1.4,作为标准的用户认证与授权模型。

    JAAS用户认证框架

    JAAS认证被实现为可插入的方式,允许应用程序同底层的具体认证技术保持独立,新增或者更新认证方法并不需要更改应用程序本身。应用程序通过实例化LoginContext 对象开始认证过程,引用配置文件中的具体认证方法,即LoginModule对象,来执行认证。

    JAAS:可插入式认证

    一旦执行代码的用户通过了认证,JAAS授权组件将和核心Java访问控制模型一起工作,来保护对敏感资源的访问。从J2SDK 1.4开始,访问控制不仅基于代码的来源和签名者(CodeSource),而且还要检查谁在运行代码。执行代码的用户被表现为Subject 对象,如果LoginModule认证成功,Subject对象被更新为相应的Principals和credentials。

    48_3_1_1  一个简单的例子

    本节通过一个简单的例子介绍JAAS开发的基本步骤。本节中的范例位于Apusic应用服务器安装目录中的docs/samples/jaas/simple目录。有关范例的内容、编译、部署与运行,可参考docs/samples/jaas/simple目录下的readme.txt文件。

    范例程序的代码分为两部分,一部分为主程序,执行用户认证过程,源程序如下:

    package samples;

    import javax.security.auth.Subject;
    import javax.security.auth.login.LoginContext;
    import javax.security.auth.login.LoginException;
    import com.sun.security.auth.callback.TextCallbackHandler;

    public class CountFiles {

        static LoginContext lc = null;
        public static void main(String[] args) {
            //使用配置文件中名字为“CountFiles”的条目
            try {
               lc = new LoginContext("CountFiles",
                                     new TextCallbackHandler());
            } catch (LoginException le) {
                le.printStackTrace();
                System.exit(-1);
            }

            try {
                lc.login();
                //如果没有异常抛出,则表示认证成功
            } catch (Exception e) {
                System.out.println("Login failed: " + e);
                System.exit(-1);
            }

            //以认证用户的身份执行代码
            Object o = Subject.doAs(lc.getSubject(), new CountFilesAction());
            System.out.println("User " + lc.getSubject( ) + " found " + o + " files.");
            System.exit(0);
        }
    }

    可以看出,主程序包含了三个重要的步骤:首先构造一个LoginContext对象,然后使用这个对象进行登录,最后,把用户作为doAs方法一个参数。

    另一部分表示用户想要执行的具体操作,源程序如下:

    package samples;

    import java.io.File;
    import java.security.PrivilegedAction;

    class CountFilesAction implements PrivilegedAction {
        public Object run() {
            File f = new File(".");
            File[] files = f.listFiles();
            return new Integer(files.length);
        }
    }

    48_3_1_2  JAAS核心类和接口

    JAAS相关的核心类和接口分为三类,公共、认证和授权。

    • 公共类:Subject,,Principal,Credential
    • 认证类和接口:LoginContext,LoginModule,CallbackHandler,Callback
    • 授权类 :Policy,AuthPermission,PrivateCredentialPermission

    详细的描述请参考《JAAS Reference Guide》。

    48_3_1_3  配置LoginModules

    JAAS认证被实现为一种可插入的方式,系统管理员可以通过配置文件为每一个应用程序配置LoginModuls来决定应用程序使用的认证技术。配置信息可以保存在文件或数据库中,通过javax.security.auth.login.Configuration 对象进行读取。javax.security.auth.login.Configuration为抽象类,JDK提供了可实例化的子类com.sun.security.auth.login.ConfigFile ,从文件中读取配置信息。配置文件中包含一个或多个条目,每一个条目指明了特定应用程序使用的认证方法。条目的结构如下:

    <name used by application to refer to this entry> {
        <LoginModule> <flag> <LoginModule options>;
        <optional additional LoginModules, flags and options>;
        };

    可以看出,每一个条目由名字和一个或多个LoginModule组成。范例程序使用的配置文件login.conf内容如下:

    CountFiles {
        com.apusic.security.auth.login.ClientPasswordLoginModule required;
    };

    详细的描述信息可以参考Configuration。

    48_3_1_4  编写Policy文件

    JAAS授权扩展了现有的Java安全体系结构,在给代码授权时可以包括一个多个Principal域,指出Principal代表的用户执行特定的代码时,具有分配的权限。因此,授权声明的基本形式为:

    grant <signer(s) field>, <codeBase URL>
      <Principal field(s)> {
        permission perm_class_name "target_name", "action";
        ....
        permission perm_class_name "target_name", "action";
      };

    缺省的策略文件实现和策略文件语法请参考《Default Policy Implementation and Policy File Syntax》。范例程序使用的策略文件policy.jaas内容如下:

    grant codeBase " file:./build" {
      permission java.security.AllPermission;
    };

    grant codeBase "file:/${apusic.home}/lib/apusic.jar" {
      permission java.security.AllPermission;
    };

    grant codeBase "file:./build/actions" Principal com.apusic.security.PrincipalImpl "admin" {
      permission java.io.FilePermission "<<ALL FILES>>", "read";
    };

    可以看出,给主程序和apusic.jar授予了所有权限;当执行具体操作的用户为“admin”时,授予了读取所有文件的权限。

    48_3_1_5  运行范例程序

    范例程序提供了ant的build.xml脚本,请用户自己下载并安装ant。运行范例程序的步骤为:

    1. 首先启动Apusic应用服务器, 范例程序将登录服务器。

    2. 编译、运行程序。在simple目录下执行ant命令,会编译源程序CountFiles.java到build目录下,编译源程序CountFilesAction.java到build/actions目录下。然后会自动运行程序,相当于在命令行敲入下面的java命令:

    java -classpath %APUSIC_HOME%/lib/apusic.jar;./build;./build/actions
         -Djava.security.manager
         -Djava.security.policy==policy.jaas
         -Djava.security.auth.login.config==login.conf
         -Dapusic.home=%APUSIC_HOME% samples.CountFiles

    根据提示输入服务器,用户名和口令。 如果用“admin”登录,程序将正常运行结束,若使用其他用户名登录,将抛出访问控制异常。

    48_3_2  JAAS和Apusic

    Apusic应用服务器提供了使用JAAS接口进行用户认证的方法,而访问控制授权则由Apusic自己的安全管理器进行管理。这样Apusic提供了两种用户认证方式,即传统的JNDI认证和新型的JAAS认证,对于Web应用则提供标准的HTTP认证。

    Apusic提供的LoginModule有:

    • com.apusic.security.auth.login.ClientPasswordLoginModule:标准的用户口令认证。
    • com.apusic.security.auth.login.ClientKrb5LoginModule:Kerberos支持的用户认证。
    • com.apusic.security.auth.login.ServerLoginModule:服务器端LoginModule,把当前线程关联的安全上下文中的principal和credentials直接存放在Subject中。

    一般的,开发者使用ClientPasswordLoginModule和ClientKrb5LoginModule从客户端登录服务器。使用JAAS可以同时使用多个身份登录服务器,也可以同时登录到多个服务器,然后通过Subject.doAs方法以不同的身份调用需要授权的服务器操作。当使用JNDI认证时也可以同时等录到多个服务器,但每个服务器上只能有一个活动用户身份。

     

    注意

     

    JAAS认证与JNDI认证不能同时使用,当使用JAAS认证之后,在Subject.doAs块中创建InitialContext时不能指定Context.PROVIDER_URL, Context.SECURITY_PRINCIPAL, Context.SECURITY_CREDENTIALS属性,即使使用这些属性创建了InitialContext也仍然使用JAAS认证的用户身份进行调用。

    Apusic还提供了CallbackHandler,开发者可以根据需要进行选择:

    • com.apusic.security.auth.callback.TextCallbackHandler:命令行方式的登录提示
    • com.apusic.security.auth.callback.DialogCallbackHandler:Swing对话框窗口方式的登录提示
    • com.apusic.security.auth.callback.DefaultCallbackHandler:在程序中通过构造函数传入登录信息

    当应用运行在客户端容器中时,JAAS认证由客户端容器自动完成,开发者可以在application-client.xml中指定callback-handler。如果未指定callback-handler,容器会根据客户端的环境选择TextCallbackHandler或DialogCallbackHandler。

    48_3_3  Apusic JAAS开发

    一般,开发者使用JAAS认证从客户端登录到服务器。在应用客户端中使用JAAS认证,需要把执行的操作(例如调用服务器上部署的EJB)包装在PrivilegedAction中。由于Apusic只把JAAS作为认证用户使用,所以只需要配置LoginModules。策略文件(Policy )作为访问控制授权语义,在这里不需要使用。本节通过一个范例程序的开发,介绍使用Apusic JAAS的基本步骤。

    本节中的范例位于Apusic应用服务器安装目录中的docs/samples/jaas/client目录。有关范例的内容、编译、部署与运行,可参考docs/samples/jaas/client目录下的readme.txt文件。

    1. 开发客户端主程序,包含JAAS认证过程:

    package samples;

    import javax.security.auth.Subject;
    import javax.security.auth.login.LoginContext;
    import javax.security.auth.login.LoginException;
    import com.apusic.security.auth.callback.DialogCallbackHandler;

    public class Client {

        static LoginContext lc = null;
        public static void main(String[] args) {
            //使用配置文件中名字为“client”的条目
            try {
                lc = new LoginContext("client",new DialogCallbackHandler());
            } catch (LoginException le) {
                le.printStackTrace();
                System.exit(-1);
            }
            try {
                lc.login();
                //如果没有异常抛出,则表示认证成功
            } catch (Exception e) {
                System.out.println("Login failed: " + e);
                System.exit(-1);
            }
            String name;
            if ( args != null && args.length >0 )
            {
                name = args[0];
            }else{
                name = "apusic";
            }
            //以认证用户的身份执行代码
            Object o = Subject.doAs(lc.getSubject(), new SampleAction(name) );
            System.out.println(o);     
        }
    }

    2. 客户执行的操作包装在PrivilegedAction子类的run方法中。在这个例子中客户端将调用EJB。

    package samples;

    import java.security.PrivilegedAction;
    import javax.naming.Context;
    import javax.naming.InitialContext;
    import javax.rmi.PortableRemoteObject;

    import samples.ejb.Hello;
    import samples.ejb.HelloHome;

    public class SampleAction implements PrivilegedAction {
        private String name;
        public SampleAction(String name){
            this.name = name;
        }
        public Object run() {
            Object obj = null;
            try{
                Context ic = new InitialContext();
                Object objref = ic.lookup("ejb/HelloJAAS");
                HelloHome home = (HelloHome) PortableRemoteObject.narrow(objref, HelloHome.class);
                Hello hello = home.create();
                obj = hello.sayHello(name) ;
            }catch(Exception e){
                e.printStackTrace();
            }
            return obj;
        }
    }

    3. 开发EJB模块。本例中的EJB只实现了一个商业方法sayHello,

    public java.lang.String sayHello(java.lang.String name) {
        return "hello, " + name;
    }

    在部署文件ejb-jar.xml中定义了两个角色:employee和manager,并且定义了方法访问许可,即只允许角色manager访问商业方法sayHello:

    ...
    <security-role>
        <role-name>employee</role-name>
    </security-role>
    <security-role>
        <role-name>manager</role-name>
    </security-role>
    <method-permission>
        <role-name>manager</role-name>
        <method>
            <ejb-name>Hello</ejb-name>
            <method-intf>Remote</method-intf>
            <method-name>sayHello</method-name>
            <method-params>
                <method-param>java.lang.String</method-param>
             </method-params>
          </method>
    </method-permission>
    ...

    4. 编译、打包和部署EJB模块。具体过程可以参考 第 49.3 节 “打包和部署EJB模块”。在apusic的部署描述文件apusic-application.xml中将ejb-jar.xml中定义的角色映射为服务器用户:

    <security-role>
        <role-name>manager</role-name>
        <principal>admin</principal>
    </security-role>
    <security-role>
        <role-name>employee</role-name>
        <principal>j2ee</principal>
    </security-role>

    最后使用ejbgen工具生成客户端运行需要的类库appclient.jar。

    5. 配置LoginModules。客户端使用JAAS认证需要读取配置文件来配置LoginModules,本范例的配置文件login.conf内容为:

    client {
        com.apusic.security.auth.login.ClientPasswordLoginModule required;
    };

    在运行客户端时通过-Djava.security.auth.login.config==login.conf将配置文件传入客户端主程序。如果Apusic配置了Kerberos支持,可以使用ClientKrb5LoginModule

    client {
        com.apusic.security.auth.login.ClientKrb5LoginModule  required url="http://localhost:6888";
    };

     

    注意

     

    如果程序运行在客户端容器中,配置文件中LoginModule的名字必须为“client”。客户端容器会自动使用名字为“client”的LoginModule进行JAAS认证。

    如果使用apclient启动客户端容器,可以指定url参数,格式为:iiop://user:password@host:port,则不会弹出登录窗口。apclient使用$APUSIC_HOME/config/ clientauth.cfg作为登录配置文件。

    6. 运行客户端。范例程序提供了ant脚本build.xml,可以自动编译、打包、部署EJB模块和运行客户端程序。运行客户端等价的命令行命令为:

    java -classpath %apusic_home%/lib/apusic.jar;./build/client;appclient.jar
         -Djava.security.auth.login.config==login.conf
         samples.Client apusic

    当客户端运行时,会弹出windows对话框,提示输入服务器、用户名和口令。如果使用“admin”登录,客户端将正常调用EJB的sayHello方法。如果使用其他用户登录,客户端将抛出访问控制异常:java.rmi.AccessException: 用户 "xxx" 没有权限访问EJB的业务方法。