配置文件
上面我已经提到,JAAS的可扩展性来源于它能够进行动态配置,而配置信息通常是保存在文本。这些文本文件有很多个配置块构成,我们通常把这些配置块称作申请(Application)。每个申请对应了一个或多个特定的LoginModule对象。
当你的代码构造一个LoginContext对象时,你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象,按照什么顺序激活以及使用什么规则激活。
配置文件的结构如下所示:
下面是一个名称为Sample的申请
上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。Flag控制当申请中包含了多个LoginModule时进行登录时的行为:Required、Sufficient、Requisite和Optional。最常用的是Required,使用它意味着对应的LoginModule对象必须被调用,并且必须需要通过所有的验证。由于Flag本身的复杂性,本文在这里不作深究。
ModuleOption允许有多个参数。例如你可以设定调试参数为True(debug=true),这样诊断输出将被送到System.out中。
配置文件可以被任意命名,并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest,配置文件是当前目录下的jaas.config,你需要在命令行中输入:
java -Djava.security.auth.login.config=jass.config JavaTest
图二描述了配置文件中各元素之间的关系
通过命令行方式进行登录验证
为了说明JAAS到底能干什么,我在这里编写了两个例子。一个是简单的由命令行输入调用的程序,另一个是服务器端的JSP程序。这两个程序都通过用户名/密码的方式进行登录,然后使用关系数据库对其进行验证。
为了通过数据库进行验证,我们需要:
1. 实现RdbmsLoginModul类,该类可以对输入的信息进行验证。
2. 编辑一个配置文件,告诉LoginContext如何使用RdbmsLoginModule。
3. 实现ConsoleCallbackHandler类,通过该类可以获取用户的输入。
4. 编写应用程序代码。
在RdbmsLoginModul类中,我们必须实现LgoinModule接口中的五个方法。首先是initialize()方法:
LoginContext在调用login()方法时会调用initialize()方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。
CallbackHandler对象将会在login()方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享,但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件ModuleOption域中定义的参数的值。配置文件如下所示:
在配置文件中,RdbmsLoginModule包含了五个参数,其中driver、url、user和password是必需的,而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize()方法中我们保存了每个参数的值。
我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码,在代码中创建了一个LoginContex对象并调用了login()方法。
当LgoinContext.login()方法被调用时,它调用所有加载了的LoginModule对象的login()方法。在我们的这个例子中是RdbmsLoginModule中的login()方法。
RdbmsLoginModule中的login()方法进行了下面的操作:
1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和PasswordCallback(这两个类包含在javax.security.auth.callback包中)。
2. 通过将callbacks作为参数传递给CallbackHandler的handle()方法来激活Callback。
3. 通过Callback对象获得用户名/密码。
4. 在rdbmsValidate()方法中通过JDBC在数据库中验证获取的用户名/密码。
下面是RdbmsLoginModule中的login()方法的代码
在ConsoleCallbackHandler类的handle()方法中你可以看到Callback对象是如何同用户进行交互的:
上面我已经提到,JAAS的可扩展性来源于它能够进行动态配置,而配置信息通常是保存在文本。这些文本文件有很多个配置块构成,我们通常把这些配置块称作申请(Application)。每个申请对应了一个或多个特定的LoginModule对象。
当你的代码构造一个LoginContext对象时,你需要把配置文件中申请的名称传递给它。LoginContext将会根据申请中的信息决定激活哪些LoginModule对象,按照什么顺序激活以及使用什么规则激活。
配置文件的结构如下所示:
Application { ModuleClass Flag ModuleOptions; ModuleClass Flag ModuleOptions; ... }; Application { ModuleClass Flag ModuleOptions; ... }; ... |
下面是一个名称为Sample的申请
Sample { com.sun.security.auth.module.NTLoginModule Rquired debug=true; } |
上面这个简单的申请指定了LoginContext对象应该使用NTLoginModule进行验证。类的名称在ModuleClass中被指定。Flag控制当申请中包含了多个LoginModule时进行登录时的行为:Required、Sufficient、Requisite和Optional。最常用的是Required,使用它意味着对应的LoginModule对象必须被调用,并且必须需要通过所有的验证。由于Flag本身的复杂性,本文在这里不作深究。
ModuleOption允许有多个参数。例如你可以设定调试参数为True(debug=true),这样诊断输出将被送到System.out中。
配置文件可以被任意命名,并且可以被放在任何位置。JAAS框架通过使用java.securty.auth.long.config属性来确定配置文件的位置。例如当你的应用程序是JaasTest,配置文件是当前目录下的jaas.config,你需要在命令行中输入:
java -Djava.security.auth.login.config=jass.config JavaTest
图二描述了配置文件中各元素之间的关系
图二 JAAS的配置文件 |
通过命令行方式进行登录验证
为了说明JAAS到底能干什么,我在这里编写了两个例子。一个是简单的由命令行输入调用的程序,另一个是服务器端的JSP程序。这两个程序都通过用户名/密码的方式进行登录,然后使用关系数据库对其进行验证。
为了通过数据库进行验证,我们需要:
1. 实现RdbmsLoginModul类,该类可以对输入的信息进行验证。
2. 编辑一个配置文件,告诉LoginContext如何使用RdbmsLoginModule。
3. 实现ConsoleCallbackHandler类,通过该类可以获取用户的输入。
4. 编写应用程序代码。
在RdbmsLoginModul类中,我们必须实现LgoinModule接口中的五个方法。首先是initialize()方法:
public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; url = (String)options.get("url"); driverClass = (String)options.get("driver"); debug = "true".equalsIgnoreCase((String)options.get("debug")); } |
LoginContext在调用login()方法时会调用initialize()方法。RdbmsLoginModule的第一个任务就是在类中保存输入参数的引用。在验证成功后将向Subject对象中送入Principal对象和凭证。
CallbackHandler对象将会在login()方法中被使用到。sharedState可以使数据在不同的LoginModule对象之间共享,但是在这个例子中我们不会使用它。最后是名为options的Map对象。options向LgoinModule对象传递在配置文件ModuleOption域中定义的参数的值。配置文件如下所示:
Example { RdbmsLoginModule required driver="org.gjt.mm.mysql.Driver" url="jdbc:mysql://localhost/jaasdb?user=root" debug="true"; }; |
在配置文件中,RdbmsLoginModule包含了五个参数,其中driver、url、user和password是必需的,而debug是可选阐述。driver、url、user和password参数告诉我们如何获得JDBC连接。当然你还可以在ModuleOption域中加入数据库中的表或列的信息。使用这些参数的目的是为了能够对数据库进行操作。在LoginModule类的initialize()方法中我们保存了每个参数的值。
我们前面提到一个LoginContext对应的配置文件告诉它应该使用文件中的哪一个申请。这个信息是通过LgoinContext的构造函数传递的。下面是初始化客户端的代码,在代码中创建了一个LoginContex对象并调用了login()方法。
ConsoleCallbackHandler cbh = new ConsoleCallbackHandler(); LoginContext lc = new LoginContext("Example", cbh); lc.login(); |
当LgoinContext.login()方法被调用时,它调用所有加载了的LoginModule对象的login()方法。在我们的这个例子中是RdbmsLoginModule中的login()方法。
RdbmsLoginModule中的login()方法进行了下面的操作:
1. 创建两个Callback对象。这些对象从用户输入中获取用户名/密码。程序中使用了JAAS中的两个Callback类:NameCallback和PasswordCallback(这两个类包含在javax.security.auth.callback包中)。
2. 通过将callbacks作为参数传递给CallbackHandler的handle()方法来激活Callback。
3. 通过Callback对象获得用户名/密码。
4. 在rdbmsValidate()方法中通过JDBC在数据库中验证获取的用户名/密码。
下面是RdbmsLoginModule中的login()方法的代码
public boolean login() throws LoginException { if (callbackHandler == null) throw new LoginException("no handler"); NameCallback nameCb = new NameCallback("user: "); PasswordCallback passCb = new PasswordCallback("password: ", true); callbacks = new Callback[] { nameCb, passCb }; callbackHandler.handle(callbacks); String username = nameCb.getName(); String password = new String(passCb.getPassword()); success = rdbmsValidate(username, password); return(true); } |
在ConsoleCallbackHandler类的handle()方法中你可以看到Callback对象是如何同用户进行交互的:
public void handle(Callback[] callbacks) throws java.io.IOException, UnsupportedCallbackException { for (int i = 0; i < callbacks.length; i++) { if (callbacks[i] instanceof NameCallback) { NameCallback nameCb = (NameCallback)callbacks[i]; System.out.print(nameCb.getPrompt()); String user=(new BufferedReader(new InputStreamReader(System.in))).readLine(); nameCb.setName(user); } else if (callbacks[i] instanceof PasswordCallback) { PasswordCallback passCb = (PasswordCallback)callbacks[i]; System.out.print(passCb.getPrompt()); String pass=(new BufferedReader(new InputStreamReader(System.in))).readLine(); passCb.setPassword(pass.toCharArray()); } else { throw(new UnsupportedCallbackException(callbacks[i], "Callback class not supported")); } } } |