`
alucardggg
  • 浏览: 8715 次
  • 性别: Icon_minigender_1
  • 来自: 杭州
文章分类
社区版块
存档分类
最新评论

使用BIRT报表时的认证问题的解决以及注意事项

阅读更多

相信在目前的报表解决方案中, Birt为比较优秀的一种也是大多数偏好open source的人会优先选用的一种, 至于好处和优点, 在此不做累述, 下面就讨论一些集成了birt的viewer环境, 而有安全机制要求的系统的解决方案, 以及在部署时的一些注意事项, 最后, 为了解决这些实际的问题, 笔者写了一个swing的java小工具帮助使用者进行部署

* 所有讨论基于Birt 2.2
目前需要解决的问题:
1. Birt报表引擎如何和当前应用集成
2. 如果使用Birt官方的viewer环境, 如何解决安全问题
3. 有关安全问题的讨论

这里提出一个可行的解决, 事实上, 我们项目也是用了该解决方案使的项目成功上线
对于birt的集成, 网上有很多解决方案, 在此不累述
解决方案不外乎几种:
A. 直接在项目的web环境内调用report engine, 手动控制Birt的api使用
优点: 完全控制报表整个产生过程
缺点: 开发代价大, 代码质量要求高, 项目测试过程复杂, 高级功能需要手动写代码
B. 应用内集成Birt的viewer环境
优点: 开发代价小, 项目测试简单, 可以使用多种Birt的高级功能, (比如TOC,分页等)
缺点: 不能完全控制报表, 对于外部访问来说, Birt viewer就是一个黑盒, 很难控制
C. 结合A. B的解决方案, 既是使用Birt viewer, 又在恰当的地方插入一些自己需要的code
优点: 开发代价不高, 自己能对关键过程做控制, 项目测试类似B
缺点:
- 修改了birt的源文件, 对于birt的版本更新会造成额外的维护量
- Birt viewer是独立的项目, 会造成开发和代码版本控制上的不便
- 由于Birt viewer要build发布, 在部署的时候会带来不便(目前2.2下面build是个问题)

解决方案A过于复杂, 在此讨论B和C方案实现下的安全性问题
如何集成Birt的viewer, 在此不是重点, 网上有很多解决方案
将Birt的viewer环境集成后,由于在web.xml里面配置了Birt的servlet入口(比如preview, run, frameset等), 因此访问报表的url会交由Birt的serlvet处理,如何对于这些url做控制来实现安全性呢?

如果有以下安全需求需求:
1. 对于不同的角色进行访问控制
2. 对于不同角色下面的权限粒度进行访问控制
3. 对于任何形式的url进行访问控制, 由于使用了birt viewer接收url, 对于请求的url有着严格的控制

从测试驱动开发的角度来说,测试该权限系统需要注意以下方面:
1. URL测试,为权限控制到URL级别,即为权限不光控制页面输入,同时也对请求的URL有严格的过滤,角色或帐号不能跨自己的域请求报表数据,全部权限测试都涉及到页面请求和直接模拟URL请求2部分
2. 角色RA登陆后看到的是不是角色RA的全部报表

测试点一:是不是角色A的报表

测试点二:是不是角色A的全部报表
3. 角色RA登陆后能访问的报表资源RESA,在不是角色RA的情况下或者RA没有Login的情况下是否能被访问
测试点一. 不登录请求资源(URL)
测试点二. Session Expired请求资源(URL)
测试点三. 跨域请求资源
      1. 使用非RA的角色或用户请求RA的资源RESA
      2. 修改URL的带有认证信息参数,验证是否能请求到资源
      3. 在角色A的情况下,使用带自己的验证信息的URL请求其他角色的资源

      4. 使用缺少参数或者增加无意义参数请求资源(比如在URL里面删除startDate等), 增加一些破坏性参数请求
      5. 使用sql注入攻击
      6. 使用其他的URL攻击, 目的: 看到跨权限域的报表
4. Logout测试

测试点一:logout后是否能进行上述的全部操作或者请求到资源

测试点二:logout后是否能能通过页面后退/重新刷新/直接请求URL 重新登录

5. 屏蔽全部birt viewer中的默认serlvet, 只留下需要的servlet


为了解决上述问题, 我们需要对于访问报表模块的url进行控制和限制, 容易想到的一种解决方案是使用Filter拦截url, 如果url中带有的验证信息和session中存储的一致, 则视为通过验证, 将该url转由birt的servlet处理, 从而从应答中得到渲染后的报表

要使用以上方法需要注意几个地方:

1. 对birt viewer expose出来的全部url-pattern都要进行filter拦截, 对于其他未使用的url-pattern映射和servlet, 必须加以禁用

2. 保证在web环境下只有filter能转发请求到birt viewer的servlet

3. 要对报表参数进行拦截, e.x: 如果报表A定义了参数Pa, Pb, 则fitler需要检测Pa, Pb参数必须存在, 不为空的情况, 因为目前birt viewer中, 如果检测参数不存在或未填而弹出参数面板, 这样会给安全拦截带来不必要的麻烦, 用户可以使用自己的认证信息, 在参数面板中输入其他资源的参数而绕过认证


目前由于没找到禁用参数面板的方法, 则使用拦截器拦截全部的URL参数组合, 这样带来的坏处是: 对于处理同一角色下的报表集合, 则需要在同一逻辑段中判断所有情况, 并且报表的URL中带有的参数需要一个兼容全部参数的全集



为了避免以上问题, 方案B是对方案A的一种改进

方案B的解决思路为:

1.        将权限拦截移至BIRT VIEWER SERVLET 内部,由阅读BIRT源代码可知, 在BIRT VIEWER SERVLET内部也有认证接口代码,只不过没有实现,说明BIRT是扩展和保留了这种可能性

--引用BIRT 2.2 源代码:

package org.eclipse.birt.report.servlet

Class: BirtEngineServlet

    /**
     * Local authentication.
     *
     * @param request
     *            incoming http request
     * @param response
     *            http response
     * @return
     */
    protected boolean __authenticate(HttpServletRequest request,
               return true
    }

由源代码可知: 目前BIRT中的源码保留该接口, 返回true则通过认证, 返回false则被reject


2.        如果在Birt Viewer内部的servlet进行拦截,则任何的内部请求最终都会通过Birt viewer serlvet, 该请求无论是谁发起, 来自何方,  这样就全面的拦截了一切资源请求

 

3.        BIRT内部的Servlet可以调用开发者的认证类,此类的灵活性较大可以任意扩展

4.        通过加入一个java包来单独的实现权限模块,这样整个架构和逻辑完全可以自己控制

 

当然, 此方案的缺点在上文已经提了, 用户可根据自身项目情况适当调整解决方案, 在项目进度要求紧, 资源紧张, 而安全要求高的情况下, 方案B能较好的满足项目需要

 

下面对比下上述2中解决方案:

 

方案A的认证过程:

由方案A可知

 

BIRT SERVLET 没有检测 URL 权限的任何能力,任何 URL 只要到达了 BIRT SERVLET 都认为是合法而导致 BIRT REPORT ENGINE 的运行从而渲染出报表,对于如何 URL 直接到达 BIRT SERVLET ,唯一的安全拦截就是 AOP authentication module 这个 Filter ,对于这种情况,由于 BIRT SERVLET 是公开的,所以 Filter 需要拦截全部的 URL 组合情况,包括参数、资源、角色等,对于这样的拦截方式,如果有其他模块调用并转发了 BIRT SERVLET ,或者在通过 Filter 之后又修改了 URL 访问 SERVLET ,那么拦截是不起作用的

 

方案B的认证过程:

对于B方案的拦截过程,只要有请求发送到 BIRT SERVLET ,则 BIRT SERVLET 会调用 AOP authentication module 进行认证,如果通过则继续处理报表,如果不通过则 reject, 由于 BIRT SERVLET 是调用 REPORT ENGINE 唯一的入口,所以对于此入口的控制就可以控制任何企图直接调用 REPORT ENGINE 的行为,在这样的解决方案中,即使其他模块调用或转发了请求到 BIRT SERVLET ,则一样会被拦截

 

2 种解决方案的对比,我们可知:使用方案 B 能解决方案A不能解决的认证问题, 此方案缺点在于由于 BIRT SERVLET BIRT Viewer 框架中的模块,要插入 AOP Authentication module, 则我们需要修改 BIRT 的源文件.

 

Birt 源文件可从Birt的官方网站下载, 以下是我使用的CVS目录:

 

当下完Birt Viewer这个项目后, 将其导入Eclipse, 并解决包依赖的问题之后, 在Birt Viewer的build path中引用我们自己的项目, 就可以开始动手修改Birt Viewer的源文件了, 并且能在birt项目中调用我们自己项目的类

 

首先先要设计认证模块, 认证模块的设计思路为:

如果 module 应答为 false ,则认证不通过, true 为通过, module 通过公开一个 IAuth 接口给 BIRT SERVLET 来实现,接口方法为 boolean doAuth()

 

和Birt Viewer的集成方法为:

如图:

BaseAuth中定义了如下方法:

 

 

/**

  * 认证顺序为

  * 1. 认证 URL ,如果成功则设置资源权限

  * 2. 认证用户 session 如果成功则设置用户权限

  * 3. 认证角色

  * 4. 认证最小粒度

  */

public abstract class BaseAuth {

   

    /**

      * 认证用户角色

      * 即角色决定能访问的资源

      * 不同角色间的资源需要访问控制

      */

    protected abstract boolean authByRole();

   

    /**

      * 同一角色下,认证用户权限最小粒度

      * 最小粒度间的资源需要访问控制

      */

    protected abstract boolean authByMeta();

   

    /**

      * 检查用户的 session 是否合法,以及是否通过认证,是否过期等

      */

    protected abstract boolean authUserSession();

   

    /**

      * 检查 URL 的基本校验

      */

    protected abstract boolean authURL();

}


IAuth中的方法则为:

public interface IAuth {

    /*

     * 验证方法

     * ture -> 验证成功

     * false -> 验证失败

     */

    public boolean doAuth();

}

 

而在AuthByHttpRequest这个实现类中, 目前的实现是基于httprequest的认证, 将来可能会有通过IDAP, webservcie等认证方式, 根据项目的不同而实现不同

 

- doAuth() 返回的应是各个认证方法的逻辑与(&)运算, 比如

 

public   boolean doAuth() {

      return (authURL() && authUserSession() && authByRole() && authByMeta());

}

 

而在各个方法的实现根据项目和业务的不同而不同, 在此可自行调整

 

* Birt Viewer Servlet 的改动:

目前笔者改动的为

package org.eclipse.birt.report.servlet;

Class: BirtEngineServlet

为什么改动这个类,原因在于使用的servlet url pattern为preview, preview对应最基本的调用birt report engine的servlet, 不加任何的ajax, 或者webservice调用,因此也是速度最快的,顺便提一下,birt2.2版本的frameset这个servlet有bug, 当用frameset 在FreeBSD下渲染报表时,(由于Java没有发布FreeBSD版的官方JDK,目前使用的为非官方的DiabloJDK), 会出现CPU100%的情况,而使用preview却没有任何问题

 

实现 __authenticate  方法:

 

public   boolean _authenticate(HttpServletRequest request,
            HttpServletResponse response) {

 

                System.out.println("invoke the birt servlet!");
        IAuth au = AuthFactory.getHttpReqBasedAuth(request, response);
        boolean isAuth = au.doAuth();
        if(!isAuth) {
            try {
                response.sendRedirect(ERROR_URL);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
       
return isAuth;

}

 

这样就以最小的改动,将认证模块集成到Birt Viewer内部,接下来的问题就是如何编译成Viewer.jar并且发布到项目中

 

- 编译和部署

由于birt2.2的build过程和原来的完全不一样,笔者build了几次都失败了>_<, 如果有人知道如何使用随项目发布的ant build编译成功,不胜感激!

我采用的是比较简单的做法:直接替换class, 方法为:

1. 先在Eclipse通过编译,然后在output里面找到对应的BirtEngineServlet.class

2. 找到项目依赖的viewer.jar, 解压缩并替换掉BirtEngineServlet.class

3. 打包成新的viewer.jar(可以打包成.zip, 然后改个名字)

 

将新的viewer.jar引入到目前项目,则认证模块就可以开始工作了

 

在开发Birt报表过程中,开发者可能会发现一些数据库连接的问题

比如:目前有10张报表都使用同一个连接配置,比如连接到localhost 的mysql

在部署的时候可能会要去修改报表的连接,比如连接到服务器的mysql

则目前birt的版本提供了library功能,也就是说,只要在library里面配置了数据源,其他报表引用librar里面的即可

但是会有开发者发现有的时候并不起作用,原因在于:

只要在某张报表的design界面中打开了数据源连接,则会在该报表的xml源码中自动加入:

    <data-sources>
        <oda-data-source extensionID="org.eclipse.birt.report.data.oda.jdbc" name="Data Source" id="6"
 extends="datasource.Data Source">
            <property name="odaDriverClass">org.postgresql.Driver</property>
            <property name="odaURL">jdbc:postgresql://192.168.100.1:5432/testproperty>
            <property name="odaUser">user</property>
            <encrypted-property name="odaPassword" encryptionID="base64">c3UydmVu</encrypted-property>

        </oda-data-source>
    </data-sources>

 

只要有以红色标注的部分存在,那么birt一定会优先调用此段配置,而此段配置是每张报表独立的-_-!,也就是说,如果有20张报表,则要手动修改每张报表的配置

解决方案为手动删除掉此红色部分,那么birt又会优先调用library里面的配置项

 

但是具体开发过程中,开发者如果不小心点开了数据源配置,而加入此段代码,在部署时又没有发现,则部署时配置的数据库连接将不起作用

而在开发中,开发者对于每张报表的debug,切换数据库连接测试,是很常见的事

为了解决这个问题,笔者为birt2.2的报表开发了了一个小工具,该工具能部署前使用,一次性修改所有报表的连接

 

下载OdaDataSetting.zip并解压缩

里面有2个jar

BirtDesignToolWithGUI.jar 为图形化界面,适用于windows下面直接运行

BirtDesignTool.jar 为命令行输入,适用于unix系统

界面:

 

点击"Process" 则开始处理报表

目前版本中,关闭UI可能Java处理进程并未关闭,请手动在任务管理器里面杀掉进程即可

 

希望大家能解决和BIRT有关的任何问题,欢迎讨论

分享到:
评论
2 楼 alucardggg 2014-08-12  
marsseeker 写道
这样每次版本升级都要去改一遍源代码了,好痛苦啊!

我现在都直接用sed命令做了。。。
1 楼 marsseeker 2014-02-10  
这样每次版本升级都要去改一遍源代码了,好痛苦啊!

相关推荐

Global site tag (gtag.js) - Google Analytics