Startseite > Techblog > Artikel von
mse

Wer kennt das nicht? Man debuggt  sich Zeile für Zeile durch den Code um Fehler in komplexen Algorithmen zu finden.

Das Auslesen von primitiven Datentypen oder Strings klappt ziemlich gut. Wenig hilfreich ist allerdings die Darstellung von Kalenderobjekten.

Variablenansicht eines Kalenderobjekts

Mit Hilfe der in Eclipse angebotenen DetailFormater lassen sich Objekte im Debug-Modus beliebig formatieren. Für ein Objekt vom Typ “java.util.GregorianCalendar” könnte die Formatierung folgendermaßen aussehen: this.getTime().toString();

Ergebnis: Fri Jul 17 22:46:07 CEST 2009

Diese Darstellung ist dann schon deutlich lesbarer.
Auch für komplexere Datenstrukturen könnte es hilfreich sein, die wichtigen Informationen auf einem Blick zu sehen.

So funktioniert es

settings

  1. Einstellungsdialog öffnen (Menü “Windows/Preferences” Pfad “Java/Debug/Detail Formaters”)
  2. Button “Add” klicken
  3. Eintragen von java.util.Calendar in das Feld “Qualified type name” field
  4. Eintragen von Enter this.getTime().toString()
  5. Button “OK” klicken

Kommentar Feed Trackback URL
mse

Rechte und Rollen

Mit dem CoreMedia JavaEditor können Berechtigungsgruppen oder Rollen erstellt und Nutzern zugeordnet werden. Über die Gruppen können Berechtigungen sehr feingranular auf Dokumentebene eingestellt werden.

javaeditor

Bei der Authentifizierung wird das Nutzerobjekt mit seinen Berechtigungen (GrantedAuthority) geladen und im SecurityContext abgelegt. Für die Autorisierung kann in allen Schichten der Architektur darauf zugegriffen werden.

Autorisierung im Template

Wenn es sich um einfache rollenbasierte Autorisierung handelt, kann die authorize-Taglib aus dem Framework benutzt werden.

<authz:authorize ifAllGranted="ROLE_SUPERVISOR">
<td>
<A HREF="del.htm?id=<c:out value="${contact.id}"/>">Del</A>
</td>
</authz:authorize>

Die Autorisierung mit Berechtigungsgruppen (Bean-basierte Autorisierung) ist komplexer. Je nach Berechtigung des Nutzers sollen unterschiedliche Inhalte aus dem CMS dargestellt werden. Es empfiehlt sich, die Funktionalität für die Prüfung der Berechtigung in eine eigene Komponente (AuthorizationUtils) auszulagern, da diese auch z.B. bei den Controllern verwendet werden kann.

AuthorizationUtils

public class AuthorizationUtils{
  ...
  public boolean isAccessAllowed(Content content){

    GrantedAuthority[] grants = null;
    if (SecurityContextHolder.getContext().getAuthentication() != null) {
      grants = SecurityContextHolder
        .getContext().getAuthentication().getAuthorities();
    }
    int length = (grants != null) ? grants.length : 0;
    String[] groupnames = new String[length];
    for (int i = 0; i < length; i++) {
      groupnames[i] = grants[i].getAuthority();
    }
    LinkedList groups = getGroups(groupnames);

    if (groups != null && getAccessControl().mayRead(content, groups)) {
      isAccessAllowed = true;
    } else {
      isAccessAllowed = false;
    }
    return isAccessAllowed;
  }

  private LinkedList getGroups(String[] groupNames) {
    LinkedList groups = new LinkedList();
    for (int i = 0; groupNames != null && i < groupNames.length; i++) {
      Group group = userRepository.getGroupByName(groupName, null);
      if (group != null) {
        groups.add(group);
      }

    }
    return groups;
  }
  private AccessControl getAccessControl() throws MipException {
    AccessControl accessControl = getRepository().getAccessControl();
    return accessControl ;
  }
  ...
}

Authorize-Tag Library

public class Authorize extends TagSupport {

  private ContentBean contentBean;

  public int doStartTag() {
    if (null == bean) {
      return SKIP_BODY;
    }

    ApplicationContext context = getContext(pageContext);
    AuthorizationUtils authorizationUtils = (AuthorizationUtils)
      context.getBean("authorizationUtils");
    try {
      if (authorizationUtils.isAccessAllowed(bean.getContent(),
          null, (HttpServletRequest) pageContext
          .getRequest())) {
        return EVAL_BODY_INCLUDE;
      }
    } catch (Exception e) {
      e.printStackTrace();
    }

    return SKIP_BODY;
  }

  protected ApplicationContext getContext(PageContext pageContext) {
    ServletContext servletContext = pageContext.getServletContext();

    return WebApplicationContextUtils
        .getRequiredWebApplicationContext(servletContext);
  }
  public ContentBean getContentBean() {
    return contentBean;
  }
  public void setContentBean(ContentBean contentBean) {
    contentBean = contentBean;
  }

}

Einbindung im Template

<mse:authorize contentBean="${self}">
  <c:out value="${self.name}" />
</mse:authorize>

Autorisierung im Controller

Auch im Controller kann man den bereits vorgestellten Bean-basierten Autorisierungsmechanismus verwenden, um z.B. komplette Seitenaufrufe zu verhindern.

SecureContentViewController

public class SecureContentViewController extends ContentViewController {

  private AuthorizationUtils authorizationUtils;

  protected Object resolveBean(String controllerPathInfo,
      Map parameters, HttpServletRequest request) {
   ...
  }

  protected String resolveView(String controllerPathInfo,
      Map parameters, HttpServletRequest request) {
    ...
  }

  protected ModelAndView handleRequestInternal(HttpServletRequest request,
    HttpServletResponse response) throws Exception {

    String pathInfo = controllerPathInfo(HttpServletRequest request);
    Object bean = resolveBean(pathInfo, request.getParameterMap(), request);

    ContentBean contentBean = (ContentBean) bean;

    if (!authorizationUtils.isAccessAllowed(contentBean.getContent())) {
      // Fehlerbehandlung
      return new ModelAndView(new RedirectView(LOGIN_URL));
    }
    return super.handleRequestInternal(request, response);
  }
  ...
}

Autorisierung auf Business-Methoden

Das Thema methodenbasierte Security kann man elegant mit den Mitteln der aspektorientierten Programmierung (AOP) lösen. Neben Frameworks wie aspectJ bietet auch Spring mit Dynamic Proxys eine AOP-Implementierung.

CheckPermissionInterceptor

public class CheckPermissionInterceptor implements MethodInterceptor {

  public Object invoke(MethodInvocation aMethodInvocation) throws Throwable {
    initSecureMethodsMap();

    String currentMethode = aMethodInvocation.getMethod().getName();

    // ist SecureMethode
    if (secureMethodsMap.containsKey(currentMethode)) {

      Authentication authentication = SecurityContextHolder.getContext()
          .getAuthentication();

      GrantedAuthority[] auth = authentication.getAuthorities();

      int length = (auth != null) ? auth.length : 0;
      String[] roleNames = new String[length];
      for (int i = 0; auth != null && i < auth.length; i++) {
        roleNames[i] = auth[i].getAuthority();
      }

      String roles = (String) secureMethodsMap.get(currentMethode);
      if(!checkPermission(roles, roleNames){
        // Keine Rechte
        throw new AccessDeniedException("User hat nicht genügend Rechte");
      }

    }
    return aMethodInvocation.proceed();
  }
  private boolean checkPermission(String allowedRoles, String[] availableRoles){
    for (int i = 0; i < availableRoles.length; i++) {
      if(allowedRoles.indexOf(availableRoles[i])!=-1){
        return true;
      }
    }
    return false;
  }
}

In der Spring-Konfiguration wird ein Dynamic Proxy mit dem Namen des Services konfiguriert. Der Proxy kennt den Service unter der Property target. Die Service-Aufrufe werden so durch den Proxy abgefangen. Nun kommt der Interceptor ins Spiel. Er prüft für konfigurierte Methoden die Berechtigung. Ist alles o.k., wird die Anfrage an den eigentlichen Service delegiert.

Spring-Konfiguration

<beans>   
    <bean id="aServiceTarget" class="de.mse.business.ServiceImpl">
        ...
    </bean>

    <bean id="aService"
        class="org.springframework.aop.framework.ProxyFactoryBean">
        <property name="proxyInterfaces">
            <value>de.mse.business.IService</value>
        </property>
        <property name="target">
            <ref bean="aServiceTarget"/>
        </property>
        <property name="interceptorNames">
            <list>
                <value>checkPermissionInterceptor</value>
            </list>
        </property>
    </bean>
    <bean id="checkPermissionInterceptor"
        class="de.mse.security.CheckPermissionInterceptor">
        <property name="secureMethods">
            <value>
               methode1=ADMIN
               methode2=ADMIN, REDAKTEUR
               ...
            </value>
        </property>
    </bean>
</beans>

Links

Kommentar Feed Trackback URL
mse

Erstellung eines CoreMedia-AuthenticationProviders für das Spring Security Framework

Einleitung und Funktionsweise

Neben dem Aufbau von geschlossenen Benutzergruppen (GBGs) im CMS kann es auch andere Gründe geben, das CoreMedia UserRepository für die Authentifizierung von Nutzern zu verwenden. Das Spring Security Framework (ehemals ACEGI Security) bietet Funktionalitäten für die Authentifizierung und Autorisierung von Nutzern. Im Folgenden Klassendiagramm sind die Interfaces und Klassen für die Authentifizierung dargestellt.

uml

Standardmäßig sind im Framework bereits verschiedene Implementierungen vorhanden (z.B. für Datenbanken, LDAP u.a.). Mit wenig Aufwand kann man eigene Implementierungen wie die gegen das CoreMedia UserRepository implementieren.

Der AuthenticationManager ist der zentrale Einstieg für die Authentifizierung. Er kann mehrere verschiedene AuthenticationProvider verwalten. Über das Authentication-Objekt kann die Authentifizierung nur durch gewünschte Provider erfolgen (AuthenticationProvider#supports(Class authentication)).
Der UserDetailsService lädt das User-Objekt aus dem jeweiligen Repository per Namen (#loadUserByUsername(String username)). Der Provider überprüft das Passwort mit der Methode additionalAuthenticationChecks(...).

Implementierung

public class CMUserDao implements UserDetailsService{
...
public UserDetails loadUserByUsername(String username)
  throws UsernameNotFoundException, DataAccessException {
UserRepository userRepository = capConnection.getUserRepository();
org.acegisecurity.userdetails.User user = null;
try {
Repository repository = ContentRepositoryFactory.instance().getContentRepository();
User cmsUser = userRepository.getUserByName(username);
Collection < Group > groups = new ArrayList < Group >();
if (cmsUser != null) {
  groups = cmsUser.getGroups();
} else {
  throw new UsernameNotFoundException(username);
}
Collection < GrantedAuthority > authorities = new ArrayList < GrantedAuthority >();
if (groups != null) {
  for (Group group : groups) {
    authorities.add(new GrantedAuthorityImpl(group.getName()));
  }
}
user = new org.acegisecurity.userdetails.User(username, "", true, true, true, true,
  authorities.toArray(new GrantedAuthority[0]));
} catch (InvalidLoginException e) {
  throw new BadCredentialsException(e.getMessage(), e);
} catch (LicensesExceededException e) {
  new BadCredentialsException(e.getMessage(), e);
} catch (ConnectionNotOpenException e) {
  new BadCredentialsException(e.getMessage(), e);
}
return user;
}
...
}
public class CMAuthenticationProvider extends
    AbstractUserDetailsAuthenticationProvider implements AuthenticationProvider {
...
protected void additionalAuthenticationChecks(UserDetails aUserDetails,
      UsernamePasswordAuthenticationToken aAuthenticationToken)
      throws AuthenticationException {

    String username = aUserDetails.getUsername();
    String password = aAuthenticationToken.getCredentials().toString();   

    boolean isValid = capConnection.isValidLogin(username, null, password);

    if (!isValid) {
      throw new BadCredentialsException(messages.getMessage(
          "AbstractUserDetailsAuthenticationProvider.badCredentials",
          "Bad credentials"), aUserDetails);
    }
  }
...
}

Benutzt man den AuthenticationProcessingFilter aus dem Framework wird nach erfolgreicher Authentifizierung das Authentication-Objekt in den SecurityContext geladen, einer ThreadLocal-Variable. Benutzt man einen eigenen Controller, muss man dies von Hand machen.

auth = authenticationManager.authenticate(...);
SecurityContextHolder.getContext().setAuthentication(auth);

Spring-Konfiguration

<bean id="authenticationProcessingFilter"
  class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
  <property name="authenticationManager"><ref bean="authenticationManager"/></property>
  ...
</bean>
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
  <property name="providers">
    <list>
      <ref local="cmsDaoAuthenticationProvider" />
    </list>
  </property>
</bean>

<bean id="cmsDao" class="de.mse.authentication.CMUserDao"/>

<bean id="cmsDaoAuthenticationProvider"
  class="de.mse.authentication.CMAuthenticationProvider">
  <property name="userDetailsService">
    <ref bean="cmsDao"/>
  </property>
</bean>

Links

Kommentar Feed Trackback URL
mse

OutOfMemory ist eines der am häufigsten auftretenden Probleme bei Java-basierten Webapplikationen. Die Ursachen dafür sind genau so vielschichtig wie die Möglichkeiten der Analyse. Ziel dieses Artikels ist es, sowohl die Grundlagen wie auch mögliche Lösungswege zu zeigen.

Bevor man jetzt leichtfertig die Software umprogrammiert (da man ja genau weiß, wo das Problem liegt), sollte man mit einer ausführlichen Analyse beginnen. Nicht selten kommt es vor, dass Software (Parameter der JVM) oder sogar das Betriebssystem nicht korrekt konfiguriert sind.

Die Process Size

Die Process Size ist der maximal verfügbare Speicher für den Prozess. Dieser ist abhängig von der Hardware und vom Betriebssystem. In einer 32 Bit-Architektur kann die Process Size maximal 4 GB sein. Für einen Java-Prozess gibt es folgende Bereiche, die im Speicher des Prozesses liegen (siehe Abbildung 1):

  • Java Heap - Der Heap ist der Speicher, in dem die Java Objekte erzeugt und verwaltet werden.
  • Permanent Generation – Die Permanent Generation ist der Speicher, in dem die JVM sich selber verwaltet. In diesem Speicher werden auch die Klassen durch den ClassLoader geladen.
  • Nativ Code – Jeder Java-Prozess benötigt auch Speicher für den Native Code. Das sind C-Bibliotheken des Betriebssystems für den Zugriff auf Systemressourcen. Zum Beispiel der Zugriff aufs Dateisystem.

Abbildung 1: Schematischer Aufbau der Speicherverteilung eines Java-Prozesses

Der Speicher für den Native Code ist nicht explizit einstellbar. Er ergibt sich aus der Differenz zwischen Process Size, Heap und Permanent Generation. Dieser Speicher darf nicht zu knapp bemessen sein, denn es kann auch OutOfMemory im Native Code geben.

Die Java Heap Size

Die Java Heap Size gibt die Größe des Speichers an, der für die Erzeugung und Verwaltung der Java-Objekte verwendet werden kann. Die Größe des Heaps ist begrenzt. Wird keine Größe explizit angegeben, wird die Defaulteinstellung verwendet. Diese variiert von Betriebssystem zu Betriebssystem. Wenn man wirklich sicher gehen möchte, stellt man die Größe explizit ein:

    -Xms[Bytes]m : initiale Größe des Heaps beim Starten des Prozesses
    -Xmx[Bytes]m : maximale Größe des Heaps über den gesamten Lebenszeitraum

Der Heap teilt sich in zwei große Bereiche: der New Generation und der Old Generation (siehe Abbildung 2).
Wie die Namen schon vermuten lassen, befinden sich in der New Generation die ganzen neuen, jungen und kurzlebigen Java-Objekte und in der Old Generation die langlebigen Java-Objekte.

Abbildung 2: Aufteilung der Speicherbereiche im Heap

Das Verhältnis zwischen New und Old Generation ist aus Speichersicht der größte Unterschied zwischen einer Desktop- und einer Serveranwendung. Eine Desktopanwendung läuft in der Regel einen Arbeitstag, also ca. 8 h. Eine Serveranwendung läuft 24 h, 7 Tage die Woche und das mehrere Monate. Daran kann man schon erkennen, dass es bei einer Desktopanwendung mehr jüngere Objekte und bei einer Serveranwendung mehr ältere Objekte gibt. Mit dem Servermodus stellt man das Verhältnis zwischen New und Old Generation auf 1:3 ein.

    -server :Servermodus für die VM

Permanent Generation

Die Permanent Generation wird von der JVM für das Laden der Klassen durch den ClassLoader verwendet. Normalerweise ist die Defaulteinstellung der Größe ausreichend. Wenn allerdings viele Klassen dynamisch durch die Applikation geladen werden (z.B. durch Reflection), kann die Defaulteinstellung zu Problemen führen. Auch die Verwendung von Persistenz- oder Caching-Frameworks führt zu größerem Speicherverbrauch in der Permanent Generation.
Über folgende Parameter kann die Defaultgröße der Permanent Generation angepasst werden:

    -XX:PermSize=[Bytes]m : initiale Größe der Permanent Generation
    -XX:MaxPermSize=[Bytes]m : maximale Größe der Permanent Generation

Garbage Collection

Man unterscheidet bei der Garbage Collection in die („einfache“) Garbage Collection und in die Full Garbage Collection. Bei der Garbage Collection werden nicht mehr referenzierte Objekte aus der New Generation freigegeben. Des Weiteren werden noch „lebende“ Objekte, die durch mehrere Garbage Collection nicht freigegeben wurden, in die Old Generation kopiert. Bei einer Full Garbage Collection werden auch Objekte aus der Old Generation aufgeräumt.
Eine Full Garbage Collection ist sehr zeitintensiv (bei 2 GB Heap Size = mehrere Sekunden). Während dieser Zeit reagiert die Applikation nicht mehr.
Ist die Heap Size zu gering eingestellt, findet eine Full Garbage Collection nach der anderen statt. Dies führt zu einer sehr schlechten Performance und zu langen Antwortszeiten.
In einer Multiprozessor-Maschine ist es deshalb ratsam, die Garbage Collection („einfache“ und Full) parallel arbeiten zu lassen.

    -XX:+UseParallelGC: parallele Garbage Collection und Full Garbage Collection

Ausblick

Nachdem in diesem Artikel die Grundlagen erläutert wurden, werden im nächsten Artikel konkrete Analyse-Möglichkeiten besprochen.

Links

Kommentar Feed Trackback URL

Tag Cloud

Unsere Themen

Kommentare

  • SharePoint_Team: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • TorstenHu: Rückblick zum Treffen der .NET Usergroup Dresden am 24.02.2010: im #Communardo #Techblog...
  • SharePoint_Team: Neuer Blogpost zur #BastaCon im #Communardo #TechBlog: http://tinyurl.com/yjqyqpb This comment was...
  • SharePoint_Team: Nur noch etwa 1 Stunde, dann beginnt die .NET Usergroup… http://bit.ly/dxDoKg This comment was...
  • SharePoint_Team: RT @TorstenHu: ViS is waiting for an operation oder Warum Copy & Paste schlecht ist: #Communardo...

Twitter