JAAS学习笔记

JAAS学习笔记
注:例子的原作者是 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!

你可能感兴趣的:(JAAS学习笔记)