I am trying to get a #PreAuthorized annotation on a controller class to work in conjunction with a #PreAuthorized annotation on methods (endpoints) of the same class.
The overview of the class looks something like this:
#RestController
#RequestMapping("/api")
#PreAuthorized("hasRole('CLASS_LEVEL_ROLE')")
public class foo {
#GetMapping("/test")
#PreAuthorized("hasRole('METHOD_LEVEL_ROLE')")
#Timed
public ResponseEntity<String> bar() {
return ResponseEntity.ok().body("entered method successfully");
}
}
Currently what is happening is only the method level annotation is being taken into account.
Ideally what would happen is only users with role 'CLASS_LEVEL_ROLE' and 'METHOD_LEVEL_ROLE' would be allowed access to bar().
I'm aware I could use
#PreAuthorized("hasRole('CLASS_LEVEL_ROLE') and hasRole('METHOD_LEVEL_ROLE')")
but I have some controllers where all endpoints would have to have the same 'CLASS_LEVEL_ROLE' and it would be more convenient to have a generalized class annotation.
#PreAuthorize allows a class level annotation. The way it is supposed to work is that if a method level annotation exists, it will override the class level annotation. You can't do a union of both. So a class level annotation can be seen as a fallback when a method level annotation is not present.
Related
I have a created an annotation that verifies whether certain security aspects are correct.
For example, #RequireClientCertificate, with an Aspect implementation RequireClientCertificateAspect that verifies whether the correct HTTP header is indeed passed in to the Spring REST controller.
This works totally fine, IF the RequireClientCertificateAspect is actually loaded, i.e. if its package is mentioned somewhere in #ComponentScan().
However, if someone forgets to add this package to #ComponentScan, or the aspect is moved to another package, or someone (accidentally) removes the package from #ComponentScan, the aspect bean isn't loaded, and the aspect is completely not applied.
I have this annotation in a common library, shared by several microservices, so it's easy for one of the microservices to accidentally get it wrong. In that case, no checking of the client certificate would be performed.
Question: How can I enforce that, if the #RequireClientCertificate annotation is used, its corresponding Aspect implementation is also loaded?
Simplified usage example:
#Controller
#RequestMapping(value = "/v1.0", produces = MediaType.APPLICATION_JSON_VALUE)
#RequireClientCertificate
public class SomeApiController {
#ResponseBody
#PostMapping("/get-token/")
public ResponseEntity<Token> getToken() {
return ResponseEntity.ok(...get token...);
}
}
Simplified version of the aspect:
#Aspect
#Component
public class RequireClientCertificateAspect {
#Around("execution(* (#RequireClientCertificate *).*(..))")
public Object requireClientCertificateAspectImplementation(ProceedingJoinPoint joinPoint) throws Throwable {
... verify request header ...
try {
return joinPoint.proceed();
finally {
... some other things I need to check ...
}
}
}
Things I've tried/considered:
I can detect 'usage' of the annotation by adding a static field with an initializer to the interface. For example:
#Target({ElementType.TYPE, ElementType.METHOD})
#Retention(RetentionPolicy.RUNTIME)
public #interface RestFactoryGatewaySecurityContext {
static public final boolean dummy = SomeClass.checkAspectIsLoaded();
}
However, such initializers are called very early, and I don't think Spring DI is 'up and running' far enough at that stage that I could even reliably determine whether the aspect bean is loaded.
Another option is to use #Autowired to inject the RequireClientCertificateAspect bean on the main app class explicitly. If somehow the bean isn't on the component scan, this will prevent Spring from instantiating the app.
So that does work, but requires someone to explicitly add this 'dummy' autowire, which in itself is easy to forget, in addition to being a bit 'ugly'.
If you use spring boot you can create your own starter.
Create file META-INF/spring.factories:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.example.MyCustomConfiguration
Then just add any validation you want to your configuration
#Configuration
public class MyCustomConfiguration{
}
You can #Autowired your RequireClientCertificateAspect into it, which will cause error if it isn't defined.
You can create method with #PostConstruct and do any validation you want.
If you went so far as creating custom starter, you can just initialize your bewns there.
More about it you can read here
i've tried in various ways to replicate the #PreAuthorize behaviour, so spel expression with a Method Invocation context:
-I started with configuring httpSecurity in my WebSecurityConfigurerAdapter extending class with an access string written in spel, only to figure out the context was on Filter Invocation , so I had no access on the request body(which i need);
-implementing and adding a custom HandlerInterceptor to the InterceptorRegistry, but again the endpoint arguments were not accessible;
-extending ConfigGlobalMethodSecurity to create a custom expressionHandler, but I seems it is only triggered by Method-level annotations;
Can someone explain me if what i've trying to do is simply impossible or is there a way?
I'd like to have the same evaluationContext as #PreAuthorize, so having access the method arguments(I mean the value they assume) using spel expressions and be able to configure it without having to annotate every single class or method.
EDIT
for reference these are the two annotations i'm using(and they work fine) the use i'm tring to replicate not by annotations but by configuration:
#Target({ElementType.METHOD, ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAnyRole(#privilegeManager.privilegedRoles) or (#privilegeManager.verify(#id, this.getType()))")
public #interface PathVariableRestriction {
}
#Target({ ElementType.METHOD, ElementType.TYPE })
#Retention(RetentionPolicy.RUNTIME)
#PreAuthorize("hasAnyRole(#privilegeManager.privilegedRoles) or #dto.getOwnerId() == #myService.getCurrentId()")
public #interface RequestBodyRestriction {
}
i'd like to authorize request not based on roles but on ids: the id of the object being subject to the crud operation provided either as #PathVariable or #RequestBody(depends on if it is a get, post, put or delete) and the id of the current user retrieved through his Authentication
This is one common use case for #PostAuthorize.
For example if you do:
#PostAuthorize("returnObject.username == authentication.name")
#GetMapping("/resource/{id}")
public MyResource getResource(String id) {
// look up
return myResource;
}
This will only return resources that belong to the currently authenticated user.
This is the recommended route.
able to configure it without having to annotate every single class or method
Alternatively, you can build your own custom authorization method security from scratch using Spring Security's underlying components.
Method Security is built on top of Spring AOP. This means that you can define your own pointcut instead of using Spring Security's annotation-based one. For example, you can do:
#EnableMethodSecurity
class SecurityConfiguration {
#Bean
#Role(BeanDefinition.ROLE_INFRASTRUCTURE)
Advisor myMethodSecurityAdvisor() {
Pointcut pointcut = myCustomPointcut();
AuthorizationManager<MethodInvocationResult> rules = myRules();
AuthorizationManagerAfterMethodInterceptor interceptor =
new AuthorizationManagerAfterMethodInterceptor(
pointcut, rules);
interceptor.setOrder(AuthorizationInterceptorsOrder.
POST_AUTHORIZE.getOrder() + 1);
return interceptor;
}
}
I'm working on a Java EE application which enables logged in users to perform actions on the system depending on their role (such as "administrator" or "guest".)
To do so, I need a simple and elegant way to check whether the currently logged in user is allowed to perform a method without having to put an if statement at the very beginning of each method to check whether the user is privileged.
My idea is to put a custom annotation before methods that should be restricted in usage and to evaluate that annotation when it's called:
#RestrictAccessToUserGroups("admin")
private void doSomethingAwesome() {
// If the currently logged in user is not member
// of "admin" user group, this method doesn't get called
}
To me, this looks like a pretty flexible and elegant way.
Unfortunately I don't know how to trigger a method which gets called the same time doSomethingAwesome() gets called to check whether the called method has annotations and to parse them.
Any idea how to achieve that (without using a third-party library)?
Well then you should look into CDI and Interceptors. Here's a tiny example:
A bean can have multiple annotated implementations that will depend on user roles. First, you define your users annotation:
#Qualifier
#Target({TYPE, METHOD, PARAMETER, FIELD})
#Retention(RUNTIME)
public #interface RestrictAccessToUserGroups {
UserGroup value();
}
public enum UserGroup {
ADMIN,
SECOND_LEVEL
}
Then you can use this annotation in different implementations of a bean:
#RestrictAccessToUserGroups(ADMIN)
#RequestScoped
public class AdminAwesomeBeanImpl implements AwesomeBean{
public void doSomethingAwesome() {
//some stuff
}
}
Then you can inject this bean implementation in a JSF page, a JAX-RS service, etc.
#Inject
#RestrictAccessToUserGroups(ADMIN)
private AwesomeBean awesomeBean;
If you want "to trigger a method which gets called the same time", then what you need are interceptors. Let's say you want to perform a user validation on method invocation (doSomethingAwesome). First you have to define your interceptor annotation binding:
#Inherited
#InterceptorBinding
#Target({TYPE,METHOD})
#Retention(RUNTIME)
public #interface RestrictAccessToUserGroupsValidator {
UserGroup value();
}
Then you implement your interceptor which is going to validate a user on method call:
#RestrictAccessToUserGroups(ADMIN)
#Interceptor
public class AdminValidator {
#AroundInvoke
public Object validate(InvocationContext ctx) throws NoSuchMethodException, Exception{
//some user validation
}
}
Then, you add your interceptor annotation on your business bean:
#RestrictAccessToUserGroupsValidator(ADMIN)
#RestrictAccessToUserGroups(ADMIN)
#RequestScoped
public class AdminAwesomeBeanImpl implements AwesomeBean{
public void doSomethingAwesome() {
//some stuff
}
}
Don't forget to add your interceptor into your beans.xml file:
<interceptors>
<class>the.package.AdminValidator</class>
</interceptors>
I hope this gives you and idea on how would it work. :)
I'm trying to protect a Controller with the #PreAuthorize annotation at type level and try to override that behavior by annotating some methods with a different #PreAuthorize. The Problem is however, that Spring is evaluating the method annotation first (grants access) and is then evaluating the class annotation (denies access).
Is there any way to reverse that order? I couldn't figure it out yet.
Edit:
On the method level, I want to grant access to non-registered Users only:
#PreAuthorize("isAnonymous()")
#RequestMapping(value = "/create", method = RequestMethod.GET)
public String renderCreateEntity(ModelMap model) {
return userService.renderCreateEntity(model);
}
The standard for this Controller however, should be to allow fully authenticated users only:
#Controller
#RequestMapping(value = "/user")
#PreAuthorize("isFullyAuthenticated()")
public class UserController { [...] }
When debug-stepping through the app, I see that isAnonymous() is evaluated first and then isFullyAuthenticated() thus resulting in an grant of access right and immediately denying access again.
Thanks for all your replys.
The answer however, was something totally different :)
I put this here in case anyone else has the same problems.
I registered a custom validator in an #InitBinder annotated method. This binding method is called AFTER the method call requested on the controller. And since this binding method was not annotated with #PreAuthorize, the request was denied.
The solution was to annotate the binding method like this:
#InitBinder
#PreAuthorize("permitAll")
public void initBinder(WebDataBinder binder) {
binder.setValidator(validator);
}
And then, the method calls from my OP evaluated like expected.
The problem is not that you need to change the order of grant and deny. The problem is simple that that method level annotations override the class level annotations.
PrePostAnnotationSecurityMetadataSource Java Doc:
Annotations may be specified on classes or methods, and method-specific annotations will take precedence.
The concrete implementation of this logic is done in the method findAnnotation of class PrePostAnnotationSecurityMetadataSource. (Unfortunately this method is private.)
So you can write your own MethodSecurityMetadataSource, if you have a look at the code of PrePostAnnotationSecurityMetadataSource, you will see how easy it is.
But one warning at the end: the end: difficult task is not rewriting the method, the difficult task is to "inject" the new MethodSecurityMetadataSource into the security system. I belive you can not do it with the spring security namespace configuration, so you need to replace spring security namespace by explicit bean declaration.
It seems logical to secure an entire class of controllers rather than each method. Can I do this:
#Controller
#Secured("ROLE_USER")
public class accountPages {
//Controllers
}
from Spring Security 3 - PACKT Publishing
Be aware that the method-level security
annotations can also be applied at the
class level as well! Method-level
annotations, if supplied, will always
override annotations specified at the
class level