You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@directory.apache.org by sm...@apache.org on 2017/05/07 17:39:43 UTC

svn commit: r1794231 - /directory/site/trunk/content/fortress/testimonials.mdtext

Author: smckinney
Date: Sun May  7 17:39:43 2017
New Revision: 1794231

URL: http://svn.apache.org/viewvc?rev=1794231&view=rev
Log:
more

Modified:
    directory/site/trunk/content/fortress/testimonials.mdtext

Modified: directory/site/trunk/content/fortress/testimonials.mdtext
URL: http://svn.apache.org/viewvc/directory/site/trunk/content/fortress/testimonials.mdtext?rev=1794231&r1=1794230&r2=1794231&view=diff
==============================================================================
--- directory/site/trunk/content/fortress/testimonials.mdtext (original)
+++ directory/site/trunk/content/fortress/testimonials.mdtext Sun May  7 17:39:43 2017
@@ -24,21 +24,21 @@ Here are the technologies stack used wit
 
 There are two types of development required, one on server side and other on the client, which is then used by my team for managing security within their own web applications:
 
- * CAS Server side development:
-  - Create own implementation for AbstractUsernamePasswordAuthenticationHandler
-  - Implement Ignite Service Registry for CAS
-
- * CAS Client side development:
-  - Create own implementation for WebExpressionVoter
-  - Create own implementation for CasAuthenticationProvider
+ 1. CAS Server side development:
+  * Create own implementation for AbstractUsernamePasswordAuthenticationHandler
+  * Implement Ignite Service Registry for CAS
+
+ 2. CAS Client side development:
+  * Create own implementation for WebExpressionVoter
+  * Create own implementation for CasAuthenticationProvider
 
 ## Code Descriptions
 
 ###Server side development:
 
- * The Authentication Handler
+####The Authentication Handler
 
-  The interesting part for this solution is, how we can maintain both Apereo CAS and Apache Fortress sessions. Luckily, CAS is using token for maintaining their session and the token is also designed to have some extended attribute to put on it. Using this knowledge, we can do something with the profile given by CAS Server to the client. Let’s have a look what I’ve done with Apereo CAS and Apache Fortress Session in below source code.
+  The interesting part for this solution is how to maintain both the Apereo CAS and Apache Fortress sessions. Luckily, CAS is using a token for maintaining their session and that token is also designed to have some extended attributes included with it.  Using this knowledge, we can modify the profile given by CAS Server to the client. Let's have a look what I've done with combining the Apereo CAS and Apache Fortress sessions in the code that follows.
 
     :::Java
 
@@ -142,239 +142,250 @@ There are two types of development requi
     }
 
 
-### Next sections
+In the above source code you can see how I construct a new principal by creating a new attribute map with values contained withing the Apache Fortress Session xml.
+
+####Attribute Populator
+
+In order to populate fortress and pass it on to the client we need to override the casServiceValidationSuccess.jsp file, located at WEB-INF/view/jsp/protocol/2.0/, since its default view won't populating the necessary attributes. Here is how I was able to accomplish that:
+
+    :::XML
     
-at above source code you can see where i construct a new principal by creating new attribute map with value of Apache Fortress Session xml.
+    <%@ page session="false" contentType="application/xml; charset=UTF-8" %>
+    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
+    <%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
+    <cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
+        <!-- cas 2 validation success -->
+        <cas:authenticationSuccess>
+            <cas:user>${fn:escapeXml(assertion.primaryAuthentication.principal.id)}</cas:user>
+            <c:if test="${not empty assertion.primaryAuthentication.principal.attributes}">
+            <cas:attributes>
+                <c:forEach var="attr" items="${assertion.primaryAuthentication.principal.attributes}" >
+                    <cas:${fn:escapeXml(attr.key)}><![CDATA[${attr.value}]]></cas:${fn:escapeXml(attr.key)}>
+                </c:forEach>
+            </cas:attributes>
+            </c:if>
+            <c:if test="${not empty pgtIou}">
+                    <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
+            </c:if>
+            <c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
+              <cas:proxies>
+                <c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
+                     <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
+                </c:forEach>
+              </cas:proxies>
+            </c:if>
+        </cas:authenticationSuccess>
+    </cas:serviceResponse>
+
+One thing that I love about CAS, even if you correctly extracted the attribute at this page (or maybe you just got hacked at this page), CAS is able to protect the returned attributes by changing the services registry configuration. see the HTTPSandIMAPS-10000001.json file. I’ve put ReturnAllAttributeReleasePolicy type for debuging all the attributes returned, you can change it later to make your application more secure as well.
+
+####Apache Ignite For Ticket Replication
+
+To have a production readiness we need to somehow manage a high availability requirement, so we're not just using a single cas server. That is why we needed to have a centralized or distributed ticket repository, to allow cas to scale. To scale the ticket repository, I chose Apache Ignite for distributing the tickets. To Implement is very simple, and is also written about in Apereo CAS documentation.
 
-    Attribute Populator
-    In order to populate fortress and pass it to the client we need to override casServiceValidationSuccess.jsp file, locate at WEB-INF/view/jsp/protocol/2.0/, since the default view is not populating the attributes. Here is how i did
+###Client side development:
 
-<%@ page session="false" contentType="application/xml; charset=UTF-8" %>
-<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
-<%@ taglib uri="http://java.sun.com/jsp/jstl/functions" prefix="fn" %>
-<cas:serviceResponse xmlns:cas='http://www.yale.edu/tp/cas'>
-    <!-- cas 2 validation success -->
-    <cas:authenticationSuccess>
-        <cas:user>${fn:escapeXml(assertion.primaryAuthentication.principal.id)}</cas:user>
-        <c:if test="${not empty assertion.primaryAuthentication.principal.attributes}">
-        <cas:attributes>
-            <c:forEach var="attr" items="${assertion.primaryAuthentication.principal.attributes}" >
-                <cas:${fn:escapeXml(attr.key)}><![CDATA[${attr.value}]]></cas:${fn:escapeXml(attr.key)}>
-            </c:forEach>
-        </cas:attributes>
-        </c:if>
-        <c:if test="${not empty pgtIou}">
-                <cas:proxyGrantingTicket>${pgtIou}</cas:proxyGrantingTicket>
-        </c:if>
-        <c:if test="${fn:length(assertion.chainedAuthentications) > 1}">
-          <cas:proxies>
-            <c:forEach var="proxy" items="${assertion.chainedAuthentications}" varStatus="loopStatus" begin="0" end="${fn:length(assertion.chainedAuthentications)-2}" step="1">
-                 <cas:proxy>${fn:escapeXml(proxy.principal.id)}</cas:proxy>
-            </c:forEach>
-          </cas:proxies>
-        </c:if>
-    </cas:authenticationSuccess>
-</cas:serviceResponse>
-
-One thing that i love from CAS, even you are correctly extract the attribute at this page (or might be you got hacked at this page). CAS Able to protected the returned attribute by changing the services registry configuration. see HTTPSandIMAPS-10000001.json file. I’ve put ReturnAllAttributeReleasePolicy type for debuging all the attribute returning, you can change it later to make your application more secure.
-
-    Apache Ignite For Ticket Replication
-    To have a production readiness we need somehow to have a high availability requirement so we can not have a single cas server. That is why we need to have a centralize or distributed ticket repository so our cas able to scale. To scale the ticket repository, i choose Apache Ignite for distributing the ticket. To Implement is very simple, it is also written at Apereo CAS documentation.
-
-Client side development:
-
-    The Spring Voter
-    Spring Framework is a great framework, they allowed you to put your own interceptor to have your own implementation. WebExpressionVoter is the class you need to extends in order you want to override the normal spring decision mechanism, usually you will use xml + regex for registering the condition. However, xml + regex is not the approach i want to have for my development team. See below code snippet, to understand what i did for make it more dynamic.
-
-  @Override
-  @SuppressWarnings("static-access")
-  public int vote(Authentication authentication, FilterInvocation fi,
-      Collection<ConfigAttribute> attributes) {
-    Authentication securityContextAuthentication =
-        SecurityContextHolder.getContext().getAuthentication();
-    int result = super.vote(securityContextAuthentication, fi, attributes);
-    if (System.getenv(IAM_SECURITY_PARAMETER) != null) {
-      LOG.warn("iam security is disable, enable all access mode is enable");
-      return result;
-    } else {
-      LOG.debug("authentication = {}",
-          ToStringBuilder.reflectionToString(securityContextAuthentication));
-      LOG.debug("super vote for : {}", result);
-      if (super.ACCESS_GRANTED == result) {
-        String requestMethod = fi.getRequest().getMethod().toLowerCase();
-        String filterUrl = getFilterUrl(fi.getHttpRequest());
-        if (filterUrl == null) {
+####The Spring Voter
+
+Spring is a great framework, they allow you to add your own interceptors to use your own implementation. WebExpressionVoter is the class you need to extend in order to override the normal spring decision mechanism.  Usually you will use xml + regex for registering the condition. However, xml + regex is not the approach I wanted for my development team. See below code snippet, to understand what I did to make this more dynamic.
+
+    :::Java
+      @Override
+      @SuppressWarnings("static-access")
+      public int vote(Authentication authentication, FilterInvocation fi,
+          Collection<ConfigAttribute> attributes) {
+        Authentication securityContextAuthentication =
+            SecurityContextHolder.getContext().getAuthentication();
+        int result = super.vote(securityContextAuthentication, fi, attributes);
+        if (System.getenv(IAM_SECURITY_PARAMETER) != null) {
+          LOG.warn("iam security is disable, enable all access mode is enable");
           return result;
+        } else {
+          LOG.debug("authentication = {}",
+              ToStringBuilder.reflectionToString(securityContextAuthentication));
+          LOG.debug("super vote for : {}", result);
+          if (super.ACCESS_GRANTED == result) {
+            String requestMethod = fi.getRequest().getMethod().toLowerCase();
+            String filterUrl = getFilterUrl(fi.getHttpRequest());
+            if (filterUrl == null) {
+              return result;
+            }
+            try {
+              CasAuthenticationToken casAuthenticationToken =
+                  ((CasAuthenticationToken) securityContextAuthentication);
+              LOG.debug("assertion : {}",
+                  ToStringBuilder.reflectionToString(casAuthenticationToken.getAssertion()));
+              String iamSessionXml = (String) casAuthenticationToken.getAssertion().getAttributes()
+                  .get(IAM_SESSION_ATTRIBUTE_KEY);
+              LOG.debug("iam session xml == {}", iamSessionXml);
+              Session iamSession = sessionCache.getIfPresent(casAuthenticationToken.getKeyHash());
+              if (iamSession == null) {
+                Unmarshaller unmarshaller = null;
+                try {
+                  unmarshaller = context.createUnmarshaller();
+                } catch (JAXBException ex) {
+                  LOG.warn("cannot create unmarshaller : ", ex);
+                }
+                iamSession = (Session) unmarshaller.unmarshal(new StringReader(iamSessionXml));
+                sessionCache.put(casAuthenticationToken.getKeyHash(), iamSession);
+              }
+              StringBuilder sessionPermissionKeyBuilder = new StringBuilder(iamSession.getSessionId()).append(filterUrl).append(requestMethod);
+              Boolean isAllowed = accessCache.getIfPresent(sessionPermissionKeyBuilder.toString());
+              if(isAllowed == null) {
+                isAllowed = accessManager.checkAccess(iamSession, new Permission(filterUrl, requestMethod));
+                accessCache.put(sessionPermissionKeyBuilder.toString(), isAllowed);
+              }
+              LOG.debug("{} is {} to access {} with method {}",
+                  new Object[] {securityContextAuthentication.getName(),
+                      isAllowed ? "granted" : "denied", filterUrl, requestMethod});
+              if (isAllowed) {
+                return super.ACCESS_GRANTED;
+              }
+            } catch (Exception e) {
+              LOG.error("catch exception when communicate with iam server", e);
+            }
+          }
+          return super.ACCESS_DENIED;
         }
-        try {
-          CasAuthenticationToken casAuthenticationToken =
-              ((CasAuthenticationToken) securityContextAuthentication);
-          LOG.debug("assertion : {}",
-              ToStringBuilder.reflectionToString(casAuthenticationToken.getAssertion()));
-          String iamSessionXml = (String) casAuthenticationToken.getAssertion().getAttributes()
-              .get(IAM_SESSION_ATTRIBUTE_KEY);
-          LOG.debug("iam session xml == {}", iamSessionXml);
-          Session iamSession = sessionCache.getIfPresent(casAuthenticationToken.getKeyHash());
-          if (iamSession == null) {
-            Unmarshaller unmarshaller = null;
+      }
+
+Yep, I'm calling fortress to check if the user is allowed to access fortress permissions or not.
+
+#####UserDetail Populator
+
+Spring uses the implementation of AbstractCasAssertionUserDetailsService to populate user details following successful authentication, you can see the example at IamUserDetails code, here is the snipet of that class:
+
+    :::Java
+    @Override
+      protected UserDetails loadUserDetails(final Assertion assertion) {
+        List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
+        LOG.debug("user asssertion : {}", ToStringBuilder.reflectionToString(assertion));
+        boolean accountNonExpired = true;
+        boolean credentialsNonExpired = true;
+        boolean accountNonLocked = true;
+        boolean enabled = true;
+        for (String attribute : this.attributes) {
+          String value = (String) assertion.getPrincipal().getAttributes().get(attribute);
+          LOG.debug("value = {}", value);
+          if (value != null) {
+            LOG.debug("adding default authorization to user");
+        grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_USER));    
+
+            Unmarshaller unmarshaller = null;    
+            Session iamSession = null;
             try {
               unmarshaller = context.createUnmarshaller();
-            } catch (JAXBException ex) {
-              LOG.warn("cannot create unmarshaller : ", ex);
+              iamSession = (Session) unmarshaller.unmarshal(new StringReader(value));
+              for (UserRole role : iamSession.getRoles()) {
+                LOG.debug("adding {} authorization to user", role.getName().toUpperCase());
+                grantedAuthorities.add(new SimpleGrantedAuthority(role.getName().toUpperCase()));
+              }
+            } catch (Exception ex) {
+              LOG.error("cannot generate user details", ex);
             }
-            iamSession = (Session) unmarshaller.unmarshal(new StringReader(iamSessionXml));
-            sessionCache.put(casAuthenticationToken.getKeyHash(), iamSession);
-          }
-          StringBuilder sessionPermissionKeyBuilder = new StringBuilder(iamSession.getSessionId()).append(filterUrl).append(requestMethod);
-          Boolean isAllowed = accessCache.getIfPresent(sessionPermissionKeyBuilder.toString());
-          if(isAllowed == null) {
-            isAllowed = accessManager.checkAccess(iamSession, new Permission(filterUrl, requestMethod));
-            accessCache.put(sessionPermissionKeyBuilder.toString(), isAllowed);
           }
-          LOG.debug("{} is {} to access {} with method {}",
-              new Object[] {securityContextAuthentication.getName(),
-                  isAllowed ? "granted" : "denied", filterUrl, requestMethod});
-          if (isAllowed) {
-            return super.ACCESS_GRANTED;
-          }
-        } catch (Exception e) {
-          LOG.error("catch exception when communicate with iam server", e);
         }
+        LOG.debug(
+            "accountNonExpired : {}, credentialsNonExpired : {}, accountNonLocked : {}, enabled : {}",
+            new Object[] {accountNonExpired, credentialsNonExpired, accountNonLocked, enabled});
+        return new User(assertion.getPrincipal().getName().toLowerCase().trim(), NON_EXISTENT_PASSWORD_VALUE, enabled,
+            accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
       }
-      return super.ACCESS_DENIED;
-    }
-  }
 
-Yep, i calling fortress to check if the user is allowed to access fortress permission or not.
+You can change the implementation later for your needs.
 
-    UserDetail Populator
-    Spring use the implementation of AbstractCasAssertionUserDetailsService to populate the user detail after the authentication success, you can see the example at IamUserDetails code, here is the snipet of that class
+#####Network Might Be a Problem
 
-@Override
-  protected UserDetails loadUserDetails(final Assertion assertion) {
-    List<GrantedAuthority> grantedAuthorities = new ArrayList<>();
-    LOG.debug("user asssertion : {}", ToStringBuilder.reflectionToString(assertion));
-    boolean accountNonExpired = true;
-    boolean credentialsNonExpired = true;
-    boolean accountNonLocked = true;
-    boolean enabled = true;
-    for (String attribute : this.attributes) {
-      String value = (String) assertion.getPrincipal().getAttributes().get(attribute);
-      LOG.debug("value = {}", value);
-      if (value != null) {
-        LOG.debug("adding default authorization to user");
-        grantedAuthorities.add(new SimpleGrantedAuthority(ROLE_USER));
+Since this is running inside a production environment, we needed to consider that sometimes there might be a trouble over our network that causes problems and requires retries. That is why it's important to allow a little delay time in our application.  Here's an example of how allow a small delay, in order to allow temorary network glitches and slowdowns to work themselves out.
 
-        Unmarshaller unmarshaller = null;
-        Session iamSession = null;
+    :::Java
+    /*
+     * Copyright 2017 to PT. Global Digital Niaga(Blibli.com)
+     * 
+     * Licensed under the Apache License, Version 2.0; you may not use this file except in compliance
+     * with the License. You may obtain a copy of the License at
+     * 
+     * http://www.apache.org/licenses/LICENSE-2.0
+     * 
+     * Unless required by applicable law or agreed to in writing, software distributed under the License
+     * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
+     * or implied. See the License for the specific language governing permissions and limitations under
+     * the License.
+     */
+    package com.gdn.iam.spring.security;
+    
+    import org.slf4j.Logger;
+    import org.slf4j.LoggerFactory;
+    import org.springframework.security.cas.authentication.CasAuthenticationProvider;
+    import org.springframework.security.core.Authentication;
+    import org.springframework.security.core.AuthenticationException;
+    
+    public class GdnCasAuthenticationProvider extends CasAuthenticationProvider {
+    
+      private static transient Logger LOG = LoggerFactory.getLogger(GdnCasAuthenticationProvider.class);
+      private long sleepForDistributeTicketTime = 300;
+    
+      @Override
+      public Authentication authenticate(Authentication authentication) throws AuthenticationException {
         try {
-          unmarshaller = context.createUnmarshaller();
-          iamSession = (Session) unmarshaller.unmarshal(new StringReader(value));
-          for (UserRole role : iamSession.getRoles()) {
-            LOG.debug("adding {} authorization to user", role.getName().toUpperCase());
-            grantedAuthorities.add(new SimpleGrantedAuthority(role.getName().toUpperCase()));
-          }
-        } catch (Exception ex) {
-          LOG.error("cannot generate user details", ex);
+          LOG.trace(
+              "will try to sleep for waiting ticket to be distributed to other node, sleep time : {}",
+              getSleepForDistributeTicketTime());
+          Thread.sleep(getSleepForDistributeTicketTime());
+        } catch (InterruptedException e) {
+          LOG.error("something wrong when sleeping", e);
         }
+        return super.authenticate(authentication);
       }
+    
+      public long getSleepForDistributeTicketTime() {
+        return sleepForDistributeTicketTime;
+      }
+    
+      public void setSleepForDistributeTicketTime(long sleepForDistributeTicketTime) {
+        this.sleepForDistributeTicketTime = sleepForDistributeTicketTime;
+      }
+    
     }
-    LOG.debug(
-        "accountNonExpired : {}, credentialsNonExpired : {}, accountNonLocked : {}, enabled : {}",
-        new Object[] {accountNonExpired, credentialsNonExpired, accountNonLocked, enabled});
-    return new User(assertion.getPrincipal().getName().toLowerCase().trim(), NON_EXISTENT_PASSWORD_VALUE, enabled,
-        accountNonExpired, credentialsNonExpired, accountNonLocked, grantedAuthorities);
-  }
-
-you can change the implementation later for your needs.
-
-    Network Might Give Problem
-    Since it is the production environment, we need to consider that sometimes it might be a trouble in our network. That is why it is important to give some delay time in our application.
-    Here is the example how i delay some time in order the network is not as my expectation.
-
-/*
- * Copyright 2017 to PT. Global Digital Niaga(Blibli.com)
- * 
- * Licensed under the Apache License, Version 2.0; you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- * 
- * http://www.apache.org/licenses/LICENSE-2.0
- * 
- * Unless required by applicable law or agreed to in writing, software distributed under the License
- * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
- * or implied. See the License for the specific language governing permissions and limitations under
- * the License.
- */
-package com.gdn.iam.spring.security;
-
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
-import org.springframework.security.cas.authentication.CasAuthenticationProvider;
-import org.springframework.security.core.Authentication;
-import org.springframework.security.core.AuthenticationException;
-
-public class GdnCasAuthenticationProvider extends CasAuthenticationProvider {
-
-  private static transient Logger LOG = LoggerFactory.getLogger(GdnCasAuthenticationProvider.class);
-  private long sleepForDistributeTicketTime = 300;
-
-  @Override
-  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
-    try {
-      LOG.trace(
-          "will try to sleep for waiting ticket to be distributed to other node, sleep time : {}",
-          getSleepForDistributeTicketTime());
-      Thread.sleep(getSleepForDistributeTicketTime());
-    } catch (InterruptedException e) {
-      LOG.error("something wrong when sleeping", e);
-    }
-    return super.authenticate(authentication);
-  }
 
-  public long getSleepForDistributeTicketTime() {
-    return sleepForDistributeTicketTime;
-  }
+###Descriptions of authentication flow
+
+The CAS authentication flow will be the same, no changes are required in terms of that authentication flow. Furthermore, you can see that flow at Apereo CAS 4.2.x documentation page.
+
+The main difference now is we don't put the ticket registry inside an in-memory database, we put it inside an Apache Ignite cache, so when other nodes are there it can replicate the ticket between them which increases efficiencies.
 
-  public void setSleepForDistributeTicketTime(long sleepForDistributeTicketTime) {
-    this.sleepForDistributeTicketTime = sleepForDistributeTicketTime;
-  }
+###Descriptions of authorization flow
 
-}
+Spring Security usually has the authorization role configuration inside your spring context xml file or using annotations in source. This is the only difference between plain spring security and that using my extended framework solution.  We put the configuration inside of Fortress. Everytime the user changes the URL, it will check the user has access to that specific URL and not through the extended voter class. If the user is authorized then the app will give them the correct page, otherwise it will route to 40X http error status page.
 
-Descriptions of authentication flow
+###Instructions to test
 
-The CAS authentication flow will be the same, there none of changes made in term of the authentication flow. Further, you can see the flow at Apereo CAS 4.2.x documentation page.
-The main different is we not put the ticket registry inside memory database, we put it on Apache Ignite cache so when other node is there it can replicate the ticket to that node.
-Descriptions of authorization flow
+For testing this example, you need to understand that Apache Fortress configuration is necessary to find fortress.properties on the classpath so it might be good if you put that configuration file at the same classpath, for instance, if you are using tomcat remove all the fortress.properties inside the classes directory and put it on $TOMCAT_HOME/lib/ folder. Make sure get Apache Fortress running at the first step. Here are the detailed instructions for testing this example:
 
-If you ever use Spring Security, usually you will put the authorization role configuration inside your xml or using annotation. This is the only difference between plain spring security with my extended framework solution, we put the configuration inside Fortress. Everytime user changing the URL, then it will check whenever the user has access to that specific URL or not through the extended voter class. If the user is authorized then the app will give the correct page, otherwise it will give 40X http status and page.
-Instructions to test
+####Server Section
 
-For testing this example, you need to understand that Apache Fortress configuration is necessary to find fortress.properties at the classpath so it might be good if you put that configuration file at the same classpath, for instance, if you are using tomcat remove all the fortress.properties inside the classes directory and put it on $TOMCAT_HOME/lib/ folder. Make sure you are make Apache Fortress running at the first step. Here are the detail instruction for testing this example :
-Server Section
+Read and find the instruction at :
+ * https://github.com/apache/directory-fortress-core
+ * https://github.com/apache/directory-fortress-enmasse
+ * https://github.com/apache/directory-fortress-commander
+ * and configure your Apache Fortress properly.
+ * Clone the project from link at Where to download section below, change the configuration properly inside cas-fortress-servers/src/main/resources folder and package it using mvn clean package.
+ * Copy the war file from cas-fortress-server/target into the web-container deploy directory.
+ * Start your web-container and you get cas fortress integrated.
 
-    Read and find the instruction at :
-    – https://github.com/apache/directory-fortress-core
-    – https://github.com/apache/directory-fortress-enmasse
-    – https://github.com/apache/directory-fortress-commander
-    and configure your Apache Fortress properly.
-    Clone the project from link at Where to download section below, change the configuration properly inside cas-fortress-servers/src/main/resources folder and package it using
-    mvn clean package.
-    Copy the war file from cas-fortress-server/target into the web-container deploy directory.
-    Start your web-container and you get cas fortress integrated.
+####Client Section
 
-Client Section
+ * Simply put the war file inside the web-container deploy directory.
+ * Open and login to your commander(fortress-web)
+ * Create a user with role ROLE_USER (you can change to what ever role). The role need to align with spring-security.xml for this statement <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />. This is the mandatory role, with this role we are seperate between the anonymous role and authenticate one.
+ * Create a permission object containing your restricted url, for instance http://localhost:8080/cas-fortress-client/profile and http://localhost:8080/cas-fortress-client/catalog.
+ * Map the permission object and role at permission tab at your commander. Currently we only support get for both of the url.
+ * Start your web-container and play with your cas-fortress-client later on.
 
-    Simply put the war file inside the web-container deploy directory.
-    Open and login to your commander(fortress-web)
-    Create a user with role ROLE_USER (you can change to what ever role). The role need to align with spring-security.xml for this statement <intercept-url pattern="/**" access="hasRole('ROLE_USER')" />. This is the mandatory role, with this role we are seperate between the anonymous role and authenticate one.
-    Create a permission object containing your restricted url, for instance http://localhost:8080/cas-fortress-client/profile and http://localhost:8080/cas-fortress-client/catalog.
-    Map the permission object and role at permission tab at your commander. Currently we only support get for both of the url.
-    Start your web-container and play with your cas-fortress-client later on.
+###Where to download
 
-Where to download
+ * https://github.com/bliblidotcom/cas-fortress-example
 
-https://github.com/bliblidotcom/cas-fortress-example
-Next Steps
+##Next Steps
 
-The next step should be implementing ARBAC solution. Since i did not allowed people to create a conditional statement inside their code to check the roles, button or page element that should be not accessible for specific user will appear, even they can not go or do the action, that causing some confusion in term or usability for my user. With ARBAC i believe i can do a whitelist for the page attribute and increase the usability.
+The next step should be implementing ARBAC solution. Since I donn't allow people to create a conditional statement inside their code to check for roles, buttons or page elements that should be not accessible for specific users will appear, even they can't perform that action.  This causes some confusion in terms or usability for my users. With ARBAC I believe I can do a whitelist for the page attributes and increase the usability for the user.