2011-01-07 12 views
6

Estamos utilizando Spring Security 3. Tenemos una implementación personalizada de PermissionEvaluator que tiene este complejo algoritmo para otorgar o denegar el acceso a nivel de método en la aplicación. Para hacer esto, agregamos una anotación @PreAuthorize al método que queremos proteger (obviamente). Todo está bien en eso. Sin embargo, el comportamiento que estamos buscando es que si se deniega una llamada a hasPermission, solo se debe omitir la llamada al método protegido, en vez de eso, recibimos un error 403 cada vez que ocurre.Prevención Llamada al método sin excepción con @PreAuthorize Anotación

¿Alguna idea de cómo prevenir eso?


Aquí puede encontrar una explicación diferente del problema; AccessDeniedException handling during methodSecurityInterception

Respuesta

4

La solución es usar MethodSecurityInterceptor personalizado, que llama al AccessDecisionManager (implícitamente, bu calling super's method) y decide si proceder con una llamada a un método.

package com.myapp; 

public class MyMethodSecurityInterceptor extends MethodSecurityInterceptor { 

    @Override 
    public Object invoke(MethodInvocation mi) throws Throwable { 
     Object result = null; 
     try { 
      InterceptorStatusToken token = super.beforeInvocation(mi);    
     } catch (AccessDeniedException e) { 
      // access denied - do not invoke the method and return null 
      return null; 
     } 

     // access granted - proceed with the method invocation 
     try { 
      result = mi.proceed(); 
     } finally { 
      result = super.afterInvocation(token, result); 
     } 

     return result;   
     } 
} 

Configurar el contexto de aplicaciones es un poco complicado: ya no se puede utilizar <sec:global-mathod-security> en este caso, existe la necesidad de definir una configuración explícita AOP (y crear la mayor parte de la estructura de frijol correspondiente de la etiqueta original hace de forma predeterminada):

<aop:config> 
    <!-- Intercept all relevant methods --> 
    <aop:pointcut id="myMethods" 
        expression='execution(* com.myapp.myService+.*(..))'/> 
    <aop:advisor advice-ref="mySecurityInterceptor" pointcut-ref="myMethods"/> 
</aop:config> 

<!-- Configure custom security interceptor --> 
<bean id="mySecurityInterceptor" 
     class="com.myapp.MyMethodSecurityInterceptor"> 
    <property name="securityMetadataSource"> 
     <bean class="org.springframework.security.access.prepost.PrePostAnnotationSecurityMetadataSource"> 
      <constructor-arg> 
       <bean class="org.springframework.security.access.expression.method.ExpressionBasedAnnotationAttributeFactory"> 
        <constructor-arg> 
         <bean class="org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler"/> 
        </constructor-arg> 
       </bean> 
      </constructor-arg> 
     </bean> 
    </property> 
    <property name="validateConfigAttributes" value="false"/> 
    <property name="accessDecisionManager" ref="accessDecisionManager"/> 
    <property name="authenticationManager" ref="authenticationManager"/> 
</bean> 

<!-- Configure AccessDecisionManager --> 
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased"> 
    <property name="decisionVoters"> 
     <list> 
      <bean class="org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter"> 
       <constructor-arg> 
        <bean class="org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice"/> 
       </constructor-arg> 
      </bean> 
     </list> 
    </property> 
</bean> 

<!-- Configure AuthenticationManager as you wish --> 
<!-- ........................................... --> 
+1

Terminé haciendo algo bastante más simple, acabo de agregar un consejo sobre el MethodSecurityInterceptor que capta la excepción con un @Around para que la propagación más allá de ese punto se evite . De esa manera podría evitar configurar Spring Security a pie, lo cual es muy intrincado. Sin embargo, lo que propones es la forma canónica de hacerlo y funciona. – Chepech

+1

¡Muy bonito! Me pregunto si hay alguna manera de que podamos cambiar el comportamiento de beforeInvocation() (usando AOP) para verificar los permisos del usuario en un método específico (que OP quiere proteger) y reemplazar mi por una instancia ficticia de MethodInvocation en lugar de preocuparme por AccessDeniedException. – Ritesh

+0

@Chepech La solución @Around parece muy simple y elegante. Si actualiza el código @Around en cuestión, con mucho gusto lo votaré. – Ritesh

2

Ok Encontré una manera de evitar AccessDeniedException. Sin embargo, esto no resuelve el problema. La ejecución del resto del código ahora se cumple normalmente, sin embargo, la llamada al método seguro no se previene incluso cuando hasPermission devuelve false.

Así es como me las arreglé para evitar que el AccessDeniedException de parando todo:

Es necesario implementar un AccessDecisionManager donde se impide la AccessDeniedException propagación. Esa es la parte fácil. El mío se ve así:

public class SkipMethodCallAccessDecisionManager extends AffirmativeBased { 
    @Override 
    public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes){ 
     try{ 
      super.decide(authentication, object, configAttributes); 
     }catch(AccessDeniedException adex){ 
      logger.debug("Access Denied on:" + object); 
     } 
    } 
} 

Luego, la parte difícil ... configurar el contexto de la aplicación.

<sec:global-method-security pre-post-annotations="enabled" access-decision-manager-ref="skipMethodCallAccessDecisionManager "/> 

<bean id="skipMethodCallAccessDecisionManager" class="com.application.auth.vote.SkipMethodCallAccessDecisionManager "> 
    <property name="decisionVoters"> 
     <list> 
      <bean class="org.springframework.security.access.prepost.PreInvocationAuthorizationAdviceVoter"> 
       <constructor-arg ref="expressionBasedPreInvocationAdvice"/> 
      </bean> 
      <!-- Insert RoleVoter if required --> 
      <bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>   
     </list> 
    </property> 
</bean> 

<bean id="expressionBasedPreInvocationAdvice" class="org.springframework.security.access.expression.method.ExpressionBasedPreInvocationAdvice"> 
    <property name="expressionHandler" ref="expressionHandler"/> 
</bean> 

¿Alguna idea sobre cómo evitar que se llame al método sin detener todo?

+0

esto no funcionará. Como se muestra en @Boris Kirzner, se llamará a mi.proceed() si no hay excepción en beforeInvocation (mi) – Ritesh

+0

@Ritesh - Sé que no funciona, lo agregué para documentar lo que estaba haciendo para proporcionar pistas sobre las personas que leen la pregunta ya que nadie respondió esto durante casi 1 mes. Declaro este hecho en la respuesta. – Chepech

+0

No me di cuenta de que era una vieja pregunta. Lo revisé cuando buscaba en Google para encontrar una solución similar. – Ritesh

2

Este es el código de la solución de asesoramiento que implementé.

Este es el código de aspecto:

@Aspect 
public class AccessDeniedHaltPreventionAdvice { 
private final Log logger = LogFactory.getLog(AccessDeniedHaltPrevention.class); 

@Around("execution(@org.springframework.security.access.prepost.PreAuthorize * *(..))") 
public Object preventAccessDeniedHalting(ProceedingJoinPoint pjp) throws Throwable{ 
    Object retVal = null; 
    try{ 
     retVal = pjp.proceed(); 
    }catch(AccessDeniedException ade){ 
     logger.debug("** Access Denied ** "); 
    }catch(Throwable t){ 
     throw t; 
    } 
    return retVal; 
} 

}

Es posible que necesite añadir un @Order anotación para asegurar que el consejo es capaz de detectar la excepción (por lo general un @Order (valor = 1) hace el trabajo).También tendrá que añadir el autorproxy aspectj al contexto de aplicaciones:

<aop:aspectj-autoproxy/> 

También es posible que tenga que jugar con los parámetros @Around, en mi caso, era bastante simple como estamos asegurando todo con Preautorizar anotaciones.

Esta es la manera más simple que pude descubrir. Sin embargo, recomiendo encarecidamente a las personas que utilicen la solución sugerida por Boris Kirzner.

Espero que esto sea útil para alguien.

+0

Intenté adaptar esto para utilizarlo en una anotación de PostAuthorize, desafortunadamente no funcionó tan bien, ya que terminé recibiendo "javax.persistence.RollbackException: transacción marcada como rollbackOnly", lo cual es extraño porque lo veo tragándose la excepción , que generalmente es lo que hace que la transacción pase al modo Retroceder. – creechy

Cuestiones relacionadas