 | 级别: 初级 Theresa Smit (tsmit@us.ibm.com), WebSphere Portal content publishing, Customer Partnership Team,IBM Brian Cheng (blcheng@us.ibm.com), WebSphere Portal System Test, IBM
2004 年 3 月 01 日 IBM Lotus Workplace Web Content Management v1.1(Java 版)包括了一个样本 LDAP 配置,可以“按此状态”使用或者根据需要修改修改来验证用户、建立组以及确定用户类别。本文将描述在 Portal 环境中如何使用该样本通过 IBM Directory Server LDAP 来实现用户验证和组访问。
引言
IBM Lotus Workplace Web Content Management(以下简称为 Web Content Management)技术(从 Presence Online/Aptrix获得)为因特网、内联网、外联网和 Portal站点提供了端到端的 Web 内容管理。Web Content Management利用后端系统中的内容并减少了开发和实现的时间,它让内容专家牢牢掌握内容的创建和管理,以达到“一次创作,随处发布”的目的。Web Content Management 可以在 Domino 和 WebSphere Application Server 上运行并提供与 WebSphere Portal 和 DB2 Content Manager 的集成。这使得该产品与 IBM 中间件很相配,并允许您利用现有的投资。然而,虽然 Web Content Management 能够在内部处理用户和组管理,但很多顾客还是使用诸如 IBM Directory Server 或 Domino Server 这样的外部 LDAP 服务器作为替代。
IBM Lotus Workplace Web Content Management v1.1(Java版)包括了一个样本 LDAP 配置,可以“按此状态”使用或者根据需要修改修改来验证用户、建立组以及确定用户类别。本文将描述在Portal 环境中如何使用该样本通过 IBM Directory Server LDAP来实现用户验证和组访问。在您开始执行这些步骤之前,我们假定您已安装并配置了您的 Web Content Management UI,并且也安装了 Content Portlet。
图 1 显示了本文所讨论的样本配置。样本场景(1)利用 Web Content Management
connect.cfg 文件中指定的 LDAP 连接属性和特性文件位置来(2)把用户的 LDAP 属性映射到一个现有的 Web Content Management Group并定义用户的概要类别,(3)结果产生特定于组和用户的内容。
图 1: 样本验证配置
本文将描述提供的 LDAP 样本。本文会说明如何配置 Web Content Management特性和配置文件,以使它们与 IBM Directory Server LDAP中的特性和配置文件一致。然后,它向您展示如何测试您的配置以确保您的场景运行正常。本文还包括了一个可选的场景,该场景包含对样本的修改以使它能够直接从 LDAP 组成员中访问组成员。
本文首先一步步地向您介绍如何配置环境以使您通过 IBM Directory Server(使用缺省的 Schema)来验证用户并且利用 LDAP 属性
givenname 来识别组成员。为了让 Web Content Management 能够识别组,就必须在 Web Content Management Administrator User Interface(UI)中创建该组的确切组名。
先决条件
在执行样本的步骤之前,必须在系统中安装或者可以通过其他系统使用 WebSpherePortal、Web Content Management 和 IBM Directory Server。所有的组件都是可运行的并且已配置了Portal,以便与 LDAP 一起运行。并且已经设定了缺省的安装目录和LDAP 属性。
样本文件位于
[WebContentMangement-base]\app\samples\LDAP
,里面包含了样本LDAP 配置用到的所有文件,而
[WebContentMangement-base]是安装 Web Content Management 产品的文件夹。
为了验证您的 LDAP属性,请运行如下
ldapsearch 命令:
ldapsearch -D uid=wpsadmin,cn=users,dc=raleigh,dc=ibm,dc=com -w wpsadmin
-b dc=raleigh,dc=ibm,dc=com uid=*
|
结果中应该包含所有注册用户的全部用户属性的列表,如图2 所示。
图 2: ldapsearch 命令的结果
为使用 LDAP 样本配置 Web Content Management
配置环境的第一步是要改变
connect.cfg 文件的设置:
- 切换到您的应用程序的配置文件夹(例如,切换到
D:\Aptrix1.1\app\config )。
- 为
connect.cfg 文件备份。
- 编辑
config.cfg 文件,根据您所使用的 LDAP 服务器指定下列值。在
<ModuleConfig> 部分,添加以下代码:
<ModuleConfig>
<Sample>
<SampleLDAPProperties value="../config/sampleldaprum.properties" />
<SampleLDAPAdministratorProperies value="../config/sampleldapadmin.properties" />
</Sample>
|
- 为 LDAP Connection 添加如下部分,以替代您的 LDAP 值:
<LDAPConnector>
<OrganizationName value="raleigh.ibm.com" />
<DefaultHost value="tsmit30.raleigh.ibm.com" />
<DefaultPort value=389 />
<DefaultScope value=SUBTREE />
<!-- The version identifier: 2 or 3 -->
<DefaultVersion value=3 />
<!-- The base search scope within the DIT -->
<DefaultBaseSearchDN value="dc=raleigh,dc=ibm,dc=com" />
<DefaultAuthDN value="uid=wpsbind,cn=users,dc=raleigh,dc=ibm,dc=com" />
<DefaultAuthPwd value="wpsbind" />
<DirectoryManagerAuthDN value="cn=root" />
<DirectoryManagerAuthPwd value="password" />
<DefaultTimeout value=1000 />
<ConnectionManager>
<MaxConnectionPools value=1000 />
<MaxConnectionsPerPool value=64 />
</ConnectionManager>
<UseSSL value=false />
</LDAPConnector>
|
- 如下所示修改
UserManager 和
AuthorizationManager 部分:
<UserManager class="com.ilwwcm.sample.ldaprum.framework.SampleCombinedUserManager" >
<LDAPUserManager>
<DefaultUniqueIDFieldName value="uid" />
<DefaultAuthDN value="uid=wpsbind,cn=users,dc=raleigh,dc=ibm,dc=com" />
<DefaultAuthPwd value="wpsbind" />
<DefaultBaseDN value="cn=users,dc=raleigh,dc=ibm,dc=com" />
</LDAPUserManager>
</UserManager>
<AuthorisationManager class=
"com.ilwwcm.sample.ldaprum.framework.SampleCombinedAuthorisationManager" >
<LDAPAuthorisationManager>
<DefaultUniqueIDFieldName value="uid" />
</LDAPAuthorisationManager>
</AuthorisationManager>
|
下一步就是更改特性文件:
- 把样本 LDAP 运行时用户管理配置文件(
sampleldapadmin.properties 和
sampleldaprum.properties )复制到您的 Web Content Management 应用程序的配置目录(例如:
D:\Aptrix1.1\app\config )。
- 更改
sampleldapadmin.properties 文件以包括 LDAP 管理员用户 ID 和密码的规范:
DirectoryManagerAuthDN=uid=wpsbind,cn=users,dc=raleigh,dc=ibm,dc=com
DirectoryManagerAuthPwd=wpsbind
|
- 更改
sampleldaprum.properties 文件以包括 Web Content Management 用户组(
NewsUser )映射到 LDAP 属性(
givenname )的规范,如下所示:
#---------------------------------------------------------------------
# LDAP Configuration Properties
#---------------------------------------------------------------------
username_att=uid
password_att=userpassword
member_att=uniquemember
user_class=person
group_class=groupOfUniqueNames
org_unit_att=cn
org_unit_att_value=users
org_att=dc
org_att_value=raleigh,dc=ibm,dc=com
common_name_att=cn
object_class_att=objectclass
#---------------------------------------------------------------------
# Group and Category LDAP attributes
#---------------------------------------------------------------------
…
#the LDAP attributes that define group membership of Users
#user_grps=groupSample1,groupSample2
USER_GRPS=GIVENNAME
…
#map an LDAP value to IBM Lotus Workplace Web Content Management Group values via:
#e.g. sampleRoleName_GrpMap=groupSample1
GIVENNAME_GRPMAP=GIVENNAME
|
- 编辑
aptrixjpe.properties 文件(位于您的 config 文件夹),指定
manager.runtimeUser 属性为 Runtime User Manager 类:
manager.runtimeUser=[CLASS_NAME]
manager.runtimeUser=com.ilwwcm.sample.ldaprum.server.SampleRuntimeUserManager
|
接下来,把 JAR文件放到合适的目录中。
- 把
ilwwcm-sample-ldaprum.jarfile 文件放到您的 IBM Lotus Workplace Web Content Management WAR 文件(位于您的 IBM WebSphere 安装的
AppServer\installedApps\
[Server-name]\
[ILWWCM.ear]\ilwwcm.war\WEB-INF\lib
目录)的
WEB-INF/lib 目录中。
- 确保为这些文件正确地设置了许可。如果您没有正确设置,在您运行该应用程序的时候就会抛出
java.lang.ClassNotFoundException 异常。
- 如果系统类路径中
没有下面所列出的 LDAP
ldapjdk.jar 和
ldapfilt.jar 文件,那么您必须把这些文件复制到您的 IBM Lotus Workplace Web Content Management WAR 文件的
WEB-INF/lib (与上述步骤所列一致)目录中。虽然我们推荐您使用您的 LDAP 服务器提供的这些 JAR 文件,但是如果您没有这些 JAR 文件,您也可以使用本样本提供的 LDAP JAR 文件作为替代。要了解更多有关配置文件的详细信息,请参阅
ILWWCM-HOME\app\samples\LDAP 目录中的
.txt 文件。
- 重启服务器。
建立用户和组
下一步就是建立所需的用户和组:
- 利用
Portal Sign-up 页面(请参见图 3 的
Portal Login 页面中的右上角)添加一位名为 theresa 的用户,务必将
givenname 属性设置为
NewsUser 。
图 3: Portal 登录页面
- 在 Web Content Management 中添加用户组:
- 以
Administrator 身份登录到 Web Content Management UI 并创建一个名为
NewsUser
的新组。也让
NewsUser 成为 UI
AccessGroup 的一员。这次访问只是为了测试的目的。
- 检验用户
theresa 没有作为 Web Content Management 用户而存在。
测试配置
样本场景提供了针对
NewsUser 组的内容,因此您需要确保允许那些把 LDAP属性 givenname 设为
NewsUser 的用户只能浏览新闻内容,而不允许那些不属于这个组的用户浏览新闻内容。
- 设置 Content Security。在 Content Library 中选择一部分内容,然后设置安全性以使
NewsUser 获得编辑权限。
- 检验配置。打开新 Web Content Management UI 并以用户
theresa 的身份登录。将只显示授权的组件。
- 以用户
theresa 的身份登录 Portal并访问在 End2End 文档中创建的
DeptNews 页面。您将只能浏览已为
NewsUser 设置了安全性的新闻。
- 以
wpsadmin 的身份登录 Portal并访问
DeptNews 页面。您将看不到新闻内容。
可选场景
在LDAP 数据库中为用户维护组成员是很常见的。图 4中显示的可选样本场景利用了
connect.cfg 文件中指定的 LDAP 连接属性和特性文件位置来(1)检索 LDAP以获取包含 User 成员的组的列表,(2)该列表映射到一个现有的 Web Content Management组,(3)结果又产生了特定于组和用户的内容。
图 4: 可选验证场景
首先,我们需要从LDAP 获取一个组成员。通过改变 UserAuthorization管理器的一些代码,您可以实现一个解决方案,该方案不仅使用LDAP 验证用户,还能为每个用户查找 LDAP 组成员,并将其应用于 Web Content Management资源。
样本代码中的组成员检索与如下 LDAP搜索命令相似:
ldapsearch -D uid=wpsadmin,cn=users,dc=raleigh,dc=ibm,dc=com -w wpsadmin
-b dc=raleigh,dc=ibm,dc=com uniqueMember=uid=theresa,cn=users,dc=raleigh,dc=ibm,dc=com cn
|
在此命令中,
?D和
?w选项表示 LDAP 连接器节(Stanza)中的 connect.config 文件指定的用户ID 和密码。
<DefaultAuthDN value="uid=wpsbind,cn=users,dc=raleigh,dc=ibm,dc=com" />
<DefaultAuthPwd value="wpsbind" />
|
?b选项表示
DefaultBaseSearchDN 节中的基
dn:
<DefaultBaseSearchDN value="dc=raleigh,dc=ibm,dc=com" />
|
uniqueMember 和
cn 属性都是在
sampleldaprum.properties 文件中定义的,已验证用户利用这两个属性来检索用户的组名。
member_att=uniqueMember
…
common_name_att=cn
|
要了解限制请参阅
“已知局限性”部分。
对于可选场景,请遵循下列步骤:
- 在 WebSphere Studio Application Developer 中创建一个项目。
- 从
INSTALL/app/samples/LDAP/source
目录导入源代码。
- 编辑 Project Java Build Path。右键单击
Project Name -> Properties -> Java Build Path -> Add Libraries,然后添加下列文件:
-
Ilwwcm-framework.jar
-
ilwwcm-server.jar
-
ldapjdk.jar
-
ldapfilt.jar
-
ilwwcm-commons-xmlpersistency.jar
-
ilwwcm-commons-utils.jar
-
ilwwcm-commons-version.jar
-
ilwwcm-commons-properties.jar
- 把资源绑定属性类添加到包
com.ilwwcm.sample.ldarum 中。要添加这些资源绑定,您必须从
ilwwcm-ldaprum.jar 文件中提取它们,并把它们导入到您的项目中去。这些资源绑定文件应该包括:
-
ldaprum_de.properties
-
ldaprum_en.properties
-
ldaprum_es.properties
-
ldaprum_fr.properties
-
ldaprum_it.properties
-
ldaprum_ja.properties
-
ldaprum_ko.properties
-
ldaprum.properties
-
ldaprum_sv.properties
-
ldaprum_zh.properties
-
ldaprum_zh_TW.properties
- 编辑
SampleLDAPUser.java 文件,把如下代码导入(import)添加到 import 部分。
/** Imports for Checking Groups in LDAP */
import com.presence.connect.utils.Config;
import com.presence.connect.utils.ConfigKeys;
import com.presence.connect.connector.ldap.LDAPConnectionParam;
import com.presence.connect.connector.ConnectionManager;
import com.presence.connect.connector.ConnectorException;
import com.presence.connect.connector.ldap.LDAPConnector;
|
- 把下列全局变量添加到类中:
/** LDAP Connection Parameters*/
private LDAPConnectionParam m_dirMgrParam;
/** LDAP Host*/
private String m_ldapHost = null;
/** LDAP port */
private int m_ldapPort;
/** Use SSL */
private boolean m_useSSL;
/** Connection Timeout */
private int m_timeout =
Connect.getAppConfig().getInt(ConfigKeys.BASE_CONNECTOR + ConfigKeys.SEPARATOR
+ ConfigKeys.LDAP_CONNECTOR_CONFIG + ConfigKeys.SEPARATOR + ConfigKeys.LDAP_TIMEOUT, -1);
|
- 把 Connection 设置和创建信息添加到
SampleLDAPUser(ConnectLDAPEntry p_ldapEntry) 方法的顶部。
/** Create new LDAPConnector and set host, port, and SSL configuration from the values in connect.cfg*/
LDAPConnector connector = new LDAPConnector();
Config config = connector.getConnectorConfig();
m_ldapHost = config.getString(ConfigKeys.LDAP_HOST);
m_ldapPort = config.getInt(ConfigKeys.LDAP_PORT);
m_useSSL = config.getBoolean(ConfigKeys.USE_SSL);
// set up an LDAP connector for the search using the
// directory manager connection parameters
// create directory manager connection param, to be used for all searches m_dirMgrParam = new LDAPConnectionParam(m_ldapHost,
m_ldapPort,
SampleAdministratorProperties.getDirectoryManagerAuthDN(),
SampleAdministratorProperties.getDirectoryManagerAuthPwd(),
m_timeout);
m_dirMgrParam.setSSL(m_useSSL);
|
- 把下列行添加到
SampleLDAPUser(ConnectLDAPEntry p_ldapEntry) 方法中以获得用户专有名称(User Distinguished Name)。在 LDAP 查询中用这段代码来查找用户所属的组。
//get the User DN to be used when finding the user's groups
String userDN = p_ldapEntry.getDN();
|
在后面直接添加:
// get the User name
String[] values = p_ldapEntry.getValues(SampleLDAPProperties.getUsernameAtt());
|
- 更改
SampleLDAPUser(ConnectLDAPEntry p_ldapEntry) 方法所调用的
setUserGroups 来传送
UserDN :
// get the UserGroups by user DN
setUserGroups(userDN);
|
- 用如下方法覆盖
setUserGroups 方法。
private void setUserGroups(String userDN) {
//create the search string, get the search property from SampleLDAPRum.properties
String p_search = new String(SampleLDAPProperties.getMemberAtt()+ "=" + userDN );
//get the Group Name Attribute from the SampleLDAPRum.properties
String groupNameAtt = SampleLDAPProperties.getCommonNameAtt();
s_log.info(2, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.SEARCHING_FOR_1,
new Object[] {p_search}));
LDAPConnector connector = new LDAPConnector();
connector.setConnectionParam(m_dirMgrParam);
// Explicitly connect
try
{
connector.connect();
}
catch (ConnectorException ce)
{
s_log.warn(1, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.SEARCH_FAILED_WARNING_3,
new Object[] {null, ce, ce}));
}
// perform search to find group.
// we may need to retry because a connection could have timed-out at the LDAP Server
// or from the other end.
// A connection may also timeout via AbstractConnection.
ConnectionManager connMgr = connector.getConnectionManager();
ConnectLDAPEntry[] results = null;
ConnectLDAPEntry result = null;
boolean continueAttempts = true;
for (int attempt = 0, maxAttempts = connMgr.getMax() + 1;
attempt < maxAttempts && continueAttempts;
attempt++)
{
s_log.info(2, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.SEARCH_ATTEMPT_1,
new Object[] {String.valueOf(attempt)}));
// ask the Connector to perform a search for the group given a member
// attribute equal to the user dn multiple groups may be returned.
try
{
results = connector.search(null, null, p_search);
for (int i=0; i < results.length; i++){
ConnectLDAPEntry groupObject = results[i];
String[] groupNames = groupObject.getValues(groupNameAtt);
if (groupNames.length > 0 ){
//we assume that each group only has 1 group name, or 1 CN,
// and we add it to our User's Group attribute
m_groups.add(groupNames[0]);
}
}
// the search finished without an exception, no need keep going
continueAttempts = false;
}
// if we get an exception, there is something wrong with the connection
// it may have been broken from the other end, so disconnect internally
// so we can try again
catch (ConnectorException e)
{
s_log.warn(1, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.SEARCH_FAILED_WARNING_3,
new Object[] {p_search, e, e}));
try
{
// disconnect the connection first because it's not working, and
// we want it to reconnect if it's used again
if (connector.getConnection() != null) {
connector.getConnection().disconnect();
}
connector.disconnect();
// try and flush any broken connections out of the pool.
connMgr.releaseFree();
}
catch (ConnectorException ce)
{
s_log.warn(1, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.FAILED_TO_DISCONNECT_EXCEPTION_2,
new Object[] {ce, ce}));
// can't continue to retry if we get an error here
break;
}
}
}
// free the connection by disconnecting the connector
try
{
// disconnect the connection first so we don't re-use it
if (connector.getConnection() != null)
{
connector.getConnection().disconnect();
}
connector.disconnect();
// try and flush any broken connections out of the pool.
connMgr.releaseFree();
}
catch (ConnectorException e)
{
s_log.warn(2, ResourceBundleUtils.getFormattedString
(SampleLdapRumBundleKeyConstants.SAMPLE_LDAPRUM_BUNDLE_NAME,
SampleLdapRumBundleKeyConstants.COULDNT_DISCONNECT_AFTER_AUTH_2,
new Object[] {e, e}));
}
}
|
- 保存文件,然后重新构建该项目。
- 将项目以
ilwwcm-sample-ldaprum.jar
的形式导出。
- 用您导出的文件替换
ilwwcm war 文件的
WEB-INF/lib 目录中的
ilwwcm-sample-ldaprum.jar 。
- 从 WebSphere Administration Console,重启 Web Content Management 应用程序。
- 测试您的修改。
- 验证
theresa 是
NewsUser LDAP/Portal 组的成员而
wpsadmin 不是该组成员。
- 以用户
theresa 的身份登录到 Portal,导航到新闻页面,您将会看到 End2End 文档中创建的新闻内容。
- 以用户
wpsadmin 的身份登录到 WebSphere Portal,然后您将会看到一条身份验证消息。
任何调试信息都会写入服务器的系统输出日志。

 |

|
调试技巧
可以通过增强跟踪提供来自 Web Content Management的更详细的信息。为了做到这一点,可以编辑
connect.cfg 文件,找到 LogManager 的入口,然后如下更改跟踪级别(TraceLevel):
<LogManager>
<ErrorLog>
<File LogFile="../connect/log/error.log" FlushLog=false Buffered=true TraceTime=true TraceDate=true
TraceLevel=3 Rollover=Size MaxFileSize=5M />
</ErrorLog>
<FullLog>
<File LogFile="../connect/log/connect.log" FlushLog=false Buffered=true TraceTime=true TraceLevel=3
TraceDate=true TraceThread=true Rollover=Size MaxFileSize=5M />
<Screen Buffered=false TraceTime=true TraceLevel=0 TraceDate=false TraceThread=true />
</FullLog>
<DebugLog>
<File LogFile="../connect/log/debug.log" FlushLog=false Buffered=false TraceTime=true TraceLevel=3
Rollover=Size MaxFileSize=5M>
<Packages>
<!-- by default not debugging any package -->
</Packages>
</File>
</DebugLog>
</LogManager>
|
可以在下列位置找到错误消息:
- WebSphere Portal 中的信息和错误消息在
[WAS-HOME]\PortalServer\log\SystemOut.log
和
[WAS-HOME]\PortalServer\log\SystemErr.log
中
。
- Web Content Management 中的信息和错误消息信息被粘贴到
[ILWWCM-Base]\app\connect\log\connect.log
。您不能使用诸如 WordPad 这样的文本编辑器直接打开此文件,然而如果您做一个副本,就可以使用 WordPad 来访问和查看副本。
在 WebSphere Portal 每次启动时都会删除这些日志。在 Windows系统中您可以利用类似下面的 BAT 文件来完成同样的工作:
d:
del D:\ILWWCM11GM\app\connect\log\*.*
del D:\WebSphere\PortalServer\log\*.*
cd \WebSphere\AppServer\bin
call startserver.bat WebSphere_Portal -user uid=wpsbind,cn=users,dc=raleigh,dc=ibm,dc=com -password wpsbind
pause
|
已知局限性
已知的局限性如下:
- 即使组存在于 LDAP 中,也必须在 Web Content Management 中定义这些组。
- 使用用户匹配类别的菜单将不适用于为组进行概要分析的类别。与 Users Profile 关联的类别仅用于指定 Current User Categories 的 Matching Profile规则的菜单。
- 不能充分支持用户通过 LDAP 访问 Web Content Management UI,在一些UI 管理操作中可能会产生不可预知的结果。
结束语
本文为使用外部管理的 LDAP提供了两个配置示例,可以用来根据用户的组成员资格给他们交付受管的内容。基于您的 LDAP属性和内容交付需求,您的配置可能会有所不同。本文可以帮助您实现与您的特定需求和环境相匹配的配置。
下载 | 名字 | 大小 | 下载方法 |
|---|
| ContentMgmtLDAP.zip | 8 KB | HTTP |
作者简介  | 
|  | Theresa Smit 在专门研究信息处理技术. 历时 28 年多的时间里,广泛地涉猎了应用程序开发知识。目前,她是 IBM WebSphere Portal 开发组的成员,帮助外部和内部的顾客使用门户技术来处理和提供Web 内容。除 WebSphere Portal 产品之外,她还为使用 IBM Lotus Workplace Web ContentManagement、WebSphere Portal 内容发布和 Personalization 组件提供支持。她还编写了其他文章并在developerWorks、WebSphere Portal、Lotus 以及 IBM Content Manager 技术讨论会上宣读。 |
 | 
|  | Brian Cheng是 IBM Portal 开发组成员,致力于系统验证测试(System VerificationTest)。他主要研究 Portal Environment 中的内容管理(Content Management)和个性化系统集成(Personalization Systems Integration)。 |
对本文的评价
|  |