In the previous post i discussed about fortress and its directory structure. In this post i will cover the configuration for securing ADF application, using fortress API, writing your own custom ELResolver for doing permission or role checks.
Configuration: Fortress uses a properties file fortress.properties for storing configuration that it uses at runtime to communicate with the openldap server. It also uses ehcache.xml file for caching the retrieved entities from the openldap server to boost performance. Both these files need to be present in your classpath. The sample file structure for fortress.properties and ehcache.xml is shown below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | # Host name and port of LDAP DIT: host=localhost port=389 # These credentials are used for read/write access to all nodes under suffix: admin.user=cn=Manager,dc=sample,dc=com # LDAP admin root pass is encrypted using 'encrypt' target in build.xml: admin.pw=W7T0G9hyr344K+DF8gfgA== # This is min/max settings for LDAP administrator pool connections that have read/write access to all nodes under suffix: min.admin.conn=10 max.admin.conn=100 # This node contains fortress properties stored on behalf of connecting LDAP clients: #the config.realm is a node stored in ou=config config.realm=DEFAULT config.root=ou=Config,dc=sample,dc=com # enable this to see trace statements when connection pool allocates new connections: debug.ldap.pool=true # Default for pool reconnect flag is false: enable.pool.reconnect=true crypto.prop=uiote12434 ehcache.config.file=ehcache.xml |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | <?xml version="1.0" encoding="UTF-8"?> <!-- Fortress CacheManager Configuration ========================== This ehcache.xml corresponds to a single CacheManager. --> <ehcache name="fortress-realm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="ehcache.xsd" updateCheck="true" monitoring="autodetect" dynamicConfig="true" > <cacheManagerEventListenerFactory class="" properties=""/> <!-- Default Cache configuration. These settings will be applied to caches created programmatically using CacheManager.add(String cacheName). This element is optional, and using CacheManager.add(String cacheName) when its not present will throw CacheException The defaultCache has an implicit name "default" which is a reserved cache name. --> <defaultCache eternal="false" overflowToDisk="false" timeToIdleSeconds="120" timeToLiveSeconds="120" diskSpoolBufferSizeMB="30" maxElementsOnDisk="10" diskPersistent="false" diskExpiryThreadIntervalSeconds="120" memoryStoreEvictionPolicy="LRU" /> <!-- Thic cache contains password policy entries. It is used to save a read on User password policy edits. There should be one element for every tenant. --> <cache name="fortress.policies" maxElementsInMemory="10" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Contains the value OrgUnits for User and Permissions. There should be two elements for every tenant. --> <cache name="fortress.ous" maxElementsInMemory="2" maxElementsOnDisk="2" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Contains the JGraphT hierarchies for RBAC roles. There should be one element for every tenant. --> <cache name="fortress.roles" maxElementsInMemory="10" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Contains the JGraphT hierarchies for ARBAC roles. There should be one element for every tenant. --> <cache name="fortress.admin.roles" maxElementsInMemory="10" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Contains the JGraphT hierarchies for Perm OUs. There should be one element for every tenant. --> <cache name="fortress.pso" maxElementsInMemory="10" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Contains the JGraphT hierarchies for User OUs. There should be one element for every tenant. --> <cache name="fortress.uso" maxElementsInMemory="10" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="2" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> <!-- Searchable cache contains Role<->DSD mapping. This configuration sets a fairly long TTL of 1 hour. --> <cache name="fortress.dsd" maxElementsInMemory="1000" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="3600" timeToLiveSeconds="3600" memoryStoreEvictionPolicy="LFU"> <searchable> <searchAttribute name="member" expression="value.getMember()"/> <searchAttribute name="name" expression="value.getName()"/> <searchAttribute name="contextId" expression="value.getContextId()"/> </searchable> </cache> <!-- Cache contains Role<->SSD mapping. --> <cache name="fortress.ssd" maxElementsInMemory="1000" maxElementsOnDisk="10" eternal="false" overflowToDisk="false" diskSpoolBufferSizeMB="20" timeToIdleSeconds="600" timeToLiveSeconds="600" memoryStoreEvictionPolicy="LFU" /> </ehcache> |
Library Dependencies:
You will need the following jars in your classpath which can be added as maven dependencies.
fortress-RC34.jar
ehcache-core.jar (version 2.6.0)
jasypt.jar (version 1.8)
commons-lang (version 2.6)
commons-configuration (version 1.10)
jgrapht-jdk (version 0.7.3)
slf4j-api
unboundid-ldapsdk-2.3.5.jar
This basically finishes your configuration.
Brief overview of fortress java API:- Though the java docs link i provided in the previous post should be apt. I will just provide a brief overview of the major API classes or interfaces.
- AdminMgr: Use the implementation of this class for managing users, application roles management, application permission managment.
- DelAdminMgr: This is used to perform action on ARBAC entities. You use this for organization creation, admin roles and permission management.
- ReviewMgr: Use this for seaching existing users, roles, permissions or permission objects.
- DelReviewMgr: Use this for searching organizations, searching roles.
- PwPolicyMgr: this can be used for managing password policies for user entities.
- AccessMgr: this is used to authenticate user and check for his/her access to a particular permission or role.
- DelAccessMgr: this can be used to check for applicable admin permissions and admin roles.
To instantiate each of the objects you can use the corresponding factory class. For example to get a AdminMgr implementation you use the following method.
1 2 3 4 5 6 7 8 9 10 11 12 | private AdminMgr createAndGetAdminMgr() { String methodName = Thread.currentThread().getStackTrace()[1].getMethodName(); AdminMgr mgr = null; try { mgr = AdminMgrFactory.createInstance(GlobalIds.HOME); } catch (SecurityException e) { logger.severe("[" + methodName + "]" + "Unable to create AdminMgr Instance", e); throw new RuntimeException("Unable to create AdminMgr Instance", e ); } return mgr; } |
After you have obtained the instance you can perform the corresponding operations on it.
Securing your application:
Although fortress comes with its security realm for tomcat or jboss. If you’d like to customize the login process you can do so using its API.
Login Process:
In your login method of the servlet or managed bean you use the following method to authenticate and create user session.
1 | Session rbacSession= acMgr.createSession(user, false); |
This method will validate the user’s password and temporal constraints. It will throw exception in case the user’s password is expired and warning if the user’s password is about to expire and user is using grace login. the way to handle these scenarios would be that if a user’s password is about to expire display this information to him/her by providing option to changePassword or continue to the application.
The following method is a sample for the login process:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | public String doLogin() { String un = (String)userName.getValue(); char[] pw = ((String)password.getValue()).toCharArray(); //my custom utility class that contains utility methods for fortress LDAPOperations ops=new LDAPOperations(); AccessMgr acMgr=ops.createAndGetAccessMgr(); Map pfsScope=ADFContext.getCurrent().getPageFlowScope(); FacesContext context = FacesContext.getCurrentInstance(); HttpSession session=(HttpSession)context.getExternalContext().getSession(true); //check whether RBACSESSION already exists if so redirect the user to the home page if(session.getAttribute("RBACSESSION")!=null){ return "success"; } User user=new User(); user.setUserId(un); user.setPassword(pw); try { Session rbacSession= acMgr.createSession(user, false); if(rbacSession!=null) { //check for warnings if they contain password expiration warning warn him of the same //and provide option to change the password List<Warning> warnings=rbacSession.getWarnings(); if(warnings!=null && !warnings.isEmpty()) { Iterator <Warning> it=warnings.iterator(); while(it.hasNext()){ Warning warning=it.next(); if(warning.getId()==GlobalPwMsgIds.PASSWORD_EXPIRATION_WARNING){ pfsScope.put("errorMessage",warning.getMsg()); session.setAttribute("RBACSESSION", rbacSession); ExtendedRenderKitService erks = Service.getRenderKitService(context, ExtendedRenderKitService.class); //show popup erks.addScript(context,"AdfPage.PAGE.findComponent('"+popUp.getClientId()+"').show();"); pfsScope.put("expireWarning",Boolean.TRUE); return ""; } } } session.setAttribute("RBACSESSION", rbacSession); context.responseComplete(); return "success"; } } catch (PasswordException e) { //this means the user's password was reset by admin and hence redirect user to change his password afterward you can log him out if(e.getErrorId()==GlobalErrIds.USER_PW_RESET){ if(session!=null){ //temporary user id and user's pw policy which can be shown on the change password page session.setAttribute("userIdTemp", un); List<User> users=new ArrayList(); users=ops.searchUsers(un); String pwPol=users.get(0).getPwPolicy(); session.setAttribute("pwPol", pwPol); context.responseComplete(); return "changePassword"; } } else{ loginLogger.fine("Login Failed due to authentication failure"+e.getMessage(),e); pfsScope.put("errorMessage","Login Failed due to authentication failure"+e.getMessage()); } } catch (ValidationException e) { loginLogger.severe("User is not allowed access"+e.getMessage(),e); pfsScope.put("errorMessage","User is not allowed access"+e.getMessage()); } catch (FinderException e) { loginLogger.severe("User does not exist"+e.getMessage(),e); pfsScope.put("errorMessage","User does not exist in the system"); } catch (SecurityException e) { loginLogger.fine("Authentication failed due to"+e.getMessage(),e); pfsScope.put("errorMessage","System Error Occured"+e.getMessage()); } ExtendedRenderKitService erks = Service.getRenderKitService(context, ExtendedRenderKitService.class); //show popup erks.addScript(context,"AdfPage.PAGE.findComponent('"+popUp.getClientId()+"').show();"); return ""; } |
Here the utility class LDAPOperations is a custom class written by me which in turn uses fortress API’s to access the information.
Checking permissions and roles:
To perform runtime role or permission checks you can use the following methods.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | public static boolean isUserInRole(String roleName,Session rbacSession){ boolean userInRole=false; List<UserRole> roles=rbacSession.getRoles(); List<UserAdminRole> adminRoles=rbacSession.getAdminRoles(); //check both in admin and normal roles if(roles!=null&&!roles.isEmpty()){ Iterator it=roles.iterator(); while(it.hasNext()){ UserRole role=(UserRole)it.next(); if(role.getName().equals(roleName)){ return true; } } } if(adminRoles!=null&&!adminRoles.isEmpty()){ Iterator it=adminRoles.iterator(); while(it.hasNext()){ UserAdminRole role=(UserAdminRole)it.next(); if(role.getName().equals(roleName)){ return true; } } } return userInRole; } |
If its a admin permission you use DelAccessMgr for a normal permission you use a AccessMgr instance.
1 2 3 4 5 6 7 8 | DelAccessMgr delMgr=ops.createAndGetDelAccessMgr(); Permission permn=new Permission(); permn.setAdmin(true); //This is a top level object under which individual permissions are stored permn.setObjectName("taskflowId"); permn.setOpName("view"); //returns true if user has access to the permission else returns false. delMgr.checkAccess(rbacSession, permn); |
That is all fine but then one may ask ADF security provides me with el methods such as isUserInRole and SecurityContext.taskflowViewable etc. To accomplish the same here you can implement your own custom ELResolver.
I will cover the same in the next post.
Go Top