这可以通过使用Byte Buddy 之类的库编织字节码来实现。除了使用标准ReflectPermission("suppressAccessChecks")
权限,您可以创建自定义权限并将AccessibleObject.setAccessible
方法替换为使用字节好友转换检查您的自定义权限的自定义方法。
此自定义权限起作用的一种可能方式是使其访问基于调用者的类加载器和正在修改访问的对象。使用它允许隔离代码(使用它自己的类加载器从单独的代码加载)调用setAccessible
它自己的 jar 中的类,但不能调用标准 Java 类或您自己的应用程序类。
这样的权限可能如下所示:
public class UserSetAccessiblePermission extends Permission {
private final ClassLoader loader;
public UserSetAccessiblePermission(ClassLoader loader) {
super("userSetAccessible");
this.loader = loader;
}
@Override
public boolean implies(Permission permission) {
if (!(permission instanceof UserSetAccessiblePermission)) {
return false;
}
UserSetAccessiblePermission that = (UserSetAccessiblePermission) permission;
return that.loader == this.loader;
}
// equals and hashCode omitted
@Override
public String getActions() {
return "";
}
}
这就是我选择实现此权限的方式,但它可以是包或类白名单或黑名单。
现在有了这个权限,你可以创建一个存根类来替换AccessibleObject.setAcessible
方法来使用这个权限。
public class AccessibleObjectStub {
private final static Permission STANDARD_ACCESS_PERMISSION =
new ReflectPermission("suppressAccessChecks");
public static void setAccessible(@This AccessibleObject ao, boolean flag)
throws SecurityException {
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
Permission permission = STANDARD_ACCESS_PERMISSION;
if (isFromUserLoader(ao)) {
try {
permission = getUserAccessPermission(ao);
} catch (Exception e) {
// Ignore. Use standard permission.
}
}
sm.checkPermission(permission);
}
}
private static Permission getUserAccessPermission(AccessibleObject ao)
throws IllegalAccessException, InvocationTargetException, InstantiationException,
NoSuchMethodException, ClassNotFoundException {
ClassLoader aoClassLoader = getAccessibleObjectLoader(ao);
return new UserSetAccessiblePermission(aoClassLoader);
}
private static ClassLoader getAccessibleObjectLoader(AccessibleObject ao) {
return AccessController.doPrivileged(new PrivilegedAction<ClassLoader>() {
@Override
public ClassLoader run() {
if (ao instanceof Executable) {
return ((Executable) ao).getDeclaringClass().getClassLoader();
} else if (ao instanceof Field) {
return ((Field) ao).getDeclaringClass().getClassLoader();
}
throw new IllegalStateException("Unknown AccessibleObject type: " + ao.getClass());
}
});
}
private static boolean isFromUserLoader(AccessibleObject ao) {
ClassLoader loader = getAccessibleObjectLoader(ao);
if (loader == null) {
return false;
}
// Check that the class loader instance is of a custom type
return UserClassLoaders.isUserClassLoader(loader);
}
}
有了这两个类,您现在可以使用 Byte Buddy 构建一个转换器,用于转换 JavaAccessibleObject
以使用您的存根。
创建转换器的第一步是创建一个 Byte Buddy 类型池,其中包括引导类和包含存根的 jar 文件。
final TypePool bootstrapTypePool = TypePool.Default.of(
new ClassFileLocator.Compound(
new ClassFileLocator.ForJarFile(jarFile),
ClassFileLocator.ForClassLoader.of(null)));
接下来使用反射来获取对该AccessObject.setAccessible0
方法的引用。这是一个私有方法,如果调用setAccessible
通过权限检查,它实际上会修改可访问性。
Method setAccessible0Method;
try {
String setAccessible0MethodName = "setAccessible0";
Class[] paramTypes = new Class[2];
paramTypes[0] = AccessibleObject.class;
paramTypes[1] = boolean.class;
setAccessible0Method = AccessibleObject.class
.getDeclaredMethod(setAccessible0MethodName, paramTypes);
} catch (NoSuchMethodException e) {
throw new RuntimeException(e);
}
有了这两件,就可以建造变压器。
AgentBuilder.Transformer transformer = new AgentBuilder.Transformer() {
@Override
public DynamicType.Builder<?> transform(
DynamicType.Builder<?> builder,
TypeDescription typeDescription, ClassLoader classLoader) {
return builder.method(
ElementMatchers.named("setAccessible")
.and(ElementMatchers.takesArguments(boolean.class)))
.intercept(MethodDelegation.to(
bootstrapTypePool.describe(
"com.leacox.sandbox.security.stub.java.lang.reflect.AccessibleObjectStub")
.resolve())
.andThen(MethodCall.invoke(setAccessible0Method).withThis().withAllArguments()));
}
}
最后一步是安装 Byte Buddy Java 代理并执行转换。包含存根的 jar 也必须附加到引导类路径。这是必要的,因为AccessibleObject
该类将由引导加载程序加载,因此任何存根也必须在那里加载。
Instrumentation instrumentation = ByteBuddyAgent.install();
// Append the jar containing the stub replacement to the bootstrap classpath
instrumentation.appendToBootstrapClassLoaderSearch(jarFile);
AgentBuilder agentBuilder = new AgentBuilder.Default()
.disableClassFormatChanges()
.with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
.ignore(none()); // disable default ignores so we can transform Java classes
.type(ElementMatchers.named("java.lang.reflect.AccessibleObject"))
.transform(transformer)
.installOnByteBuddyAgent();
当使用 SecurityManager 并隔离存根类和您在运行时加载的单独 jar 中应用选择性权限的代码时,这将起作用。必须在运行时加载 jars 而不是将它们作为标准依赖项或捆绑的库使事情变得有点复杂,但这似乎是在使用SecurityManager
.
我的 Github 存储库sandbox-runtime有一个完整、深入的沙盒运行时环境示例,它执行隔离的不受信任的代码和更具选择性的反射权限。我还有一篇博文,其中包含有关选择性 setAccessible 权限部分的更多详细信息。