Security for JSF componenten using Spring AOP

Spring as factory for JSF components

Like in the recent article "JSF components using Spring and Velocity templates" I will use Spring to create JSF components n this example.

Last time the approach works as follows: configure a factory in faces-config.xml like this:

  <factory>
    <application-factory>
      org.springframework.web.jsf.SpringApplicationFactory
    </application-factory>
  </factory>

This ApplicationFactory brings spring into play to create jsf components. I used this factory last time to create and configure components like spring beans via the application context.
But if spring creates components we could easily apply more spring features e.g. spring AOP:
If you would like to display a component depending on the ROLE a user has, you could write a MethodInterceptor, that catches (and checks) all calls to the "encode*" methods of the component. And the SpringApplication class is even more useful. AOP can easily applied to all standard components ...

Spring ProxyFactory to wrap the standard components

Under th hood spring uses the ProxyFactory to implement AOP features like creating advised objects. Exactly this mechanism can be applied to all standard jsf components (like outputText for instance). The corrosponding method in the SpringApplication class looks like this:

    /**
     * Tries to create a UI component via the original Application and looks for
     * the UI component in Spring's root application context if it is not
     * obtainable through JSF's Application.
     *
@param componentType the component type and Spring bean name for the UIComponent
     *
@return the resulting UIComponent
     *         through either the JSF Application or Spring Application Context.
     *
@throws FacesException if the component could not be created or located in Spring
     */
   
public UIComponent createComponent(String componentType) throws FacesException {
       
FacesException originalException = null;
       
try {
           
// Get component from Spring root context
           
if (logger.isDebugEnabled()) {
               
logger.debug("Attempting to find component '" + componentType + "' in root WebApplicationContext");
           
}
           
FacesContext facesContext = FacesContext.getCurrentInstance();
           
if( facesContext != null ) {
             
WebApplicationContext wac = getWebApplicationContext(facesContext);
             
if (wac.containsBean(componentType)) {
                 
if (logger.isDebugEnabled()) {
                     
logger.debug("Successfully found component '" + componentType + "' in root WebApplicationContext");
                 
}
                 
return (UIComponent) wac.getBean(componentType);
             
}
            }

           
// Create component with original application
           
if (logger.isDebugEnabled()) {
               
logger.debug("Attempting to create component with type '" + componentType + "' using original Application");
           
}
           
UIComponent originalComponent = this.originalApplication.createComponent(componentType);
           
if (originalComponent != null) {
             
try {
               
System.out.println("trying to proxy " + originalComponent);
                ProxyFactory fac =
new ProxyFactory(originalComponent);
                fac.addAdvice
(new RenderMethodInterceptor());
               
//fac.addAdvisor(new TestAdvisor());
               
fac.setProxyTargetClass(true);
               
return (UIComponent) fac.getProxy();
             
} catch( org.springframework.aop.framework.AopConfigException e) {
               
System.out.println("could not proxy comp type: "+ componentType);
               
return originalComponent;
             
}
            }
           
originalException = new FacesException("Original Application returned a null component");
       
} catch (FacesException e) {
           
originalException = e;
       
}
       
       
if (logger.isDebugEnabled()) {
           
logger.debug("Could not create component '" + componentType + "'");
       
}
       
throw new FacesException("Could not create component using original Application.  " +
                                
"Also, could not find component in root WebApplicationContext", originalException);
   
}

Doing it that way I can control the rendering of all standard jsf components via a MethodInterceptor as well. You can use Acegi or standard security mechanism provided by the j2ee container to control the visibility (possibly even the display style) of the components.

Using Acegi the MethodInterceptor looks like this:

    /**
     * an interceptor that control the rendering of the component. it checks the security
     * context (here acegi) and compares the users roles to the required role
     *
@author sr
     *
     */
   
public static class RenderMethodInterceptor implements MethodInterceptor {

     
private boolean hasRole(GrantedAuthority[] authorities, String role) {
       
for (int i = 0; i < authorities.length; i++) {
       
if( authorities[i].getAuthority().equals(role) ) {
         
return true;
       
}
      }
       
return false;
     
}
     
   
public Object invoke(MethodInvocation mi) throws Throwable {

     
String methodName = mi.getMethod().getName();
     
     
if( methodName.equals("isRendered") || methodName.startsWith("encode") ) {
       
// check rendering
       
UIComponent comp =  (UIComponent) mi.getThis();
        String requiredRole =
(String) comp.getAttributes().get("requiredRole");
       
if( hasRole( SecurityContextHolder.getContext().getAuthentication().getAuthorities(), requiredRole ) ) {
         
System.out.println("comp will not be rendered. " + mi.getThis());
         
return new Boolean(false);
       
} else {
         
return mi.proceed();
       
}
      }
else {
       
return mi.proceed();
     
}
    }
     
    }

Facelets make things easier

Facelets simplifies your life: we could attach arbitrary attribute to a component without to need to declare them before. To put a simple requiredRole='admin' into the commandButton is sufficient and yet the button is visible for admins only.

If someone's interested I will provide the complete source code for download.

Comments

Display comments as (Linear | Threaded)

  1. Sandro Sonntag says:

    Das habe ich schon lange gesucht, die Möglichkeit AOP an UI Komponenten anzuwenden. Ungeahnte Möglichkeiten erschießen sich da.
    So könnte ich nun Themen wie Webtracking als Aspekt einschieben ohne meine Code zu belasten.

    Warum finde ich erst jetzt diesen Post?

  2. Stefan Rinke says:

    Freut mich das Dir diese Idee gefällt. In der Tat kann Spring als Factory in ganz unterschiedlichen Szenarien äusserst nützlich sein.

    Gruß Stefan

  3. Stefan Rinke says:

    Auf Nachfrage hab ich den Sourcecode hinzugefügt (siehe Ende des Eintrags).

    Gruß Stefan


Add Comment


Enclosing asterisks marks text as bold (*word*), underscore are made via _word_.
Standard emoticons like :-) and ;-) are converted to images.

To prevent automated Bots from commentspamming, please enter the string you see in the image below in the appropriate input box. Your comment will only be submitted if the strings match. Please ensure that your browser supports and accepts cookies, or your comment cannot be verified correctly.
CAPTCHA