FineReport:任意时刻只允许在一个客户端登陆账号的插件

简介:

在使用FineReport报表系统中处于账户安全考虑有些企业希望同一账号在任意时刻智能在统一客户端登录那么A用户在C1客户端登陆后,该账号又在另外一个C2客户端登陆,服务器如何取判断呢?

开发原理

当服务器在得知AC1登陆后,在cookie里面写入一个标识ID~将浏览器标记,然后以后的访问自然就能够根据匹配用户名和对应的标记来确定这个用户是不是在换浏览器登陆了,当匹配到用户异地登陆,就要把之前已经登陆的用户先登出,再登陆新请求的用户。当然关闭页面事件里要向后台先发送一个请求,后台要记得清除改用户标记的缓存。

那么客户端怎么知道自己的账号在异地登陆了呢?

这个就要基于心跳了~因为我们的http不是长连接的,所以只能模拟了,弄一个轮询ajax不断的问服务器,我是否在异地登陆,因为之前服务器任何一个账号登陆都会又一个ID标识,那么当接收到一个客户端心跳时,我们只要拿出里面的ID和用户名跟保存的匹配~匹配到存在该用户名,但是ID不对,那说明一定是另外一个客户端登陆了这个账号了,这个时候就告知客户端,你的账号已经异地登陆,然后前端提示刷新就可以了。

如何实现?

这里要用到FineReport提供的接口,RequestInterceptor

接口内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package  com.fr.stable.fun;
  
import  com.fr.stable.fun.mark.Layer;
import  com.fr.stable.fun.mark.Mutable;
import  com.fr.stable.web.RequestCMDReceiver;
  
/**
  * Created by richie on 16/8/9.
  * 请求拦截器,通过传递op和cmd进行内置请求的拦截
  */
public  interface  RequestInterceptor  extends  Mutable, RequestCMDReceiver, Layer {
  
     String MARK_STRING =  "RequestInterceptor" ;
  
     int  CURRENT_LEVEL =  1 ;
}

相关引用类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
package  com.fr.stable.web;
  
import  javax.servlet.http.HttpServletRequest;
import  javax.servlet.http.HttpServletResponse;
  
/**
  * Created by richie on 16/8/9.
  * 请求接收器
  */
public  interface  RequestCMDReceiver {
  
     /**
      * cmd参数值
      * @return cmd参数值
      */
     String getCMD();
  
     /**
      * 执行
      * @param req http请求
      * @param res http应答
      * @param sessionID 会话ID
      * @throws Exception 处理失败则抛出异常
      */
     void  actionCMD(HttpServletRequest req, HttpServletResponse res,
                    String sessionID)  throws  Exception;
  
     /**
      * 执行请求
      * @param req http请求
      * @param res http响应
      * @throws Exception 处理失败则抛出异常
      */
     void  actionCMD(HttpServletRequest req, HttpServletResponse res)  throws  Exception;
}

注册方式

1
2
3
<extra-core>
    <RequestInterceptor  class = "com.fr.plugin.xxx.youclassname"  op= "fs_load"  cmd= "login"  pid= "com.fr.plugin.xxx.name" />
</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
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
@Override
         public  void  actionCMD(HttpServletRequest req, HttpServletResponse res)
             throws  Exception {
                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
                 String heartBeat = WebUtils.getHTTPRequestParameter(req,  "__heartbeat__" );
                 if (ComparatorUtils.equals(heartBeat,  "__active__" )){
                         if (StringUtils.isEmpty(username)){
                                 username = WebUtils.getHTTPRequestParameter(req,  "__username__" );
                                 if (!StringUtils.isEmpty(username)){
                                         req.getSession( true ).removeAttribute( "__username__" );
                                 }
                         }
                         //如果用户名不为空且已登录的列表中不包含该用户名说明已经被踢下线
                         if (!StringUtils.isEmpty(username) && !log.containsKey(username)){
                                 writeResult(res, false );
                                 return  ;
                         }
                         //如果在已登录的列表中找到了该用户名的记录,但是ID不匹配也说明被踢下线了
                         if (log.containsKey(username)){
                                 String crtUUID = WebUtils.getHTTPRequestParameter(req,  "_sessionid_" );
                                 SingleLoginBean logBean = log.get(username);
                                 String oldId = logBean.getId();
                                 if (!ComparatorUtils.equals(crtUUID,oldId)){
                                         writeResult(res, false );
                                         return ;
                                 } else {
                                         //将当前时刻设置为最近活跃时刻
                                         logBean.setWait4removeTime( new  Date().getTime());
                                 }
                         }
                         writeResult(res, true );
                         //登出太久不活跃的用户 30S以上
                         checkAllUser();
                         return ;
                 }
                 super .actionCMD(req, res);
         }

片段2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
protected  void  signOnSuccess(HttpServletRequest req, HttpServletResponse res, PrintWriter writer, String url)  throws  IOException, JSONException {
                 String username = WebUtils.getHTTPRequestParameter(req, Constants.FR_USERNAME);
                 String uuid = req.getSession( true ).getId();
                 SingleLoginBean logBean =  new  SingleLoginBean(uuid,req,res,req.getSession( true ));
                 logBean.setWait4removeTime( new  Date().getTime());
                 //后面的用户登录成功后需要先将旧的用户转移到等待删除的列表中
                 remove4logout(req);
                 //将新登录的用户添加到已经登录的用户中
                 log.put(username, logBean);
                 if  ( "true" .equals(WebUtils.getHTTPRequestParameter(req, ParameterConsts.__REDIRECT__))) {
             res.sendRedirect(url);
         else  {
             writer.print(JSONObject.create().put( "url" , url));
         }
     }

下面就是JS轮询了

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




本文转自 雄霸天下啦 51CTO博客,原文链接:http://blog.51cto.com/10549520/1884272,如需转载请自行联系原作者
相关文章
|
数据安全/隐私保护
fastadmin是如何设置没有权限的用户不能访问某些页面的?
fastadmin是如何设置没有权限的用户不能访问某些页面的?
443 0
|
数据库
Discuz! X3.5 登录不了管理后台的处理方法集合
1. 取消IP认证。由于Discuz!论坛会认证IP,不允许不同IP地址同时登录后台,所以取消IP认证即可。修改方法:在网站根目录,Discuz!配置文件config目录下config_global.php 中找到如下代码:$_config[‘admincp’][‘checkip’] = 1把代码中“1”改成“0”。(修改后允许多IP同时登录后台,所以论坛的安全系数会降低)
859 0
Discuz! X3.5 登录不了管理后台的处理方法集合
|
5月前
|
SQL 缓存 数据管理
数据管理DMS产品使用合集之打开多个SQL窗口,在关闭浏览器重新登录只剩第一个窗口且部分脚本丢失,是什么导致的
阿里云数据管理DMS提供了全面的数据管理、数据库运维、数据安全、数据迁移与同步等功能,助力企业高效、安全地进行数据库管理和运维工作。以下是DMS产品使用合集的详细介绍。
52 0
|
6月前
|
小程序 开发者
体验版小程序为何无法访问云端服务器后端接口(请求失败...(已完美解决附加图片))?
体验版小程序为何无法访问云端服务器后端接口(请求失败...(已完美解决附加图片))?
256 0
|
11月前
|
分布式计算 网络安全 MaxCompute
请问为什么maxcompute的授权操作会有几分钟的滞后时间,不会立即生效呢?但是我对2个账户分别打开命令行,其中一个主账号操作完,另一个账号要过几分钟才能生效,这个是有延迟么?
请问为什么maxcompute的授权操作会有几分钟的滞后时间,不会立即生效呢?但是我对2个账户分别打开命令行,其中一个主账号操作完,另一个账号要过几分钟才能生效,这个是有延迟么?
76 1
|
11月前
|
存储 小程序 关系型数据库
后台交互-个人中心->小程序登录微信登录接口演示,小程序授权登录理论,小程序授权登录代码演示,微信表情包存储问题
后台交互-个人中心->小程序登录微信登录接口演示,小程序授权登录理论,小程序授权登录代码演示,微信表情包存储问题
104 0
jira学习案例24-用useAuth切换登录与非登录状态
jira学习案例24-用useAuth切换登录与非登录状态
140 0
jira学习案例24-用useAuth切换登录与非登录状态
|
安全 测试技术 数据安全/隐私保护
appuploader 常规使用登录方法
双击 appuploader.exe 启动 appuploader。点击底部的未登录,弹出登录框。在登录框内输入 apple 开发者账号
|
前端开发 安全 JavaScript
【web渗透思路】任意账号的注册、登录、重置、查看
【web渗透思路】任意账号的注册、登录、重置、查看
760 0
【web渗透思路】任意账号的注册、登录、重置、查看