1. JaaS代码
在前面已经说明了JAAS的使用,在这一节,主要是解析JAAS在认证时的代码流程,我们可以看到其认证过程只有两行代码:
LoginContext lc = new LoginContext("JaaSSampleTest", new TextCallbackHandler());
lc.login();
我们可以把它分为二个部分:上下文设置, 登录
1.1 上下文的设置
LoginContext lc = new LoginContext("JaaSSampleTest", new TextCallbackHandler());
程序首先会通过jaas.conf获取JaaSSampleTest所对应的类:
JaaSSampleTest {
com.sun.security.auth.module.Krb5LoginModule required;
};
从jaas.conf配置文件,可以知道所应的类为Krb5LoginModule ,查看Krb5LoginModule 的类:
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<String, ?> sharedState,
Map<String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
... ...
即Krb5LoginModule 初始化的时候,将TextCallbackHandler的句柄设进去。后面会调用其handle()方法
1.2 登录过程
当程序创建了LoginContext的时候,就会调用
lc.login()
通过LoginContext.login()的代码,知道,它的主要动作在:
invokePriv(LOGIN_METHOD);
invokePriv(COMMIT_METHOD);
我们查看首先会调用的Krb5LoginModule::login()
public boolean login() throws LoginException {
int len;
//首先校验参数信息
validateConfiguration();
... ...
if (tryFirstPass) {
try {
//此函数会根据需求获取对应的TGT信息。这其中有两种情况:
//1. 用户执行完kinit之后,会保存对应的TGT信息到对应的文件中。这个文件位置由krb5.conf中的default_ccache_name进行定义 。 程序会直接读取这个文件信息而获取TGT信息
//2. 第二种情况,如果用户不希望直接通过配置文件获取,就可以通过直接发送消息的方式获取对应的TGT信息。
//其相应的代码在:KrbAsReqBuilder::action()中
attemptAuthentication(true);
... ...
}
其实login()的主要动作就是执行attemptAuthentication()这个函数。而这个函数中的主要动作就是获取TGT信息。而在attemptAuthentication(),会回调到CallbackHandler::handle()函数,处理用户名与密码。
下面再看一下commit()的作用, 对于commit的作用,程序的注释中有如下一段话:
/* * Let us add the Krb5 Creds to the Subject's * private credentials. The credentials are of type * KerberosKey or KerberosTicket */
从它的代码中也可以看到,它将之前的login()中的cred设置到subject中。
1.3 小结
再次梳理一下整个jaas的认证过程,可以分为如下几个步骤:
初始化LoginContext的上下文,这其中就包含两部分:
- 从jaas.conf中读取实际对应的context Moudle,
- 将CallbackHandler设置到Module, 而这个CallbackHanlder::handler()的函数中最重要的就是
name与password的处理
调用login()方法, 而这个login()方法又可以分为三个步骤:
- 回调Moudle的login()方法
- 在Module方法中会回调CallbackHandler的handler方法
- 最后就是回调Moudle的commit方法
2 密码非交互登录
我们知道KDC认证有两种方法:keytab方式与密码方式。Krb5LoginModule 提供了keytab直接登录的方式,但是无法使用它完成密码登录。下面给出将用户名、密码非交互登录的方式
public class JaasTestPassword {
public static void main(String[] args) {
String username = args[0];
String passwd = args[1];
Krb5Configuration conf = new Krb5Configuration();
try {
LoginContext lc = new LoginContext("JaaSSampleTest",
new Subject(),
JaasTestPassword.createJaasCallbackHandler(username,
passwd), conf);
lc.login();
Subject sub = lc.getSubject();
} catch (LoginException le) {
System.err.println("Authentication failed:");
System.exit(-1);
}
System.out.println("Authentication succeeded!");
}
public static CallbackHandler createJaasCallbackHandler(
final String principal, final String password) {
return new CallbackHandler() {
public void handle(Callback[] callbacks) throws IOException,
UnsupportedCallbackException {
for (Callback callback : callbacks) {
if (callback instanceof NameCallback) {
NameCallback nameCallback = (NameCallback) callback;
nameCallback.setName(principal);
} else if (callback instanceof PasswordCallback) {
PasswordCallback passwordCallback = (PasswordCallback) callback;
passwordCallback.setPassword(password.toCharArray());
} else {
throw new UnsupportedCallbackException(callback,
"Unsupported callback: "
+ callback.getClass()
.getCanonicalName());
}
}
}
};
}
}
配置文件的代码:
public class Krb5Configuration extends Configuration {
private AppConfigurationEntry[] entry = new AppConfigurationEntry[1];
Map paramMap = new HashMap();
private AppConfigurationEntry krb5LoginModule = new AppConfigurationEntry(
"com.sun.security.auth.module.Krb5LoginModule",
LoginModuleControlFlag.REQUIRED, paramMap);
@Override
public AppConfigurationEntry[] getAppConfigurationEntry(String name) {
// TODO Auto-generated method stub
if (entry[0] == null) {
paramMap.put("debug", "false"); //是否打印debug日志
paramMap.put("storeKey", "true");
paramMap.put("doNotPrompt", "false"); //直接使用密码登录,设置为false
paramMap.put("useTicketCache", "false"); //是否使用ticket
entry[0] = krb5LoginModule;
}
return entry;
}
}
3. 小结
- 整个JaaS登录,我们可以把它分为三个部分:LogContext, Config配置信息、CallBackHandler部分。其中Config配置给定登录方式(keytab还是cache)等信息,而CallBackHandler则是通过handler()方法将KDC认证需要用到的信息(如princal信息,密码或keytab)设置到相应的配置中。从而完成认证
- 在整个login()是从KDC获取TGT,而commit()则将这个TGT真充到Subject中,在这里根本不涉及到客户端与服务端的交互。