Skip to content

Commit 33a4e9e

Browse files
committed
Prevent non public bean to be exposed to JMX
Previously, a package private `@ManagedResource` annotated bean was registered to the JMX domain even if any attempt to invoke an operation on it will fail since it has to be public. This commit validates that any `@ManagedResource` annotated bean is public and throws an InvalidMetadataException otherwise. Note that the actual bean type does not have to be public as long as the class annotated with `@ManagedResource` in the hierarchy is pubic and no extra operations or attributes are defined on the child. Issue: SPR-14042
1 parent 5828648 commit 33a4e9e

File tree

4 files changed

+179
-74
lines changed

4 files changed

+179
-74
lines changed

spring-context/src/main/java/org/springframework/jmx/export/annotation/AnnotationJmxAttributeSource.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919
import java.lang.annotation.Annotation;
2020
import java.lang.reflect.Array;
2121
import java.lang.reflect.Method;
22+
import java.lang.reflect.Modifier;
2223
import java.util.Collection;
2324
import java.util.Set;
2425

@@ -40,6 +41,7 @@
4041
* @author Rob Harrop
4142
* @author Juergen Hoeller
4243
* @author Jennifer Hickey
44+
* @author Stephane Nicoll
4345
* @since 1.2
4446
* @see ManagedResource
4547
* @see ManagedAttribute
@@ -64,6 +66,11 @@ public org.springframework.jmx.export.metadata.ManagedResource getManagedResourc
6466
if (ann == null) {
6567
return null;
6668
}
69+
Class<?> declaringClass = AnnotationUtils.findAnnotationDeclaringClass(ManagedResource.class, beanClass);
70+
Class<?> target = (declaringClass != null && !declaringClass.isInterface() ? declaringClass : beanClass);
71+
if (!Modifier.isPublic(target.getModifiers())) {
72+
throw new InvalidMetadataException("@ManagedResource class '" + target.getName() + "' must be public");
73+
}
6774
org.springframework.jmx.export.metadata.ManagedResource managedResource = new org.springframework.jmx.export.metadata.ManagedResource();
6875
AnnotationBeanUtils.copyPropertiesToBean(ann, managedResource, this.embeddedValueResolver);
6976
return managedResource;

spring-context/src/test/java/org/springframework/jmx/export/annotation/AnotherAnnotationTestBeanImpl.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
/**
2020
* @author Stephane Nicoll
2121
*/
22-
class AnotherAnnotationTestBeanImpl implements AnotherAnnotationTestBean {
22+
public class AnotherAnnotationTestBeanImpl implements AnotherAnnotationTestBean {
2323

2424
private String bar;
2525

spring-context/src/test/java/org/springframework/jmx/export/annotation/EnableMBeanExportConfigurationTests.java

Lines changed: 168 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@
1919
import javax.management.MBeanServer;
2020
import javax.management.ObjectName;
2121

22+
import org.junit.After;
23+
import org.junit.Rule;
2224
import org.junit.Test;
25+
import org.junit.rules.ExpectedException;
2326

2427
import org.springframework.beans.factory.config.PropertyPlaceholderConfigurer;
2528
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
@@ -33,6 +36,7 @@
3336
import org.springframework.context.annotation.ScopedProxyMode;
3437
import org.springframework.jmx.export.MBeanExporterTests;
3538
import org.springframework.jmx.export.TestDynamicMBean;
39+
import org.springframework.jmx.export.metadata.InvalidMetadataException;
3640
import org.springframework.jmx.support.MBeanServerFactoryBean;
3741
import org.springframework.jmx.support.ObjectNameManager;
3842
import org.springframework.jmx.support.RegistrationPolicy;
@@ -44,109 +48,107 @@
4448
* Tests for {@link EnableMBeanExport} and {@link MBeanExportConfiguration}.
4549
*
4650
* @author Phillip Webb
51+
* @author Stephane Nicoll
4752
* @see AnnotationLazyInitMBeanTests
4853
*/
4954
public class EnableMBeanExportConfigurationTests {
5055

56+
@Rule
57+
public final ExpectedException thrown = ExpectedException.none();
58+
59+
private AnnotationConfigApplicationContext ctx;
60+
61+
@After
62+
public void closeContext() {
63+
if (this.ctx != null) {
64+
this.ctx.close();
65+
}
66+
}
67+
5168
@Test
5269
public void testLazyNaming() throws Exception {
53-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
54-
LazyNamingConfiguration.class);
55-
try {
56-
MBeanServer server = (MBeanServer) ctx.getBean("server");
57-
ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
58-
assertNotNull(server.getObjectInstance(oname));
59-
String name = (String) server.getAttribute(oname, "Name");
60-
assertEquals("Invalid name returned", "TEST", name);
61-
}
62-
finally {
63-
ctx.close();
64-
}
70+
load(LazyNamingConfiguration.class);
71+
validateAnnotationTestBean();
72+
}
73+
74+
private void load(Class<?>... config) {
75+
this.ctx = new AnnotationConfigApplicationContext(config);
6576
}
6677

6778
@Test
6879
public void testOnlyTargetClassIsExposed() throws Exception {
69-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
70-
ProxyConfiguration.class);
71-
try {
72-
MBeanServer server = (MBeanServer) ctx.getBean("server");
73-
ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
74-
assertNotNull(server.getObjectInstance(oname));
75-
assertEquals("TEST", server.getAttribute(oname, "Name"));
76-
}
77-
finally {
78-
ctx.close();
79-
}
80+
load(ProxyConfiguration.class);
81+
validateAnnotationTestBean();
82+
}
83+
84+
@Test
85+
public void testPackagePrivateExtensionCantBeExposed() {
86+
this.thrown.expect(InvalidMetadataException.class);
87+
this.thrown.expectMessage(PackagePrivateTestBean.class.getName());
88+
this.thrown.expectMessage("must be public");
89+
new AnnotationConfigApplicationContext(PackagePrivateConfiguration.class);
90+
}
91+
92+
@Test
93+
public void testPackagePrivateImplementationCantBeExposed() {
94+
this.thrown.expect(InvalidMetadataException.class);
95+
this.thrown.expectMessage(PackagePrivateAnnotationTestBean.class.getName());
96+
this.thrown.expectMessage("must be public");
97+
new AnnotationConfigApplicationContext(PackagePrivateInterfaceImplementationConfiguration.class);
98+
}
99+
100+
@Test
101+
public void testPackagePrivateClassExtensionCanBeExposed() throws Exception {
102+
load(PackagePrivateExtensionConfiguration.class);
103+
validateAnnotationTestBean();
80104
}
81105

82106
@Test
83107
public void testPlaceholderBased() throws Exception {
84108
MockEnvironment env = new MockEnvironment();
85109
env.setProperty("serverName", "server");
86-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext();
87-
ctx.setEnvironment(env);
88-
ctx.register(PlaceholderBasedConfiguration.class);
89-
ctx.refresh();
90-
try {
91-
MBeanServer server = (MBeanServer) ctx.getBean("server");
92-
ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
93-
assertNotNull(server.getObjectInstance(oname));
94-
String name = (String) server.getAttribute(oname, "Name");
95-
assertEquals("Invalid name returned", "TEST", name);
96-
}
97-
finally {
98-
ctx.close();
99-
}
110+
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
111+
context.setEnvironment(env);
112+
context.register(PlaceholderBasedConfiguration.class);
113+
context.refresh();
114+
this.ctx = context;
115+
validateAnnotationTestBean();
100116
}
101117

102118
@Test
103119
public void testLazyAssembling() throws Exception {
104120
System.setProperty("domain", "bean");
105-
AnnotationConfigApplicationContext ctx =
106-
new AnnotationConfigApplicationContext(LazyAssemblingConfiguration.class);
121+
load(LazyAssemblingConfiguration.class);
107122
try {
108-
MBeanServer server = (MBeanServer) ctx.getBean("server");
109-
110-
ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
111-
assertNotNull(server.getObjectInstance(oname));
112-
String name = (String) server.getAttribute(oname, "Name");
113-
assertEquals("Invalid name returned", "TEST", name);
114-
115-
oname = ObjectNameManager.getInstance("bean:name=testBean5");
116-
assertNotNull(server.getObjectInstance(oname));
117-
name = (String) server.getAttribute(oname, "Name");
118-
assertEquals("Invalid name returned", "FACTORY", name);
119-
120-
oname = ObjectNameManager.getInstance("spring:mbean=true");
121-
assertNotNull(server.getObjectInstance(oname));
122-
name = (String) server.getAttribute(oname, "Name");
123-
assertEquals("Invalid name returned", "Rob Harrop", name);
124-
125-
oname = ObjectNameManager.getInstance("spring:mbean=another");
126-
assertNotNull(server.getObjectInstance(oname));
127-
name = (String) server.getAttribute(oname, "Name");
128-
assertEquals("Invalid name returned", "Juergen Hoeller", name);
123+
MBeanServer server = (MBeanServer) this.ctx.getBean("server");
124+
125+
validateMBeanAttribute(server, "bean:name=testBean4", "TEST");
126+
validateMBeanAttribute(server, "bean:name=testBean5", "FACTORY");
127+
validateMBeanAttribute(server, "spring:mbean=true", "Rob Harrop");
128+
validateMBeanAttribute(server, "spring:mbean=another", "Juergen Hoeller");
129129
}
130130
finally {
131131
System.clearProperty("domain");
132-
ctx.close();
133132
}
134133
}
135134

136135
@Test
137136
public void testComponentScan() throws Exception {
138-
AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(
139-
ComponentScanConfiguration.class);
140-
try {
141-
MBeanServer server = (MBeanServer) ctx.getBean("server");
142-
ObjectName oname = ObjectNameManager.getInstance("bean:name=testBean4");
143-
assertNotNull(server.getObjectInstance(oname));
144-
String name = (String) server.getAttribute(oname, "Name");
145-
assertNull(name);
146-
}
147-
finally {
148-
ctx.close();
149-
}
137+
load(ComponentScanConfiguration.class);
138+
MBeanServer server = (MBeanServer) this.ctx.getBean("server");
139+
validateMBeanAttribute(server, "bean:name=testBean4", null);
140+
}
141+
142+
private void validateAnnotationTestBean() throws Exception {
143+
MBeanServer server = (MBeanServer) this.ctx.getBean("server");
144+
validateMBeanAttribute(server,"bean:name=testBean4", "TEST");
145+
}
146+
147+
private void validateMBeanAttribute(MBeanServer server, String objectName, String expected) throws Exception {
148+
ObjectName oname = ObjectNameManager.getInstance(objectName);
149+
assertNotNull(server.getObjectInstance(oname));
150+
String name = (String) server.getAttribute(oname, "Name");
151+
assertEquals("Invalid name returned", expected, name);
150152
}
151153

152154

@@ -271,4 +273,97 @@ public MBeanServerFactoryBean server() throws Exception {
271273
}
272274
}
273275

276+
@Configuration
277+
@EnableMBeanExport(server = "server")
278+
static class PackagePrivateConfiguration {
279+
280+
@Bean
281+
public MBeanServerFactoryBean server() throws Exception {
282+
return new MBeanServerFactoryBean();
283+
}
284+
285+
@Bean
286+
public PackagePrivateTestBean testBean() {
287+
return new PackagePrivateTestBean();
288+
}
289+
}
290+
291+
@ManagedResource(objectName = "bean:name=packagePrivate")
292+
private static class PackagePrivateTestBean {
293+
294+
private String name;
295+
296+
@ManagedAttribute
297+
public String getName() {
298+
return this.name;
299+
}
300+
301+
@ManagedAttribute
302+
public void setName(String name) {
303+
this.name = name;
304+
}
305+
}
306+
307+
308+
@Configuration
309+
@EnableMBeanExport(server = "server")
310+
static class PackagePrivateExtensionConfiguration {
311+
312+
@Bean
313+
public MBeanServerFactoryBean server() throws Exception {
314+
return new MBeanServerFactoryBean();
315+
}
316+
317+
@Bean
318+
public PackagePrivateTestBeanExtension testBean() {
319+
PackagePrivateTestBeanExtension bean = new PackagePrivateTestBeanExtension();
320+
bean.setName("TEST");
321+
return bean;
322+
}
323+
}
324+
325+
private static class PackagePrivateTestBeanExtension extends AnnotationTestBean {
326+
327+
}
328+
329+
@Configuration
330+
@EnableMBeanExport(server = "server")
331+
static class PackagePrivateInterfaceImplementationConfiguration {
332+
333+
@Bean
334+
public MBeanServerFactoryBean server() throws Exception {
335+
return new MBeanServerFactoryBean();
336+
}
337+
338+
@Bean
339+
public PackagePrivateAnnotationTestBean testBean() {
340+
return new PackagePrivateAnnotationTestBean();
341+
}
342+
}
343+
344+
private static class PackagePrivateAnnotationTestBean implements AnotherAnnotationTestBean {
345+
346+
private String bar;
347+
348+
@Override
349+
public void foo() {
350+
351+
}
352+
353+
@Override
354+
public String getBar() {
355+
return this.bar;
356+
}
357+
358+
@Override
359+
public void setBar(String bar) {
360+
this.bar = bar;
361+
}
362+
363+
@Override
364+
public int getCacheEntries() {
365+
return 0;
366+
}
367+
}
368+
274369
}

src/asciidoc/integration.adoc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3508,6 +3508,9 @@ be marked with the `ManagedAttribute` annotation. When marking properties you ca
35083508
either the annotation of the getter or the setter to create a write-only or read-only
35093509
attribute respectively.
35103510

3511+
NOTE: A `ManagedResource` annotated bean must be public as well as the methods exposing
3512+
an operation or an attribute.
3513+
35113514
The example below shows the annotated version of the `JmxTestBean` class that you saw
35123515
earlier:
35133516

0 commit comments

Comments
 (0)