I have a web application configured to use Spring Security 3.2 in standard way.
I'm using the @PreAuthorize
annotation to secure the Controllers method.
Now, I would like to deny access to each controller method UNLESS it is annotated with @PreAuthorize
.
I have tried the following approaches:
super controller
Each controller extends from a super controller annotated with: @PreAutorize("denyAll")
. This approach doesn't seem to work because the controllers' methods annotations are ignored. Everything is forbidden.
@PreAutorize("denyAll")
public class SuperController {
}
public class MyController extends SuperController {
@PreAuthorize("hasRole('SUPERHERO')")
@RequestMapping(value = URL_PREFIX + "Add.do", method = RequestMethod.GET)
public String doStuff(Model model) {
...
}
}
aop
Using a pointcut expression in the global method security tag
<global-method-security pre-post-annotations="enabled">
<protect-pointcut expression="execution(* com.acme.*Controller.*(..))" access="denyAll" />
</global-method-security>
This approach also fails: controllers's methods which are not annotated are still accessible.
The @PreAuthorize annotation checks the given expression before entering the method, whereas the @PostAuthorize annotation verifies it after the execution of the method and could alter the result.
Method-level security is implemented by placing the @PreAuthorize annotation on controller methods (actually one of a set of annotations available, but the most commonly used). This annotation contains a Spring Expression Language (SpEL) snippet that is assessed to determine if the request should be authenticated.
Spring Security provides method level security using @PreAuthorize and @PostAuthorize annotations. This is expression-based access control. The @PreAuthorize can check for authorization before entering into method. The @PreAuthorize authorizes on the basis of role or the argument which is passed to the method.
The difference between @Secured and @PreAuthorize are as follows : The main difference between @Secured and @PreAuthorize is that @PreAuthorize can work with Spring EL. We can access methods and properties of SecurityExpressionRoot while using @PreAuthorize but not with @Secured.
I'm answering my own question here.
I've solved the problem by using a HandlerInterceptorAdapter.
I'm not sure it is the most Spring-idiomatic way to achieve the result, but it's good enough for me.
public class MvcPreAuthorizeAnnotationCheckerInterceptor extends HandlerInterceptorAdapter {
final HandlerMethod hm;
if (handler instanceof HandlerMethod) {
hm = (HandlerMethod) handler;
PreAuthorize annotation = hm.getMethodAnnotation(PreAuthorize.class);
if (annotation == null) {
// check if the class is annotated...
annotation = hm.getMethod().getDeclaringClass().getAnnotation(PreAuthorize.class);
if (annotation == null) {
// add logging
// or send a NON AUTHORIZED
response.sendRedirect(request.getContextPath());
}
}
return true;
}
}
And in the Spring config:
<mvc:interceptors>
<beans:ref bean="mvcPreAuthorizeAnnotationCheckerInterceptor"/>
</mvc:interceptors>
<beans:bean id="mvcPreAuthorizeAnnotationCheckerInterceptor" class="com.acme.MvcPreAuthorizeAnnotationCheckerInterceptor"/>
I came up with a similar approach, but its not executed for every request but just extends the ConfigAttribute
's for the method:
A minor drawback could be that it does not allow for easy logging or whatever the big benefit is that it follows the same deny behavior as other not allowed endpoints.
SecurityConfig:
@Configuration
@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfiguration extends GlobalMethodSecurityConfiguration {
@Override
protected MethodSecurityMetadataSource customMethodSecurityMetadataSource() {
return new CustomPermissionAllowedMethodSecurityMetadataSource();
}
}
MetadataSource:
public class CustomPermissionAllowedMethodSecurityMetadataSource extends AbstractFallbackMethodSecurityMetadataSource {
@Override
protected Collection<ConfigAttribute> findAttributes(Class<?> clazz) {
return null;
}
@Override
protected Collection<ConfigAttribute> findAttributes(Method method, Class<?> targetClass) {
Annotation[] annotations = AnnotationUtils.getAnnotations(method);
List<ConfigAttribute> attributes = new ArrayList<>();
// if the class itself is annotated as a @Controller we should by default deny access to every method
if (AnnotationUtils.findAnnotation(targetClass, Controller.class) != null) {
attributes.add(DENY_ALL_ATTRIBUTE);
}
if (annotations != null) {
for (Annotation a : annotations) {
// but not if the method has at least a PreAuthorize or PostAuthorize annotation
if (a instanceof PreAuthorize || a instanceof PostAuthorize) {
return null;
}
}
}
return attributes;
}
@Override
public Collection<ConfigAttribute> getAllConfigAttributes() {
return null;
}
}
I also wrote a small article about this, with some further background: https://www.baeldung.com/spring-deny-access
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