CMS, CoreMedia, Java
Schreibe einen Kommentar

CoreMedia und Spring Security verheiratet – Teil 2: Autorisierung

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.

 

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">
        ...
    </bean>

    <bean id="aService"
       >
        <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"
       >
        <property name="secureMethods">
            <value>
               methode1=ADMIN
               methode2=ADMIN, REDAKTEUR
               ...
            </value>
        </property>
    </bean>
</beans>

Links

Share on FacebookShare on Google+Tweet about this on TwitterPin on Pinterest

Schreib einen Kommentar

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