在JBoss中自己定義JAAS登錄模塊處理登錄事件

在我心目中理想的登錄模塊應該是這樣的,我可以通過配置文件將一些事交給應用服務器的JAAS去處理,但是有必要時我也能夠通過重載某些方法攔截登錄處理的信息來達到自己的某些目的,比如記錄登錄事件、讀取登錄用戶的信息放到session中,甚至是再加上一個處理驗證碼。這些能夠實現嗎?在Tomcat中好象不行,但是在Jboss中呢?
       由於一直沒有時間去解決在JAAS中集成自己編寫的驗證處理模塊的技術,我上一篇介紹的方法在我的項目中用了半年,等我把其他緊要的事情了結之後又回過頭來琢磨登錄模塊的事了。
       我反反覆覆看了login-config.xml文件,我對這一段產生了一些想法:
<login-module code = "org.jboss.security.auth.spi.DatabaseServerLoginModule"
             flag = "required">
       我能不能繼續 org.jboss.security.auth.spi.DatabaseServerLoginModule,然後把這個code的值替換爲我自己編寫的類呢?Jboss既然提供了這麼靈活的配置,那麼說理論上應該是可行的,於是我又拿過來Jboss的源代碼,仔細地瞭解了登錄方面的一些接口和類,尤其是這個DatabaseServerLoginModule類更是把每一行代碼都反覆的研究過,我試着編寫了一個 com.benjamin.commons.security.LoginModule類,並且重載了login函數和initialize函數,加入了一些測試代碼,試驗了幾次測試結果之後決定從重載login函數着手。這個函數是這樣定義的:
public boolean login() throws LoginException ;
       在這個函數裏可以通過 this.getUsername()來取得登錄的用戶名,然後根據用戶名從數據庫讀取用戶信息也沒問題,但是session呢?我怎麼取得 session對象並往裏面加東西?這個類即沒有繼承servlet也沒有任何屬性或參數與request有關,看來這個session是沒辦法獲取了?我很不甘心就這樣放棄,在google上輸入了各種關鍵字來查找有用的信息,國內介紹JAAS的資料本來就少之又少,更別說這牛角尖一樣偏門的方面了,終於網絡不負有心人,我在一個國外的論壇上找到了一點資料,原來在Jboss中可以通過PolicyContext這個類來獲取應用服務器的安全性方面的上下文,其中就包括獲取HttpServletRequest,具體方法是這樣的:
 
HttpServletRequest request = 
                     (HttpServletRequest)PolicyContext.getContext(WEB_REQUEST_KEY);
 
後來我更發現只要是在Jboss容器運行的任何類都可以通過這種方式獲取request對象。
有了request獲取session當然就不成問題了:
HttpSession session=request.getSession(true);
最後我的這個login函數變成了這樣:
 
    public boolean login() throws LoginException {
       boolean loginAccepted =false;
       HttpServletRequest request = null;
    
       try {
//         System.out.println("開始調用JAAS驗證 ...");
           request = (HttpServletRequest)PolicyContext.getContext("javax.servlet.http.HttpServletRequest" );
           HttpSession session=request.getSession(true);
       //  String loadPath = this.getClass().getProtectionDomain().getCodeSource().getLocation().toString();
           
//         log.info("開始調用JAAS驗證 ...");
           loginAccepted = super.login();
           log.info("已通過JAAS驗證,開始獲取登錄用戶信息");
       } catch (LoginException e) {
           request.setAttribute("ex", e);
           LoginException ex=new& nbsp;LoginException("用戶" + this.getUsername() +"登錄失敗:" + e.getMessage());
           log.error(e);
           throw ex;
       } catch (PolicyContextException e) {      
           LoginException ex=new& nbsp;LoginException("獲取request失敗:" + e.getMessage());
           log.error(ex);
           request.setAttribute("ex", ex);
           throw ex;
       }
       
       if(loginAccepted) {         
           try {
               String userName=this.getUsername();
 
              //根據登錄用戶名讀取用戶信息保存到session中
                if (loginBo.login(userName,request)) {
                    log.info(userName + "  登錄成功.");
                }
                else{
                      log.error(userName + " 登錄失敗!");           
                      throw new LoginException(" 登錄失敗,無法從數據庫獲取用戶信息!");
                }                      
           }          
           catch(Exception e) {
              LoginException ex=new& nbsp;LoginException("登錄失敗:" + e.getMessage());
              log.error(ex);
              request.setAttribute("ex", ex);
               throw ex;
           }       
           
       }
       return loginAccepted;
    }
 
    然後我把login-config.xml的相關配置改成了這個樣子:
 
 <application-policy name = "jbosstest">
       <authentication>
                   <!--  重載org.jboss.security.auth.spi.DatabaseServerLoginModule的類 -->
          <login-module code = "com.benjamin.commons.security.LoginModule"
             flag = "required">
             <module-option name = "dsJndiName">java:/testds</module-option>
             <module-option name = "principalsQuery">SELECT PASSWORD FROM REL_PUB_OPERATOR  WHERE LOGINID=?</module-option>
             <module-option name = "rolesQuery">SELECT ROLENAME,’Roles’ FROM VREL_ROLE WHERE LOGINID=?</module-option>
             <module-option name="hashAlgorithm">MD5</module-option>
<module-option name="hashEncoding">base64</module-option>          </login-module>
       </authentication>
    </application-policy>
 
    這樣我想實現的讀取用戶信息到session的功能就實現了,但是我想更靈活更通用一點,我在這個項目中用的loginBo的類和其中的login(userName,request)方法很可能在其他項目中就不一樣了,我應該也通過配置來實現自定義自己的登錄完畢後的處理類。我在前面幾次測試的過程中發現<login-module … /> 的值是可以在 loginModule類的initialize函數裏通過options.get(String)方法獲取得到,於是我改了改登錄配置文件,加了一行:
    <module-option name="loginBo">com.system.business.LoginBo</module-option>
 
變成這樣:
 
<application-policy name = "jbosstest">
       <authentication>
                   <!--  重載org.jboss.security.auth.spi.DatabaseServerLoginModule的類 -->
          <login-module code = "com.benjamin.commons.security.LoginModule"
             flag = "required">
             <module-option name = "dsJndiName">java:/testds</module-option>
             <module-option name = "principalsQuery">SELECT PASSWORD FROM REL_PUB_OPERATOR  WHERE LOGINID=?</module-option>
             <module-option name = "rolesQuery">SELECT ROLENAME,’Roles’ FROM VREL_ROLE WHERE LOGINID=?</module-option>
             <module-option name="hashAlgorithm">MD5</module-option>
                                                         <module-option name="hashEncoding">base64</module-option>
                            <module-option name="loginBo">com.benjamin.system.business.LoginBo</module-option>       
          </login-module>       
</authentication>
  </application-policy>
 
然後又在我的LoginModule類裏重載了initialize函數:
 
    public void initialize(Subject subject,CallbackHandler callbackHandler,Map sharedState,Map options){
       super.initialize(subject, callbackHandler, sharedState, options);
       if(options.get("loginBo")!=null){
           try {
               loginBo=(ILoginBo)Class.forName(options.get("loginBo").toString()).newInstance();
           } catch (InstantiationException e) {
              log.error(e);
           } catch (IllegalAccessException e) {
              log.error(e);
           } catch (ClassNotFoundException e) {
              log.error("沒有找到loginBo類:" + e);
           }
       }
    }
 
這樣我就可以很靈活地通過配置實現代碼的移植重用,在其它項目裏只要重新實現一個loginBo類並修改配置<module-option name="loginBo">爲這個類就可以了。
完成之後重新發布到jboss上,運行Jboss,打開登錄頁面,登錄成功,非常完美!
按理這樣就算大功告成了,但是當我再次登錄的時候發現登錄後轉向的頁面出了一些錯誤,session裏讀出來的值都是null,再試了幾次,換另外一個用戶名登錄又是好的,但退出後再次用這個用戶名登錄時就不行了。這個問題困擾了我好幾天,我感覺應該是Jboss保存了登錄用戶的信息在緩衝區導致一些方面衝突。但是怎麼解決呢?
最後還是在網上找到了解決辦法,看來離開網絡我是沒辦法再混下去了。
修改JBOSS安裝目錄下的server\default\deploy\security-service.xml文件,找到這樣的一個節點
 <mbean code="org.jboss.security.plugins.JaasSecurityManagerService"
      name="jboss.security:service=JaasSecurityManager">
<attribute name="DefaultCacheTimeout">60</attribute>
<attribute name="DefaultCacheResolution">600</attribute>
 </mbean>
 
將DefaultCacheTimeout和DefaultCacheResolution的值都改爲0,即不將登錄信息保存到緩衝區,其它地方不變,修改完後這兩個節點應該是這樣的:
 
<attribute name="DefaultCacheTimeout">0</attribute>
<attribute name="DefaultCacheResolution">0</attribute>
 
   重啓Jboss,登錄,沒問題,再次登錄也沒問題,經過多次測試後發現這一招果然管用,我用的Jboss版本是4.0.3SP1,後來我在佈署其它版本的Jboss時發現沒有security-service.xml文件,但是我在這個版本的Jboss的主目錄下的server\default\conf\jboss-service.xml配置文件裏也找到了同樣的DefaultCacheTimeout和 DefaultCacheResolution配置,照樣修改爲0,運行以後發現完全正常。
       至此我想實現的功能基本上都可以實現了,我之所以加上一個“基本上”,是因爲我前面還提到了一個加驗證碼的功能,我試過幾次沒有成功,因爲我在登錄模塊裏只能獲取到頁面提交的用戶名和密碼信息,我試過加上一個textbox來填寫驗證碼,但是不知道在哪裏能取得出來,前面所提的方法裏獲取的request裏是取不到,不知道還有什麼其它方式。希望有興趣的朋友可以自己去試一下,試出來了別忘了告訴我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章