单独部署 url 安全之加解密函数

       
润乾报表与用户的系统集成,一般有两种方案。一是集成到用户系统中。二是将润乾单独部署到一个应用下。
两种方案各有利弊:第一种方案对于安全性等方面可以统一管理,但是报表本身如果数据量大并发大造成的压力会直接影响用户自己的系统。第二种方案在报表服务器承受压力过大数据量过大的时候,不会影响到用户本身的系统,也就是说就算报表服务器压力饱和进入等待状态,用户的系统也可以正常的使用,众所周知润乾报表的调用一般直接通过 url 的方式进行调用,那么第二种方案中的 url 就无法被用户系统所控制,造成了一定的安全隐患。

下面就介绍几种解决此类问题的方案之中的一种:对 url 进行加解密

案例场景:
通过用户系统访问一张报表时,当然会带着一些用户信息或是其他参数传递过来。如果这时这个 url 的明文被其他人记住,比如 url 拼参数 username=zhangsan,这时李四记住了这个 url 并且与自己访问该资源的 url 进行对比发现传入参数为用户姓名拼音,由于采用的是第二种方案,用户系统并不能控制,这样就可以肆无忌惮的去查看其他人的信息,导致信息的泄漏。

解决思路:
这时我们可以采取对 url 中的明文进行加解密的操作,通过用户系统传递参数之前进行加密,然后传递到报表服务器端,在 jsp 中或者报表中进行解密。(下文介绍是在报表中解密)

实现方法:
一、首先确定加解密规则,这里使用的是 AES 加解密方法,密钥为 16 位英文,由于是说明将密钥定义死在相应类中。
二、通过测试类将 username=zhangsan 加密为:
880CB3013AB0D0F3A91B1C36B323F6E40294D3BE6EEFAA38135690CA3DCC61A2 模拟用户系统传递参数到报表服务器的 url 为:
http://127.0.0.1:6001/demo/repor … A2&raq=demo.raq
三、编写 AES 加解密的自定义函数,接受参数时只用到解密函数,加密函数是在报表中超链接等位置再次使用时可以调用加密函数。
1、实现 AES 加解密的类
public class AES {  
      
    private static final String AES = “AES”;  

    private static final String CRYPT_KEY = “aaaaaaaaaaaaaaaa”;  

    /**
     * 加密
     *  
     * @param encryptStr
     * @return
     */
    public static byte[] encrypt(byte[] src, String key) throws Exception {  
        Cipher cipher = Cipher.getInstance(AES);  
        SecretKeySpec securekey = new SecretKeySpec(key.getBytes(), AES);  
        cipher.init(Cipher.ENCRYPT_MODE, securekey);// 设置密钥和加密形式  
        return cipher.doFinal(src);  
    }  

    /**
     * 解密
     *  
     * @param decryptStr
     * @return
     * @throws Exception
     /
    public static byte[] decrypt(byte[] src, String key)  throws Exception  {  
        Cipher cipher = Cipher.getInstance(AES);  
        SecretKeySpec securekey = new SecretKeySpec(key.getBytes(), AES);// 设置加密 Key  
        cipher.init(Cipher.DECRYPT_MODE, securekey);// 设置密钥和解密形式  
        return cipher.doFinal(src);  
    }  
      
    /
*
     * 二行制转十六进制字符串
     *  
     * @param b
     * @return
     */
    public static String byte2hex(byte[] b) {  
        String hs = "";  
        String stmp = "";  
        for (int n = 0; n < b.length; n++) {  
            stmp = (java.lang.Integer.toHexString(b[n] & 0XFF));  
            if (stmp.length() == 1)  
                hs = hs + “0” + stmp;  
            else
                hs = hs + stmp;  
        }  
        return hs.toUpperCase();  
    }  

    public static byte[] hex2byte(byte[] b) {  
        if ((b.length % 2) != 0)  
            throw new IllegalArgumentException(“长度不是偶数”);  
        byte[] b2 = new byte[b.length / 2];  
        for (int n = 0; n < b.length; n += 2) {  
            String item = new String(b, n, 2);  
            b2[n / 2] = (byte) Integer.parseInt(item, 16);  
        }  
        return b2;  
    }  
      
    /**
     * 解密
     *  
     * @param data
     * @return
     * @throws Exception
     */
    public final static String decrypt(String data) {  
        try {  
            return new String(decrypt(hex2byte(data.getBytes()),  
                    CRYPT_KEY));  
        } catch (Exception e) {  
        }  
        return null;  
    }  

    /**
     * 加密
     *  
     * @param data
     * @return
     * @throws Exception
     */
    public final static String encrypt(String data) {  
        try {  
            return byte2hex(encrypt(data.getBytes(), CRYPT_KEY));  
        } catch (Exception e) {  
        }  
        return null;  
    }  
      
}

2、润乾自定义加解密函数中调用 AES 类
加密自定义函数:
public class MyEncode extends Function {

    public Object calculate(Context ctx, boolean isInput) {

       // 判断参数个数
       if (this.paramList.size() < 1) {
           MessageManager mm = EngineMessage.get();
           throw new ReportError(“encrypt:”
                  + mm.getMessage(“function.missingParam”));
       }
       // 取得第一个参数, 默认为表达式,需要把该表达式算出来,结果才是函数的参数值
       Expression param1 = (Expression) this.paramList.get(0);
       if (param1 == null) { // 判断参数是否为空
           MessageManager mm = EngineMessage.get();
           throw new ReportError(“encrypt:”
                  + mm.getMessage(“function.invalidParam”));
       }
        // 算出第一个参数值
       Object result1 = Variant2.getValue(param1.calculate(ctx, isInput),
              false, isInput);
       // 判断第一个参数值是否为空
       if (result1 == null) {
           return null;
       }

       AES aes = new AES();
       String value = aes.encrypt(result1.toString());
       return value;
    }
}
解密自定义函数:
public class MyDecode extends Function {

    public Object calculate(Context ctx, boolean isInput) {

       // 判断参数个数
       if (this.paramList.size() < 1) {
           MessageManager mm = EngineMessage.get();
           throw new ReportError(“encrypt:”
                  + mm.getMessage(“function.missingParam”));
       }
       // 取得第一个参数, 默认为表达式,需要把该表达式算出来,结果才是函数的参数值
       Expression param1 = (Expression) this.paramList.get(0);
       if (param1 == null) { // 判断参数是否为空
           MessageManager mm = EngineMessage.get();
           throw new ReportError(“encrypt:”
                  + mm.getMessage(“function.invalidParam”));
       }
       // 算出第一个参数值
       Object result1 = Variant2.getValue(param1.calculate(ctx, isInput),
              false, isInput);
       if (result1 == null) {
           return null;
       }
       AES aes = new AES();
       String value = aes.decrypt(result1.toString());
      
       return value;
    }
}
四、 自定义函数注册
1,将编译后的 class 文件复制到
    \reportHome\designer\web\WEB-INF\classes
    \reportHome\webapps\demo\WEB-INF\classes
2,在
\reportHome\designer\web\WEB-INF\classes\config\customFunctions.properties 文件加入以下内容.
    encode=0,com.runqian.MyEncode
    decode=0,com.runqian.MyDecode
3,如果 \reportHome\webapps\demo\WEB-INF\classes 目前下没有 config 目录,则到 \reportHome\designer\classes 下复制.
如果已经存在,在该目录下执行上述步骤 2.

重新启动设计器服务器。

在设计器中测试内容:
=encode(“ceshi”);
=decode(“555DF8AB73DAB1FF191A2B977430E02B”);

出现结果为正确.

五、报表中设定参数 aes 接受 url 中传递过来的值,通过动态参数调用解密函数将真实值取到进行报表中的运算 |