Security für JSF-Komponenten mit Spring AOP

[:de]

Spring als Factory für JSF-Components

Wie schon im letzten Beitrag “JSF-Components mit Spring und Velocity-Templates” soll auch bei diesem Beispiel Spring zum Erzeugen von JSF-Componenten benutzt werden.

Noch einmal kurz das Vorgehen: in der faces-config.xml wird mit:

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

eine ApplicationFactory eingetragen, die Spring ins Spiel bringt, um JSF-Components zu erzeugen. Im letzten Artikel hatte ich das benutzt, um Komponenten wie SpringBeans zu erzeugen und zu konfigurieren.

Wenn Spring die Komponenten erzeugt, dann lassen sich alle Spring Features voll zum Einsatz bringen, unter anderem auch AOP:

Man kann z.B. abhängig von der ROLE eines User eine Komponente anzeigen oder nicht. Hierzu schreibt man einen MethodInterceptor, der die Aufrufe der “encode*”-Calls auf die Komponente überprüft. Die SpringApplication kann aber noch mehr. AOP läßt sich nämlich auch auf die Standard-Komponenten anwenden …

Spring ProxyFactory zum Wrappen der Standard-Komponenten

Hinter den Kulissen nutzt Spring für AOP die ProxyFactory zum Erzeugen von “Advised”-Objects. Genau diesen Mechanismus kann man auch auf alle Standard-JSF-Components anwenden. Die entsprechende Methode in der SpringApplication sieht dann so aus:

    /**
     * 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);
    }

Auf diese Weise läßt sich nun das Rendering auch aller Standard-Komponenten über den MethodInterceptor steuern. Man kann so z.B. mit Acegi oder Standard-Security-Mechanismen des WebContainers die Anzeige (oder auch den Style der Komponenten steuern.

Für Acegi sieht der MethodInterceptor so aus:

    /**
     * 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);
    }

Facelets machens wiederum einfacher

Facelets erleichtern auch hier das Leben: man kann eine Komponente mit beliebigen Attributen “schmücken” ohne dass diese zuvor deklariert werden müssen. Es genügt also ein requiredRole=’admin’ in den Command-Button zu schreiben und schon wird er nur noch für Admins sichtbar.

Kompletten Sourcecode gibt’s hier.[/:de][:en]

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 in 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>

Using this ApplicationFactory enables spring 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:

    /**
     * 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);
    }

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 you can download the source code here.

[/:en]

Dieser Beitrag wurde unter Java veröffentlicht. Setze ein Lesezeichen auf den Permalink.

3 Antworten auf Security für JSF-Komponenten mit Spring AOP

Hinterlasse einen Kommentar zu Sandro Sonntag Antworten abbrechen

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind markiert *