这个问题困扰我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这个标记了,很容易找到其定义:
- <tag>
- <name>resourceURL</name>
- <tag-class>com.liferay.taglib.portlet.ResourceURLTag</tag-class>
- <tei-class>com.liferay.taglib.portlet.ResourceURLTei</tei-class>
- <body-content>JSP</body-content>
- <attribute>
- <name>cacheability</name>
- <required>false</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- <attribute>
- <name>escapeXml</name>
- <required>false</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- <attribute>
- <name>id</name>
- <required>false</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- <attribute>
- <name>secure</name>
- <required>false</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- <attribute>
- <name>var</name>
- <required>false</required>
- <rtexprvalue>true</rtexprvalue>
- </attribute>
- </tag>
可以看出,这个标记有很多属性,他们都是可选的(required=false),并且他们都可以使用run time表达式,比如JSP表达式,或者EL表达式。
并且,它的主要处理类是ResourceURLTag类,而额外信息类在ResourceURLTei中,我们依次看:
对于ReosourceURLTag,它继承了ActionURLTag类,并且doTag方法如下:
- public static void doTag(
- String lifecycle, String windowState, String portletMode,
- String var, String varImpl, Boolean secure,
- Boolean copyCurrentRenderParameters, Boolean escapeXml, String name,
- String resourceID, String cacheability, long plid, long refererPlid,
- String portletName, Boolean anchor, Boolean encrypt,
- long doAsGroupId, long doAsUserId, Boolean portletConfiguration,
- Map<String, String[]> params, PageContext pageContext)
- throws Exception {
- HttpServletRequest request =
- (HttpServletRequest)pageContext.getRequest();
- ..
- if (Validator.isNotNull(windowState)) {
- liferayPortletURL.setWindowState(
- WindowStateFactory.getWindowState(windowState));
- }
- if (Validator.isNotNull(portletMode)) {
- liferayPortletURL.setPortletMode(
- PortletModeFactory.getPortletMode(portletMode));
- }
- if (secure != null) {
- liferayPortletURL.setSecure(secure.booleanValue());
- }
- else {
- liferayPortletURL.setSecure(PortalUtil.isSecure(request));
- }
- if (copyCurrentRenderParameters != null) {
- liferayPortletURL.setCopyCurrentRenderParameters(
- copyCurrentRenderParameters.booleanValue());
- }
- .....
主要就是从页面上的标记中取出一些值,然后设置相应的变量给liferayPortletURL,最终发送给struts框架。
安全请求还是非安全请求:
在我这个难题中,第29-31行是比较重要的,因为我的问题就在于,对于Enterprise版本,只有Admin用户可以吧这个请求发送出去。所以我们来探究这一部分。
在PortalUtil中:
- public static boolean isSecure(HttpServletRequest request) {
- return getPortal().isSecure(request);
- }
我们转到PortalImpl中:
- public boolean isSecure(HttpServletRequest request) {
- HttpSession session = request.getSession();
- Boolean httpsInitial = (Boolean)session.getAttribute(
- WebKeys.HTTPS_INITIAL);
- boolean secure = false;
- if ((PropsValues.COMPANY_SECURITY_AUTH_REQUIRES_HTTPS) &&
- (!PropsValues.SESSION_ENABLE_PHISHING_PROTECTION) &&
- (httpsInitial != null) && (!httpsInitial.booleanValue())) {
- secure = false;
- }
- else {
- secure = request.isSecure();
- }
- return secure;
- }
从这段代码,我们可以看出,它先判断是否是https请求,如果是返回true.否则,对于http请求,它去看COMPANY_SECURITY_AUTH_REQUIRES_HTTPS 和SESSION_ENABLE_PHISHING_PROTECTION 字段,
这两个字段我们可以在控制面板中看到:
对于现在在家的Community,分别是false和true(明天上班去检查下企业版的配置),所以,对于我们的Community版本,这个判断条件为(false,因为短路表达式),所以secure=request.isSecure(),
liferayPortletURL是最重要的URL,这个URL是<portlet:resourceURL>的等效形式,它由两部分组成,一是基本URL,另外是吧标记内的参数依次填充进去。基本URL的代码在
PortalResponseImpl类中。
谁去消费这个URL?
不管如何,页面上的resourceURL标记都会创建一个URL,并且必须指向当前的portlet,并且必须触发serveRequest请求,然后在请求中消费自己携带的参数:
JSR-286原文P226:
- 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方法:
- public void service(
- HttpServletRequest request, HttpServletResponse response)
- throws IOException, ServletException {
- String portletId = (String)request.getAttribute(WebKeys.PORTLET_ID);
- PortletRequest portletRequest = (PortletRequest)request.getAttribute(
- JavaConstants.JAVAX_PORTLET_REQUEST);
- PortletResponse portletResponse = (PortletResponse)request.getAttribute(
- JavaConstants.JAVAX_PORTLET_RESPONSE);
- String lifecycle = (String)request.getAttribute(
- PortletRequest.LIFECYCLE_PHASE);
- FilterChain filterChain = (FilterChain)request.getAttribute(
- PORTLET_SERVLET_FILTER_CHAIN);
- LiferayPortletSession portletSession =
- (LiferayPortletSession)portletRequest.getPortletSession();
- portletRequest.setAttribute(WebKeys.PORTLET_ID, portletId);
- portletRequest.setAttribute(PORTLET_SERVLET_CONFIG, getServletConfig());
- portletRequest.setAttribute(PORTLET_SERVLET_REQUEST, request);
- portletRequest.setAttribute(PORTLET_SERVLET_RESPONSE, response);
- HttpSession session = request.getSession();
- PortletSessionTracker.add(session);
- portletSession.setHttpSession(session);
- try {
- PortletFilterUtil.doFilter(
- portletRequest, portletResponse, lifecycle, filterChain);
- }
- catch (PortletException pe) {
- _log.error(pe, pe);
- throw new ServletException(pe);
- }
- }
然后我们到PortletFilterUtil中去看:
- public static void doFilter(
- PortletRequest portletRequest, PortletResponse portletResponse,
- String lifecycle, FilterChain filterChain)
- throws IOException, PortletException {
- ..
- }
- else if (lifecycle.equals(PortletRequest.RESOURCE_PHASE)) {
- ResourceRequest resourceRequest = (ResourceRequest)portletRequest;
- ResourceResponse resourceResponse =
- (ResourceResponse)portletResponse;
- filterChain.doFilter(resourceRequest, resourceResponse);
- }
- }
因为我们是在RESOURCE_PHASE,所以它会调用08行-13行,然后就执行调用链:
在FilterChainImpl类中:
- public void doFilter(
- ResourceRequest resourceRequest, ResourceResponse resourceResponse)
- throws IOException, PortletException {
- if (_portletFilters.size() > _pos) {
- ResourceFilter resourceFilter = (ResourceFilter)_portletFilters.get(
- _pos++);
- resourceFilter.doFilter(resourceRequest, resourceResponse, this);
- }
- else {
- ResourceServingPortlet resourceServingPortlet =
- (ResourceServingPortlet)_portlet;
- resourceServingPortlet.serveResource(
- resourceRequest, resourceResponse);
- }
- }
可以看出,这个FilterChain会依次调用链子上的所有实现了ResourceServingPortlet接口的Portlet的serveResource方法,因为我们例子中ServiceEnvPortlet和MachinePortlet都实现了ResourceServingPortlet接口,所以他们的serveResource()方法如果有,都会被调用。而调用逻辑都写在serveResource()方法里面了,调用逻辑就是去解析上面构造的liferayPortletURL.
现在应该清楚了,希望明天可以搞定问题。