Security für JSF-Komponenten mit Spring AOP

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:

    /**
     * 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 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.

Kommentare

Ansicht der Kommentare: (Linear | Verschachtelt)

  1. Sandro Sonntag schreibt:

    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 schreibt:

    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 schreibt:

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

    Gruß Stefan


Kommentar schreiben


Umschließende Sterne heben ein Wort hervor (*wort*), per _wort_ kann ein Wort unterstrichen werden.
Standard-Text Smilies wie :-) und ;-) werden zu Bildern konvertiert.

Um maschinelle und automatische Übertragung von Spamkommentaren zu verhindern, bitte die Zeichenfolge im dargestellten Bild in der Eingabemaske eintragen. Nur wenn die Zeichenfolge richtig eingegeben wurde, kann der Kommentar angenommen werden. Bitte beachten Sie, dass Ihr Browser Cookies unterstützen muss um dieses Verfahren anzuwenden.
CAPTCHA