Maven WEB 项目使用ProGuard进行混淆,最佳解决方案

近期公司的Android项目做了混淆,虽说对于保护代码并不是100%的,但混淆后的代码可以使那些不法份子难以阅读,这样也能对代码的保护做出贡献。
于是,公司写的一大堆WEB项目也想做保护。但几大问题随之而来:

公司的所有项目全部是Maven项目,网上的混淆方案不是陈旧就是无效
网上的大部分解决方案感觉像是对简单DEMO进行混淆,根本不能用于复杂的WEB项目中
网上的大部分解决方案是针对Android项目的,针对WEB的少之又少
针对以上问题,本人花费一个月研究了WEB+Maven项目的混淆,终于收获果实,解决了这一大空缺难题。

项目介绍
就如之前所述,我们要混淆的项目绝不是一个简单的WEB DEMO,必须要包含了大量第三方框架。
本文中介绍的项目使用了主流的一些框架:

Spring 4.1.1.RELEASE
SpringMVC 4.1.1.RELEASE
JackSon 2.5.0
MyBatis 3.3.0
Shiro 1.2.3
Log4J 1.2.17
SLF4J 1.7.10
Druid Pool 1.0.15
patchca 1.0.0
Jetty 9.2.7.v20150116
项目包结构

该项目是典型的Maven WEB项目,对于Maven WEB项目的结构不再赘述,这里对各种包做一下解释:

annotation 注解包,里面是自己写的注解类,主要混淆对象
controller SpringMVC的控制器包,主要混淆对象
credntials Shiro的自定义凭证,次要混淆对象
dao DAO包,主要混淆对象
exception 异常包,自定义了一些异常,主要混淆对象
filter Shiro的自定义过滤器,次要混淆对象
interceptor Shiro的自定义拦截器,次要混淆对象
job SpringTASK的定时任务包,次要混淆对象
mapper Mybatis的XML映射文件包,非混淆对象
model 实体包,非混淆对象
realm Shiro的自定义域包,次要混淆对象
service 实体的服务包,次要混淆对象
token Shiro的自定义令牌包,次要混淆对象
utils 公司自己的工具类,主要混淆对象
主要混淆对象 对类的名称、属性、方法名都进行混淆
次要混淆对象 对类的名称不混淆,类的属性、方法名选择性混淆
非混淆对象 不进行混淆,混淆后可能出现异常

Maven 配置(pom.xml)
本文的重头戏,使用Maven集成的ProGuard插件,混淆配置不用单独建立文件

<?xml version=”1.0″ encoding=”UTF-8″?>
<project xmlns=”http://maven.apache.org/POM/4.0.0″
xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<groupId>…</groupId>
<artifactId>zhukun.shiro-spring</artifactId>
<version>1.0-SNAPSHOT</version>

<!– 属性–>
<properties>
<!– 项目编码–>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<!– 单元测试包–>
<junit.version>4.12</junit.version>
<!– JAVAEE支持包–>
<jstl.version>1.2</jstl.version>
<servlet.version>3.1.0</servlet.version>
<!– 日志包–>
<log4j.version>1.2.17</log4j.version>
<slf4j.version>1.7.10</slf4j.version>
<aspectj.version>1.6.12</aspectj.version>
<!– commons支持–>
<commons-logging.version>1.1.3</commons-logging.version>
<commons-collections.version>3.2.1</commons-collections.version>
<commons-fileupload.version>1.3.1</commons-fileupload.version>
<!– shiro安全框架–>
<shiro.version>1.2.3</shiro.version>
<!– druid连接池–>
<druid.version>1.0.15</druid.version>
<!– 数据库及数据库框架–>
<mysql.version>5.1.30</mysql.version>
<mybatis.version>3.3.0</mybatis.version>
<mybatis-spring.version>1.2.3</mybatis-spring.version>
<!– Mybatis分页插件–>
<mybatis-paginator.version>1.2.16</mybatis-paginator.version>
<!– Mybatis生成器插件–>
<mybatis-generator.version>1.3.2</mybatis-generator.version>
<!– Spring及SpringMVC支持包–>
<spring.version>4.1.1.RELEASE</spring.version>
<jackson.version>2.5.0</jackson.version>
<!– 验证码支持包–>
<patchca.version>1.0.0</patchca.version>
<!– Jetty插件–>
<jetty.version>9.2.7.v20150116</jetty.version>
<!– Maven编译插件–>
<maven-compiler.version>2.3.2</maven-compiler.version>
</properties>

<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>jstl</artifactId>
<version>${jstl.version}</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>${log4j.version}</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>${aspectj.version}</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-log4j12</artifactId>
<version>${slf4j.version}</version>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>${commons-logging.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-core</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-web</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-spring</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-ehcache</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>org.apache.shiro</groupId>
<artifactId>shiro-quartz</artifactId>
<version>${shiro.version}</version>
</dependency>
<dependency>
<groupId>commons-collections</groupId>
<artifactId>commons-collections</artifactId>
<version>${commons-collections.version}</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql.version}</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>${servlet.version}</version>
</dependency>
<dependency>
<groupId>commons-fileupload</groupId>
<artifactId>commons-fileupload</artifactId>
<version>${commons-fileupload.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>${mybatis.version}</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>${mybatis-spring.version}</version>
</dependency>
<dependency>
<groupId>com.github.miemiedev</groupId>
<artifactId>mybatis-paginator</artifactId>
<version>${mybatis-paginator.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>${jackson.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>${spring.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.patchca</groupId>
<artifactId>patchca</artifactId>
<version>${patchca.version}</version>
</dependency>
</dependencies>

<build>
<finalName>shiro-spring</finalName>
<!–使Maven打包时能打包src目录下的XML文件–>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>${maven-compiler.version}</version>
<configuration>
<source>1.7</source>
<target>1.7</target>
<encoding>UTF-8</encoding>
</configuration>
</plugin>
<plugin>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-maven-plugin</artifactId>
<version>${jetty.version}</version>
<configuration>
<webApp>
<contextPath>/shiro-spring</contextPath>
</webApp>
<httpConnector>
<!– 设置端口–>
<port>8080</port>
</httpConnector>
</configuration>
</plugin>

<!– MyBatis自动生成Mapper插件–>
<plugin>
<groupId>org.mybatis.generator</groupId>
<artifactId>mybatis-generator-maven-plugin</artifactId>
<version>${mybatis-generator.version}</version>
<configuration>
<verbose>true</verbose>
<overwrite>true</overwrite>
</configuration>
</plugin>

<!– ProGuard混淆插件–>
<plugin>
<groupId>com.github.wvengen</groupId>
<artifactId>proguard-maven-plugin</artifactId>
<version>2.0.11</version>
<executions>
<execution>
<!– 混淆时刻,这里是打包的时候混淆–>
<phase>package</phase>
<goals>
<!– 使用插件的什么功能,当然是混淆–>
<goal>proguard</goal>
</goals>
</execution>
</executions>
<configuration>
<!– 是否将生成的PG文件安装部署–>
<attach>true</attach>
<!– 是否混淆–>
<obfuscate>true</obfuscate>
<!– 指定生成文件分类 –>
<attachArtifactClassifier>pg</attachArtifactClassifier>
<options>
<!– JDK目标版本1.7–>
<option>-target 1.7</option>
<!– 不做收缩(删除注释、未被引用代码)–>
<option>-dontshrink</option>
<!– 不做优化(变更代码实现逻辑)–>
<option>-dontoptimize</option>
<!– 不路过非公用类文件及成员–>
<option>-dontskipnonpubliclibraryclasses</option>
<option>-dontskipnonpubliclibraryclassmembers</option>
<!– 优化时允许访问并修改有修饰符的类和类的成员 –>
<option>-allowaccessmodification</option>
<!– 确定统一的混淆类的成员名称来增加混淆–>
<option>-useuniqueclassmembernames</option>
<!– 不混淆所有包名,本人测试混淆后WEB项目问题实在太多,毕竟Spring配置中有大量固定写法的包名–>
<option>-keeppackagenames</option>
<!– 不混淆所有特殊的类–>
<option>-keepattributes Exceptions,InnerClasses,Signature,Deprecated,SourceFile,LineNumberTable,LocalVariable*Table,*Annotation*,Synthetic,EnclosingMethod</option>
<!– 不混淆所有的set/get方法,毕竟项目中使用的部分第三方框架(例如Shiro)会用到大量的set/get映射–>
<option>-keepclassmembers public class * {void set*(***);*** get*();}</option>

<!– 不混淆job包下的所有类名,且类中的方法也不混淆–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.job.** { &lt;methods&gt;; }</option>
<!– 不混淆filter包下的所有类名,这里主要是对Shiro的路踢人过滤器混淆,对类的属性和方法进行了混淆–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.filter.** </option>
<!– 不混淆凭证包下的所有类名,但对类中的属性、方法进行混淆,原因是Spring配置中用到了这个类名–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.credntials.** </option>
<!– 混淆目的同上,这个是拦截器的包,包中有防止重复提交的拦截器–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.interceptor.** </option>
<!– 混淆目的同上,这个是域包,包中有用户登录域–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.realm.** </option>
<!– 不混淆model包中的所有类以及类的属性及方法,实体包,混淆了会导致ORM框架及前端无法识别–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.model.** {*;}</option>
<!– 以下两个包因为大部分是Spring管理的Bean,不对包类的类名进行混淆,但对类中的属性和方法混淆–>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.service.** </option>
<option>-keep class com.chinatelecom.gz.wy.zhukun.shiro_spring.dao.**</option>
</options>
<outjar>${project.build.finalName}-pg.jar</outjar>
<!– 添加依赖,这里你可以按你的需要修改,这里测试只需要一个JRE的Runtime包就行了 –>
<libs>
<lib>${java.home}/lib/rt.jar</lib>
</libs>
<!– 加载文件的过滤器,就是你的工程目录了–>
<inFilter>com/chinatelecom/gz/wy/zhukun/shiro_spring/**</inFilter>
<!– 对什么东西进行加载,这里仅有classes成功,毕竟你也不可能对配置文件及JSP混淆吧–>
<injar>classes</injar>
<!– 输出目录–>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
</plugins>
</build>
</project>

以上代码中的注释足够各位参考了,若有问题欢迎留言

执行
clean package -DskipTests

使用Maven运行以上代码,执行完成后在target目录中会生成三个文件:

classes-pg.jar 混淆后的classes文件,里面包含完整的项目结构
proguard_map.txt 混淆内容的映射
proguard_seed.txt 参与混淆的类
混淆完成后,将classes-pg.jar解压到应用服务器覆盖原有的classes文件,通常目录为

X:\jetty9或tomcat7\webapps\shiro-spring\WEB-INF\classes

运行服务,项目运行正常

反编译
既然是混淆了的代码,那我们现在作为盗码者来反编译一下classes文件

可以看出,混淆成功了,盗码者读起来不是一二般的痛苦,我们的目的已经达到

遗留问题
虽然混淆是在Maven打包的时候进行,但是生成的war包及classes目录并未混淆,还需要将jar包中的内容提取,比较麻烦,不知道有没有让生成的war包就是已经混淆的办法。
本人的JAVA环境是JDK1.7 64位,其它的JDK并未尝试
不能对Spring等配置文件混淆,这样包结构还是存在,减弱了盗码者的读码难度