Improving authorization performance in Liferay

I have recently run a benchmark on a Liferay 6.1 portal. Most of the bottlenecks were the result of the portlets themselves, but a major point was the authorization mechanism of Liferay.
The way Liferay authorization works is that for every component on the screen an authorization check is made. Now, this makes sense. But the problem is that the default implementation is a bit naive – it goes to the database to check if the user has the correct authorization. And since rights can be inherited – each check goes multiple times to the database.
So, I set out to solve this irritating issue by using a cache. Now – this cache is naive too – if a user was granted a new role – a server restart is needed. But this implementation is easy to extend.

  1. Create a new class, that extends com.liferay.portal.security.permission.AdvancedPermissionChecker
  2. Open the portal-ext.properties file.
  3. Add the property permissions.checker={your class name} to it
  4. The class code should look like this:
    package com.tona.liferay.permission;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class TonaPermissionChecker extends
    		com.liferay.portal.security.permission.AdvancedPermissionChecker {
    
    	private static Map<PermissionKey, Boolean> permissionCache = new HashMap<PermissionKey, Boolean>();
    
    	public boolean hasPermission(long groupId, String name, String primKey,
    			String actionId) {
    
    		PermissionKey key = new PermissionKey(groupId, name, primKey, actionId);
    		if (permissionCache.containsKey(key)) {
    			return permissionCache.get(key);
    		}
    
    		boolean result = super.hasPermission(groupId, name, primKey, actionId);
    		permissionCache.put(key, result);
    
    		return result;
    
    	}
    
    	private class PermissionKey {
    		private long groupId;
    		private String name;
    		private String primKey;
    		private String actionId;
    
    		public PermissionKey(long groupId, String name, String primKey,
    				String actionId) {
    			super();
    			this.groupId = groupId;
    			this.name = name;
    			this.primKey = primKey;
    			this.actionId = actionId;
    		}
    
    		public long getGroupId() {
    			return groupId;
    		}
    
    		public void setGroupId(long groupId) {
    			this.groupId = groupId;
    		}
    
    		public String getName() {
    			return name;
    		}
    
    		public void setName(String name) {
    			this.name = name;
    		}
    
    		public String getPrimKey() {
    			return primKey;
    		}
    
    		public void setPrimKey(String primKey) {
    			this.primKey = primKey;
    		}
    
    		public String getActionId() {
    			return actionId;
    		}
    
    		public void setActionId(String actionId) {
    			this.actionId = actionId;
    		}
    
    		@Override
    		public int hashCode() {
    			final int prime = 31;
    			int result = 1;
    			result = prime * result
    					+ ((actionId == null) ? 0 : actionId.hashCode());
    			result = prime * result + (int) (groupId ^ (groupId >>> 32));
    			result = prime * result + ((name == null) ? 0 : name.hashCode());
    			result = prime * result
    					+ ((primKey == null) ? 0 : primKey.hashCode());
    			return result;
    		}
    
    		@Override
    		public boolean equals(Object obj) {
    			if (this == obj)
    				return true;
    			if (obj == null)
    				return false;
    			if (getClass() != obj.getClass())
    				return false;
    			PermissionKey other = (PermissionKey) obj;
    			if (actionId == null) {
    				if (other.actionId != null)
    					return false;
    			} else if (!actionId.equals(other.actionId))
    				return false;
    			if (groupId != other.groupId)
    				return false;
    			if (name == null) {
    				if (other.name != null)
    					return false;
    			} else if (!name.equals(other.name))
    				return false;
    			if (primKey == null) {
    				if (other.primKey != null)
    					return false;
    			} else if (!primKey.equals(other.primKey))
    				return false;
    			return true;
    		}
    	}
    }
    
  5. Package the class in a JAR file, and put in TOMCAT_HOME/webapps/ROOT/WEB-INF/lib