这个问题困扰我2天了,目前还没解决:主要是我们吧一个项目从Community Edition迁移到Enterprise Edition,我们前端是Extjs发送一个Ajax请求到Portal层的一个方法叫MachinePortlet,本来,在CE上面,无论是Admin用户还是非Admin用户,都没问题。迁移到Enterprise Edition时,只有Admin用户可以正确的访问,而对于非Admin用户,请求无论如何都无法到达MachinaPortlet的serveResource()方法,根本没调用。我想了十来种方法,没有一种解决问题。我觉得,应该和我们的license有很大关系,刚好吃过饭,我可以安心在家反思这个问题,刚好明天可以验证,可惜明天要准备去US的签证,估计也就半天空闲时间。郁闷。

 

其实问题的关键,在于这个页面上的<portlet:resourceURL id=..>是如何关联到portlet的serveResource()方法上的,因为我怀疑这个过程中涉及到有关安全的问题,所以非Admin的用户不给我转发(奇怪的是Community Edition不存在这个问题)。下面我对其进行了一些深入研究:

 

首当其冲的,当然是resourceURL这个标记了,很容易找到其定义:

  1. <tag> 
  2.         <name>resourceURL</name> 
  3.         <tag-class>com.liferay.taglib.portlet.ResourceURLTag</tag-class> 
  4.         <tei-class>com.liferay.taglib.portlet.ResourceURLTei</tei-class> 
  5.         <body-content>JSP</body-content> 
  6.         <attribute> 
  7.             <name>cacheability</name> 
  8.             <required>false</required> 
  9.             <rtexprvalue>true</rtexprvalue> 
  10.         </attribute> 
  11.         <attribute> 
  12.             <name>escapeXml</name> 
  13.             <required>false</required> 
  14.             <rtexprvalue>true</rtexprvalue> 
  15.         </attribute> 
  16.         <attribute> 
  17.             <name>id</name> 
  18.             <required>false</required> 
  19.             <rtexprvalue>true</rtexprvalue> 
  20.         </attribute> 
  21.         <attribute> 
  22.             <name>secure</name> 
  23.             <required>false</required> 
  24.             <rtexprvalue>true</rtexprvalue> 
  25.         </attribute> 
  26.         <attribute> 
  27.             <name>var</name> 
  28.             <required>false</required> 
  29.             <rtexprvalue>true</rtexprvalue> 
  30.         </attribute> 
  31.     </tag> 

可以看出,这个标记有很多属性,他们都是可选的(required=false),并且他们都可以使用run time表达式,比如JSP表达式,或者EL表达式。

并且,它的主要处理类是ResourceURLTag类,而额外信息类在ResourceURLTei中,我们依次看:

 

对于ReosourceURLTag,它继承了ActionURLTag类,并且doTag方法如下:

  1. public static void doTag( 
  2.             String lifecycle, String windowState, String portletMode, 
  3.             String var, String varImpl, Boolean secure, 
  4.             Boolean copyCurrentRenderParameters, Boolean escapeXml, String name, 
  5.             String resourceID, String cacheability, long plid, long refererPlid, 
  6.             String portletName, Boolean anchor, Boolean encrypt, 
  7.             long doAsGroupId, long doAsUserId, Boolean portletConfiguration, 
  8.             Map<String, String[]> params, PageContext pageContext) 
  9.         throws Exception { 
  10.  
  11.         HttpServletRequest request = 
  12.             (HttpServletRequest)pageContext.getRequest(); 
  13.  
  14.      ..
  15.  
  16.         if (Validator.isNotNull(windowState)) { 
  17.             liferayPortletURL.setWindowState( 
  18.                 WindowStateFactory.getWindowState(windowState)); 
  19.         } 
  20.  
  21.         if (Validator.isNotNull(portletMode)) { 
  22.             liferayPortletURL.setPortletMode( 
  23.                 PortletModeFactory.getPortletMode(portletMode)); 
  24.         } 
  25.  
  26.         if (secure != null) { 
  27.             liferayPortletURL.setSecure(secure.booleanValue()); 
  28.         } 
  29.         else { 
  30.             liferayPortletURL.setSecure(PortalUtil.isSecure(request)); 
  31.         } 
  32.  
  33.         if (copyCurrentRenderParameters != null) { 
  34.             liferayPortletURL.setCopyCurrentRenderParameters( 
  35.                 copyCurrentRenderParameters.booleanValue()); 
  36.         } 
  37.  .....

主要就是从页面上的标记中取出一些值,然后设置相应的变量给liferayPortletURL,最终发送给struts框架。

 

安全请求还是非安全请求:

在我这个难题中,第29-31行是比较重要的,因为我的问题就在于,对于Enterprise版本,只有Admin用户可以吧这个请求发送出去。所以我们来探究这一部分。

在PortalUtil中:

  1. public static boolean isSecure(HttpServletRequest request) { 
  2.         return getPortal().isSecure(request); 
  3.     } 

我们转到PortalImpl中:

  1. public boolean isSecure(HttpServletRequest request) { 
  2.         HttpSession session = request.getSession(); 
  3.  
  4.         Boolean httpsInitial = (Boolean)session.getAttribute( 
  5.             WebKeys.HTTPS_INITIAL); 
  6.  
  7.         boolean secure = false
  8.  
  9.         if ((PropsValues.COMPANY_SECURITY_AUTH_REQUIRES_HTTPS) && 
  10.             (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) && 
  11.             (httpsInitial != null) && (!httpsInitial.booleanValue())) { 
  12.  
  13.             secure = false
  14.         } 
  15.         else { 
  16.             secure = request.isSecure(); 
  17.         } 
  18.  
  19.         return secure; 
  20.     } 

从这段代码,我们可以看出,它先判断是否是https请求,如果是返回true.否则,对于http请求,它去看COMPANY_SECURITY_AUTH_REQUIRES_HTTPS 和SESSION_ENABLE_PHISHING_PROTECTION 字段,

这两个字段我们可以在控制面板中看到:

Liferay 6.1 从CE 迁移到 EE版本的一个棘手的问题(1)_serveResource

Liferay 6.1 从CE 迁移到 EE版本的一个棘手的问题(1)_serveResource_02

 

对于现在在家的Community,分别是false和true(明天上班去检查下企业版的配置),所以,对于我们的Community版本,这个判断条件为(false,因为短路表达式),所以secure=request.isSecure(),

 

liferayPortletURL是最重要的URL,这个URL是<portlet:resourceURL>的等效形式,它由两部分组成,一是基本URL,另外是吧标记内的参数依次填充进去。基本URL的代码在

PortalResponseImpl类中。

 

谁去消费这个URL?

不管如何,页面上的resourceURL标记都会创建一个URL,并且必须指向当前的portlet,并且必须触发serveRequest请求,然后在请求中消费自己携带的参数:

JSR-286原文P226:

  1. The portlet resourceURL tag creates a URL that must point to the current portlet and must trigger a serveResource request with the supplied parameters 

 

关于这一点,如何证明呢?

折腾了1小时,完全弄明白了,在我们写的portlet对应的web.xml中定义了PortletServlet,在其中的doService方法中会调用doFilter方法:

  1. public void service( 
  2.             HttpServletRequest request, HttpServletResponse response) 
  3.         throws IOException, ServletException { 
  4.  
  5.         String portletId = (String)request.getAttribute(WebKeys.PORTLET_ID); 
  6.  
  7.         PortletRequest portletRequest = (PortletRequest)request.getAttribute( 
  8.             JavaConstants.JAVAX_PORTLET_REQUEST); 
  9.  
  10.         PortletResponse portletResponse = (PortletResponse)request.getAttribute( 
  11.             JavaConstants.JAVAX_PORTLET_RESPONSE); 
  12.  
  13.         String lifecycle = (String)request.getAttribute( 
  14.             PortletRequest.LIFECYCLE_PHASE); 
  15.  
  16.         FilterChain filterChain = (FilterChain)request.getAttribute( 
  17.             PORTLET_SERVLET_FILTER_CHAIN); 
  18.  
  19.         LiferayPortletSession portletSession = 
  20.             (LiferayPortletSession)portletRequest.getPortletSession(); 
  21.  
  22.         portletRequest.setAttribute(WebKeys.PORTLET_ID, portletId); 
  23.         portletRequest.setAttribute(PORTLET_SERVLET_CONFIG, getServletConfig()); 
  24.         portletRequest.setAttribute(PORTLET_SERVLET_REQUEST, request); 
  25.         portletRequest.setAttribute(PORTLET_SERVLET_RESPONSE, response); 
  26.  
  27.         HttpSession session = request.getSession(); 
  28.  
  29.         PortletSessionTracker.add(session); 
  30.  
  31.         portletSession.setHttpSession(session); 
  32.  
  33.         try { 
  34.             PortletFilterUtil.doFilter( 
  35.                 portletRequest, portletResponse, lifecycle, filterChain); 
  36.         } 
  37.         catch (PortletException pe) { 
  38.             _log.error(pe, pe); 
  39.  
  40.             throw new ServletException(pe); 
  41.         } 
  42.     } 

然后我们到PortletFilterUtil中去看:

  1. public static void doFilter( 
  2.             PortletRequest portletRequest, PortletResponse portletResponse, 
  3.             String lifecycle, FilterChain filterChain) 
  4.         throws IOException, PortletException { 
  5.  
  6.     .. 
  7.         } 
  8.         else if (lifecycle.equals(PortletRequest.RESOURCE_PHASE)) { 
  9.             ResourceRequest resourceRequest = (ResourceRequest)portletRequest; 
  10.             ResourceResponse resourceResponse = 
  11.                 (ResourceResponse)portletResponse; 
  12.  
  13.             filterChain.doFilter(resourceRequest, resourceResponse); 
  14.         } 
  15.     } 

因为我们是在RESOURCE_PHASE,所以它会调用08行-13行,然后就执行调用链:

在FilterChainImpl类中:

  1. public void doFilter( 
  2.             ResourceRequest resourceRequest, ResourceResponse resourceResponse) 
  3.         throws IOException, PortletException { 
  4.  
  5.         if (_portletFilters.size() > _pos) { 
  6.             ResourceFilter resourceFilter = (ResourceFilter)_portletFilters.get( 
  7.                 _pos++); 
  8.  
  9.             resourceFilter.doFilter(resourceRequest, resourceResponse, this); 
  10.         } 
  11.         else { 
  12.             ResourceServingPortlet resourceServingPortlet = 
  13.                 (ResourceServingPortlet)_portlet; 
  14.  
  15.             resourceServingPortlet.serveResource( 
  16.                 resourceRequest, resourceResponse); 
  17.         } 
  18.     } 

可以看出,这个FilterChain会依次调用链子上的所有实现了ResourceServingPortlet接口的Portlet的serveResource方法,因为我们例子中ServiceEnvPortlet和MachinePortlet都实现了ResourceServingPortlet接口,所以他们的serveResource()方法如果有,都会被调用。而调用逻辑都写在serveResource()方法里面了,调用逻辑就是去解析上面构造的liferayPortletURL.

 

现在应该清楚了,希望明天可以搞定问题。