 | 级别: 中级 Ahmed Abbass (aabbass@eg.ibm.com), IT 架构师, IBM
2008 年 3 月 17 日
通过使用 IBM® WebSphere® Portal V5.1 或 V6.0 在您的门户应用程序中实现“Remember Me”功能,为您提供更好的用户体验。
来自 IBM WebSphere Developer Technical Journal.
引言
“Remember Me”是一种由许多网站提供的流行功能,它通过仅质询一次用户凭据简化了登录流程,并提供了更好的用户体验。如果用户在登录时选择此功能,它能使浏览器“记住”该用户的身份,便于以后访问该站点。用户凭据将作为 cookie 保留在浏览器中,直到该 cookie 过期,或者用户从该站点注销。
本文介绍如何在启用了安全功能的 WebSphere Portal 上实现 Remember Me 功能,同时还介绍了所涉及的示例实现步骤。为方便起见,本文包含的下载文件中提供了完整示例。
本文假设您非常了解 Servlet 编程、Portlet 编程和 WebSphere 安全性。
Remember Me 的工作原理
一般情况下,Remember Me 流程方案包括以下三个基本部分:
-
登录,用户使用登录 Portlet 在这里输入用户名和密码,然后选择 Remember Me 选项以启动该功能。图 1 显示了该流程中的“登录”流部分:
- 当用户使用自定义登录 Portlet 输入用户名和密码时将开始该流,然后选中 Remember Me 选项。
- WebSphere Application Server 将根据用户注册表验证这些凭据。此流遵从基于 WebSphere 表单的身份验证的缺省行为,其中用户名和密码包括在请求中。
- 如果身份验证成功,应用程序服务器将把该请求转到 WebSphere Portal,以便为身份验证和个性化目的从该用户的概要中检索其余的信息。如果身份验证不成功,则将该用户转到包含自定义登录 Portlet 的登录页。
- 如果在登录时选择了 Remember Me 选项,则 Custom login portlet 将加密该用户密码并与用户名一起存储在相应的浏览器 cookie 中。然后将该用户转给请求的页。如果未选择 Remember Me,则该门户仅显示请求的页。
在图 1 中,图表上的白框显示了由 IBM WebSphere Application Server 或 WebSphere Portal 执行的标准功能。其他框表示由自定义组件执行的功能(请参见图表说明)。
图 1. 登录和浏览流
-
浏览,在浏览期间,用户可以请求该门户下的任何 URL(无论其是否受保护)并利用该站点。图 1 还显示了该流程中的浏览流部分。
当您请求该门户下的任何页时,自定义开发的 Servlet 筛选器可以截获该请求,以检查该用户是具有了有效的经过身份验证的会话,还是在该用户最后一次登录时选择了 Remember Me。如果属于其中的任何一种情况,Servlet 筛选器将把该请求转到与请求的页对等的受保护门户页。例如,如果请求了 http://myportal/wps/portal/mypage,则会将该用户转到 http://myportal/wps/myportal/mypage。
如果该用户此前已经通过应用程序服务器的身份验证(即具有有效的、经过身份验证的会话),则应用程序服务器会将该用户凭据转到 WebSphere Portal。然后该流将像在“登录”流程中那样继续进行。
另一方面,如果该用户没有有效的、经过身份验证的会话,Remember Me 解决方案将通过向自定义开发的 Trust Association Interceptor (TAI) 委派控制来覆盖缺省的 WebSphere 安全身份验证行为,TAI 然后尝试从“登录”流程中创建的 cookie 中提取用户凭据(如果存在这些凭据)。如果不存在此类 cookie,则会将该用户转到该门户的登录页,以便他能够输入其凭据。该流将像在“登录”流程中那样继续进行。
-
注销,用户选择 Sign out 选项结束会话(图 2)。
当注销时,Custom login portlet 将删除用户使用 Remember Me 选项登录时创建的 cookie。
图 2. 注销流
本文中提供的示例代码显示了同一 Portlet 中的登录和注销功能,但是,如果愿意,您可以在单独的 Portlet 中分别实现这两种功能。
实现该功能
您可能会想到,基于前面介绍的三步骤流程,存在三种支持此解决方案的自定义开发组件:
-
Servlet 筛选器
将非安全门户页中的用户请求转到安全门户页。
-
自定义 Trust Association Interceptor
将根据用户注册表实现缺省的身份验证行为(给定用户名和密码)。它还从 cookie 中检索用户凭据以用于身份验证。
-
自定义登录 Portlet
将加密用户密码并将其与用户名一起存储在浏览器 cookie 中。当用户决定注销时,同一门户还负责删除这些 cookie。
专用于此方案的重要组件是 WebSphere Application Server 和 WebSphere Portal 的标准功能,它们不需要任何自定义开发。
|
表 1. Remember Me 组件
|
| 组件 | 可部署格式 | 项目类型 |
|---|
| Servlet 筛选器 | JAR 文件 | Java 项目 | | 自定义 TAI | JAR 文件 | Java 项目 | | 自定义登录 Portlet | Portlet WAR | Portlet 项目 |
表 1 中还列出了这些组件的示例源,本文的下载文件中提供了这些示例源。您可以将此资料作为项目互换文件导入到 IBM Rational® Application Developer 工作区中,然后在继续下一部分时使用它们,这些资料介绍了这些组件的开发。
1. Servlet 筛选器
Servlet 筛选器代表一个可插入容器的点,您可以在其中注册筛选器,以便侦听特定的请求模式。筛选器使您能够在请求到达其目标资源之前控制该请求,这意味着您可以在此阶段执行所需的任何检查或预处理,修改请求属性,甚至在某些情况下将该请求重定向到另一资源。
在以下两种情况中,此示例使用服务器筛选器将一个请求重定向到安全门户区域 (/myportal):
- 用户已登录,具有有效的可信会话,并尝试在此会话期间访问 /portal 下的资源,从而避免了门户对该用户的注销。
- 用户尝试在启用 Remember Me 的情况下访问 /portal 下的资源。
(该门户的缺省行为是无论用户在何时访问 /portal 下的资源,都将注销该用户。)
要实现这一点,请在 javax.servlet.Filter 接口中实现 doFilter() 方法。清单 1 显示了 doFilter() 的一个快照;下载资料中提供了它的完整实现。
清单 1. Servlet 筛选器的源代码
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest req = (HttpServletRequest) request;
HttpServletResponse resp = (HttpServletResponse) response;
//Retrieve name/pwd from cookies
//Full implementation of getCookie method is in FilterUtils class available
//for download
String name = FilterUtils.getCookie(req, PredefinedConstants.USER_ID);
String password = FilterUtils.getCookie(req,PredefinedConstants.USER_PASSWORD);
//Check if the user has an authentic portal session
//Full implementation of hasSession method is in FilterUtils class available
//for download
boolean hasSession = FilterUtils.hasSession(req);
//If no credentials sent and no session then just go on
if ((!hasSession && (name == null || password == null))
|| req.getRequestURL() == null) {
if (chain != null)
chain.doFilter(request, response);
} else {
//Redirect to /myportal
//Constants are defined in PredefinedConstants class available for
//download
String newURL = req.getRequestURL().toString().replaceAll
(UNSECURED_PORTAL, SECURED_PORTAL);
resp.sendRedirect(newURL);
}
} |
上述代码假定未将该门户配置为提供公共会话。否则,使用方法 FilterUtils.hasSession() 将不足以确定该用户是否具有经过身份验证的会话。
2. Trust Association Interceptor
Trust association interceptors (TAI) 表示 WebSphere Application Server 身份验证机制的扩展点,因此,也为依赖于 WebSphere Application Server 安全基础设施的 WebSphere Portal 提供了扩展点。
启用信任关联和向 WebSphere Application Server 注册您的自定义信任关联拦截器可将指向安全资源的身份验证请求流程委派给您自己的身份验证逻辑。因此,您能够将有效的门户会话和对安全资源的访问授权给 WebSphere Application Server 无法使用其缺省的身份验证机制进行身份验证的请求。例如,在本示例中,您需要从请求 cookie 中提取用户凭据并使用它们根据用户注册表验证用户的身份。
实现 TAI 是一个分为两步的流程:
- 询问拦截器是否可以验证某个请求;在此情况下,您可以检查在请求 cookie 中是否发送了有效的用户凭据。
- 如果对第一个问题的回答是肯定的,将信任该拦截器,然后将要求它提供发出请求的可信任用户的用户名称。
在 RememberMeTAI.java 中,您可以找到两种与上述两个步骤对应的实现方法:
- 清单 2 中显示的 isTargetInterceptor() 处理步骤 1。
- 清单 3 中的 getAuthenticatedUsername() 处理步骤 2。
清单 2. isTargetInterceptor()
public boolean isTargetInterceptor(HttpServletRequest req)
throws WebTrustAssociationException {
//This method is invoked when a request to a secured resource is initiated and
//there is no session.
//This interceptor should handle this request if the request has valid cookies
//in the header.
//Retrieve name/pwd from cookies
//Full implementation of getCookie method is in TAIUtils class available
//for download
String name = TAIUtils.getCookie(req,PredefinedConstants.USER_ID);
String password = TAIUtils.getCookie(req,PredefinedConstants.USER_PASSWORD);
//If no credentials sent, then let portal redirect to the login page
if(name == null || password == null){
return false;
}
//Authenticating user
//Full implementation of decryptText method is in Cryptography class available
//for download
password = Cryptography.decryptText(password, PredefinedConstants.CIPHER_KEY);
//Check name/pwd combination in the user registry
//Make sure you cache the context to avoid performance leaks, I'm leaving it
//for simplicity
InitialContext ctx;
try {
ctx = new InitialContext();
UserRegistry reg = (UserRegistry) ctx.lookup
(PredefinedConstants.USER_REGISTRY_SERVICE);
String res = reg.checkPassword(name, password);
return res != null;
} catch (Exception e) {
throw new WebTrustAssociationException(e.getMessage());
}
} |
清单 3. getAuthenticatedUsername ()
public String getAuthenticatedUsername(HttpServletRequest req)
throws WebTrustAssociationUserException {
//If this method is called then this interceptor has verified the cookies and
//authenticated the user in isTargetInterceptor method, so we just need to return
//the username from the corresponding cookie
//Full implementation of getCookie method is in TAIUtils class available for
//download
String userName = TAIUtils.getCookie(req, PredefinedConstants.USER_ID);
if(userName == null)
throw new WebTrustAssociationUserException("No credentials are sent");
return userName;
} |
3. 自定义登录 Portlet
在实现了您的 TAI 和 Servlet 筛选器后,最后一步是实现带有 Remember Me 选项的登录 Portlet,以便让用户选择此功能。此 Portlet 项目具有单一的 Portlet,该 Portlet 使用门户身份验证服务登录用户并基于其登录状态向该用户呈现两个页面。本场景介绍了此项目如何支持 Remember Me 功能实现:
- 该用户第一次打开门户页,login.jsp 呈现在 Portlet 中。
图 3. 带有 Remember Me 选项的登录页
- 该用户输入凭据、选中复选框,然后提交表单。Portlet 的操作阶段将通过 WebSphere Portal 身份验证服务调用现成的登录 Portlet 行为,以让该用户登录,然后,如果已经选中了 Remember Me,该用户的凭据将被传递到呈现阶段。
- 将 loggedIn.jsp 页重新呈现给该用户,它可以将用户名和加密的密码保存到浏览器 cookie 中,并提供一个注销按钮来清除这些 cookie。
清单 4. loggedIn.jsp 中的 Cookie 操作
<%
//Set cookies
if(renderRequest.getPortletSession().getAttribute(PredefinedConstants.USER_ID,
PortletSession.APPLICATION_SCOPE) != null
&& renderRequest.getPortletSession().getAttribute(PredefinedConstants.USER_PASSWORD,
PortletSession.APPLICATION_SCOPE) != null){
String name = (String)renderRequest.getPortletSession().getAttribute
(PredefinedConstants.USER_ID, PortletSession.APPLICATION_SCOPE);
String password =
(String)renderRequest.getPortletSession().getAttribute(PredefinedConstants.USER_PASSWORD,
PortletSession.APPLICATION_SCOPE);
//Encrypt password
//Full implementation of encryptText() method is in Cryptography class available
//for download
password = Cryptography.encryptText(password, PredefinedConstants.CIPHER_KEY);
renderRequest.getPortletSession().removeAttribute(PredefinedConstants.USER_ID,
PortletSession.APPLICATION_SCOPE);
renderRequest.getPortletSession().removeAttribute(PredefinedConstants.USER_
PASSWORD, PortletSession.APPLICATION_SCOPE);
%>
set_cookie ( "<%=PredefinedConstants.USER_ID %>", "<%=name%>", 2100, 01, 15, "/",
"", "" );
set_cookie ( "<%=PredefinedConstants.USER_PASSWORD %>", "<%=password%>", 2100, 01,
15, "/", "", "" );
<%
}
%> |
确保阅读了下载资料中的 MyLogInPortlet.java、login.jsp 和 loggedIn.jsp,以了解完整的实现详细信息。
部署该功能
您已经了解了对每个组件的剖析,现在可以部署它们了。
1. Servlet 筛选器
将 Servlet 筛选器部署到 WebSphere Application Server 的操作步骤:
- 将 RememberMeServletFilter 项目作为 JAR 文件导出到您的 <门户安装目录>/shared/app 目录中。
- 根据您的网络配置导出 WebSphere Portal EAR 文件 wps.ear。如果您有一个集群环境,则必须从 WebSphere Application Server Network Deployment 计算机中导出 WebSphere Portal EAR:
- 从命令行中,更改为 <WAS 安装目录>/bin 目录。
- 调用
wsadmin 命令,将 wps.ear 导出到临时目录(确保在一行中输入了所有的命令);以下命令是针对 Windows 的:
wsadmin.bat -user admin_user_id -password admin_password -c "$AdminApp export wps c:/directory/wps.ear"
其中:
- admin_user_id 是管理员的用户 ID
- admin_password 是管理员的密码。
- 创建 /wps_expanded 子目录。使用 EARExpander 工具展开导出的 EAR 文件的内容(确保所有命令都在一行上输入);以下命令是针对 Windows 的:
EARExpander.bat -ear c:\directory\wps.ear -operationDir c:\directory\wps_expanded -operation expand
- 在 wps.ear/wps.war/WEB-INF/web.xml 中编辑 web.xml。将此筛选器标签添加到其他任何筛选器标签之前:
<filter>
<filter-name>Portal Filter</filter-name>
<filter-class> com.ibm.filter.RememberMeFilter</filter-class>
</filter>
b. Add the following filter-mapping tag before any other filter-mapping tags
<filter-mapping>
<filter-name>Portal Filter</filter-name>
<url-pattern>/portal/*</url-pattern>
</filter-mapping> |
注意,<filter-class> 标签应包括上述筛选器类的完全限定的名称,<url-pattern> 标签说明了此请求对哪个 URL 模式感兴趣。
- 从您最初导出原始 wps.ear 文件的目录中删除该文件。
- 使用 EARExpander 命令将 EAR 目录折叠回到 EAR 文件;以下命令是针对 Windows 的:
EARExpander.bat -ear c:\directory\wps.ear -operationDir c:\directory\wps_expanded -operation collapse
- 使用 wsadmin 命令更新 WebSphere Portal EAR 文件。(如果您具有托管计算单元(带有或不带有集群),请在部署管理器计算机上执行此步骤。)
wsadmin.bat -user admin_user_id -password admin_password -c "$AdminApp install /directory/wps.ear {-update -appname wps -nodeployejb}"
其中:
- admin_user_id 是管理员的用户 ID
- admin_password 是管理员的密码
- 重新启动 WebSphere Portal。在集群配置中,重新启动该集群。
2. 自定义 Trust Association Interceptor
部署自定义 TAI 并向 WebSphere Application Server 注册的操作步骤:
- 将您的自定义拦截器部署到 WebSphere Application Server 的操作步骤:
- 作为 JAR 文件导出您的 RememberMeTAI 项目。
- 复制 JAR 文件并将其粘贴到 WebSphere Application Server 计算机的以下本地路径中:
<WAS installation directory>/lib/ext
- 将 TAI 注册到 WebSphere Application Server 的操作步骤:
- 登录到 WebSphere Application Server 管理控制台(一般在 https://server-ip:9043/admin 中)。
- 从左菜单中,选择 Security => Global security。
- 从身份验证部分中,选择 Authentication mechanisms => LTPA。
- 从 Additional 属性中选择 Trust association
- 选中 Enable trust association 复选框并单击 OK。
- 这将提示您保存更改两次,请始终单击 Save。
- 确保您位于完成上述 d 步骤之后所在的页(或者移动到该页),然后单击 Interceptors。
- 选择所有现存的拦截器并单击 Delete。
- 单击 New 添加新的拦截器,并用拦截器类的完全限定名称来填写类名称属性。
- 单击 OK,并在提示您保存配置时单击 Save。
- 重新启动 WebSphere Application Server 和 WebSphere Portal。
3. 自定义登录 Portlet
将自定义登录 Portlet 部署到 WebSphere Portal 的操作步骤:
- 将 RememberMePortlet 项目导出为 WAR 文件。
- 使用 WebSphere Portal 管理界面将 WAR 文件作为门户应用程序来安装。
- 将您的自定义登录 Portlet 添加到门户缺省的登录页 (wps.login),然后从此页删除缺省的登录 Portlet。
- 向自定义登录 Portlet 授予匿名用户访问权限。
结束语
Remember me 功能可以通过扩展 WebSphere Application Server 安全基础设施在 IBM WebSphere Portal 上实现。本文介绍的解决方案涉及部署 Servlet 筛选器、自定义 Trust Association Interceptor 和 Portlet。通过结合使用自定义模块和标准产品功能,可以向用户保证您的应用程极为安全地“记住”它们。
致谢
作者要感谢 Ahmed El-Maadawy、Hassan Ali 和 Mohamed Shaheen,感谢他们对本文的审阅和所提出的宝贵意见。
下载 | 描述 | 名字 | 大小 | 下载方法 |
|---|
| Sample portlet project | RememberMePortletProject.zip | 12 KB | HTTP |
|---|
| Sample servlet filter | RememberMeServletFilter.zip | 4 KB | HTTP |
|---|
| Sample TAI | RememberMeTAI.zip | 5 KB | HTTP |
|---|
参考资料
关于作者  | 
|  | Ahmed Abbas 是 Cairo Technology Development Center 的一名 IT 架构师。在其实验室的工作中,Ahmed 致力于缩小底层技术理论和实现中实际问题之间的差距,以更好地服务于用户业务需求。 |
对本文的评价
|  |