zhaoyingkun
作者zhaoyingkun·2015-06-15 15:02
其它·北京林业大学

通过 REST 服务集成 WebSphere Portal 与 WebSphere Commerce

字数 24258阅读 2217评论 0赞 0

简介

您可以通过 WebSphere Commerce 提供的 REST(具象状态传输)机制来集成 WebSphere Portal 与 WebSphere Commerce。也可以为系统实现单一身份验证机制并集成 Web Content Manager。

集成 WebSphere Portal 和 WebSphere Commerce 可以带来以下好处:

  • 能够让 WebSphere Portal 实现对移动设备的支持,从而实现演示和响应式设计功能来支持多种屏幕分辨率。

  • 与外部用户存储库集成的单点登录身份验证。

  • 能够呈现来自各种来源(包括 WebSphere Commerce)的内容。

  • WebSphere Portal 中拥有一项开箱即用的个性化功能,可以根据用户角色和权限来调整演示。

  • 支持 Java 规范要求 JSR286,可以实现基于事件的 portlet 通信和其他功能。

  • 能够与 Web Content Management 集成,该功能使用户能够在没有技术支持人员干预的情况下管理店铺内容,并部署逻辑上有关联的演示。

  • 目录搜索功能和订单管理。

WebSphere Commerce 提供了开箱即用的 Web 和 REST 服务。这些 Web 服务要求向 WebSphere Portal 提供一种 Web 服务定义语言,以便可以使用已生成的客户端来访问该服务。要使用 REST API 访问数据或执行更新,WebSphere Portal 可直接通过 HTTP 或 HTTPS 连接来连接服务。RESTful 服务比基于 SOAP 的服务更轻便,可支持基于 HTTP 的模式。但是,要被服务理解,通过 HTTP 发送的消息必须是 JSON 格式。程序化的客户端也必须解析字符串响应和提取必要的数据。有许多库(比如 Google Gson)可以将 JSON 字符串映射到 Java 对象,反之亦然。Java 9 也提供了一个轻量型 JSON API。

WebSphere Portal 和 WebSphere Commerce 都可以维护自己的用户存储库。但是,在某些情况下,两个服务器应共享一个用户存储库,这个存储库可能存储在外部子系统(比如一个基于 LDAP 的子系统)中。您也可以这样配置您的系统,让 WebSphere Portal 和 WebSphere Commerce 都使用 IBM Tivoli Directory Server 提供的企业用户目录。

出于本文的目的,我们参考的是 WebSphere Portal V8.0 和 WebSphere Commerce V7.0。

建立安全的通信

要集成 WebSphere Portal 和 WebSphere Commerce,必须在它们之间建立一种通信机制和安全连接。WebSphere Portal 和 WebSphere Commerce 都使用一个 WebSphere Application Server。因为服务器实例之间的数据交换是通过 HTTP 和 HTTPS 协议实现的,所以这些实例必须交换受信任的密钥。

要集成 WebSphere Portal 和 WebSphere Commerce,需要:

  1. 确保 WebSphere Portal 和 WebSphere Commerce 服务器都在运行。

  2. 登录到 WebSphere Portal Server 的管理控制台。

  3. 导航到 SSL certificate and key management > Key stores and certificates > NodeDefaultKeyStore > Signer certificates

  4. 单击 Retrieve from port

  5. 输入 WebSphere Commerce 服务器的主机名、端口编号和别名

  6. 单击 Retrieve signer information

  7. 单击 Apply

WebSphere Portal(或配置中的客户端)现在拥有通过安全连接与服务器进行通信所需的经过批准的签名者证书。

在由分布式节点和单元组成的典型企业环境中,LTPA 令牌可适当地替代签名者证书。

要配置基于 LTPA 令牌的安全通信机制,需要:

  1. 确保 WebSphere Portal 和 WebSphere Commerce 服务器都在运行。

  2. 登录到 WebSphere Portal Server 的管理控制台。

  3. 导航到 Global Security

  4. 在 Authentication 部分,单击 LTPA

  5. 在 Cross-cell single sign-on 部分,输入并确认密码。输入完全限定的密钥文件名。确保文件名包含连接的物理存储媒体和逻辑驱动程序的路径。

  6. 单击 Export keys

  7. 导航到密钥文件并复制到可共享的媒体。

  8. 登录到 WebSphere Commerce 服务器的管理控制台。

  9. 单击 Global Security > LTPA

  10. 单击 Import keys

共享的 LTPA 令牌现在可以同时用在两个服务器上,以便在它们之间建立安全连接。

构建 portlet

WebSphere 使用了基于 Java (JSR286) 的 portlet。一种不错的做法是创建一个扩展 GenericPortlet 类的基础 portlet,如图 1 所示:

public class BasePortlet extends GenericPortlet { …}

图 1. Portlet 类图

Figure4.jpg

图 2. Portlet 类图


Figure5.jpg

Portlet 将视图整合为 JSP 页面,并且处理逻辑是使用 Java 编写的。模块化逻辑并遵循模型-视图-控制器 (MVC) 设计模式始终不错的做法。将 REST 处理逻辑整合到帮助器 Java 类中也是一种不错的做法。例如,考虑一个典型的企业 portlet 应用程序,它聚合来自多个来源的数据和服务,以及调节身份验证和授权要求。在大多数情况下,有多个 portlet 来实现不同功能,比如登录、用户注册和密码重置。

图 3. Portlet 组件图

Figure6.jpg

处理

WebSphere Commerce 使用了 HTTP 所支持的现有的 GET、POST、PUT 和 DELETE 方法。WebSphere Portal 的 HTTP 客户端发起一个 HTTP POST 请求来验证某个已注册的顾客,或者为来宾用户生成临时身份。REST 层调用安全运行时来创建身份验证令牌 WCToken 和 WCTrustedToken,供未来的请求使用。WCToken 在安全和非安全的连接中都可以使用。WCTrustedToken 用在安全的 HTTPS 请求中。JAX-RS (Java API for RESTful Web Services) 资源调用实体提供程序来生成响应,响应会返回给客户端。

图 4. 登录顺序图

Figure7.jpg

1. 首先,必须对用户执行身份验证。向 WebSphere Commerce 发出一个登录请求。响应包含两个令牌 WCToken 和 WCTrustedToken,后续请求需要使用它们。然后 portlet 可呈现一个 JSP 页面视图。

代码清单:

public void doView(RenderRequest request, RenderResponse response) throws PortletException, IOException {

    response.setContentType(request.getResponseContentType());

    PortletRequestDispatcher rd = getPortletContext().getRequestDispatcher(getJspFilePath(request, LOGIN_JSP));

    rd.include(request,response);

}

2. portlet 包含 processAction 方法,用户单击 Login 按钮时会调用该方法:

代码清单:

public void processAction(ActionRequest request, ActionResponse response) throws PortletException, IOException {

    username = request.getParameter("username");

    password = request.getParameter("password");

    executeLogin(request);                 

}

3. 在执行身份验证后,executeLogin 方法将用户登录到应用程序:

代码清单:

private void executeLogin(ActionRequest request) {   

    try{

        URL url = new URL("https://" + ipaddress + "/wcs/resources/store/10001/loginidentity");                

        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        conn.setDoOutput(true);

        conn.setRequestMethod("POST");

        conn.setRequestProperty("Content-Type", "application/json");

        String input =

        "{"logonId" : " + username + ", "logonPassword" :" + password + "}";

        OutputStream os = conn.getOutputStream();

        os.write(input.getBytes());

        os.flush();

        if (conn.getResponseCode() != HttpsURLConnection.HTTP_CREATED) {

            throw new RuntimeException("Failed : HTTP error code : "

                + conn.getResponseCode());

        }

        BufferedReader br = new BufferedReader(new InputStreamReader((conn.getInputStream())));

        String output;

        while ((output = br.readLine()) != null) {

            if (output.indexOf("WCToken")>-1){

            String tokens[] = output.split(":");

            tokens[1] = tokens[1].substring(2, tokens[1].length()-2);

                WCToken = (tokens[1]);

            }

        if (output.indexOf("WCTrustedToken")>-1){

        String tokens[] = output.split(":");

        tokens[1] = tokens[1].substring(2, tokens[1].length()-2);

        WCTrustedToken = (tokens[1]);

        }

    }      

    PortletSession session = request.getPortletSession();

    session.setAttribute("WCToken", WCToken, PortletSession.APPLICATION_SCOPE);

    session.setAttribute("WCTrustedToken", WCTrustedToken, PortletSession.APPLICATION_SCOPE);

    conn.disconnect();

    } catch (MalformedURLException e) {

        e.printStackTrace();

      } catch (IOException e) {

    e.printStackTrace();

}

}

要从应用程序注销用户,可以执行以下方法:

public static void executeLogout()  {   

try {       

        URL url = new URL("https://" + ipaddress + /wcs/resources/store/10001/loginidentity/@self");

        HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();

        conn.setDoOutput(true);

        conn.setRequestMethod("DELETE");

        conn.setRequestProperty("Content-Type", "application/json");

    conn.setRequestProperty("WCToken", WCToken);

    conn.setRequestProperty("WCTrustedToken", WCTrustedToken);

        OutputStream os = conn.getOutputStream();

        os.flush();

        if (conn.getResponseCode() != HttpsURLConnection.HTTP_OK) {

            throw new RuntimeException("Failed : HTTP error code : "

                + conn.getResponseCode());

        }

        BufferedReader br = new BufferedReader(new InputStreamReader(

                (conn.getInputStream())));

        conn.disconnect();

      } catch (MalformedURLException e) {

        e.printStackTrace();

      } catch (IOException e) {

        e.printStackTrace();

    }

}

在登录后,用户可以浏览目录,查看可用产品和服务列表,向购物车添加商品等。要以编程方式订购商品,必须向 URI /wcs/resources/store/10001/cart 发送一个 HTTP 请求,该请求中包含 JSON 字符串对象 {"orderItem":[{"productId":"10040","quantity":"1.0"}]}

该请求会从 WebSphere Commerce 服务器得到以下响应:{"orderItem":[{"orderItemId":"150001"}],"orderId":"17001"}

要更新购物车中的商品,可以向 URI /wcs/resources/store/10001/cart/@self 发送一个包含 JSON 字符串 {"orderItem":[{"quantity":"2.0","orderItemId":"60002"}]} 的 HTTP 请求。该请求会得到以下响应:{"orderItem":[{"orderItemId":"60002"}],"orderId":"10501"}。从编程角度讲,它可通过部署在 WebSphere Portal 上的以下 Java 代码来完成:

public  void executeAddToCart(ActionRequest request)  {   

    try {      

        URL url = new URL("http://"+ipaddress+ "/wcs/resources/store/10001/cart");

        HttpURLConnection connection = (HttpURLConnection) url.openConnection();

        connection.setDoOutput(true);

        connection.setRequestMethod("POST");

        connection.setRequestProperty("Content-Type", "application/json");

        connection.setRequestProperty("WCToken", WCToken);

        String input =         

            "{"orderItem": [{"productId": "10040", "quantity": "1.0"}]}" ;

        OutputStream os = connection.getOutputStream();

        os.write(input.getBytes());

        os.flush(); 

        if (connection.getResponseCode() != HttpsURLConnection.HTTP_CREATED) {

                throw new RuntimeException("Failed : HTTP error code : " + connection.getResponseCode());

        }

        BufferedReader rd = new BufferedReader(new InputStreamReader(connection.getInputStream()));

        StringBuilder sb = new StringBuilder();

        String line = null;

            while ((line = rd.readLine()) != null){

            sb.append(line + 'n');

                if (line.indexOf("orderId")>-1){

                String tokens[] = line.split(":");

                tokens[1] = tokens[1].substring(2, tokens[1].length()-2)

                orderId = (tokens[1]);

                }

            }

    }

    catch (Exception e) {    

        e.printStackTrace();     

    }

}

图 5. 订单结账场景顺序图

Figure8.jpg

下一个逻辑用例是用户结账。这一步涉及到服务调用、预结帐和结账。将同一个 JSON 字符串 {"orderId":"10501"} 分别发送到以下 URI:/wcs/resources/store/10001/cart/@self/precheckout/wcs/resources/store/10001/cart/@self/checkout,服务器返回字符串 {"orderId":"10501"}。再次声明,预结账和结账功能是以编程方式实现的:

public void precheckout(ActionRequest request){            

    try {     

        URL url = new URL("https://"+ipaddress+ "/wcs/resources/store/10001/cart/@self/precheckout");          

        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

        connection.setDoOutput(true);

        connection.setRequestMethod("PUT");

        connection.setRequestProperty("Content-Type", "application/json");

        connection.setRequestProperty("WCToken", WCToken);

        connection.setRequestProperty("WCTrustedToken", WCTrustedToken);

        String input = "{"orderId" : "" + orderId + ""}";

        OutputStream os = connection.getOutputStream();

        os.write(input.getBytes());

        os.flush();

    }

public  void checkout(ActionRequest request)  {   

    try {           

        URL url = new URL("https://"+ipaddress+ "/wcs/resources/store/10001/cart/@self/checkout");         

        HttpsURLConnection connection = (HttpsURLConnection) url.openConnection();

        connection.setDoOutput(true);

        connection.setRequestMethod("POST");

        connection.setRequestProperty("Content-Type", "application/json");

        connection.setRequestProperty("WCToken", WCToken);

        connection.setRequestProperty("WCTrustedToken", WCTrustedToken);

        String input =  "{"orderId" : "" + orderId + ""}";

        OutputStream os = connection.getOutputStream();

        os.write(input.getBytes());

        os.flush();

        .

        .

        .

        if (connection.getResponseCode() != HttpsURLConnection.HTTP_CREATED) {

        throw new RuntimeException("Failed : HTTP error code : "

                    + connection.getResponseCode());

        }

    }

}

也可以向 URI /wcs/resources/store/10001/order/@history 发出一个请求,从 WebSphere Commerce 服务器获取订单历史。

管理用户会话

在用户经过身份验证并向购物车添加商品后,必须持久保存与给定用户 ID 有关联的数据。如果有多个用户在使用该应用程序,还必须在部署在 WebSphere Portal 上的不同 portlet 之间共享数据,并将它们显示在不同页面上。可以通过各种技术来跟踪用户数据和活动。其中的一个选择是使用 portlet 会话。要使用 portlet 会话,必须指定一个具体的会话范围 APPLICATION_SCOPEPORTLET_SCOPE。具体会话范围的指定通常与某个 HTTP 会话和某个 Portal DynaCache 一起完成,后者将会存储可在一个集群中重复的可共享数据。下面的代码段演示了如何在 portlet 会话中设置用户数据。具体地讲,该代码段演示了如何保存身份验证令牌,这些令牌可在针对 WebSphere 服务器的后续请求中使用:

PortletSession session = request.getPortletSession();

session.setAttribute("WCToken", WCToken, PortltSession.APPLICATION_SCOPE);

session.setAttribute("WCTrustedToken", WCTrustedToken, PortletSession.APPLICATION_SCOPE);

以下代码从一个会话获取一个安全令牌:

renderRequest.getPortletSession().getAttribute("WCToken",PortletSession.APPLICATION_SCOPE);

要发出后续请求:

conn.setRequestMethod("POST");

conn.setRequestProperty("Content-Type", "application/json");

conn.setRequestProperty("WCToken", WCToken);

conn.setRequestProperty("WCTrustedToken", WCTrustedToken);

portlet 的一个最有用的功能是,它可与其他 portlet 进行通信。尽管标准 portlet API 未定义通信机制,但可以使用 PortletSession 对象在 portlet 之间传递信息。PortletSession 对象是门户在多个浏览器请求中识别和存储有关用户的短暂信息的机制。应用程序中的一个 portlet 可将值写入到 PortletSession 对象,同一个应用程序中的其他 portlet 可读取和使用这些值。这种通信方式得益于 portlet 请求生命周期。当某个门户页面上存在用户交互时,就会发生以下事件序列:

图 6. 通过事件交换门户数据

Figure9.jpg

  1. 发生事件的 portlet 首先处理该事件。此 portlet 可向会话的应用程序范围添加属性。然后,该属性可供应用程序中的所有 portlet 访问。

  2. 该 portlet 完成对事件的响应后,页面上的每个 portlet(包括处理了该事件的 portlet)将呈现自身。呈现时,所有 portlet 都可访问应用程序范围属性,并使用它响应用户操作。

portlet 事件模型是一种松散耦合、代理式模型,让 portlet 对状态更改做出反应,无需用户直接操作。事件并没有只用于从 portlet 到 portlet 的通信 。portlet 容器也可发起可由 portlet 使用的事件:

public void processAction(ActionRequest request, ActionResponse response)

    throws PortletException, java.io.IOException {

    QName qName = new Qname("http://DN286.com", "DN2CN");

    String symbol = (String) request.getParameter("symbol");

    response.setEvent(qName, symbol);

}

.

.

.

public void processEvent(EventRequest request, EventResponse response)

throws PortletException, IOException {

    super.processEvent(request, response);

    value = request.getEvent().getValue().toString();

    response.setRenderParameter("symbol", value);      

}

有关基于事件的 portlet 间通信的更详细描述,请参阅 使用 IBM WebSphere Portal 和 JSR286 执行基于事件的 portlet 间通信。

LTPA 令牌

在生产环境中,LTPA(轻量型第三方身份验证)是在多个服务器之间启用和同步单点登录身份验证的首选方法。LTPA 选项还需要从 WebSphere Application Server 管理控制台配置应用服务器。可以使用以下 Java 代码从 WebSphere Application Server 获取 LTPA 令牌:

private  String getSecurityToken(){

    byte[] token = null;

    try{

        Subject security_subject = WSSubject.getRunAsSubject();

        if (security_subject != null){

            Set security_credentials =

            security_subject.getPublicCredentials(WSCredential.class);

            WSCredential security_credential =

            (WSCredential)security_credentials.iterator().next();

            String user = (String) security_credential.getSecurityName();

            if(user.equalsIgnoreCase("UNAUTHENTICATED")){

                return null;

            }

            token = security_credential.getCredentialToken();

            if(token == null){

                return null;

            }

            String ltpaToken = com.ibm.ws.webservices.engine.encoding.Base64.encode(token);             

            return ltpaToken;

        }

    }

    catch (Exception e){

        e.printStackTrace();

    }

    return null;

图 7. 使用 LTPA 令牌执行身份验证

Figure10.jpg

门户响应式设计

WebSphere Portal V8.0 提供了一组工件(比如 CSS 媒体查询)来支持响应式 Web 设计。portlet 可以拥有自己的 JSP 格式的视图。通常,Web 应用程序包含多个 portlet,这意味着这些 portlet 视图必须以一致的方式适应不同的设备和屏幕分辨率。典型的智能电话的屏幕分辨率在 320 到 480 像素之间。网络本和平板电脑的屏幕分辨率在 768 到 1024 像素之间,台式机拥有超过 1024 像素的屏幕分辨率。必须根据研究计划网站的目标观众及其使用模式的研究结果,制定支持某种特定设备或屏幕分辨率的决定。值得一提的是,基于 Web 的设备和分辨率模拟器通常不适合测试用途。一种更可靠的方法是测试实际的设备,或者使用来自开发工具包的模拟器,比如 Android SKD。有关 WebSphere Portal V8.0 的响应式 Web 设计功能的更多信息,请参阅 使用 WebSphere Portal 实现响应式 Web 设计,第 3 部分:开发多通道 portlet。

处理 JSON 字符串

可以使用 Java 库 Google Gson 将 Java 对象转换为其 JSON 表示。通过使用 Gson,还可以将 JSON 字符串转换为等效的 Java 对象。例如:

DataObject obj = gson.fromJson(br, DataObject.class);

DataObject 是一个具有初始化的值的普通 Java 对象。您可以在以后使用 Gson 在此对象与 JSON 格式字符串之间相互转换。DataObject 是一种 Java 数据结构,一个封装了传递到 JSON 对象的属性的类:

public class DataObject {   

    private String WCToken = null;

    private String WCTrustedToken = null;

    private String userId = null;

    private String personalizationID = null;

   

    @Override

    public String toString() {

        StringBuffer sb = new StringBuffer();

        sb.append("[wcToken="+getWCToken()+"]");

        sb.append("[wcTrustedToken="+getWCTrustedToken()+"]");

        sb.append("[userId="+getUserId()+"]");

        return sb.toString();

    }

    public String getWCTrustedToken() {

        return WCTrustedToken;

    }

    public void setWCTrustedToken(String trustedToken) {

        WCTrustedToken = trustedToken;

    }

    public void setWCToken(String token) {

        WCToken = token;

    }

    public String getWCToken() {

        return WCToken;

    }

    public String getUserId() {

        return userId;

    }

    public void setUserId(String userId) {

        this.userId = userId;

    }

    public String getPersonalizationID() {

        return personalizationID;

    }

    public void setPersonalizationID(String personalizationID){

        this.personalizationID = personalizationID;

    }

}

部署在 WebSphere Portal 和 WebSphere Commerce 上的 portlet 应用程序之间的通信基于 WebSphere Commerce 所公开的 REST 接口。从 WebSphere Portal 应用程序发出的 REST 调用是通过 HTTP 或 HTTPS 传输的。作为 java.net 包的替代者,您可以使用 Apache HttpComponents 包(一种使用 Java 实现的 HTTP 客户端)。Apache HttpComponents 是一个可靠的完整 Java 库解决方案,它执行 HTTP 操作,而且包含 RESTful 服务和功能。Apache HttpClient 是该框架的一个组件,它是 HTTP 1.0 和 1.1 版的一种基于 Java 的实现,在一个可扩展的面向对象框架中包含了所有 HTTP 方法(GET、POST、PUT、DELETE、HEAD、OPTIONS 和 TRACE)。该框架支持使用 HTTPS(基于 SSL 的 HTTP)进行加密、设置连接超时的能力,以及用于多线程应用程序的连接管理支持。下面的代码段发起了一个 HTTP 连接:

CloseableHttpClient httpclient = HttpClients.createDefault();

HttpPost httpPost = new HttpPost ("https://" + ipaddress + "/wcs/resources/store/" + storeId + "/loginidentity");

httpPost.setHreader (Httpheaders.CONTENT_TYPE, "application/json");

StringEntity entity = new StringEntity (input, HTTP.UTF_8);

httpPost.setEntity(entity);

ResponseHandler <String> responseHandler = new ResponseHandler <String> () {

    public String handleResponse(final HttpResponse response) throws ClientProtocolException, IOException {

        ...

    }

};

String responseBody = httpclinet.execute(httpPost, responseHandler);

门户 Web 内容集成

WebSphere Portal V8.0 拥有一个内置的 Web 内容管理系统 IBM Web Content Manager。借助 Web Content Manager,您可以存储和管理 Web 内容工件,比如文件、图像、文本和 HTML 文件。内容可通过公开的 API、Web 服务的一个客户层或 Atom 或 RSS 提要来访问。在用户必须快速更新一个图像、文本或 HTML 片段,而不重新部署应用程序或重新启动服务器或集群的场景中,Web Content Manager 特别有用。其他可管理的工件包括文本文件、PDF 文档、图像和媒体文件。

如果觉得我的文章对您有用,请点赞。您的支持将鼓励我继续创作!

0

添加新评论0 条评论

Ctrl+Enter 发表

作者其他文章

相关文章

相关问题

相关资料

X社区推广