 | 级别: 中级 Ben Ritchie (ben.ritchie@uk.ibm.com), 助理软件工程师,WebSphere MQ Client Development, IBM
2006 年 3 月 30 日 本文向您介绍如何使用 WebSphere MQ Object Authority Manager 来实现自定义的授权服务,以便对来自 WebSphere MQ JMS 客户端的连接请求进行身份验证。
引言
Java™ Message Service (JMS) 是面向消息的中间件产品的一个流行的 Java 接口。在 JMS 中,安全性通常委托给消息传递提供程序,并将获得安全性配置的客户端作为它们使用的受管理对象的一部分。然而,该接口为用户识别和身份验证提供此方法:
public Connection ConnectionFactory.createConnection(String username, String password); |
此方法用于创建与队列管理器的连接(与特定于域的 JMS 应用程序所使用的 QueueConnectionFactory 和 TopicConnectionFactory 方法相对应)。在缺省情况下,这些方法提供的身份验证更局限于 JMS 的 WebSphere® MQ 实现。本文向您介绍如何使用 WebSphere MQ V6 引入的新的 Object Authority Manager (OAM) 功能来提供更全面的用户身份验证。
使用 OAM Authenticate User 功能
要提供自定义的用户名/密码身份验证,您需要实现 WebSphere MQ V6 引入的两个新的授权服务功能。MQZ_AUTHENTICATE_USER 是由队列管理器调用的,用于在连接调用期间对用户进行身份验证,而 MQZ_FREE_USER 是在应用程序断开连接时调用的,用于释放资源。您还需要定义一个初始化入口点,它调用 MQZEP 来注册 MQZ_AUTHENTICATE_USER 和 MQZ_FREE_USER 的实现。所有其他授权服务入口点都将由缺省的 OAM 提供。
在本例中,MQZ_AUTHENTICATE_USER 将实现为只接受密码为 secret 的用户 testuser(在示例代码中将它们定义为常量 USER_ID 和 PASSWORD)。其他任何用户名/密码组合都将导致函数返回 MQRC_NOT_AUTHORIZED,它将导致在 JMS 客户端引发 javax.jms.JMSSecurityException。下面显示了所需代码的摘要;要获得完整的实现,您可以下载示例代码。
清单 1. MQZ_AUTHENTICATE_USER 的简单实现
// hard-code a single allowed userId and password
const char USER_ID[] = "testuser";
const char PASSWORD[] = "secret";
/****************************************************************************/
/* Simple implementation of MQZ_AUTHENTICATE_USER. This retrieves a userId
/* and password from the supplied MQCSP structure, and compares them with
/* values defined above. If they match then it returns MQCC_OK and MQRC_NONE,
/* while if they differ it returns MQCC_FAILED with MQRC_NOT_AUTHORIZED.
/****************************************************************************/
static void MQENTRY EXAMPLE_AUTHENTICATE_USER(MQCHAR48 QMgrName, PMQCSP pSecurityParms,
PMQZAC pApplicationContext, PMQZIC pIdentityContext,
PMQPTR pCorrelationPtr, PMQBYTE pComponentData,
PMQLONG pContinuation, PMQLONG pCompCode, PMQLONG pReason) {
// if authType set to MQCSP_AUTH_USER_ID_AND_PWD then we need to check the userId and password
if (pSecurityParms -> AuthenticationType == MQCSP_AUTH_USER_ID_AND_PWD) {
// pointer to the UserId, and user Id length
MQPTR pUserId = pSecurityParms->CSPUserIdPtr;
MQLONG userIdLength = pSecurityParms->CSPUserIdLength;
// pointer to the Password, and password length
MQPTR pPassword = pSecurityParms->CSPPasswordPtr;
MQLONG passwordLength = pSecurityParms->CSPPasswordLength;
// do not continue with next service component
*pContinuation = MQZCI_STOP;
// check for null pointers to the userId and password
if (pUserId && pPassword) {
// check the username and password
if (strncmp((char*)pUserId, USER_ID, userIdLength) ||
strncmp((char*)pPassword, PASSWORD, passwordLength)) {
// strncmp returned a non-zero value, so they don't match
*pCompCode = MQCC_FAILED;
*pReason = MQRC_NOT_AUTHORIZED;
}
else {
// identical, return MQCC_OK
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}
}
else {
// either pUserId or pPassword is null and can't be checked, so return MQCC_FAILED
*pCompCode = MQCC_FAILED;
*pReason = MQRC_NOT_AUTHORIZED;
}
}
else {
// authType is set to MQCSP_AUTH_NONE so no checking to do, return MQCC_OK and MQRC_NONE
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}
}
|
代码中将用户名和密码存储在 MQCSP 结构中,并在调用时将指向此结构的指针 pSecurityParms 传递给该函数。该函数首先查看 MQCSP 结构的 AuthenticationType 属性是否设置为 MQCSP_AUTH_USER_ID_AND_PWD,以此来检查是否需要任何身份验证。如果需要,则使用 strncmp() 函数来将用户名和密码与硬编码的值进行比较。如果它们都匹配,则函数返回 MQCC_OK;否则返回 MQCC_FAILED 和原因代码 MQRC_NOT_AUTHORIZED。
MQZ_FREE_USER 的相应实现是非常简单的;MQZ_AUTHENTICATE_USER 没有分配任何资源,所以可以只返回 MQCC_OK。如果该用户身份验证函数分配了资源,则应该在这里释放:
清单 2. MQZ_FREE_USER 的简单实现
static void MQENTRY EXAMPLE_FREE_USER(MQCHAR48 QMgrName, PMQZFP pFreeParms,
PMQBYTE pComponentData, PMQLONG pContinuation, PMQLONG pCompCode, PMQLONG pReason) {
// finally, simply return MQCC_OK
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
}
|
最后,需要实现一个入口点,以调用 MQZEP 函数来将 EXAMPLE_AUTHENTICATE_USER 和 EXAMPLE_FREE_USER 函数添加到此服务组件的入口点矢量中:
清单 3. 注册 EXAMPLE_AUTHENTICATE_USER 和 EXAMPLE_FREE_USER 的入口点
void MQENTRY MQStart(MQHCONFIG hc, MQLONG Options, MQCHAR48 QMgrName, MQLONG ComponentDataLength,
PMQBYTE pComponentData, PMQLONG pVersion, PMQLONG pCompCode, PMQLONG pReason) {
// return values
*pCompCode = MQCC_OK;
*pReason = MQRC_NONE;
*pVersion = MQZAS_VERSION_5;
// Initialise the entry points that we have defined; EXAMPLE_AUTHENTICATE_USER first
MQZEP(hc,MQZID_AUTHENTICATE_USER,(PMQFUNC)EXAMPLE_AUTHENTICATE_USER,pCompCode,pReason);
// if that succeeded (i.e. pCompCode isn't MQCC_FAILED), initialize EXAMPLE_FREE_USER
if (*pCompCode == MQCC_OK) {
MQZEP(hc,MQZID_FREE_USER,(PMQFUNC)EXAMPLE_FREE_USER,pCompCode,pReason);
}
// if one of these failed, set the reason to MQRC_INITIALIZATION_FAILED
if (*pCompCode != MQCC_OK) {
*pReason = MQRC_INITIALIZATION_FAILED;
}
}
|
这里,两次调用的都是 MQZEP。第一个注册 EXAMPLE_AUTHENTICATE_USER 入口点,如果成功,则第二个调用注册 EXAMPLE_FREE_USER。如果两次调用都成功,则函数返回 MQCC_OK;否则返回 MQCC_FAILED 和 MQRC_INITIALIZATION_FAILED。
构建授权服务
下面几部分介绍如何在 Microsoft® Windows® 和 UNIX® 上构建和安装授权服务。假设 WebSphere MQ 已经安装并配置为使用其缺省的目录位置。如果在安装过程中指定不同的目录,则需要适当地修改以下命令。
Microsoft Windows
在 Windows 上,授权服务必须编译为 .dll,并与 mqm.lib 和 mqmzf.lib 相链接。在 Microsoft Visual Studio 中使用以下过程。首先设置必需的路径(如果需要):
C:\Program Files\Microsoft Visual Studio .NET 2003\Vc7\bin\vcvars32.bat |
其次,用以下命令编译和链接
cl /MD /W3 /Zp4 /D "WIN32" /D "_WINDOWS" /c /I "C:\IBM\WebSphere MQ\Tools\c\include"
example.c link /subsystem:windows /dll "C:\Program Files\IBM\WebSphere MQ\Tools\lib\mqm.lib"
"C:\Program Files\IBM\WebSphere MQ\Tools\lib\mqmzf.lib" example.obj /export:MQStart
|
最后,将 example.dll 复制到 WebSphere MQ bin 目录(通常是 C:\Program Files\IBM\WebSphere MQ\bin)。
UNIX 平台
在 UNIX 上,您必须将授权服务与共享库 libmqm* 和 libmqmzf* 相链接(线程后缀和文件扩展名因平台而异)。OAM 是在一个线程化的环境中运行的,因此必须使用这些库的线程化版本。编译和链接授权服务需要的命令因平台而异。以下命令显示如何编译和链接 32 位 Linux 和 64 位 AIX。它们将在 /var/mqm/exits 目录中生成一个名为 example 的共享库:
Linux Intel(32 位)
gcc -e MQStart -shared -I/opt/mqm/lib -L/usr/lib -lc -lnsl -ldl -lmqmzf_r
-o /var/mqm/exits/example example.c
|
AIX(64 位)
xlc_r -q64 -e MQStart -bM:SRE -I/usr/mqm/inc -L/usr/mqm/lib64 -lmqmzf_r
-o /var/mqm/exits/example example.c
|
有关在其他平台上编译授权服务的信息,请参阅 WebSphere MQ Application Programming Guide。
安装 OAM
Microsoft Windows
您必须通过编辑 Windows 注册表来安装授权服务(在编辑注册表时要十分谨慎)。使用 Windows regedit 实用程序:
- 选择注册表项
HKEY_LOCAL_MACHINE/SOFTWARE/IBM/MQSeries/CurrentVersion/Configuration/QueueManager/
。
- 打开合适的
QueueManager
项,例如
ExampleQM
。
- 选择
ServiceComponent
项。
regedit 窗口应该如下所示:
图 1. 添加示例授权服务之前的 Windows 注册表
- 右键单击
ServiceComponent
并选择 New => Key。
- 将该项命名为
example.auth.service。
- 左键单击该新项,将其选中。
- 在右侧窗口中,右键单击并选择 New => String Value。
- 将 String Value 命名为
Name。
- 右键单击 String 值,选择 Modify 并输入值
example.auth.service。
- 重复最后三个步骤,再添加三个 String Value:
- 名称:
Module,值:C:\Program Files\IBM\WebSphere MQ\bin\example.dll
- 名称:
Service,值:AuthorizationService
- 名称:
ComponentDataSize,值:0
现在,regedit 窗口应该如下所示:
图 2. 添加示例授权服务之后的 Windows 注册表
Unix
在 UNIX 平台上,必须在 qm.ini 中添加一个 stanza。编辑文件 /var/mqm/qmgrs/QMname/qm.ini,其中
QMname
是正在使用的 WebSphere MQ 队列管理器的名称,并将以下行添加到该文件中缺省 ServiceComponent stanza 之前。
ServiceComponent:
Name=example.auth.service
Module=/var/mqm/exits/example
Service=AuthorizationService
ComponentDataSize=0
|
安装确认
WebSphere MQ 队列管理器必须重新启动才能激活该新的授权服务。在重新启动队列管理器之后,可以通过 WebSphere MQ Explorer 来确认安装。通过 strmqcfg 命令启动 Explorer,然后右键单击安装授权服务的队列管理器,并选择 Properties。接下来,在打开的窗口中选择 Installable Services。ServiceComponents 选项卡中应该列出了该自定义的授权服务:
图 3. 通过 MQ Explorer 确认安装
JMS 身份验证
WebSphere MQ JMS 应用程序可以通过两种方式连接到 WebSphere MQ 队列管理器。在绑定模式下,客户端使用 Java 本地接口(Java Native Interface,JNI)直接连接到 WebSphere MQ,而在客户端模式下,它们使用传输控制协议/网际协议 (TCP/IP) 连接。使用身份验证服务的方式随所选择的模式的不同而不同。
绑定模式下的身份验证
当使用绑定连接时,JMS 用户名和密码直接传递到 OAM 用户身份验证函数以进行身份验证。如果提供的凭据被拒绝,则将引发 javax.jms.SecurityException。对于上述示例授权服务,请参见以下代码片段:
清单 4. 用无效的用户名和密码连接到队列管理器
import javax.jms.*;
public class BindingsExample {
ConnectionFactory cf = null;
Connection conn = null;
public static void main(String[] args) {
try {
//configure JNDI context
Context ctx = new InitialDirConext(...);
// get a pre-defined connection factory from JNDI
cf = (ConnectionFactory)ctx.lookup("ExampleCF");
// connect
conn = cf.createConnection("myUser","myPassword");
}
catch (Exception e) {
//process JMSException/NamingException etc.
}
}
}
|
这将导致异常:
javax.jms.JMSSecurityException: MQJMS2013:
invalid security authentication supplied for MQQueueManager
|
如果把 createConnection(...) 方法调用替换为
conn = cf.createConnection("testuser","secret"); |
则会成功创建该连接。
客户端模式下的身份验证
客户端模式下的身份验证略微复杂一些。在缺省情况下,当 JMS 客户端在客户端模式下连接时,它不会向队列管理器发送 MQCSP 结构。如果要使用授权服务,则必须在客户端安全退出中定义此结构:
- 如果调用该退出时的退出原因为
MQXR_SEC_PARMS,则该退出将创建一个新的 MQConnectionSecurityParameters 对象。
- 该退出从字段
channelDefParms.remoteUserId 和 channelDefParms.remotePassword 获得为 JMS createConnection(String username, String password) 方法提供的用户名和密码,其中 channelDefParms 是在调用该退出时传递给它的 MQChannelExit 对象。
- 如果需要,该退出将修改 userId 和 Password(例如,为了对密码进行哈希以便发送;在本例中也必须对授权服务进行编码以使用哈希密码)并使用方法
setCSPUserId(String UserId) 和 setCSPPassword(String password) 在 MQConnectionSecurityParameters 对象中设置它们。
- 该退出使用方法
setAuthenticationType(int authType) 来将 authenticationType 字段设置为 MQC.MQCSP_AUTH_USER_ID_AND_PWD。
- 最后,该退出使用方法
setMQCSP(MQConnectionSecurityParameters csp) 将 MQConnectionSecurityParameters 对象设置到 MQChannelExit 对象中:
清单 5. 创建 MQConnectionSecurityParameters 对象的简单安全退出代码片段
package example;
public class SecurityExit implements com.ibm.mq.MQSecurityExit {
public byte[] securityExit(MQChannelExit channelExitParms,
MQChannelDefinition channelDefParms,
byte[] agentBuffer) {
switch (channelExitParms.exitReason) {
case MQC.MQXR_INIT:
// initialize etc.
break;
case MQC.MQXR_INIT_SEC:
// transmit MCAUserId if desired etc.
break;
case MQC.MQXR_SEC_PARMS:
// create a new MQCSP object
MQConnectionSecurityParameters csp = new MQConnectionSecurityParameters();
// set authenticationType to AUTH_USER_ID_AND_PWD
csp.setAuthenticationType(MQC.MQCSP_AUTH_USER_ID_AND_PWD);
// set CSP UserId and password from the JMS-supplied values
csp.setCSPUserId(channelDefParms.remoteUserId.trim());
csp.setCSPPassword(channelDefParms.remotePassword.trim());
// finally add MQCSP object to the channelExitParms
channelExitParms.setMQCSP(csp);
break;
case MQC.MQXR_TERM:
// etc.
}
return agentBuffer;
}
}
|
当该退出用 MQXR_SEC_PARMS 调用后返回时,如果 MQChannelExit.getMQCSP() 非空,则系统会将该退出定义的 MQConnectionSecurityParameters 对象的内容发送到队列管理器,并传递给授权服务以进行身份验证。
其他两个配置步骤也是必要的。首先,提供的 CSPUserId 必须与目标服务器上的用户相对应。其次,可能需要将正在使用的通道上的 MCAUSER 属性设置为授权访问队列管理器资源的用户。在完整的安全性解决方案中,通常在用 MQXR_INIT_SEC 调用时由客户端安全退出对此进行设置,但也可能通过 WebSphere MQ Explorer 设置,或者使用 runmqsc 命令设置:
alter channel(channel name) chltype(svrconn) mcauser(UserId) |
如果 ConnectionFactory 是在 JNDI 名称空间中定义的,则可以使用 JMSAdmin 工具通过以下命令设置该退出:
ALTER CF(CFname) SECEXIT("example.SecurityExit") |
ConnectionFactory 必须设置为使用 TRANSPORT(CLIENT);否则此命令将失败。如果 ConnectionFactory 是以编程方式定义的,则可以按照以下方式设置该退出:
ConnectionFactory cf = new MQConnectionFactory();
cf.setSecurityExit("example.SecurityExit");
|
现在,通过此 ConnectionFactory 创建连接将使用示例身份验证服务来检验提供的用户名和密码。
结束语
本文介绍了如何实现和部署自定义身份验证服务,以及如何从 JMS 应用程序使用它。该示例很简单,但包含了要为来自 WebSphere MQ JMS 客户端的连接请求提供自定义授权所需的全部元素。
致谢
作者感谢 Eileen Dreyer 审阅了原稿,也感谢 David Postlethwaite 时常就 Object Authority Manager 的工作提出建议。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Code sample in zip format | example.zip | 2 KB |
FTP | HTTP |
|---|
参考资料
关于作者  | |  |
Ben Ritchie 是位于 UK 的 IBM Hursley Software Lab 的一位软件工程师,他是 WebSphere MQ Client Development 小组的一名 Java 开发人员。Ben 在获得苏塞克斯大学天体物理学博士学位之后,于 2001 年加入 IBM,并从那时起开始研究 JMS,首先研究的是 WebSphere MQ Everyplace,最近开始研究 WebSphere MQ。您可以通过 ben.ritchie@uk.ibm.com 与 Ben 联系。 |
对本文的评价
|  |