FineReport:任意時刻只允許在一個客戶端登陸賬號的外掛

傑華園發表於2016-12-20

在使用FineReport報表系統中處於賬戶安全考慮有些企業希望同一賬號在任意時刻智慧在統一客戶端登入那麼A使用者在C1客戶端登陸後,該賬號又在另外一個C2客戶端登陸,伺服器如何取判斷呢?

開發原理

當伺服器在得知AC1登陸後,在cookie裡面寫入一個標識ID~將瀏覽器標記,然後以後的訪問自然就能夠根據匹配使用者名稱和對應的標記來確定這個使用者是不是在換瀏覽器登陸了,當匹配到使用者異地登陸,就要把之前已經登陸的使用者先登出,再登陸新請求的使用者。當然關閉頁面事件裡要向後臺先傳送一個請求,後臺要記得清除改使用者標記的快取。

那麼客戶端怎麼知道自己的賬號在異地登陸了呢?

這個就要基於心跳了~因為我們的http不是長連線的,所以只能模擬了,弄一個輪詢ajax不斷的問伺服器,我是否在異地登陸,因為之前伺服器任何一個賬號登陸都會又一個ID標識,那麼當接收到一個客戶端心跳時,我們只要拿出裡面的ID和使用者名稱跟儲存的匹配~匹配到存在該使用者名稱,但是ID不對,那說明一定是另外一個客戶端登陸了這個賬號了,這個時候就告知客戶端,你的賬號已經異地登陸,然後前端提示重新整理就可以了。

如何實現?

這裡要用到FineReport提供的介面,RequestInterceptor

介面內容


點選(此處)摺疊或開啟

  1. package com.fr.stable.fun;
  2.  
  3. import com.fr.stable.fun.mark.Layer;
  4. import com.fr.stable.fun.mark.Mutable;
  5. import com.fr.stable.web.RequestCMDReceiver;
  6.  
  7. /**
  8.  * Created by richie on 16/8/9.
  9.  * 請求攔截器,透過傳遞op和cmd進行內建請求的攔截
  10.  */
  11. public interface RequestInterceptor extends Mutable, RequestCMDReceiver, Layer {
  12.  
  13.     String MARK_STRING = "RequestInterceptor";
  14.  
  15.     int CURRENT_LEVEL = 1;
  16. }


相關引用類


點選(此處)摺疊或開啟

  1. package com.fr.stable.web;
  2.  
  3. import javax.servlet.http.HttpServletRequest;
  4. import javax.servlet.http.HttpServletResponse;
  5.  
  6. /**
  7.  * Created by richie on 16/8/9.
  8.  * 請求接收器
  9.  */
  10. public interface RequestCMDReceiver {
  11.  
  12.     /**
  13.      * cmd引數值
  14.      * @return cmd引數值
  15.      */
  16.     String getCMD();
  17.  
  18.     /**
  19.      * 執行
  20.      * @param req http請求
  21.      * @param res http應答
  22.      * @param sessionID 會話ID
  23.      * @throws Exception 處理失敗則丟擲異常
  24.      */
  25.     void actionCMD(HttpServletRequest req, HttpServletResponse res,
  26.                    String sessionID) throws Exception;
  27.  
  28.     /**
  29.      * 執行請求
  30.      * @param req http請求
  31.      * @param res http響應
  32.      * @throws Exception 處理失敗則丟擲異常
  33.      */
  34.     void actionCMD(HttpServletRequest req, HttpServletResponse res) throws Exception;
  35. } 


註冊方式


點選(此處)摺疊或開啟

  1. <extra-core>
  2.    <RequestInterceptor class="com.fr.plugin.xxx.youclassname" op="fs_load" cmd="login" pid="com.fr.plugin.xxx.name"/>
  3. </extra-core>


其中pid的值應該和外掛的id值一致,透過這樣的註冊方式,就可以使用自己定義的處理邏輯來覆蓋掉預設的登入驗證請求。

以上,透過故意製造報錯的方式我們能夠看到~FR登陸請求都是繼承於

com.fr.fs.web.service.FSLoadLoginAction 這個類的~

進一步反編譯JAR可以看到~這個類是繼承於

com.fr.web.core.ActionNoSessionCMD  最後實現 ActionCMD, RequestInterceptor

那麼正好,我們的外掛主類就可以免去很多自己寫,直接繼承於FSLoadLoginAction就可以用來處理所有的自定義登陸請求

【凡是需要在登陸時做得事情都可以在這裡做】

當然actionCMD(HttpServletRequest req, HttpServletResponse res)這個執行方法還是要重寫的~

還有就是protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url)這個登陸成功之後需要做一些上面說的操作~

下面是兩個程式碼片段,主要就是處理登陸標記和登出清除的. 

片段1


點選(此處)摺疊或開啟

  1. @Override
  2.         public void actionCMD(HttpServletRequest req, HttpServletResponse res)
  3.             throws Exception {
  4.                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
  5.                 String heartBeat = WebUtils.getHTTPRequestParameter(req, "__heartbeat__");
  6.                 if(ComparatorUtils.equals(heartBeat, "__active__")){
  7.                         if(StringUtils.isEmpty(username)){
  8.                                 username = WebUtils.getHTTPRequestParameter(req, "__username__");
  9.                                 if(!StringUtils.isEmpty(username)){
  10.                                         req.getSession(true).removeAttribute("__username__");
  11.                                 }
  12.                         }
  13.                         //如果使用者名稱不為空且已登入的列表中不包含該使用者名稱說明已經被踢下線
  14.                         if(!StringUtils.isEmpty(username) && !log.containsKey(username)){
  15.                                 writeResult(res,false);
  16.                                 return ;
  17.                         }
  18.                         //如果在已登入的列表中找到了該使用者名稱的記錄,但是ID不匹配也說明被踢下線了
  19.                         if(log.containsKey(username)){
  20.                                 String crtUUID = WebUtils.getHTTPRequestParameter(req, "_sessionid_");
  21.                                 SingleLoginBean logBean = log.get(username);
  22.                                 String oldId = logBean.getId();
  23.                                 if(!ComparatorUtils.equals(crtUUID,oldId)){
  24.                                         writeResult(res,false);
  25.                                         return;
  26.                                 }else{
  27.                                         //將當前時刻設定為最近活躍時刻
  28.                                         logBean.setWait4removeTime(new Date().getTime());
  29.                                 }
  30.                         }
  31.                         writeResult(res,true);
  32.                         //登出太久不活躍的使用者 30S以上
  33.                         checkAllUser();
  34.                         return;
  35.                 }
  36.                 super.actionCMD(req, res);
  37.         }


片段2


點選(此處)摺疊或開啟

  1. protected void signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url) throws IOException, JSONException {
  2.                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
  3.                 String uuid = req.getSession(true).getId();
  4.                 SingleLoginBean logBean = new SingleLoginBean(uuid,req,res,req.getSession(true));
  5.                 logBean.setWait4removeTime(new Date().getTime());
  6.                 //後面的使用者登入成功後需要先將舊的使用者轉移到等待刪除的列表中
  7.                 remove4logout(req);
  8.                 //將新登入的使用者新增到已經登入的使用者中
  9.                 log.put(username, logBean);
  10.                 if ("true".equals(WebUtils.getHTTPRequestParameter(req, ParameterConsts.__REDIRECT__))) {
  11.             res.sendRedirect(url);
  12.         } else {
  13.             writer.print(JSONObject.create().put("url", url));
  14.         }
  15.     }
下面就是JS輪詢了

點選(此處)摺疊或開啟

  1. var askServer4Active = function(){
  2.                 var sessionid = getCrtSessionid();
  3.                 if( sessionid == "" || sessionid == null ){
  4.                         return ;
  5.                 }
  6.                 var url = FR.servletURL+"?op=fs_load&cmd=login&__heartbeat__=__active__&_sessionid_="+sessionid;
  7.                 FR.ajax({
  8.                         url: url,
  9.                         type: "POST",
  10.                         dataType:"JSON",
  11.                         success: function(msg){
  12.                                 if(!msg.success){
  13.                                         if(active){
  14.                                                 active = false;
  15.                                                 clearInterval(timer);
  16.                                                 FR.Msg.alert("警告","您的賬號已在其他客戶端登陸!\n如非本人授權,請及時修改密碼!\n3秒後頁面將跳轉至登陸頁!");
  17.                                                 setTimeout(function(){
  18.                                                         document.location = FR.servletURL+"?op=fs";
  19.                                                 },3000);
  20.                                         }
  21.                                 }else{
  22.                                         active = true;
  23.                                 }
  24.                         }
  25.                 });
  26.         };


來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/21472864/viewspace-2131028/,如需轉載,請註明出處,否則將追究法律責任。

相關文章