posts - 156,  comments - 601,  trackbacks - 0
注:例子的原作者是 Paul Feuer和John Musser,我在上面加入些注释以便更好的理解Jaas的实现
首先,我们来看一下 JAAS 一个认证操作的实现流程

先看一下这个认证操作会使用的接口如下:

javax.security.auth.callback.CallbackHandler
javax.security.auth.spi.LoginModule

下面看一下这个基于JAAS认证的实现流程:
JaasTest 实现一个登录的请求操作:
 1 public class JaasTest {
 2 
 3     public static void main(String[] args) {
 4 
 5         boolean loginSuccess = false;
 6         Subject subject = null;
 7 
 8         try {
 9                 // ConsoleCallbackHandler 实现CallbackHandler接口 处理用户名和密码(这里从键盘输入)
10             ConsoleCallbackHandler cbh = new ConsoleCallbackHandler();
11                         // 启动LoginContext, 装载LoginModule 
12                         //login module配置文件,通过java -Djava.security.auth.login.config=jaas.config指定
13                         //其中 example就是module名字
14             LoginContext lc = new LoginContext("Example", cbh);
15 
16             try {
17                 lc.login(); //进行登录,它会回调 CallbackHandler的handle方法,要求
18                 //handle方法把传入的参数Callback[]中找到NameCallback类型,调用NameCallback.setName把
19                 //用户名传进去,找到PasswordCallback类型,调用PasswordCallback.setPassword方法把密码传进去
20                 //如果登录失败,则把抛LoginException异常
21                 loginSuccess = true;
22                                 //登录成功后,可以返回Subject对象 1.(保存一组(Set)Principal对象),一般保存人员信息
23                                 //2.(保存一组(Set) Credential Object getPublicCredentials(Class<T> c) 一般保存密码
24                 subject = lc.getSubject();
25 
26                 Iterator it = subject.getPrincipals().iterator();
27                 while (it.hasNext()) 
28                     System.out.println("Authenticated: " + it.next().toString());
29                             
30                 it = subject.getPublicCredentials(Properties.class).iterator();
31                 while (it.hasNext()) 
32                     ((Properties)it.next()).list(System.out);
33 
34                 lc.logout(); //注销
35             } catch (LoginException lex) {
36                 System.out.println(lex.getClass().getName() + "" + lex.getMessage());
37             }
38         } catch (Exception ex) {
39             System.out.println(ex.getClass().getName() + "" + ex.getMessage());
40         }
41 
42         System.exit(0);
43     }
44 }


实现步骤如下:
A:
//创建LoginContext实例,把login module 名称和CallbackHandler接口实现
LoginContext lc = new LoginContext("Example", cbh);

B:
lc.login //进行登录操作,此时,会根据通过java -Djava.security.auth.login.config=jaas.config指定
//的配置, 加jaas.config文件 jaas.config写法如下:
Example {
   RdbmsLoginModule required debug="true" url="jdbc:mysql://localhost/jaasdb?user=root&password=pass" driver="org.gjt.mm.mysql.Driver";
};

//login module name
Example1 {
     //RdbmsLoginModule: module class name
     //required 固定
     //debug url driver都为参数,可以在LoginModule实现类的 initialize(Subject subject, CallbackHandler callbackHandler,
   //         Map sharedState, Map options)方法中取得
   RdbmsLoginModule required debug="true" url="jdbc:mysql://localhost/jaasdb?user=root&password=pass" driver="org.gjt.mm.mysql.Driver";
};

接下来,LoginContext会去 回调 CallbackHandler的handle(Callback[] callbacks)方法
handle方法把传入的参数Callback[]中找到NameCallback类型,调用NameCallback.setName把
用户名传进去,找到PasswordCallback类型,调用PasswordCallback.setPassword方法把密码传进去
下面来看一下ConsoleCallbackHandler 实现代码:

 1 public class ConsoleCallbackHandler implements CallbackHandler {
 2 
 3     /**
 4      * <p>Creates a callback handler that prompts and reads from the
 5      * command line for answers to authentication questions.
 6      * This can be used by JAAS applications to instantiate a
 7      * CallbackHandler.
 8      */
 9     public ConsoleCallbackHandler() {
10     }
11 
12     /**
13      * Handles the specified set of callbacks.
14      * This class supports NameCallback and PasswordCallback.
15      *
16      * @param   callbacks the callbacks to handle
17      * @throws  IOException if an input or output error occurs.
18      * @throws  UnsupportedCallbackException if the callback is not an
19      * instance of NameCallback or PasswordCallback
20      */
21     public void handle(Callback[] callbacks) 
22         throws java.io.IOException, UnsupportedCallbackException {
23 
24         for (int i = 0; i < callbacks.length; i++) {
25                         System.out.println(callbacks[i]);
26             if (callbacks[i] instanceof NameCallback) {
27                 System.out.print(((NameCallback)callbacks[i]).getPrompt());
28                 String user=(new BufferedReader(new InputStreamReader(System.in))).readLine();
29                 //设置用户名
30                 ((NameCallback)callbacks[i]).setName(user);
31             } else if (callbacks[i] instanceof PasswordCallback) {
32                 System.out.print(((PasswordCallback)callbacks[i]).getPrompt());
33                 String pass=(new BufferedReader(new InputStreamReader(System.in))).readLine();
34                 //设置密码
35                 ((PasswordCallback)callbacks[i]).setPassword(pass.toCharArray());
36             } else {
37                 throw(new UnsupportedCallbackException(
38                             callbacks[i], "Callback class not supported"));
39             }
40         }
41     }
42 }
43 

C:
接下来,loginContext会 回调依次调用 LoginModule的 方法, 次序如下:
LoginModule.login
LoginModule.commit

如果commit返回 false,则会调用 LoginModule.abort方法
看一下RdbmsLoginModule类的实现:
  1 public class RdbmsLoginModule implements LoginModule {
  2 
  3     // initial state
  4     CallbackHandler callbackHandler;
  5     Subject  subject;
  6     Map      sharedState;
  7     Map      options;
  8 
  9     // temporary state
 10     Vector   tempCredentials;
 11     Vector   tempPrincipals;
 12 
 13     // the authentication status
 14     boolean  success;
 15 
 16     // configurable options
 17     boolean  debug;
 18     String   url;
 19     String   driverClass;
 20 
 21     /**
 22      * <p>Creates a login module that can authenticate against
 23      * a JDBC datasource.
 24      */
 25     public RdbmsLoginModule() {
 26         tempCredentials = new Vector();
 27         tempPrincipals  = new Vector();
 28         success = false;
 29         debug   = false;
 30     }
 31 
 32     /**
 33      * Initialize this <code>LoginModule</code>.
 34      *
 35      * <p>
 36      *
 37      * @param subject the <code>Subject</code> to be authenticated. <p>
 38      *
 39      * @param callbackHandler a <code>CallbackHandler</code> for communicating
 40      *            with the end user (prompting for usernames and
 41      *            passwords, for example). <p>
 42      *
 43      * @param sharedState shared <code>LoginModule</code> state. <p>
 44      *
 45      * @param options options specified in the login
 46      *            <code>Configuration</code> for this particular
 47      *            <code>LoginModule</code>.
 48      */
 49     public void initialize(Subject subject, CallbackHandler callbackHandler,
 50             Map sharedState, Map options) {
 51 
 52         // save the initial state
 53         this.callbackHandler = callbackHandler;
 54         this.subject     = subject;
 55         this.sharedState = sharedState;
 56         this.options     = options;
 57 
 58         // initialize any configured options
 59         if (options.containsKey("debug"))
 60             debug = "true".equalsIgnoreCase((String)options.get("debug"));
 61 
 62         url          = (String)options.get("url");
 63         driverClass  = (String)options.get("driver");
 64 
 65         if (debug) {
 66             System.out.println("\t\t[RdbmsLoginModule] initialize");
 67             System.out.println("\t\t[RdbmsLoginModule] url: " + url);
 68             System.out.println("\t\t[RdbmsLoginModule] driver: " + driverClass);
 69         }
 70     }
 71 
 72     /**
 73      * <p> Verify the password against the relevant JDBC datasource.
 74      *
 75      * @return true always, since this <code>LoginModule</code>
 76      *      should not be ignored.
 77      *
 78      * @exception FailedLoginException if the authentication fails. <p>
 79      *
 80      * @exception LoginException if this <code>LoginModule</code>
 81      *      is unable to perform the authentication.
 82      */
 83     public boolean login() throws LoginException {
 84 
 85         if (debug)
 86             System.out.println("\t\t[RdbmsLoginModule] login");
 87 
 88         if (callbackHandler == null)
 89             throw new LoginException("Error: no CallbackHandler available " +
 90                     "to garner authentication information from the user");
 91 
 92         try {
 93             // Setup default callback handlers.
 94             Callback[] callbacks = new Callback[] {
 95                 new NameCallback("Username: "),
 96                 new PasswordCallback("Password: "false)
 97             };
 98 
 99             callbackHandler.handle(callbacks);
100 
101             String username = ((NameCallback)callbacks[0]).getName();
102             String password = new String(((PasswordCallback)callbacks[1]).getPassword());
103 
104             ((PasswordCallback)callbacks[1]).clearPassword();
105 
106             success = rdbmsValidate(username, password);
107 
108             callbacks[0= null;
109             callbacks[1= null;
110 
111             if (!success)
112                 throw new LoginException("Authentication failed: Password does not match");
113 
114             return(true);
115         } catch (LoginException ex) {
116             throw ex;
117         } catch (Exception ex) {
118             success = false;
119             throw new LoginException(ex.getMessage());
120         }
121     }
122 
123     /**
124      * Abstract method to commit the authentication process (phase 2).
125      *
126      * <p> This method is called if the LoginContext's
127      * overall authentication succeeded
128      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
129      * succeeded).
130      *
131      * <p> If this LoginModule's own authentication attempt
132      * succeeded (checked by retrieving the private state saved by the
133      * <code>login</code> method), then this method associates a
134      * <code>RdbmsPrincipal</code>
135      * with the <code>Subject</code> located in the
136      * <code>LoginModule</code>.  If this LoginModule's own
137      * authentication attempted failed, then this method removes
138      * any state that was originally saved.
139      *
140      * <p>
141      *
142      * @exception LoginException if the commit fails
143      *
144      * @return true if this LoginModule's own login and commit
145      *      attempts succeeded, or false otherwise.
146      */
147     public boolean commit() throws LoginException {
148 
149         if (debug)
150             System.out.println("\t\t[RdbmsLoginModule] commit");
151 
152         if (success) {
153 
154             if (subject.isReadOnly()) {
155                 throw new LoginException ("Subject is Readonly");
156             }
157 
158             try {
159                 Iterator it = tempPrincipals.iterator();
160                 
161                 if (debug) {
162                     while (it.hasNext())
163                         System.out.println("\t\t[RdbmsLoginModule] Principal: " + it.next().toString());
164                 }
165 
166                 subject.getPrincipals().addAll(tempPrincipals);
167                 subject.getPublicCredentials().addAll(tempCredentials);
168 
169                 tempPrincipals.clear();
170                 tempCredentials.clear();
171 
172                 if(callbackHandler instanceof PassiveCallbackHandler)
173                     ((PassiveCallbackHandler)callbackHandler).clearPassword();
174 
175                 return(true);
176             } catch (Exception ex) {
177                 ex.printStackTrace(System.out);
178                 throw new LoginException(ex.getMessage());
179             }
180         } else {
181             tempPrincipals.clear();
182             tempCredentials.clear();
183             return(true);
184         }
185     }
186 
187     /**
188      * <p> This method is called if the LoginContext's
189      * overall authentication failed.
190      * (the relevant REQUIRED, REQUISITE, SUFFICIENT and OPTIONAL LoginModules
191      * did not succeed).
192      *
193      * <p> If this LoginModule's own authentication attempt
194      * succeeded (checked by retrieving the private state saved by the
195      * <code>login</code> and <code>commit</code> methods),
196      * then this method cleans up any state that was originally saved.
197      *
198      * <p>
199      *
200      * @exception LoginException if the abort fails.
201      *
202      * @return false if this LoginModule's own login and/or commit attempts
203      *     failed, and true otherwise.
204      */
205     public boolean abort() throws javax.security.auth.login.LoginException {
206 
207         if (debug)
208             System.out.println("\t\t[RdbmsLoginModule] abort");
209 
210         // Clean out state
211         success = false;
212 
213         tempPrincipals.clear();
214         tempCredentials.clear();
215 
216         if (callbackHandler instanceof PassiveCallbackHandler)
217             ((PassiveCallbackHandler)callbackHandler).clearPassword();
218 
219         logout();
220 
221         return(true);
222     }
223 
224     /**
225      * Logout a user.
226      *
227      * <p> This method removes the Principals
228      * that were added by the <code>commit</code> method.
229      *
230      * <p>
231      *
232      * @exception LoginException if the logout fails.
233      *
234      * @return true in all cases since this <code>LoginModule</code>
235      *        should not be ignored.
236      */
237     public boolean logout() throws javax.security.auth.login.LoginException {
238 
239         if (debug)
240             System.out.println("\t\t[RdbmsLoginModule] logout");
241 
242         tempPrincipals.clear();
243         tempCredentials.clear();
244 
245         if (callbackHandler instanceof PassiveCallbackHandler)
246             ((PassiveCallbackHandler)callbackHandler).clearPassword();
247 
248         // remove the principals the login module added
249         Iterator it = subject.getPrincipals(RdbmsPrincipal.class).iterator();
250         while (it.hasNext()) {
251             RdbmsPrincipal p = (RdbmsPrincipal)it.next();
252             if(debug)
253                 System.out.println("\t\t[RdbmsLoginModule] removing principal "+p.toString());
254             subject.getPrincipals().remove(p);
255         }
256 
257         // remove the credentials the login module added
258         it = subject.getPublicCredentials(RdbmsCredential.class).iterator();
259         while (it.hasNext()) {
260             RdbmsCredential c = (RdbmsCredential)it.next();
261             if(debug)
262                 System.out.println("\t\t[RdbmsLoginModule] removing credential "+c.toString());
263             subject.getPrincipals().remove(c);
264         }
265 
266         return(true);
267     }
268 
269     /**
270      * Validate the given user and password against the JDBC datasource.
271      * <p>
272      *
273      * @param user the username to be authenticated. <p>
274      * @param pass the password to be authenticated. <p>
275      * @exception Exception if the validation fails.
276      */
277     private boolean rdbmsValidate(String user, String pass) throws Exception {
278         
279         Connection con;
280         String query = "SELECT * FROM USER_AUTH where userid='" + user + "'";
281         Statement stmt;
282         RdbmsPrincipal  p = null;
283         RdbmsCredential c = null;
284         boolean passwordMatch = false;
285 
286         try {
287             Class.forName(driverClass);
288         }
289         catch (java.lang.ClassNotFoundException e) {
290             System.err.print("ClassNotFoundException: ");
291             System.err.println(e.getMessage());
292             throw new LoginException("Database driver class not found: " + driverClass);
293         }
294 
295         try {
296             if (debug)
297                 System.out.println("\t\t[RdbmsLoginModule] Trying to connect");
298 
299             con = DriverManager.getConnection(url, "Administrator""");
300 
301             if (debug)
302                 System.out.println("\t\t[RdbmsLoginModule] connected!");
303 
304             stmt = con.createStatement();
305 
306             if (debug)
307                 System.out.println("\t\t[RdbmsLoginModule] "+query);
308 
309             ResultSet result  = stmt.executeQuery(query);
310             String dbPassword = null, dbFname = null, dbLname = null;
311             String updatePerm = null, deletePerm = null;
312             boolean isEqual   = false;
313 
314             while (result.next()) {
315                 if (!result.isFirst()) 
316                     throw new LoginException("Ambiguous user (located more than once): "+user);
317                 dbPassword = result.getString(result.findColumn("password"));
318                 dbFname    = result.getString(result.findColumn("first_name"));
319                 dbLname    = result.getString(result.findColumn("last_name"));
320                 deletePerm = result.getString(result.findColumn("delete_perm"));
321                 updatePerm = result.getString(result.findColumn("update_perm"));
322             }
323 
324             if (dbPassword == null)
325                 throw new LoginException("User " + user + " not found");
326 
327             if (debug)
328                 System.out.println("\t\t[RdbmsLoginModule] '"+pass + "' equals '" + dbPassword + "'?");
329 
330             passwordMatch = pass.equals(dbPassword);
331             if (passwordMatch) {
332                 if (debug) 
333                     System.out.println("\t\t[RdbmsLoginModule] passwords match!");
334 
335                 c = new RdbmsCredential();
336                 c.setProperty("delete_perm", deletePerm);
337                 c.setProperty("update_perm", updatePerm);
338                 this.tempCredentials.add(c);
339                 this.tempPrincipals.add(new RdbmsPrincipal(dbFname + " " + dbLname));
340             } else {
341                 if (debug)
342                     System.out.println("\t\t[RdbmsLoginModule] passwords do NOT match!");
343             }
344             stmt.close();
345             con.close();
346         }
347         catch (SQLException ex) {
348             System.err.print("SQLException: ");
349             System.err.println(ex.getMessage());
350             throw new LoginException("SQLException: "+ex.getMessage());
351         }
352         return(passwordMatch);
353     }
354 }
355 

Good Luck!
Yours Matthew!
posted on 2008-05-28 09:27 x.matthew 阅读(2655) 评论(2)  编辑  收藏 所属分类: Spring|Hibernate|Other framework

只有注册用户登录后才能发表评论。


网站导航: