Java Agent修改Shiro key

前言

​ 之前佬哥给我提了个需求,如何修改Shiro Key,我们知道Shiro在高版本key默认是随机生成的,没有在配置文件中,即使在配置文件中配置了key,也要重启服务器完成修改也不太好,虽然之前知道可以通过Java Agent的方式完成修改,但当时没有了解过这个技术,最近刚好学习了Java Agent,所以抽空完成提过的需求。

实现

​ 我们只要找到Shiro加载key部分的代码,将key的值改掉即可。shiro的解密逻辑在AbstractRememberMeManager#decrypt中。可以看到解密的key是从getDecryptionCipherKey()得到的。

1
2
3
4
5
6
7
8
9
protected byte[] decrypt(byte[] encrypted) {
byte[] serialized = encrypted;
CipherService cipherService = getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.decrypt(encrypted, getDecryptionCipherKey());
serialized = byteSource.getBytes();
}
return serialized;
}

getDecryptionCipherKey是直接返回了decryptionCipherKey的结果,所以我们可以:

  • 修改getDecryptionCipherKey函数的返回结果
  • 修改decryptionCipherKey属性值
1
2
3
public byte[] getDecryptionCipherKey() {
return decryptionCipherKey;
}

修改getDecryptionCipherKey函数的返回结果

​ 修改getDecryptionCipherKeyagent实现如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public byte[] transform(ClassLoader loader, String className, Class<?> classBeingRedefined,
ProtectionDomain protectionDomain, byte[] classfileBuffer) throws IllegalClassFormatException {
if(className.equals("org/apache/shiro/mgt/AbstractRememberMeManager")){
try { ClassPool classPool = ClassPool.getDefault();
classPool.appendClassPath(new LoaderClassPath(loader));
CtClass clazz = classPool.makeClass(new ByteArrayInputStream(classfileBuffer), false);
CtMethod method = clazz.getDeclaredMethod("getDecryptionCipherKey");
method.setBody("return (org.apache.shiro.codec.Base64.decode(\"4AvVhmFLUs0KTA3Kprsdag==\"));");
return clazz.toBytecode();
} catch (Exception e) {
e.printStackTrace();
}
}
return null;
}

依赖包坑点

​ 虽然实现本身非常简单,但是在测试的过程中遇到了一个小坑,就是ClassPool后的代码一直没有执行,调试了很久才发现是因为没有打包javasist的依赖。

​ 还有一个坑点是因为我刚开始是使用了maven构建项目的,没打包jar是可以正常执行的,打包后会爆找不到AttachNotSupportedException类的异常,经过排查是因为依赖了Tools.jar,但这个依赖在pom.xml中的配置如下:

1
2
3
4
5
6
7
<dependency>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
<version>1.8</version>
<scope>system</scope>
<systemPath>${java.home}/../lib/tools.jar</systemPath>
</dependency>

​ 可以看到依赖的是本地系统的jar,而非远程下载的jar包,所以在打包的时候没有将这个jar打包进去,最后我是使用了手动将tools.jar添加Libs目录中解决了问题。

获取目录问题

​ 由于我是将AttachAgent打包在一个jar包中的,所以要在运行是获取到jar包的路径并通过loadAgent函数加载,查阅资料后发现可以通过下面的语句获取。

1
String agentpath= AttachAgent.class.getProtectionDomain().getCodeSource().getLocation().getPath();

​ 但是这样获取的路径是不能直接使用的,在windows下是多了一个/的,所以我们要把它处理掉。

image-20211021110218088

1
2
3
   if(agentpath.contains("C:")||agentpath.contains("D:")||agentpath.contains("E:")){
agentpath=agentpath.substring(1);
}

更改加密key

​ 虽然上面已经更改了解密的key,但是如果解密的key和加密的key不一致,那么如果用户正常使用Remeberme记住密码就会导致登录不成功,所以我们我们还要修改加密的key,保证不影响网站的正常使用。

​ 加密的逻辑是在getEncryptionCipherKey中实现的, 所以我们只要更改这个函数的返回值就可以了。

1
2
3
4
5
6
7
8
9
protected byte[] encrypt(byte[] serialized) {
byte[] value = serialized;
CipherService cipherService = getCipherService();
if (cipherService != null) {
ByteSource byteSource = cipherService.encrypt(serialized, getEncryptionCipherKey());
value = byteSource.getBytes();
}
return value;
}

​ 所以只要在transform中再修改下getEncryptionCipherKey方法的返回值即可。

1
2
CtMethod method2 = clazz.getDeclaredMethod("getEncryptionCipherKey");
method2.setBody("return (org.apache.shiro.codec.Base64.decode(\"4AvVhmFLUs0KTA3Kprsdag==\"));");

总结

​ 虽然还可以通过修改字段的方式实现,不过既然已经满足了需求,就先不分析另一种方法了。

参考