java war包混淆,spring项目的代码混淆(proguard)

前不久被要求对java web的war包做代码混淆,我使用proguard6.0.3完成的。后面也许还会用到,因此记录下过程和配置方法,demo代码按照图1结构进行组织。仅对混淆功能进行说明,其他功能如压缩、优化不考虑。

混淆需求

混淆代码的需求如下:

混淆包名、类名、成员属性、方法名等。

不混淆涉及到dao命名空间配置、aop切点的类名或方法名。

不混淆注解。

不混淆proguard默认配置的类名或方法名。

本Demo中按照需求表现为:

混淆controller和service中的包名、类名、成员属性、方法名。

假设aop切面涉及到controller包名,因此不混淆controller包名;假设aop切面涉及到service.findOne()方法,因此不混淆service类的findOne()方法;dao中所有类涉及到mybatis命名空间,因此不混淆dao中所有类。

不能混淆注解,如@RestController,@Service等,否则spring无法正确解析。

proguard默认配置了不混淆enum、native方法等,保留这些默认配置。

java war包混淆,spring项目的代码混淆(proguard)_第1张图片

图1 Demo代码包结构

PROGUARD配置实践

proguard的配置可以先使用GUI版程序进行配置,然后将配置复制出来保存为.pro文件,再使用命令行执行混淆操作(命令行执行混淆比GUI更快)。

图2~图4为GUI界面的混淆配置:

java war包混淆,spring项目的代码混淆(proguard)_第2张图片

图2 PROGUARD混淆界面

图2中显示了混淆的基本配置:

Obfuscates表示开启混淆。

Print mapping表示混淆的映射保存到的文件,一定要保存,否则后期排查无法找到混淆前的名字。

Keep package names表示不混淆的包名。

Keep attributes表示不混淆的属性,其中包含Annotation。

Native method names表示不混淆native方法

Keep additional class names and class member names表示不混淆类名或成员名,用于配置具体的不混淆的类、属性或方法,见图3、图4。

图3为类名的混淆配置,即配置不混淆的类(keep表示保留,即不混淆)。Class栏填写表示不混淆的类,现需求为只对controller和service进行混淆,也就是除了这2个包之外所有的包都不混淆,所以是keep!controller且!service包,那么配置将为图3中所示。

java war包混淆,spring项目的代码混淆(proguard)_第3张图片

图3 类名混淆配置

根据测试,发现proguard的设定为:如果只设置Class栏,而不设置Class member栏,那么只会保留其他包和类的包名和类名,而成员属性和方法依然会被混淆,因此必须指定所有的属性和方法名,即图3中所示(也可为*)。

图4为方法名(或成员属性)的混淆配置,即配置不混淆的方法名,按图配置即可,Keep栏需要选择Keep class members only。

java war包混淆,spring项目的代码混淆(proguard)_第4张图片

图4 方法名混淆配置

混淆的配置暂时只用到上述配置,其余配置含义可以通过查阅proguard文档获知。

另外,还有一些额外的附加配置,比如打印参数,JDK版本以及jar包问题,图5为额外配置。

java war包混淆,spring项目的代码混淆(proguard)_第5张图片

图5 额外配置

Preverify勾选表示开启预检查,建议勾选,否则可能混淆出来的jar或class无法使用。

Target选择对应版本,选错了好像也没有发现有什么异样。

Note potential mistakes in the configuration去掉勾选表示不打印NOTE信息,建议去掉勾选,否则控制台会打印很多没必要查看的信息,并且会降低混淆执行速度。

Warn about possibly erroneous input勾选表示打印WARN信息,建议勾选,如果有错误可以查看。

Ignore warnings about possibly erroneous input勾选表示忽略warn信息,建议勾选,否则会因为warn而无法混淆,很多时候warn只作参考,并不会对混淆结果造成影响。

Skip non-public library class members默认会勾选,据提示信息可以得知可以执行更快速,但如果有需求也可以去掉勾选。

Keep directories勾选表示混淆后的jar包保持原有的目录,如果混淆了包名,那么这些包名原始的目录或者其中的目录都将是空文件夹,建议勾选,否则有可能运行混淆的代码找不到jar包中的class。

配置完毕后,到proguard左侧的Process里,执行下方的View configuration可以得到配置脚本,脚本见文末案列.pro配置文件及注释。

混淆WAR包实践

从图1中可以看到,common、repository、service模块是单独的模块,分别包含各自模块或MVC架构层的代码,web模块是webapp所在的模块,因此当项目打war包后会出现图6所示的包结构,前3者的包会以jar包的形式出现在lib目录中,而web模块中的controller代码将在war包的classes中。

java war包混淆,spring项目的代码混淆(proguard)_第6张图片

图6 打war包后的包结构

此时如果按照这样的包结构直接将war包执行混淆,那么混淆后的war包会出现web模块的controller代码无法混淆的情况,并且如果没有设置指定keepdirectories,那么controller等代码还会被删除掉。

出现上述问题的原因是proguard只支持混淆jar包和存放在包名层级目录的父目录下的class文件。jar包很好理解,关键是class文件的混淆。假设class为p1.p2.C1,那么对应的包名层级目录为p1/p2/C1.class,此时如果要混淆C1.class,那么需要上述包名层级目录有父目录,假设为classes,即目录层级为classes/p1/p2/C1.class,然后proguard配置路径为classes才可行。

备注:如果proguard配置路径为class文件,即C1.class则会得到warn提示Warning: class [C1.class] unexpectedly contains class [p1.p2.C1]而导致无法混淆;如果配置路径不是classess而是p1,那么将得到warn提示Warning: class [p2/C1.class] unexpectedly contains class [p1.p2.C1]。看到这里应该能明白为什么要配置为父目录classes了吧。

再回过来看看图6,war包根目录下的文件夹是WEB-INF,然后才是class层级目录的父目录classes,根据上述描述,proguard只支持混淆存放在包名层级目录的父目录下的class文件,因此如果直接对war执行混淆,controller的代码会多出一层目录,导致无法混淆。

如果要执行混淆,办法有2个:

1、将web包下的所有class代码移到单独的模块,与common等这种模块类似

2、war包拆开后对jar包和class文件分别混淆,再合并打成war包

方法1肯定更方便,但是由于项目已经按照图6的结构完成了,如果贸然改变包结构可能会有风险,而且其他项目也是按照这种结构架设的,如果都去更改模块结构会特别麻烦。因此考虑采用方法2,这种方式虽然看起来麻烦,但可以编写脚本来完成拆包和打包任务,最重要的是其他项目也是可以复用该脚本的。

拆分war包需要将war包解压,然后对jar包和WEB-INF/classes下的class文件分别混淆,再将混淆后的文件合并回解压文件夹,最后再重新打成war包即可。具体方法不赘述,仅列出war包的解压、打包方法、混淆命令行语句。需要注意的是合并混淆文件之前要先删除WEB-INF/classes下的class文件,因为混淆后包名会改变,不会覆盖原先的文件,而jar包混淆后文件名不变,是可以覆盖原先的jar包的。

1

2

3

4

5

6

7

8#解压,只能解压到当前目录

jar -xf proj.war

#打包,将proj下面的所有文件打包到proj-obfuscate.war包中

jar -cf proj-obfuscate.war -C proj/ .

#proguard按照config.pro执行

java -jar proguard.jar @config.pro

执行完成,可以得到混淆后的war包,包结构如图7所示。

java war包混淆,spring项目的代码混淆(proguard)_第7张图片

图7 混淆后的war包结构

案例代码

1. .pro配置文件及注释1

2

3

4

5

6

7# 输入输出,分别配置classes下的class文件和jar包混淆的输入输出

# 括号中为过滤的文件,即不查询的文件,如果混淆过程中报错文件找不到,可以将该文件过滤掉

-injars temp-orgin\WEB-INF\classes(!org/aspectj/org/eclipse/jdt/internal/compiler/parser/UpdateParserFiles.class)

-outjars temp-obfuscated\WEB-INF\classes

-injars temp-orgin\WEB-INF\lib\service-6.0.3.jar(!org/aspectj/org/eclipse/jdt/internal/compiler/parser/UpdateParserFiles.class)

-outjars temp-obfuscated\WEB-INF\lib\service-6.0.3.jar

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# 混淆配置

-target 1.8

-libraryjars C:\Program Files\Java\jdk1.8.0_40\jre\lib\rt.jar

-dontnote

-ignorewarnings

-dontshrink

-dontoptimize

-keepdirectories

-printmapping 'mapping.txt'

# keep配置

-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod

# controller包名不混淆,否则aop无法切入

-keeppackagenames online.dinghuiye.controller

# Obfuscate controller and service

-keep class !online.dinghuiye.controller.**,!online.dinghuiye.service.** {

;

;

}

# 不混淆指定方法名的方法

-keepclassmembers class * {

*** findOne(...);

}

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22# 保留的proguard的默认配置

# Also keep - Enumerations. Keep the special static methods that are required in

# enumeration classes.

-keepclassmembers enum * {

public static **[] values();

public static ** valueOf(java.lang.String);

}

# Also keep - Database drivers. Keep all implementations of java.sql.Driver.

-keep class * extends java.sql.Driver

# Also keep - Swing UI L&F. Keep all extensions of javax.swing.plaf.ComponentUI,

# along with the special 'createUI' method.

-keep class * extends javax.swing.plaf.ComponentUI {

public static javax.swing.plaf.ComponentUI createUI(javax.swing.JComponent);

}

# Keep - Native method names. Keep all native class/method names.

-keepclasseswithmembers,includedescriptorclasses class * {

native ;

}

2. 混淆前后代码对比SomeUtil没有混淆

SomeDao没有混淆

SomeService

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23// 混淆前

package online.dinghuiye.service;

import java.util.List;

public interface SomeService

{

Object findOne();

List findList();

}

// 混淆后

package a.a.a;

import java.util.List;

public abstract interface a

{

public abstract Object findOne();

public abstract List a();

}

SomeServiceImpl

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

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59// 混淆前

package online.dinghuiye.service.impl;

import online.dinghuiye.repository.dao.SomeDao;

import online.dinghuiye.service.SomeService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

import java.util.List;

@Service

public class SomeServiceImpl implements SomeService

{

@Autowired

private SomeDao dao;

@Override

public Object findOne(){

List list = dao.findList();

if (list.size() > 0) {

return list.get(0);

}

return null;

}

@Override

public List findList(){

return dao.findList();

}

}

// 混淆后

package a.a.a.a;

import java.util.List;

import online.dinghuiye.repository.dao.SomeDao;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.stereotype.Service;

@Service

public class a implements a.a.a.a

{

@Autowired

private SomeDao a;

public Object findOne()

{

List list = this.a.findList();

if (list.size() > 0) {

return list.get(0);

}

return null;

}

public List a()

{

return this.a.findList();

}

}

SomeController

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

38

39

40

41// 混淆前

package online.dinghuiye.controller;

import online.dinghuiye.service.SomeService;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping("/")

public class SomeController

{

@Autowired

private SomeService service;

@RequestMapping("list")

public Object findList(){

return service.findList();

}

}

// 混淆后

package online.dinghuiye.controller;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.web.bind.annotation.RequestMapping;

import org.springframework.web.bind.annotation.RestController;

@RestController

@RequestMapping({"/"})

public class a

{

@Autowired

private a.a.a.a a;

@RequestMapping({"list"})

public Object a()

{

return this.a.a();

}

}

3. mapping.txt1

2

3

4

5

6

7

8

9

10

11

12online.dinghuiye.controller.SomeController -> online.dinghuiye.controller.a:

online.dinghuiye.service.SomeService service -> a

13:13:void () ->

20:20:java.lang.Object findList() -> a

online.dinghuiye.service.SomeService -> a.a.a.a:

java.lang.Object findOne() -> findOne

java.util.List findList() -> a

online.dinghuiye.service.impl.SomeServiceImpl -> a.a.a.a.a:

online.dinghuiye.repository.dao.SomeDao dao -> a

14:14:void () ->

21:25:java.lang.Object findOne() -> findOne

30:30:java.util.List findList() -> a

参考文档

你可能感兴趣的:(java,war包混淆)