MQ 9.3.4 CD release introduced JSON Web Token (JWT) based authentication for queue managers running on Linux and AIX platforms. The queue manager can be configured to communicate with a token provider to validate the token sent up by a MQ client application. On successful validation of the token, the client application gets authenticated.
Prior to MQ 9.4.3 CD release, JMS Client applications must write additional code to get JW token from a token provider. The token is then be sent to the queue manager via the PASSWORD property on JmsConnectionFactory. The USERID property is set to null value to indicate the JMS client to treat the value passed on in PASSWORD property as a token. The following sample sudo code snippet demonstrates the concept:
private String getJWToken() {
// Connect over HTTPS to Token endpoint
// Flow token request
// Receive response containing token
// return token
}
// Connect to queue manager
private void connect() {
System.setProperty("javax.net.ssl.trustStore", "$HOME/clientTruststore.p12");
System.setProperty("javax.net.ssl.trustStorePassword", "passw0rd");
System.setProperty("javax.net.ssl.trustStoreType","pkcs12");
String token = getJWToken();
// Set the properties for connecting to the queue manager
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, "localhost");
cf.setIntProperty(WMQConstants.WMQ_PORT, "1414");
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, "DEV.APP.SVRCONN");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "QM1");
cf.setStringProperty(WMQConstants.USERID, null);
cf.setStringProperty(WMQConstants.PASSWORD, token);
connection = cf.createConnection();
}
It must be noted that every MQ JMS application that wants to use token-based authentication must write code as above. Won't it be very useful if the MQ JMS client itself does the job on applications behalf? Yes, the MQJMS client in MQ 9.4.3 CD release does exactly the same - given the URL of a token provider endpoint, it gets the token and sends it to queue manager for authentication.
MQ JMS Client now provides three new JmsConnectionFactory properties for this purpose.
TOKEN_ENDPOINT - URI endpoint of a token provider.
TOKEN_CLIENT_ID - Client ID as defined in a token provider.
TOKEN_CLIENT_SECRET - A secret known only to client application and the token provider.
Using the values provided for the above properties, MQJMS Client retrieves token over a HTTPS connection to a token provider. As the connection to token provider is secure, token providers’ public certificate must be added to client application's trust store. Here is an example code snippet that demonstrates the new feature.
System.setProperty("javax.net.ssl.trustStore", "$HOME/clientTruststore.p12");
System.setProperty("javax.net.ssl.trustStorePassword", "passw0rd");
System.setProperty("javax.net.ssl.trustStoreType","pkcs12");
// Create a connection factory
JmsFactoryFactory ff = JmsFactoryFactory.getInstance(WMQConstants.WMQ_PROVIDER);
JmsConnectionFactory cf = ff.createConnectionFactory();
// Set properties to get tokens
cf.setStringProperty(WMQConstants.TOKEN_CLIENT_ID, "mykeycloakclient");
cf.setStringProperty(WMQConstants.TOKEN_CLIENT_SECRET, "mykeycloaksecret");
cf.setStringProperty(WMQConstants.TOKEN_ENDPOINT, "https://mykeycloak.server/oidc/token");
// Set the properties for connecting to the queue manager
cf.setStringProperty(WMQConstants.WMQ_HOST_NAME, "localhost");
cf.setIntProperty(WMQConstants.WMQ_PORT, "1414");
cf.setStringProperty(WMQConstants.WMQ_CHANNEL, "DEV.APP.SVRCONN");
cf.setIntProperty(WMQConstants.WMQ_CONNECTION_MODE, WMQConstants.WMQ_CM_CLIENT);
cf.setStringProperty(WMQConstants.WMQ_QUEUE_MANAGER, "QM1");
// Create JMS connection
connection = cf.createConnection();
Can I configure JWT endpoint via JMSAdmin tool?
Yes, JMSAdmin (and its Jakarta equivalent JMS30Admin) tool has also been enhanced. You can now define a connection factory with properties required for token-based authentication. New attributes provided in Connection Factory for this purpose are:
TOKEN_ENDPOINT –
Long name: TOKENENDPOINT
Short name: TKEP
TOKEN_CLIENT_ID –
Long name: TOKENCLIENTID
Short name: TKCD
TOKEN_CLIENT_SECRET –
Long name: TOKENSECRET
Short name: TKST
Here is how you can define a connection factory with new properties:
Using long names:
DEFINE CF(CONFACT) HOSTNAME (localhost) PORT (1414) CHANNEL(QM1_SVRCONN) TOKENCLIENTID (MyClientID) TOKENENDPOINT (https://keycloak.ibm.com:8080/api/token)
Using short names:
DEFINE CF(CONFACT) HOST (localhost) PORT (1414) HOSTNAME (localhost) PORT (1414) CHANNEL(QM1_SVRCONN) TCID (MyClientID) TKEP (https://keycloak.ibm.com:8080/api/token)
As the token secret is sensitive, it is encrypted and stored in the .bindings file. Also the token secret does not get displayed while typing in on the JMSAdmin console. Users can provide their own key for encrypting the token secret or use the default key. If application supplies the key for encrypting token secret, then key must be supplied while starting the JMSAdmin tool. Here is how you start the JMSAdmin tool with a keyfile:
JMSAdmin -sf $HOME/jmsadmin.key
The same key must be supplied via "com.ibm.mq.jmsadmin.keyfile"
JVM system property to the application that is using the .bindings file containing encrypted token secret. Here is an example code snippet:
String initialContextUrl = "file:/home/JNDI-Directory";
String connectionFactoryFromJndi = "JWTCF";
try {
// Set the key file location
System.setProperty("com.ibm.mq.jmsadmin.keyfile", "/home/jmsadmin.key");
System.setProperty("javax.net.ssl.trustStore", "$HOME/clientTruststore.p12");
System.setProperty("javax.net.ssl.trustStorePassword", "passw0rd");
System.setProperty("javax.net.ssl.trustStoreType","pkcs12");
// Instantiate the initial context
String contextFactory = "com.sun.jndi.fscontext.RefFSContextFactory";
Hashtable<String, String> environment = new Hashtable<String, String>();
environment.put(Context.INITIAL_CONTEXT_FACTORY, contextFactory);
environment.put(Context.PROVIDER_URL, initialContextUrl);
Context context = new InitialDirContext(environment);
// Lookup the connection factory
MQConnectionFactory cf = (MQConnectionFactory) context.lookup(connectionFactoryFromJndi);
}
Try out the new feature and let us know your feedback. You can reach out via the Early Access program, submit Ideas through the IBM Ideas Portal, or if you're not sure where to start - just drop me (shashikanth@in.ibm.com) or my colleagues Bikas (Bikas.Kumar.Patro@ibm.com) or harshitsharma@ibm.com an email.