We are using Spring Security 3. We have a custom implementation of PermissionEvaluator that has this complex algorithm to grant or deny access at method level on the application. To do that we add a @PreAuthorize annotation to the method we want to protect (obviously). Everything is fine on that. However the behavior that we are looking for is that if a hasPermission call is denied, the protected method call only needs to be skipped, instead we are getting a 403 error each time that happens.
Any ideas how to prevent that?
You can find a different explanation of the problem here; AccessDeniedException handling during methodSecurityInterception
The solution is to use custom MethodSecurityInterceptor
, which calls the AccessDecisionManager
(implicitly, bu calling super's method) and decides than whether to proceed with a method call.
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;
}
}
Setting up the app context is a bit tricky: since you can not use <sec:global-mathod-security>
in this case, there is a need to define an explicit AOP configuration (and create most of the corresponding bean structure the original tag does by default):
<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 -->
<!-- ........................................... -->
This is the code for the advice solution I implemented.
This is the Aspect code:
@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;
}
}
You may need to add a @Order annotation to ensure that the advice is able to catch the exception (usually a @Order(value=1) does the work). Also you'll need to add the aspectj autorproxy to the App context:
<aop:aspectj-autoproxy/>
You may also need to play around with the @Around parameters, In my case it was pretty simple as we are securing everything with PreAuthorize annotations.
This the simplest way I could figure out. However, I strongly recommend people to use the solution suggested by Boris Kirzner.
Hope this is helpful to someone.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With