Communardo Software GmbH, Kleiststraße 10 a, D-01129 Dresden

CoreMedia und Spring Security verheiratet - Teil 2: Autorisierung

Rechte und Rollen

Mit dem CoreMedia JavaEditor kön­nen Berechtigungsgruppen oder Rollen erstellt und Nutzern zuge­ord­net wer­den. Über die Gruppen kön­nen Berechtigungen sehr fein­gra­nu­lar auf Dokumentebene ein­ge­stellt werden.

javaeditor

Bei der Authentifizierung wird das Nutzerobjekt mit sei­nen Berechtigungen (GrantedAuthority) gela­den und im SecurityContext abge­legt. Für die Autorisierung kann in allen Schichten der Architektur dar­auf zuge­grif­fen werden.

Autorisierung im Template

Wenn es sich um ein­fa­che rol­len­ba­sierte Autorisierung han­delt, 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 kom­ple­xer. Je nach Berechtigung des Nutzers sol­len unter­schied­li­che Inhalte aus dem CMS dar­ge­stellt wer­den. Es emp­fiehlt sich, die Funktionalität für die Prüfung der Berechtigung in eine eigene Komponente (AuthorizationUtils) aus­zu­la­gern, da diese auch z.B. bei den Controllern ver­wen­det wer­den 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 vor­ge­stell­ten Bean-basierten Autorisierungsmechanismus ver­wen­den, um z.B. kom­plette 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 metho­den­ba­sierte Security kann man ele­gant mit den Mitteln der aspekt­ori­en­tier­ten Programmierung (AOP) lösen. Neben Frameworks wie aspectJ bie­tet 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 kon­fi­gu­riert. Der Proxy kennt den Service unter der Property tar­get. Die Service-Aufrufe wer­den so durch den Proxy abge­fan­gen. Nun kommt der Interceptor ins Spiel. Er prüft für kon­fi­gu­rierte Methoden die Berechtigung. Ist alles o.k., wird die Anfrage an den eigent­li­chen 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

Related Posts

Pin It on Pinterest