更多最新文章欢迎大家访问我的个人博客😄:豆腐别馆
Liferay使用第三方权限系统控制Portlet权限问题记录
前言:在无法彻底摸透一个框架或技术之前,相关环境、框架等请务必选择与其相对应的版本!!!版本对不上号可能会出现各种本不该出现的异常,徒劳伤神。
在Liferay6.2 CE版中,如使用集成了Liferay ide7版的Eclipse,即使JDK、JRE都为7,也都将无法正常生成自定义外部接口的WebService客户端jar!
本文概要
- 使用第三方权限系统控制Portlet权限
- 调用对外接口时报Authenticated access required身份验证异常
- 服务端与客户端未在同一机器上调用对外接口出现的WebService 403问题
一、第三方权限系统控制Portlet权限
主要涉及到的技术点有:
- 如何发布使用Liferay自带对外接口
- 当自带对外接口不够用时如何发布自定义的对外接口
- Liferay如何使用原生SQL语句查询
- 调用权限相关接口时注意actionids与bitwisevalue之间的位与关系运算
1、发布调用Liferay自带对外接口,这个网上有相关资料,不做过多描述,基本流程为:下载Liferay客户端jar,下其对应版本的客户端jar即可下载链接,有如下jar包:
2、当自带对外接口不够用时,可编写自定义对外接口,可参考该篇博文:
Liferay自定义对外接口,但里面内容不知是版本问题还是其它,6.25版来说,里面的内容有误,需:
(1)将文章里导包的说明替换成上面链接下载下来的jar
(2)当执行到build-client时,若正常执行完毕,在docroot/WEB-INF下应生成client文件夹,包含xxx-portlet-client.jar和namespace-mapping.properties文件。而非portal-client.jar,复制xxx-portlet-client.jar到第三方系统,与调用Liferay自带外部接口一样调用即可。
3、这里主要是涉及到Liferay资源权限表(Resourcepermission)里的actionids与资源动作表(resourceaction)里的bitwisevalue的位与关系的判断与控制。
参考这篇博客里对于Liferay权限体系的介绍Liferay权限体系简介,
以获取对某一Portlet拥有查看权限的角色ID为例,自定义SQL查询:
(1)在xxx.service.persistence里面新建xxxFinderImpl方法,继承自BasePersistenceImpl类。此处的命名必须是xxxFinderImpl(xxx为实体)
(2)执行service builder,此时会在service包的xxx.service.persistence下面生成xxxFinder的接口类和对应的xxxFinderUtil类。
(3)让xxxFinderImpl继承xxxFinder类,在此类中编写代码,如下:
public class ExternalPrivilegeFinderImpl extends BasePersistenceImpl<ResourcePermission> implements
ExternalPrivilegeFinder {
@SuppressWarnings("unchecked")
public List<String> getRoleIdHaveViewPermission(String name, int scope,
String primkey) {
List<String> list = new ArrayList<String>();
StringBuffer sql = new StringBuffer(
"SELECT r.roleId FROM resourcepermission r WHERE r.name = '"
+ name + "' ");
if (scope != 0) {
sql.append("AND r.scope = " + scope + " ");
}
if (primkey != null) {
sql.append("AND r.primkey = '" + primkey + "' ");
}
sql.append("AND r.actionIds&1 = 1 GROUP BY r.roleId");
Session session = null;
try {
List<BigInteger> blist = openSession().createSQLQuery(
sql.toString()).list();
for (BigInteger b : blist) {
list.add(b.toString());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSession(session);
}
return list;
}
}
(4)执行ServiceBuilder,现在会在xxxFinderUtil里面生成相应的接口,但是我们不能直接调用xxxFinderUtil方法,需要将我们的这个方法添加到xxxLocalServiceImpl里面。我们在xxxLocalServiceImpl里面添加相应的方法,在xxxLocalServiceImpl里面使用xxxFinder.xxx()进行调用。
(5)再次执行build-service,现在就可以通过xxxLocalServiceUtil类调用自定义的查询类了,至此自定义查询完毕
(6)接下来要在第三方系统使用该接口,就跟上面提到的使用Liferay自带对外接口方法一致:build-wsdd -> deploy -> 启动Tomcat -> build-client
(7)上面的几个步骤都没问题之后,拿到生成的客户端xxx-portlet-client.jar导入第三方系统即可。
依旧以获取对某一Portlet拥有查看权限为例,由于调用相关权限接口时其scope、primKey等参数将需要动态赋值,特别当某个Portlet被多次拖拽到相同或不同页面上显示之后,其primkey将会出现变化,即在ResourcePermission表中将会出现多条不同primKey的数据,因此需要获取ResourcePermission下拥有查看权限的集合后进行遍历调用。集合获取代码如下:
/**
* 获取资源查看权限列表
*
* @param name
* @return
*/
@SuppressWarnings({ "unchecked", "static-access" })
public List<ResourcePermission> getResourcePermission(String name, Long roleId) {
List<ResourcePermission> list = new ArrayList<ResourcePermission>();
StringBuffer sql = new StringBuffer("SELECT r.* FROM ResourcePermission r WHERE r.name = '" + name + "' ");
if(roleId != null) {
sql.append("AND r.roleId = " + roleId + " ");
}
sql.append("AND r.actionIds&1 = 1");
Session session = null;
try {
List<Object> oList = openSession().createSQLQuery(sql.toString()).list();
for(int x = 0; x < oList.size(); x ++) {
Object[] obj = (Object[]) oList.get(x);
BigInteger bResourcePermissionId = (BigInteger)obj[0];
BigInteger bCompanyId = (BigInteger) obj[1];
BigInteger bRoleId = (BigInteger) obj[5];
BigInteger bOwnerId = (BigInteger) obj[6];
BigInteger bActionIds = (BigInteger) obj[7];
ResourcePermission r = new ResourcePermissionUtil().create(bResourcePermissionId.longValue());
r.setCompanyId(bCompanyId.longValue());
r.setName(obj[2].toString());
r.setScope((Integer) obj[3]);
r.setPrimKey(obj[4].toString());
r.setRoleId(bRoleId.longValue());
r.setOwnerId(bOwnerId.longValue());
r.setActionIds(bActionIds.longValue());
list.add(r);
}
} catch (Exception e) {
e.printStackTrace();
} finally {
closeSession(session);
}
return list;
}
此处只需要注意下面两个点:
(1)Hibernat执行含有查询条件的原生SQL返回列表时,返回的是一个List<Object>
(2)注意代码中部分参数为long类型但数据库为BigInteger类型之间的转换。
也有疑问:在此无法使用Hibernate的addEntity()方法自动进行对象的封装,提示找不到ResourcePermission实体,因Liferay中对Hibernate进行了封装,暂未去找其映射文件/注解,因此直接采用暴力添加进List<ResourcePermission>
,如有更好解决方法者,请告诉我,O(∩_∩)O谢谢。
第三方权限系统调用测试代码(此处原返回的List<xxx>
将会默认被封装成返回一个xxx实体类型的数组):
/**
* 获取资源权限角色
*
* @param name
* @return
*/
public void getResourcePermission(String name, Long roleId) {
try {
ExternalPrivilegeServiceSoapServiceLocator privilegeLocaltor = new ExternalPrivilegeServiceSoapServiceLocator();
ExternalPrivilegeServiceSoap service = privilegeLocaltor
.getPlugin_t_ExternalPrivilegeService(APIUtil.getPortalURLForAddress(privilegeLocaltor
.getPlugin_t_ExternalPrivilegeServiceAddress()));
((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);
((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);
ResourcePermissionSoap[] resourcePermissionSoaps = service
.getResourcePermission(name, roleId);
for (ResourcePermissionSoap r : resourcePermissionSoaps) {
System.out.println(r.getName());
}
} catch (ServiceException | RemoteException e) {
e.printStackTrace();
}
}
第三方系统系统调用:
/**
*
* @Title: bindbing
* @Description: TODO(绑定门户角色权限)
* @param: @param model
* @param: @param portletId
* @param: @param roleId
* @param: @return
* @return: String
* @throws
*/
@RequestMapping(value="/binding",method=RequestMethod.POST )
@ResponseBody
public JsonVo bindbing(String portletId,String ident,long companyId,String roleAlias){
JsonVo vo = new JsonVo();
try {
LiferayUitl liferayUitl = new LiferayUitl();
Setting setting = SystemUtils.getSetting();
RoleSoap soap = liferayUitl.getRoleId(roleAlias, companyId);
ResourcePermissionSoap[] privilege = liferayUitl.getPrivilege(portletId, soap.getRoleId());
if (ident.equals("add")) {
liferayUitl.addResourcePermission(liferayUitl.GROUPID, companyId,
portletId, setting.getScope(), setting.getPrimKey(),
soap.getRoleId(), liferayUitl.ACTIONID);
} else {
for (ResourcePermissionSoap resourcePermissionSoap : privilege) {
liferayUitl.removeResourcePermission(liferayUitl.GROUPID, resourcePermissionSoap.getCompanyId(),
portletId, resourcePermissionSoap.getScope(),soap.getRoleId(), liferayUitl.ACTIONID);
}
}
vo.setSuccess(true);
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
vo.setSuccess(false);
}
return vo;
}
二、调用对外接口时报Authenticated access required身份验证异常
异常如下:
AxisFault
faultCode: {http://schemas.xmlsoap.org/soap/envelope/}Server.userException
faultSubcode:
faultString: java.rmi.RemoteException: Authenticated access required
faultActor:
faultNode:
faultDetail:
{http://xml.apache.org/axis/}hostname:PC-201606131336
java.rmi.RemoteException: Authenticated access required
at org.apache.axis.message.SOAPFaultBuilder.createFault(SOAPFaultBuilder.java:222)
at org.apache.axis.message.SOAPFaultBuilder.endElement(SOAPFaultBuilder.java:129)
at org.apache.axis.encoding.DeserializationContext.endElement(DeserializationContext.java:1087)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.endElement(AbstractSAXParser.java:609)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanEndElement(XMLDocumentFragmentScannerImpl.java:1782)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl$FragmentContentDriver.next(XMLDocumentFragmentScannerImpl.java:2973)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentScannerImpl.next(XMLDocumentScannerImpl.java:606)
at com.sun.org.apache.xerces.internal.impl.XMLNSDocumentScannerImpl.next(XMLNSDocumentScannerImpl.java:117)
at com.sun.org.apache.xerces.internal.impl.XMLDocumentFragmentScannerImpl.scanDocument(XMLDocumentFragmentScannerImpl.java:510)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:848)
at com.sun.org.apache.xerces.internal.parsers.XML11Configuration.parse(XML11Configuration.java:777)
at com.sun.org.apache.xerces.internal.parsers.XMLParser.parse(XMLParser.java:141)
at com.sun.org.apache.xerces.internal.parsers.AbstractSAXParser.parse(AbstractSAXParser.java:1213)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl$JAXPSAXParser.parse(SAXParserImpl.java:648)
at com.sun.org.apache.xerces.internal.jaxp.SAXParserImpl.parse(SAXParserImpl.java:332)
at org.apache.axis.encoding.DeserializationContext.parse(DeserializationContext.java:227)
at org.apache.axis.SOAPPart.getAsSOAPEnvelope(SOAPPart.java:696)
at org.apache.axis.Message.getSOAPEnvelope(Message.java:435)
at org.apache.axis.handlers.soap.MustUnderstandChecker.invoke(MustUnderstandChecker.java:62)
at org.apache.axis.client.AxisClient.invoke(AxisClient.java:206)
at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
at org.apache.axis.client.Call.invoke(Call.java:2767)
at org.apache.axis.client.Call.invoke(Call.java:2443)
at org.apache.axis.client.Call.invoke(Call.java:2366)
at org.apache.axis.client.Call.invoke(Call.java:1812)
at com.liferay.client.soap.portal.service.http.Portal_ResourcePermissionServiceSoapBindingStub.removeResourcePermission(Portal_ResourcePermissionServiceSoapBindingStub.java:250)
at com.test.Test.removeResourcePermission(Test.java:112)
at com.test.Test.main(Test.java:376)
出现该异常的主要原因是Liferay本身对外接口对以邮箱地址及密码的验证方式的支持不友好(不知7版该问题是否得到解决),解决该问题的方法有二:
1、修改Liferay默认认证方式,将用户认证方式更改为除邮件地址外的方式,同时调用代码处传入相对应的参数即可解决。具体流程:管理 -> 控制面板 -> Portal设置 -> 认证:
如图所示设置为通过屏幕名称认证,但若使用此种方式将会导致无法跟原先一样以邮箱登录,即将会更改为与所选认证方式一致的参数作为登录认证账号。这显然不是我想要的。因此未采用此种方式,而是使用方式2。
2、在方法调用之前提前进行身份验证:
((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);
((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);
Liferay的对外接口其底层其实就是WebService,Java调用WebService时,服务端如需要client客户端进行授权验证,那么这时只需要在client端提供用户名和密码即可,其实这个异常就是WebService的401异常(unauthorized未授权错误。)
调用代码示例:
/**
* 添加权限
*
* @param groupId
* @param companyId
* @param name
* @param scope
* @param primKey
* @param roleId
* @param actionId
* @return
*/
public boolean addResourcePermission(long groupId, long companyId,
String name, int scope, String primKey, long roleId, String actionId) {
try {
ResourcePermissionServiceSoapService localtor = new ResourcePermissionServiceSoapServiceLocator();
ResourcePermissionServiceSoap service = localtor
.getPortal_ResourcePermissionService(APIUtil.getPortalURLForAddress(localtor
.getPortal_ResourcePermissionServiceAddress()));
((Stub) service)._setProperty(Call.USERNAME_PROPERTY, USERNAME);
((Stub) service)._setProperty(Call.PASSWORD_PROPERTY, PASSWORD);
service.addResourcePermission(groupId, companyId, name, scope, primKey, roleId, actionId);
System.out.println("添加成功");
return true;
} catch (ServiceException | RemoteException e) {
e.printStackTrace();
}
return false;
}
三、服务端与客户端未在同一机器上调用对外接口时出现的WebService 403问题
一开始的服务端与客户端调用都是在我本机子上完成,并未有该异常出现,但是当客户端不在我本机上时,这异常就出来了,如下:
AxisFault
faultCode: {http://xml.apache.org/axis/}HTTP
faultSubcode:
faultString: (403)Forbidden
faultActor:
faultNode:
faultDetail:
{}:return code: 403
<html><head><title>Apache Tomcat/7.0.62 - Error report</title><style><!--H1 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:22px;} H2 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:16px;} H3 {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;font-size:14px;} BODY {font-family:Tahoma,Arial,sans-serif;color:black;background-color:white;} B {font-family:Tahoma,Arial,sans-serif;color:white;background-color:#525D76;} P {font-family:Tahoma,Arial,sans-serif;background:white;color:black;font-size:12px;}A {color : black;}A.name {color : black;}HR {color : #525D76;}--></style> </head><body><h1>HTTP Status 403 - Access denied for 192.168.2.117</h1><HR size="1" noshade="noshade"><p><b>type</b> Status report</p><p><b>message</b> <u>Access denied for 192.168.2.117</u></p><p><b>description</b> <u>Access to the specified resource has been forbidden.</u></p><HR size="1" noshade="noshade"><h3>Apache Tomcat/7.0.62</h3></body></html>
{http://xml.apache.org/axis/}HttpErrorCode:403
(403)Forbidden
at org.apache.axis.transport.http.HTTPSender.readFromSocket(HTTPSender.java:744)
at org.apache.axis.transport.http.HTTPSender.invoke(HTTPSender.java:144)
at org.apache.axis.strategies.InvocationStrategy.visit(InvocationStrategy.java:32)
at org.apache.axis.SimpleChain.doVisiting(SimpleChain.java:118)
at org.apache.axis.SimpleChain.invoke(SimpleChain.java:83)
at org.apache.axis.client.AxisClient.invoke(AxisClient.java:165)
at org.apache.axis.client.Call.invokeEngine(Call.java:2784)
at org.apache.axis.client.Call.invoke(Call.java:2767)
at org.apache.axis.client.Call.invoke(Call.java:2443)
at org.apache.axis.client.Call.invoke(Call.java:2366)
at org.apache.axis.client.Call.invoke(Call.java:1812)
at com.liferay.client.soap.portal.service.http.Portal_ResourcePermissionServiceSoapBindingStub.addResourcePermission(Portal_ResourcePermissionServiceSoapBindingStub.java:226)
at com.test.Test.addResourcePermission(Test.java:92)
at com.test.Test.main(Test.java:382)
解决过程稍有点绕,先上解决方法:修改Liferay portal-tomcat下portal-setup-wizard.properties文件,在里面加上相应配置即可:
axis.servlet.hosts.allowed = 允许访问的客户端IP(以逗号分隔,为空则全部允许)
axis.servlet.https.required = false
解决思路:
上网一查,发现还真有人遇到类似的,但网上所提供的方法都是在tomcat\webapps\ROOT\WEB-INF\classes\portal-ext.properties文件中设置axis.servlet.hosts.allowed的值,一开始还有点小确幸这么快找到解决方法,但翻开我的tomcat一看,醉了,压根没有portal-ext.properties这个配置文件。总不能我新建一个上去吧(咳咳,我还真新建加上去了,但是没用- -),不知道是因为这网上复制来复制去的解决方法没经过验证还是我姿势不对,总之都不是一个好消息,那我只能回到异常本身慢慢看。
此处报的异常虽是在调用Liferay对外接口时所报,但实则依旧是WebService的范畴。而403异常,我们都知道是服务端拒绝了客户端访问,即客户端缺少相应访问权限,由此得出必定是服务端做了相应权限控制,而之前的一番查找也不是一无所获,至少我知道了在Liferay对外接口中有这么一个参数在控制着客户端权限,翻开Liferay源码一看,果然,在每一个对外的接口的xxxServiceSoap中都有这么一个注释:
You can see a list of services at http://localhost:8080/api/axis.
Set the property <b>axis.servlet.hosts.allowed</b> in portal.properties to configure security.
源码注释已经明白地说明我可以在portal.properties文件中配置axis.servlet.hosts.allowed
参数来控制安全权限,还是找源码,发现在源码src下的portal.properties文件有这么一个配置:
##
## Axis Servlet
##
#
# See the properties "main.servlet.hosts.allowed" and
# "main.servlet.https.required" on how to protect this servlet.
#
axis.servlet.hosts.allowed=127.0.0.1,SERVER_IP
axis.servlet.https.required=false
二话不说就上Tomcat里面去找这个portal.properties配置文件去了,嘿,一找同名的文件倒不少,慢慢排除掉一些Tomcat里面portlet下的、编译后生成的、缓存下的等等文件夹下的properties,最后排除只剩下三个都在Tomcat下,文件名一致、内容一致的properties,由于不确定那只能一个个试了,但是,试了个遍,包括全部添上去,还是不行,在清除完Tomcat缓存依旧不行后无奈下就再到网上去翻找了,少有的几个帖子里(基本都一样内容的帖子- -)依旧都提及到了Tomcat下portal-ext.properties这个配置文件,啊,几个帖子总不能都空穴来风,想想我的Tomcat没有,源码上可能会有,一找源码,发现在源码src\tools\db-upgrade文件夹下还真有这么个properties,
内容如下:
jdbc.default.jndi.name=
jdbc.default.driverClassName=com.mysql.jdbc.Driver
jdbc.default.url=jdbc:mysql://localhost/lportal?useUnicode=true&characterEncoding=UTF-8&useFastDateParsing=false
jdbc.default.username=
jdbc.default.password=
嘿,JDBC配置!这不是我portal-tomcat下的portal-setup-wizard.properties文件里该有的内容嘛,OK,往里加上客户端配置,一试果然异常没有了。
时间与资历有限,难免有局限之处,请谅解指导。在此感谢IT人生录里相关资料对我的帮助及博主的答疑。