经过几天研究,终于跑起来了第一个JAAS例子。把代码贴上来,免得以后忘了。
MyCallbackHandler.java(处理用户输入)
package com.pingan.jaas.tutorial; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PushbackInputStream; import java.util.Arrays; 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.TextOutputCallback; import javax.security.auth.callback.UnsupportedCallbackException; 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) { // 根据指定的类型显示信息 TextOutputCallback toc = (TextOutputCallback) callbacks[i]; switch (toc.getMessageType()) { case TextOutputCallback.INFORMATION: System.out.println(toc.getMessage()); break; case TextOutputCallback.ERROR: System.out.println("ERROR: " + toc.getMessage()); break; case TextOutputCallback.WARNING: System.out.println("WARNING: " + toc.getMessage()); break; default: throw new IOException("Unsupported message type: " + toc.getMessageType()); } } else if (callbacks[i] instanceof NameCallback) { // 如果命令行没有提供用户名,提示用户输入用户名 NameCallback nc = (NameCallback) callbacks[i]; System.err.print(nc.getPrompt()); System.err.flush(); nc .setName((new BufferedReader(new InputStreamReader( System.in))).readLine()); } else if (callbacks[i] instanceof PasswordCallback) { // prompt the user for a password PasswordCallback pc = (PasswordCallback) callbacks[i]; System.err.print(pc.getPrompt()); System.err.flush(); pc.setPassword(readPassword(System.in)); } else { throw new UnsupportedCallbackException(callbacks[i], "Unrecognized Callback"); } } } private char[] readPassword(InputStream in) throws IOException { char[] lineBuffer; char[] buf; buf = lineBuffer = new char[128]; int room = buf.length; int offset = 0; int c; loop: while (true) { switch (c = in.read()) { case -1: case '\n': break loop; case '\r': int c2 = in.read(); if ((c2 != '\n') && (c2 != -1)) { if (!(in instanceof PushbackInputStream)) { in = new PushbackInputStream(in); } ((PushbackInputStream) in).unread(c2); } else break loop; default: if (--room < 0) { buf = new char[offset + 128]; room = buf.length - offset - 1; System.arraycopy(lineBuffer, 0, buf, 0, offset); Arrays.fill(lineBuffer, ' '); lineBuffer = buf; } buf[offset++] = (char) c; break; } } if (offset == 0) { return null; } char[] ret = new char[offset]; System.arraycopy(buf, 0, ret, 0, offset); Arrays.fill(buf, ' '); return ret; } }
SampleAcn.java(客户端)
package com.pingan.jaas.tutorial; import javax.security.auth.login.LoginContext; import javax.security.auth.login.LoginException; public class SampleAcn { public static void main(String[] args) { LoginContext lc = null; try { lc = new LoginContext("test5", new MyCallbackHandler()); } catch (LoginException le) { System.err .println("Cannot create LoginContext. " + le.getMessage()); System.exit(-1); } catch (SecurityException se) { System.err .println("Cannot create LoginContext. " + se.getMessage()); System.exit(-1); } // 给用户3次登录机会 int retryTimes = 3; int i; for (i = 0; i < retryTimes; i++) { try { // attempt authentication lc.login(); // if we return with no exception, // authentication succeeded break; } catch (LoginException le) { System.err.println("Authentication failed:"); System.err.println(" " + le.getMessage()); try { Thread.sleep(3000); } catch (Exception e) { // ignore } } } // did they fail three times? if (i == retryTimes) { System.out.println("Sorry"); System.exit(-1); } System.out.println("Authentication succeeded!"); } }
SamplePrincipal.java(自定义Principal类)
package com.pingan.jaas.tutorial; import java.security.Principal; public class SamplePrincipal implements Principal, java.io.Serializable { private static final long serialVersionUID = -5974294088673749367L; private String name; public SamplePrincipal(String name) { if (name == null) throw new NullPointerException("illegal null input"); this.name = name; } public String getName() { return name; } public String toString() { return ("SamplePrincipal: " + 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(); } }
SampleLoginModule.java(自定义登录模块)
package com.pingan.jaas.tutorial; import java.util.Map; import javax.security.auth.Subject; 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; import javax.security.auth.login.FailedLoginException; import javax.security.auth.login.LoginException; import examples.util.LogHelper; public class SampleLoginModule { // initial state private Subject subject; private CallbackHandler callbackHandler; private Map sharedState; private Map options; // configurable option private boolean debug = false; // the authentication status private boolean succeeded = false; private boolean commitSucceeded = false; // username and password private String username; private char[] password; // testUser's SamplePrincipal private SamplePrincipal userPrincipal; public void initialize(Subject subject, CallbackHandler callbackHandler, Map sharedState, Map options) { this.subject = subject; this.callbackHandler = callbackHandler; this.sharedState = sharedState; this.options = options; // initialize any configured options debug = "true".equalsIgnoreCase((String) options.get("debug")); } 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[2]; callbacks[0] = new NameCallback("user name: "); callbacks[1] = new PasswordCallback("password: ", false); try { callbackHandler.handle(callbacks); username = ((NameCallback) callbacks[0]).getName(); char[] tmpPassword = ((PasswordCallback) callbacks[1]) .getPassword(); if (tmpPassword == null) { // treat a NULL password as an empty password tmpPassword = new char[0]; } password = new char[tmpPassword.length]; System.arraycopy(tmpPassword, 0, password, 0, tmpPassword.length); ((PasswordCallback) callbacks[1]).clearPassword(); } catch (java.io.IOException ioe) { throw new LoginException(ioe.toString()); } catch (UnsupportedCallbackException uce) { throw new LoginException("Error: " + uce.getCallback().toString() + " not available to garner authentication information " + "from the user"); } // print debugging information if (debug) { System.out.println("\t\t[SampleLoginModule] " + "user entered user name: " + username); System.out.print("\t\t[SampleLoginModule] " + "user entered password: "); for (int i = 0; i < password.length; i++) System.out.print(password[i]); System.out.println(); } // 校验用户名和密码(用户名为testUser,密码为testPassword,区分大小写) boolean usernameCorrect = false; boolean passwordCorrect = false; if (username.equals("testUser")) usernameCorrect = true; if (usernameCorrect && password.length == 12 && password[0] == 't' && password[1] == 'e' && password[2] == 's' && password[3] == 't' && password[4] == 'P' && password[5] == 'a' && password[6] == 's' && password[7] == 's' && password[8] == 'w' && password[9] == 'o' && password[10] == 'r' && password[11] == 'd') { // authentication succeeded!!! passwordCorrect = true; if (debug) System.out.println("\t\t[SampleLoginModule] " + "authentication succeeded"); succeeded = true; return true; } else { // authentication failed -- clean out state if (debug) System.out.println("\t\t[SampleLoginModule] " + "authentication failed"); succeeded = false; username = null; for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; if (!usernameCorrect) { throw new FailedLoginException("User Name Incorrect"); } else { throw new FailedLoginException("Password Incorrect"); } } } public boolean commit() throws LoginException { LogHelper.log2Console("succeeded=" + succeeded); if (succeeded == false) { return false; } else { // add a Principal (authenticated identity) // to the Subject // assume the user we authenticated is the SamplePrincipal userPrincipal = new SamplePrincipal(username); if (!subject.getPrincipals().contains(userPrincipal)) subject.getPrincipals().add(userPrincipal); if (debug) { System.out.println("\t\t[SampleLoginModule] " + "added SamplePrincipal to Subject"); } // in any case, clean out state username = null; for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; commitSucceeded = true; return true; } } public boolean abort() throws LoginException { LogHelper.log2Console("succeeded=" + succeeded); if (succeeded == false) { return false; } else if (succeeded == true && commitSucceeded == false) { // login succeeded but overall authentication failed succeeded = false; username = null; if (password != null) { for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; } userPrincipal = null; } else { // overall authentication succeeded and commit succeeded, // but someone else's commit failed logout(); } return true; } public boolean logout() throws LoginException { subject.getPrincipals().remove(userPrincipal); succeeded = false; succeeded = commitSucceeded; username = null; if (password != null) { for (int i = 0; i < password.length; i++) password[i] = ' '; password = null; } userPrincipal = null; return true; } }
该类没有实现javax.security.auth.spi.LoginModule接口,似乎也可以运行。为简单起见,代码里面用户名固定为testUser,密码固定为testPassword。
还有一个MyLoginModule类,跟SampleLoginModule类是一样的,只是修改了硬编码的用户名和密码,便于测试。
Sample_jaas.config(JAAS配置文件)
Sample { com.pingan.jaas.tutorial.SampleLoginModule required debug=true; }; Sample2 { com.pingan.jaas.tutorial.MyLoginModule required debug=true; }; all_required { com.pingan.jaas.tutorial.SampleLoginModule required debug=true; com.pingan.jaas.tutorial.MyLoginModule required debug=true; }; test3 { com.pingan.jaas.tutorial.SampleLoginModule required debug=true; com.pingan.jaas.tutorial.MyLoginModule Sufficient debug=true; }; test4 { com.pingan.jaas.tutorial.MyLoginModule Sufficient debug=true; com.pingan.jaas.tutorial.SampleLoginModule required debug=true; }; test5 { com.pingan.jaas.tutorial.MyLoginModule Optional debug=true; com.pingan.jaas.tutorial.SampleLoginModule required debug=true; };
为了测试不同情况,配置了多个组合,具体使用哪个组合,在上文中客户端代码指定。
编译后运行客户端,需要在命令行指定JAAS配置文件位置,参考命令行如下:
java -Djava.security.auth.login.config=com/pingan/jaas/tutorial/sample_jaas.config -cp ".;%CLASSPATH%;C:\bea\weblogic81\server\lib\weblogic.jar;C:\bea\weblogic81\server\lib\mbeantypes\wlManagement.jar;" com.pingan.jaas.tutorial.SampleAcn
运行结果:
C:\bea\user_projects\applications\Test3\Test3Web\WEB-INF\classes>java -Djava.security.auth.login.config=com/pingan/jaas/tutorial/sample_jaas.config -cp ".;%CLASSPATH%;C:\bea\weblogic81\server\lib\weblogic.jar;C:\bea\weblogic81\server\lib\mbeantypes\wlManagement.jar;" com.pingan.jaas.tutorial.SampleAcn
user name: testUser
password: testPassword
[MyLoginModule] 用户输入的用户名: testUser
[MyLoginModule] 用户输入的密码:testPassword
[MyLoginModule] authentication failed
user name: testUser
password: testPassword
[SampleLoginModule] user entered user name: testUser
[SampleLoginModule] user entered password: testPassword
[SampleLoginModule] authentication succeeded
com.pingan.jaas.tutorial.MyLoginModule.commit(131)
---------------------------------- <2011-02-14 10:03:14> ----------------------------------
succeeded=false
---------------------------------- <2011-02-14 10:03:14> ----------------------------------
com.pingan.jaas.tutorial.SampleLoginModule.commit(131)
---------------------------------- <2011-02-14 10:03:14> ----------------------------------
succeeded=true
---------------------------------- <2011-02-14 10:03:14> ----------------------------------
[SampleLoginModule] added SamplePrincipal to Subject
Authentication succeeded!
还有一个帮助类LogHelper.java:
package examples.util; import java.text.SimpleDateFormat; import java.util.Date; public class LogHelper { private static final String 换行符 = "\r\n"; private static final String 分隔线 = "----------------------------------"; private static final SimpleDateFormat sf = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss"); public static void log2Console(String msg) { StackTraceElement[] stackTraceElements = new Throwable() .getStackTrace(); StackTraceElement stackTraceElement = stackTraceElements[1]; StringBuffer sb = new StringBuffer(); // sb.append(换行符); sb.append(换行符).append(stackTraceElement.getClassName()); sb.append('.'); sb.append(stackTraceElement.getMethodName()).append("("); sb.append(stackTraceElement.getLineNumber()).append(")"); sb.append(换行符); String separateLine = buildSeparateLine(); sb.append(separateLine); sb.append(msg).append(换行符); sb.append(separateLine); System.out.println(sb.toString()); } public static void log2Console(String[] array) { StackTraceElement[] stackTraceElements = new Throwable() .getStackTrace(); StackTraceElement stackTraceElement = stackTraceElements[1]; StringBuffer sb = new StringBuffer(); // sb.append(换行符); sb.append(换行符).append(stackTraceElement.getClassName()); sb.append('.'); sb.append(stackTraceElement.getMethodName()).append("("); sb.append(stackTraceElement.getLineNumber()).append(")"); sb.append(换行符); String separateLine = buildSeparateLine(); sb.append(separateLine); if (array == null) { sb.append("数组为空(NULL)").append(换行符); } else { sb.append('{'); for (int i = 0; i < array.length; i++) { if (i >= 0) { sb.append(", "); } sb.append('"').append(array[i]).append('"'); } sb.append('}').append(换行符); } sb.append(separateLine); System.out.println(sb.toString()); } private static String buildSeparateLine() { StringBuffer sb = new StringBuffer(); Date currentTime = new Date(); sb.append(分隔线).append(" <").append(sf.format(currentTime)).append("> ") .append(分隔线).append(换行符); return sb.toString(); } }