IBM®
跳转到主要内容
    中国 [选择]    使用条款
 
 
Select a scope:Search for:    
    首页    产品    服务与解决方案     支持与下载    个性化服务    
跳转到主要内容

developerWorks 中国  >  WebSphere  >

使用 Object Authority Manager 来增强 WebSphere MQ JMS 安全性

developerWorks
文档选项

未显示需要 JavaScript 的文档选项

样例代码


级别: 中级

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 应用程序所使用的 QueueConnectionFactoryTopicConnectionFactory 方法相对应)。在缺省情况下,这些方法提供的身份验证更局限于 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_USERMQZ_FREE_USER 的实现。所有其他授权服务入口点都将由缺省的 OAM 提供。

在本例中,MQZ_AUTHENTICATE_USER 将实现为只接受密码为 secret 的用户 testuser(在示例代码中将它们定义为常量 USER_IDPASSWORD)。其他任何用户名/密码组合都将导致函数返回 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_USEREXAMPLE_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_FAILEDMQRC_INITIALIZATION_FAILED

构建授权服务

下面几部分介绍如何在 Microsoft® Windows® 和 UNIX® 上构建和安装授权服务。假设 WebSphere MQ 已经安装并配置为使用其缺省的目录位置。如果在安装过程中指定不同的目录,则需要适当地修改以下命令。

Microsoft Windows

在 Windows 上,授权服务必须编译为 .dll,并与 mqm.libmqmzf.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 实用程序:

  1. 选择注册表项 HKEY_LOCAL_MACHINE/SOFTWARE/IBM/MQSeries/CurrentVersion/Configuration/QueueManager/
  2. 打开合适的 QueueManager 项,例如 ExampleQM
  3. 选择 ServiceComponent 项。

regedit 窗口应该如下所示:


图 1. 添加示例授权服务之前的 Windows 注册表
图 1
  1. 右键单击 ServiceComponent 并选择 New => Key
  2. 将该项命名为 example.auth.service
  3. 左键单击该新项,将其选中。
  4. 在右侧窗口中,右键单击并选择 New => String Value
  5. 将 String Value 命名为 Name
  6. 右键单击 String 值,选择 Modify 并输入值 example.auth.service
  7. 重复最后三个步骤,再添加三个 String Value:
    1. 名称:Module,值:C:\Program Files\IBM\WebSphere MQ\bin\example.dll
    2. 名称:Service,值:AuthorizationService
    3. 名称:ComponentDataSize,值:0

现在,regedit 窗口应该如下所示:


图 2. 添加示例授权服务之后的 Windows 注册表
图 2

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 ServicesServiceComponents 选项卡中应该列出了该自定义的授权服务:


图 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 结构。如果要使用授权服务,则必须在客户端安全退出中定义此结构:

  1. 如果调用该退出时的退出原因为 MQXR_SEC_PARMS,则该退出将创建一个新的 MQConnectionSecurityParameters 对象。
  2. 该退出从字段 channelDefParms.remoteUserIdchannelDefParms.remotePassword 获得为 JMS createConnection(String username, String password) 方法提供的用户名和密码,其中 channelDefParms 是在调用该退出时传递给它的 MQChannelExit 对象。
  3. 如果需要,该退出将修改 userId 和 Password(例如,为了对密码进行哈希以便发送;在本例中也必须对授权服务进行编码以使用哈希密码)并使用方法 setCSPUserId(String UserId)setCSPPassword(String password)MQConnectionSecurityParameters 对象中设置它们。
  4. 该退出使用方法 setAuthenticationType(int authType) 来将 authenticationType 字段设置为 MQC.MQCSP_AUTH_USER_ID_AND_PWD
  5. 最后,该退出使用方法 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 formatexample.zip2 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 联系。




对本文的评价

太差! (1)
需提高 (2)
一般;尚可 (3)
好文章 (4)
真棒!(5)

建议?




回页首


IBM 公司保留在 developerWorks 网站上发表的内容的著作权。未经IBM公司或原始作者的书面明确许可,请勿转载。如果您希望转载,请通过 提交转载请求表单 联系我们的编辑团队。
    关于 IBM 隐私条约 联系 IBM 使用条款