Can Spring Security use @PreAuthorize on Spring controllers methods?
Yes, it works fine.
You need <security:global-method-security pre-post-annotations="enabled" />
in ...-servlet.xml
. It also requires CGLIB proxies, so either your controllers shouldn't have interfaces, or you should use proxy-target-class = true
.
Spring Security @PreAuthorize on controllers
You cannot do that because ant matchers and @PreAuthorize
work at different level.
The ant matchers works at http security level. Spring security filter looks at the request, and if it find that access should be denied, it does not even pass the request to the dispatcher servlet, and directly send a 403 error.
PreAuthorize
work at method level. When a method is about to be called, an AOP proxy controls if the access should be allowed. So the 2 authorizations level are chained, instead of the second overriding the first.
Anyway, I strongly advice you not to use @PreAuthorize("hasRole('ADMIN')")
on a controller :
- it can easily be done with a simple ant matcher
- it forces you to allow proxying on a controller, either with class proxying instead of JDK proxying or by using interfaces for controllers
IMHO, @PreAuthorize
is best suited at service level, because you can mix domain objects with user granted authorities to get fine grained authorizations.
With which methods does @Secured and @PreAuthorize annotation work?
No, not because it's private, but because Spring-Security is based on Spring-AOP.
On Spring-AOP, the call between methods that are in the same classes won't call aspects.
With @Secured annotation, a test is made before the method. If the user hasn't the right roles, an exception is thrown.
@PreAuthorize is practically the same, except it allows more advanced behavior.
You can also configure security using WebSecurityConfigurerAdapter.
And do not forget to enable the Pre/post annotations with @EnableGlobalMethodSecurity(prePostEnabled = true)
Spring Security: @PreAuthorize works only together with @RequestMapping
In the second example the test
method is being called directly from the handleRequest
method. Spring has no mechanism to intercept method calls from with in the same class. Thus, the Proxy / AOP method inception for @PreAutorize
is never invoked.
More on the topic of Spring Proxy
Using @PreAuthorize annotation on a method within a controller’s method
When you call methodController() directly from exampleForMethodPreAuthorize(), the call if not invoked through a proxy. Only proxy calls are enhanced with annotated behaviour.
Credits to this answer: https://stackoverflow.com/a/28168213/1849366. It also suggests two workarounds.
Option 1: Moving methodController() method to another bean.
Option 2: Call the method methodController() through a proxy using as follows.
@GetMapping(value= "/method")
public String exampleForMethodPreAuthorize() {
if(context.getBean(MyController.class).methodController()){
return "forMethodPreAuthorize";
}
else return null;
}
Spring Security: Deny access to controller methods, if @PreAuthorize annotation is missing
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"/>
PreAuthorize not working on Controller
A common problem with using PrePost annotations on controllers is that Spring method security is based on Spring AOP, which is by default implemented with JDK proxies.
That means that it works fine on the service layer which is injected in controller layer as interfaces, but it is ignored on controller layer because controller generally do not implement interfaces.
The following is just my opinion:
- prefered way: move the pre post annotation on service layer
- if you cannot (or do not want to), try to have your controller implement an interface containing all the annotated methods
- as a last way, use proxy-target-class=true
Related Topics
Deciphering Variable Information While Debugging Java
Is It Better Practice to Use String.Format Over String Concatenation in Java
How to Get a Spring Bean in a Servlet Filter
Port of Random Generator from C to Java
Why Does Autoreconnect=True Not Seem to Work
Firestore Query Documents Startswith a String
Keylistener, Keypressed Versus Keytyped
How to Quit a Java App from Within the Program
Which Concurrent Queue Implementation Should I Use in Java
Why Isn't a Qualified Static Final Variable Allowed in a Static Initialization Block
Why Do We Have to Override the Equals() Method in Java
How to Format Localdate to String
How to Use Conditional Breakpoint in Eclipse
How to Serialize a List in Java
The Concatenation of Chars to Form a String Gives Different Results
Why Does "Split" on an Empty String Return a Non-Empty Array
Performance Considerations for Keyset() and Entryset() of Map