LoginModule
它是认证服务器端的实现,用于验证客户端的信息,实现者实现 javax.security.auth.spi.LoginModule 接口的 login、commit、abort、logout 方法来完成用户的登录和登出操作,示例代码:
package jaas;
import javax.security.auth.Subject;
import javax.security.auth.callback.*;
import javax.security.auth.login.FailedLoginException;
import javax.security.auth.login.LoginException;
import javax.security.auth.spi.LoginModule;
import java.security.Principal;
import java.util.Map;
public class MyLoginModule implements LoginModule {
// username and password
private String username;
private char[] password;
// the authentication status
private boolean userPwdSucceeded = false;
private boolean commitSucceeded = false;
// user's Principal
private Principal userPrincipal;
// initial state
private Subject subject;
private CallbackHandler callbackHandler;
/**
* Initialize this <code>LoginModule</code>.
*/
public void initialize(Subject subject,
CallbackHandler callbackHandler,
Map<java.lang.String, ?> sharedState,
Map<java.lang.String, ?> options) {
this.subject = subject;
this.callbackHandler = callbackHandler;
}
/**
* Authenticate the user by prompting for a user name and password.
*/
public boolean login() throws LoginException {
// prompt for a user name and password
if (callbackHandler == null)
throw new LoginException("Error: no CallbackHandler available " +
"to garner authentication information from the user");
Callback[] callbacks = new Callback[4];
callbacks[0] = new NameCallback("user name");
callbacks[1] = new PasswordCallback("password", false);
callbacks[2] = new TextOutputCallback(TextOutputCallback.INFORMATION, "hello, just a msg!");
callbacks[3] = new TextOutputCallback(TextOutputCallback.WARNING, "just warn you!");
try {
callbackHandler.handle(callbacks);
NameCallback nameCallback = (NameCallback) callbacks[0];
PasswordCallback passwordCallback = (PasswordCallback) callbacks[1];
username = nameCallback.getName();
char[] tmpPassword = passwordCallback.getPassword();
passwordCallback.clearPassword();// clean password in memory space
if (tmpPassword == null) {
tmpPassword = new char[0];// treat a NULL password as an empty password
}
password = new char[tmpPassword.length];
System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length);
} catch (Exception e) {
e.printStackTrace();
}
// verify the username/password
boolean usernameCorrect = false;
if (username.equals("user")) usernameCorrect = true;
if (usernameCorrect &&
password.length == 3 &&
password[0] == 'p' &&
password[1] == 'w' &&
password[2] == 'd') {
userPwdSucceeded = true;
} else {
userPwdSucceeded = false;
cleanUserAndPwdData();
if (!usernameCorrect) {
throw new FailedLoginException("User Name Incorrect");
} else {
throw new FailedLoginException("Password Incorrect");
}
}
return userPwdSucceeded;
}
public boolean commit() throws LoginException {
if (!userPwdSucceeded) return false;
// add a Principal (authenticated identity) to the Subject
userPrincipal = new SamplePrincipal(username);
subject.getPrincipals().add(userPrincipal);
// in any case, clean out state
cleanUserAndPwdData();
return commitSucceeded = true;
}
public boolean abort() throws LoginException {
if (!userPwdSucceeded) return false;
if (commitSucceeded) {
logout();
} else {
cleanState();
}
return true;
}
public boolean logout() throws LoginException {
subject.getPrincipals().remove(userPrincipal);
cleanState();
userPwdSucceeded = commitSucceeded;
return true;
}
private void cleanState() {
userPwdSucceeded = false;
cleanUserAndPwdData();
userPrincipal = null;
}
private void cleanUserAndPwdData() {
username = null;
if (password != null) {
for (int i = 0; i < password.length; i++)
password[i] = ' ';
password = null;
}
}
}
test_jaas.config 登录配置文件
/** 登录配置 **/
Sample {
jaas.MyLoginModule required debug=true;
};
Principal 用户身份信息
示例代码:
package jaas;
import java.security.Principal;
public class SamplePrincipal implements Principal {
private String name;
public SamplePrincipal(String name) {
this.name = name;
}
public String getName() {
return name;
}
public boolean equals(Object o) {
if (o == null)
return false;
if (this == o)
return true;
if (!(o instanceof SamplePrincipal))
return false;
SamplePrincipal that = (SamplePrincipal) o;
if (this.getName().equals(that.getName()))
return true;
return false;
}
public int hashCode() {
return name.hashCode();
}
}
CallbackHandler 用户交互回调接口
用户代码通过该回调可传入用户名、密码 或其它信息 供 验证服务器使用,java 预置了多种回调实现:
* @see javax.security.auth.callback.ChoiceCallback
* @see javax.security.auth.callback.ConfirmationCallback
* @see javax.security.auth.callback.LanguageCallback
* @see javax.security.auth.callback.NameCallback
* @see javax.security.auth.callback.PasswordCallback
* @see javax.security.auth.callback.TextInputCallback
* @see javax.security.auth.callback.TextOutputCallback
示例代码:
package jaas;
import javax.security.auth.callback.*;
import java.io.IOException;
public class MyCallbackHandler implements CallbackHandler {
public void handle(Callback[] callbacks) throws IOException, UnsupportedCallbackException {
for (int i = 0; i < callbacks.length; i++) {
if (callbacks[i] instanceof TextOutputCallback) {// display the message according to the specified type
TextOutputCallback toc = (TextOutputCallback) callbacks[i];
switch (toc.getMessageType()) {
case TextOutputCallback.INFORMATION:
System.out.println(toc.getMessage());
break;
case TextOutputCallback.ERROR:
System.err.println("ERROR: " + toc.getMessage());
break;
case TextOutputCallback.WARNING:
System.err.println("WARNING: " + toc.getMessage());
break;
default:
throw new IOException("Unsupported message type: " + toc.getMessageType());
}
} else if (callbacks[i] instanceof NameCallback) {// prompt the user for a username
NameCallback nc = (NameCallback) callbacks[i];
String name = "user";// TODO 这里可以实现为从控制台允许用户输入等方式接收用户参数。。。
nc.setName(name);
} else if (callbacks[i] instanceof PasswordCallback) {// prompt the user for sensitive information
PasswordCallback pc = (PasswordCallback) callbacks[i];
String pwd = "pwd";// TODO 这里可以实现为从控制台允许用户输入等方式接收用户参数。。。
pc.setPassword(pwd.toCharArray());
} else {
throw new UnsupportedCallbackException
(callbacks[i], "Unrecognized Callback");
}
}
}
}
测试
当登录模块和用户回调实现好以后,就可以进行测试,示例代码:
package jaas;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;
public class Main {
public static void main(String[] args) throws LoginException {
// 配置文件中查找 Sample 名字的 LoginModule,并指定 CallbackHandler
LoginContext lc = new LoginContext("Sample", new MyCallbackHandler());
try {
lc.login();
Subject subject = lc.getSubject();
// System.out.println(subject);
System.out.println("Authentication succeeded!");
} catch (LoginException le) {
System.err.println("Authentication failed:" + le.getMessage());
}
}
}
注意,上述的 new LoginContext("Sample", new MyCallbackHandler()); 是将 登录模块和用户回调关联起来,Sample为登录配置文件中指定的名字,java的jaas服务会通过 -D参数读取并初始化该登录模块,回调接口为用户自定义的交互对象,运行参数如下:
-Djava.security.auth.login.config=/xx/test/test_jaas.config
运行结果
由于示例代码中在 MyCallbackHandler 中硬编码指定了正确的用户名和密码,所以可以看到正确的输出:“Authentication succeeded!”
MyCallbackHandler 为自定义的交互类,可以实现为通过 web 请求中获取用户名和密码,或者从控制台 System.in 接收输入的用户名和密码等。