Error encountered implementing OAuth 2.0 Resource Server multi-tenancy - spring-security

I have implemented code as specified here to add the multi-tenancy by issuer feature to my Spring Security configuration. However, when my Spring Boot application starts, I encounter the following error:
2021-10-26 | 10:31:37.762 | main | WARN | ConfigServletWebServerApplicationContext | Trace: | Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'springSecurityFilterChain' defined in class path resource [org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.class]: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [javax.servlet.Filter]: Factory method 'springSecurityFilterChain' threw exception; nested exception is org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' available
2021-10-26 | 10:31:39.361 | main | ERROR | o.s.b.d.LoggingFailureAnalysisReporter | Trace: |
***************************
APPLICATION FAILED TO START
***************************
Description:
Method springSecurityFilterChain in org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration required a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' that could not be found.
Action:
Consider defining a bean of type 'org.springframework.security.oauth2.jwt.JwtDecoder' in your configuration.
The documentation states:
This is nice because the issuer endpoints are loaded lazily. In fact, the corresponding JwtAuthenticationProvider is instantiated only when the first request with the corresponding issuer is sent.
I wouldn't think that at application startup a JwtDecoder would be expected to be already instantiated according to this documentation. What am I missing in my configuration?
Update
After Steve Riesenberg's help, I have the following code compiling now. You can see in my code snippet what I used to have working (i.e., before we had the multi-tenant requirement) is now commented out:
//.jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);
String[] issuers = new String[] {"https://www.example.com/auth/realms/example"};
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(issuers);
...
.anyRequest()
.authenticated()
.and()
.oauth2ResourceServer(
oauth2ResourceServerConfigurer ->
oauth2ResourceServerConfigurer
.authenticationManagerResolver(jwtIssuerAuthenticationManagerResolver)
.authenticationEntryPoint(authenticationExceptionHandler));
// .jwt().jwtAuthenticationConverter(jwtAccessTokenConverter);
However, without the ability now to supply my own token converter since I had to remove .jwt(), I'm still unclear on what the default converter provides me.
Also, I'm not clear why I need to use the third constructor of JwtIssuerAuthenticationManagerResolver and provide my own AuthenticationManagerResolver<String>? If my code above is compiling, why do I need to do this?

The JwtDecoder is required if you've configured the resource server with a JwtAuthenticationProvider (because it requires a specific JwtDecoder). This would happen if you do for example:
http
...
.oauth2ResourceServer(oauth2 -> oauth2
.authenticationManagerResolver(authenticationManagerResolver)
.jwt(Customizer.withDefaults())
)
Since the authenticationManagerResolver is an alternative that branches at the AuthenticationManager level, you don't want to use a JwtAuthenticationProvider. It will be used internally by the JwtIssuerAuthenticationManagerResolver.
Remove .jwt() in that case to prevent the configurer from wiring one up.
Update
The section in the docs on Dynamic Tenants gives some more info on various customization options.
In your case, without the use of .jwt() you cannot as easily wire in a JwtAuthenticationConverter that can customize the returned granted authorities.
The JwtIssuerAuthenticationManagerResolver is internally using a TrustedIssuerJwtAuthenticationManagerResolver. This is what performs the multi-tenancy capability, by extracting an issuer claim from the JWT, and creating a JwtDecoder + new JwtAuthenticationProvider(jwtDecoder) based on the matched issuer.
In order to customize the JwtAuthenticationProvider, you will have to re-implement this class so you can inject your JwtAuthenticationConverter into each created instance. You will implement AuthenticationManagerResolver<String> to do this. Call it CustomTrustedIssuerJwtAuthenticationManagerResolver (see this line).
You just need to provide that to the JwtIssuerAuthenticationManagerResolver, like this:
String[] issuers = new String[] {"https://www.example.com/auth/realms/example"};
AuthenticationManagerResolver<String> authenticationManagerResolver =
new CustomTrustedIssuerJwtAuthenticationManagerResolver(issuers);
JwtIssuerAuthenticationManagerResolver jwtIssuerAuthenticationManagerResolver =
new JwtIssuerAuthenticationManagerResolver(authenticationManagerResolver);
...

Related

Spring Security LDAP Changes After 5.7

I am trying to understand the steps to support LDAP with Spring security...replacing our current basic authentication. Recently I made the changes to support the deprecation of the WebSecurityConfigurerAdapter and all is working fine. So now I want to swap out the basic authentication AuthenticationManager or AuthenticationProvider with their LDAP equivalents. There are several websites with partial information and I cant quite put the pieces together.
So I am starting with the spring guide on authenticating ldap (https://github.com/spring-guides/gs-authenticating-ldap) I took the "complete" example, modified for our version of Spring Boot 2.7.5 and the app starts and validates fine. I tried updating the example according to several websites from :
#Autowired
public void configure(AuthenticationManagerBuilder auth) throws Exception {
auth
.ldapAuthentication()
.userDnPatterns("uid={0},ou=people")
.groupSearchBase("ou=groups")
.contextSource()
.url("ldap://localhost:8389/dc=springframework,dc=org")
.and()
.passwordCompare()
.passwordEncoder(new BCryptPasswordEncoder())
.passwordAttribute("userPassword");
To :
#Bean
public EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean() {
EmbeddedLdapServerContextSourceFactoryBean contextSourceFactoryBean =
EmbeddedLdapServerContextSourceFactoryBean.fromEmbeddedLdapServer();
contextSourceFactoryBean.setPort(0);
return contextSourceFactoryBean;
}
#Bean
public AuthenticationManager authenticationManager(BaseLdapPathContextSource contextSource) {
LdapPasswordComparisonAuthenticationManagerFactory factory = new LdapPasswordComparisonAuthenticationManagerFactory(
contextSource, new BCryptPasswordEncoder());
factory.setUserDnPatterns("uid={0},ou=people");
factory.setPasswordAttribute("pwd");
return factory.createAuthenticationManager();
}
Thinking this closer to the recommendations and closer to what I would be using in our real project. Now when I try to build ... the test phase keeps failing with the following stack trace
ERROR[m] loginWithValidUserThenAuthenticated Time elapsed: 0 s <<< ERROR!
java.lang.IllegalStateException: Failed to load ApplicationContext
Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'authenticationManager' defined in class path resource [com/example/authenticatingldap/WebSecurityConfig.class]: Unsatisfied dependency expressed through method 'authenticationManager' parameter 0; nested exception is org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contextSourceFactoryBean': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Unable to load LDIF classpath*:*.ldif
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'contextSourceFactoryBean': FactoryBean threw exception on object creation; nested exception is java.lang.IllegalStateException: Unable to load LDIF classpath*:*.ldif
Caused by: java.lang.IllegalStateException: Unable to load LDIF classpath*:*.ldif
Caused by: com.unboundid.ldap.sdk.LDAPException: An entry with DN 'dc=springframework,dc=org' already exists in the server.
I am using the same ldif file that worked with the other configuration. The code follows the other examples and recommendation but the embedded LDAP server wont load the file. Has anyone seen and solved this problem? Once this is working, do we simply remove the Embedded LDAP bean and somehow point the configuration to the real LDAP server?

Configure the landing page after SSO authentication

I want to know how to setup the relay state with the new saml library. Basically once I am authenticated via the asserting party, I want to have a relay state url (a JSP in my application), where I should land. In the saml extensions library, as far as I know, the relay state url was set in the SAMLMessageContext object.
Actual Behaviour The mechanism of setting the page where I would be redirected to after SAML login has changed. I am not sure how to set the desired JSP where I want to land in the new library.
Expected behavior After the successful call to the assertionConsumerServiceLocation in my application, I should be taken to a Url configured by me (Relaystate). I need help in configuring this URL.
I tried to set up the relay state like this :-
Saml2AuthenticationRequestResolver authenticationRequestResolver(
RelyingPartyRegistrationResolver registrations) {
OpenSaml4AuthenticationRequestResolver authenticationRequests =
new OpenSaml4AuthenticationRequestResolver(registrations);
authenticationRequests.setRelayStateResolver(relayStateResolver);
return authenticationRequests;
}
I have defined the relayStateResolver like the following :-
private Converter<HttpServletRequest, String> relayStateResolver = (request) -> "my_desired_jsp_url_string";
Are my above configurations correct, and would they help me in landing on the desired JSP page after successful login?
Currently, below is the error I am facing with the above implementation. I am working to fix that (I need to get the Opensaml4 from Shibboleth's repository of artifacts), but wanted to know if the above configuration is correct before making that fixing effort.
Jan 03, 2023 5:54:28 AM org.apache.catalina.core.StandardWrapperValve
invoke SEVERE: Servlet.service() for servlet [dispatcher] in context
with path [/company] threw exception [Filter execution threw an
exception] with root cause java.lang.NoSuchMethodError:
org.opensaml.saml.saml2.core.AuthnRequest.setIssueInstant(Ljava/time/Instant;)V
at
org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver.lambda$resolve$1(OpenSaml4AuthenticationRequestResolver.java:60)
at
org.springframework.security.saml2.provider.service.web.authentication.OpenSamlAuthenticationRequestResolver.resolve(OpenSamlAuthenticationRequestResolver.java:133)
at
org.springframework.security.saml2.provider.service.web.authentication.OpenSaml4AuthenticationRequestResolver.resolve(OpenSaml4AuthenticationRequestResolver.java:59)
at
org.springframework.security.saml2.provider.service.web.Saml2WebSsoAuthenticationRequestFilter.doFilterInternal(Saml2WebSsoAuthenticationRequestFilter.java:184)
at
org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
Because RelayState can be used for several things and is not always used to describe the post-login redirect URL (the spec says MAY), it is not defaulted to do this.
You can configure Spring Security to redirect to the RelayState parameter by configuring a SimpleUrlAuthenticationSuccessHandler like so:
#Bean
public SecurityFilterChain appEndpoints(HttpSecurity http) {
SimpleUrlAuthenticationSuccessHandler successHandler =
new SimpleUrlAuthenticationSuccessHandler();
successHandler.setTargetUrlParameter(Saml2ParameterNames.RELAY_STATE);
http
.saml2Login((saml2) -> saml2
.authenticationSuccessHandler(successHandler)
// ...
)
// ...
return http.build();
}
You will also need to configure the relay state resolver, as you have already shown in your post.

How can I customize exception mappings of default AuthenticationEventPublisher in Grails Spring Security Plugin

Background:
In my project, I needed to have two factor authentication (by sending OTP to registered email) which I implemented by extending DaoAuthenticationProvider.
In order to identify reason of why OTP authentication has failed, my custom authentication provider throws new custom exceptions for example BadOtpException, ExpiredOtpException, ConsumedOtpException. All these exceptions are subclasses of BadCredentialsException. This authentication flow is working fine.
Issue:
The Authentication events that were earlier getting published while I was using DaoAuthenticationProvider are now not getting published with my custom authentication provider.
Cause:
Upon some troubleshooting I figured out that Grails Spring Security Core plugin uses DefaultAuthenticationEventPublisher to publish the events. And this class publishes events on the basis of exception mappings which contain exception name vs event name to resolve the event that needs to be published whenever exception occurs. And these mappings are configured in its constructor.
Since mappings of my custom exceptions are not present in this constructor, the authentication failure events are not getting published.
Tried Solution:
I tried overriding DefaultAuthenticationEventPublisher and added new exception mappings by invoking super.setAdditionalExceptionMappings(). Here is the custom event publisher:
class CustomAuthenticationEventPublisher extends DefaultAuthenticationEventPublisher {
CustomAuthenticationEventPublisher() {
}
CustomAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
super(applicationEventPublisher)
println('CustomAuthenticationEventPublisher')
Properties exceptionMappings = new Properties()
exceptionMappings.setProperty(BadOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name)
super.setAdditionalExceptionMappings(exceptionMappings)
}
}
In resources.groovy, I registered my custom event publisher using following:
beans = {
authenticationEventPublisher(CustomAuthenticationEventPublisher)
}
But above solution is not working. Even the println() statement in the constructor is not getting logged. Seems like the bean is not getting registered.
Am I doing anything wrong in the above solution?
Is there any other way I can override the exception mappings?
With bit of more troubleshooting, I realized that zero argument constructor of the class CustomAuthenticationEventPublisher was being called instead of the other one.
So I tried setting the exception mappings in constructor and it worked.
Here is the code that worked form me:
class CustomAuthenticationEventPublisher extends DefaultAuthenticationEventPublisher {
CustomAuthenticationEventPublisher() {
println('CustomAuthenticationEventPublisher')
Properties exceptionMappings = new Properties()
exceptionMappings.setProperty(BadOtpException.class.name, AuthenticationFailureBadCredentialsEvent.class.name)
super.setAdditionalExceptionMappings(exceptionMappings)
}
CustomAuthenticationEventPublisher(ApplicationEventPublisher applicationEventPublisher) {
super(applicationEventPublisher)
}
}
Thanks.

Error in Zuul SendErrorFilter during forward

When my Zuul Filter is unable to route to a configured URL, the 'RibbonRoutingFilter' class throws a ZuulException saying "Forwarding error" and the control goes to the 'SendErrorFilter' class.
Now when the SendErrorFilter class tries to do a forward, another exception happens during this forward call.
dispatcher.forward(ctx.getRequest(), ctx.getResponse());
The exception happening during this forward call is
Caused by: java.lang.IllegalArgumentException: UT010023: Request org.springframework.cloud.netflix.zuul.filters.pre.Servlet30WrapperFilter$Servlet30RequestWrapper#6dc974ea was not original or a wrapper
at io.undertow.servlet.spec.RequestDispatcherImpl.forward(RequestDispatcherImpl.java:103) ~[undertow-servlet-1.1.3.Final.jar:1.1.3.Final]
at org.springframework.cloud.netflix.zuul.filters.post.SendErrorFilter.run(SendErrorFilter.java:74) ~[spring-cloud-netflix-core-1.0.0.RELEASE.jar:1.0.0.RELEASE]
at com.netflix.zuul.ZuulFilter.runFilter(ZuulFilter.java:112) ~[zuul-core-1.0.28.jar:na]
at com.netflix.zuul.FilterProcessor.processZuulFilter(FilterProcessor.java:197) ~[zuul-core-1.0.28.jar:na]
Finally when the control comes to my custom ZuulErrorFilter , i do not get the original exception. Instead the exception object i get is the one that occurs during the forward.
Update:
I found that a errorPath property can be configured to point to a Error Handling Service. If it is not configured, Zuul by default looks for a service named /error and tries to dispatch to that service. Since we did not have any service for /error , the dispatcher.forward() was throwing error.
Question
How can we skip this fwd to an error handling service ? We have a ErrorFilter to log the error. We do not want to have a error handling service.
We had faced the same issue and there is a simple solution to fix the Undertow "eating" the original exception, following my blog post:
http://blog.jmnarloch.io/2015/09/16/spring-cloud-zuul-error-handling/
You need to set the flag allow-non-standard-wrappers to true. In Spring Boot this is doable through registering custom UndertowDeploymentInfoCustomizer. Example:
#Bean
public UndertowEmbeddedServletContainerFactory embeddedServletContainerFactory() {
UndertowEmbeddedServletContainerFactory factory = new UndertowEmbeddedServletContainerFactory();
factory.addDeploymentInfoCustomizers(new UndertowDeploymentInfoCustomizer() {
#Override
public void customize(DeploymentInfo deploymentInfo) {
deploymentInfo.setAllowNonStandardWrappers(true);
}
});
return factory;
}
Now regarding the question, either way I would highly encourage you to implement your own ErrorController, because otherwise you may experience odd Spring Boot behaviour (in our setup - ralying on the default was always generating the Whitelabel error page with 200 HTTP status code - which never happens on Tomcat in contradiction) and in this way was not consumable by AJAX calls for instance.
Related Github issue: https://github.com/spring-cloud/spring-cloud-netflix/issues/524

The JNDI lookup for the JTA UserTransaction is not available to MBean threads in Websphere Application Server 7

I'm trying to invoke business logic via JMX (using 'standard' MBeans) in a web application in Websphere Application Server 7 with JTA switched on and would like to know why this business logic can't see the JTA UserTransaction when invoked from an MBean (because it can when invoked via the web app's UI).
When hibernate attempts to look up the UserTransaction via 'java:comp/UserTransaction', the following exception is thrown:
org.hibernate.TransactionException: Could not find UserTransaction in JNDI [java:comp/UserTransaction]
at org.hibernate.transaction.JTATransactionFactory.getUserTransaction(JTATransactionFactory.java:173)
at org.hibernate.transaction.JTATransactionFactory.createTransaction(JTATransactionFactory.java:149)
...
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:48)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:37)
at java.lang.reflect.Method.invoke(Method.java:600)
at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:105)
at com.sun.jmx.mbeanserver.StandardMBeanIntrospector.invokeM2(StandardMBeanIntrospector.java:39)
at com.sun.jmx.mbeanserver.MBeanIntrospector.invokeM(MBeanIntrospector.java:220)
at com.sun.jmx.mbeanserver.PerInterface.getAttribute(PerInterface.java:77)
at com.sun.jmx.mbeanserver.MBeanSupport.getAttribute(MBeanSupport.java:228)
at com.sun.jmx.interceptor.DefaultMBeanServerInterceptor.getAttribute(DefaultMBeanServerInterceptor.java:678)
at com.sun.jmx.mbeanserver.JmxMBeanServer.getAttribute(JmxMBeanServer.java:650)
at com.ibm.ws.management.AdminServiceImpl.getAttribute(AdminServiceImpl.java:853)
at com.ibm.ws.management.remote.AdminServiceForwarder.getAttribute(AdminServiceForwarder.java:270)
at javax.management.remote.rmi.RMIConnectionImpl.doOperation(RMIConnectionImpl.java:1415)
at javax.management.remote.rmi.RMIConnectionImpl.access$200(RMIConnectionImpl.java:84)
at javax.management.remote.rmi.RMIConnectionImpl$PrivilegedOperation.run(RMIConnectionImpl.java:1276)
at javax.management.remote.rmi.RMIConnectionImpl.doPrivilegedOperation(RMIConnectionImpl.java:1371)
at javax.management.remote.rmi.RMIConnectionImpl.getAttribute(RMIConnectionImpl.java:612)
at javax.management.remote.rmi._RMIConnectionImpl_Tie.getAttribute(_RMIConnectionImpl_Tie.java:578)
at javax.management.remote.rmi._RMIConnectionImpl_Tie._invoke(_RMIConnectionImpl_Tie.java:98)
at com.ibm.CORBA.iiop.ServerDelegate.dispatchInvokeHandler(ServerDelegate.java:622)
at com.ibm.CORBA.iiop.ServerDelegate.dispatch(ServerDelegate.java:475)
at com.ibm.rmi.iiop.ORB.process(ORB.java:513)
at com.ibm.CORBA.iiop.ORB.process(ORB.java:1574)
at com.ibm.rmi.iiop.Connection.respondTo(Connection.java:2841)
at com.ibm.rmi.iiop.Connection.doWork(Connection.java:2714)
at com.ibm.rmi.iiop.WorkUnitImpl.doWork(WorkUnitImpl.java:63)
at com.ibm.ejs.oa.pool.PooledThread.run(ThreadPool.java:118)
at com.ibm.ws.util.ThreadPool$Worker.run(ThreadPool.java:1563)
Caused by: javax.naming.ConfigurationException: A JNDI operation on a "java:" name cannot be completed because the server runtime is not able to associate the operation's thread with any J2EE application component. This condition can occur when the JNDI client using the "java:" name is not executed on the thread of a server application request. Make sure that a J2EE application does not execute JNDI operations on "java:" names within static code blocks or in threads created by that J2EE application. Such code does not necessarily run on the thread of a server application request and therefore is not supported by JNDI operations on "java:" names. [Root exception is javax.naming.NameNotFoundException: Name "comp/UserTransaction" not found in context "java:".]
at com.ibm.ws.naming.java.javaURLContextImpl.throwConfigurationExceptionWithDefaultJavaNS(javaURLContextImpl.java:428)
at com.ibm.ws.naming.java.javaURLContextImpl.lookup(javaURLContextImpl.java:399)
at com.ibm.ws.naming.java.javaURLContextRoot.lookup(javaURLContextRoot.java:214)
at com.ibm.ws.naming.java.javaURLContextRoot.lookup(javaURLContextRoot.java:154)
at javax.naming.InitialContext.lookup(InitialContext.java:455)
at org.hibernate.transaction.JTATransactionFactory.getUserTransaction(JTATransactionFactory.java:163)
... 53 more
Caused by: javax.naming.NameNotFoundException: Name "comp/UserTransaction" not found in context "java:".
at com.ibm.ws.naming.ipbase.NameSpace.lookupInternal(NameSpace.java:1178)
at com.ibm.ws.naming.ipbase.NameSpace.lookup(NameSpace.java:1095)
at com.ibm.ws.naming.urlbase.UrlContextImpl.lookup(UrlContextImpl.java:1233)
at com.ibm.ws.naming.java.javaURLContextImpl.lookup(javaURLContextImpl.java:395)
... 57 more
This problem looks like it's more than just a hibernate configuration problem - hibernate is looking for the UserTransaction at what IBM say is the correct UserTransaction JNDI location ('java:comp/UserTransaction') - see this infocenter document.
Furthermore, I can reproduce the problem in a simple web app that has an MBean that does the lookup:
public class JTALookup extends NotificationBroadcasterSupport implements JTALookupMBean {
Log log = LogFactory.getLog(JTALookup.class);
/**
* {#inheritDoc}
* #see JTALookupMBean#lookupUserTransaction()
*/
#Override
public void lookupUserTransaction() {
try {
log.info("Attempting 'java:comp/UserTransaction' lookup");
Object usrTxn = new InitialContext().lookup("java:comp/UserTransaction");
log.info("Successfully looked up 'java:comp/UserTransaction' [" + usrTxn + "]." );
} catch (NamingException e) {
log.info("'java:comp/UserTransaction' lookup failed");
throw new RuntimeException("Failed to lookup JTA user transaction", e);
}
}
and a context listener that invokes the lookup during start up and then registers the MBean:
public void contextInitialized(ServletContextEvent sce) {
log.info("Initialising context");
JTALookup jtaLookup = new JTALookup();
jtaLookup.lookupUserTransaction(); // This succeeds
log.info("Looked up JTA transaction");
MBeanServer mbServer = AdminServiceFactory.getMBeanFactory().getMBeanServer();
log.info("Got MBeanServer");
try {
mbServer.registerMBean(jtaLookup, new ObjectName("webJTALookupStub:type=JTALookup"));
log.info("Registered dummy MBean");
} catch (Exception e) {
log.info("Failed to register dummy MBean");
throw new RuntimeException("Failed to register dummy MBean", e);
}
}
The lookup on 'java:comp/UserTransaction' succeeds during context initialisation, but fails (with a similar stack trace to that above) when invoked via jmx, like so:
public static void main(String[] args) {
JMXServiceURL url = new JMXServiceURL(
"service:jmx:rmi://" + "your.server.name.co.uk" + ":" + "2809" + "/jndi/JMXConnector"
);
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.PROVIDER_URL, "corbaloc:iiop:gbbldd66.sys.chp.co.uk:2809/WsnAdminNameService");
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.ibm.websphere.naming.WsnInitialContextFactory");
// Establish the JMX connection.
JMXConnector jmxc = JMXConnectorFactory.connect(url, env);
// Get the MBean server connection instance.
MBeanServerConnection mbsc = jmxc.getMBeanServerConnection();
ObjectName mbeanName = new ObjectName("webJTALookupStub:type=JTALookup");
JTALookupMBean mBean = JMX.newMBeanProxy(mbsc, mbeanName, JTALookupMBean.class, true);
mBean.lookupUserTransaction(); // This fails
The 'Extending the WebSphere Application Server administrative system with custom MBeans' document in IBM's infocenter suggests that standard MBeans that have been tested in applications outside WAS should just work.
IBM do state that the UserTransaction lookup is not available to:
CMT Enterprise beans `http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/index.jsp?topic=/com.ibm.websphere.base.doc/info/aes/ae/cjta_glotran.html
Async Beans created by EJBs `http://publib.boulder.ibm.com/infocenter/wasinfo/v7r0/topic/com.ibm.websphere.javadoc.doc/web/apidocs/com/ibm/websphere/asynchbeans/package-summary.html?resultof=%22%61%73%79%6e%63%68%62%65%61%6e%22%20%22%75%73%65%72%74%72%61%6e%73%61%63%74%69%6f%6e%22%20%22%75%73%65%72%74%72%61%6e%73%61%63%74%22%20
Apologies for the non-functional links - I'm a new user and so can only post two working links.
Do plain old MBeans fall into either of these categories from IBM's point of view?
Interestingly, the UserTransaction appears to be available on JNDI lookup 'jta/UserTransaction' and using that as a fallback option seems to work - but:
WAS 7 is Java EE 5 compliant and as of J2EE 1.3 'java:comp/UserTransaction' is the specified JNDI location for the UserTransaction - see the J2EE 1.3 spec `http://java.sun.com/j2ee/j2ee-1_3-fr-spec.pdf
Using a lookup from an earlier version of the EE specification seems like a potential source of other bugs, and might only be addressing part of my problem - the fact that WAS thinks my MBean's thread is not associated with an application could cause other issues.
One further point to note is that the UserTransaction is also hidden to threads for work submitted from the MBean to the application's work manager (an IBM work manager) - which could be because it's treating that work like it's an async bean submitted by an EJB?
Possible explanations that have occurred to me are:
There might issues with how IBM set up MBean threads in WAS 7 and associate then with the applications that register the MBeans.
There might be some additional configuration options for the MBean registration that would let WAS know that it should associate the MBean with the application that registered it. I have tried several alternative approaches but saw the same exception each time:
Registering the MBeans with UserCollaborators and xml descriptors
Registering them with ModelMBeanInfo
Registering them with the AdminService rather than the MBeanServer
Enhancing the ObjectName for the MBean with additional properties (Application, J2EEApplication) at registration
There might be some additional configuration options for the jmx client request that would let WAS know that it should associate the MBean invokation with the appropriate application. This forum post suggests it's possible to configure a client application to have access to the initial context: `http://www.ibm.com/developerworks/forums/thread.jspa?messageID=14021995
I might just not be supposed to try to use MBeans this way - despite IBM's statements that I should be able to. It's been suggested that EJBs are the appropriate solution for this kind of requirement.
Any light that can be shed on this problem would be greatly appreciated.
MBeans run on a separate thread than your application, so they don't have access to the application naming context in JNDI, and therefore they don't have access to your UserTransaction.
I think your final potential explanation is likely most accurate:
I might just not be supposed to try to use MBeans this way - despite IBM's statements that I should be able to. It's been suggested that EJBs are the appropriate solution for this kind of requirement.
MBeans may not be appropriate for this type of work. Rather, using EJBs or a web service might be more appropriate.
You have to set TransactionManagementType.BEAN on transactionManagement like so:
#TransactionManagement(TransactionManagementType.BEAN)

Resources