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. :)
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 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.
I'm new to Spring AOP (and AOP in general), need to implement the following:
#HasPermission(operation=SecurityOperation.ACTIVITY_EDIT, object="#act")
public Activity updateActivity(Activity act)
{
...
}
#HasPermission is my custom annotation, which will be used to mark all methods requiring pre-authorization. I'm using my custom implementation of security checks based on Apache Shiro. Generally, I guess that I will need to define pointcut which matches all annotated methods and also provide implementation of the aspect (either before or around).
Questions I have are re. aspect implementation.
How do I extract operation and object parameters from the annotation?
How can I resolve SpEL expression in object definition and get object passed as 'act' parameter?
I know it's a late answer but after we were migrating some JavaEE project to Spring we made some basic security model based on AspectJ:
Firstly we annotate our service methods with custom #OperationAuthorization :
#OperationAuthorization
public ListOfUserGroupsTo getUserGroupsByClientId(Integer clientId) throws GenericException {
return userGroupRepository.getAllUserGroupsForClient(clientId);
}
Then we have a class with #Aspect & #Component annotations which intercepts methods with specific annotations:
#Aspect
#Component
public class AuthorizationAspect {
#Autowired
AuthorizationService authorizationService;
#Before(value = "#annotation(ch.avelon.alcedo.authorization.annotations.OperationAuthorization)")
public void before(JoinPoint joinPoint) throws Throwable {
Object[] args = joinPoint.getArgs();
Method method = ((MethodSignature) joinPoint.getSignature()).getMethod();
authorizationService.checkOperationAuthorization(method, args);
}
In AuthorizationService a method with all arguments are passed. Check whether the client is authorized to get user groups. If it's not: throw our Exception and method stops.
I'm trying to implement a #Restricted annotation, to secure controller methods in a way that users can only access them, when they are logged in and have a certain role. I'm on Tomcat 7 using JSF and CDI, so no EJB. The interceptor gets called as long as the annotation interface does not specify any parameters. As soon as I add a #Nonbinding Role value() default Role.ADMIN; parameter, neither the interceptor nor the controller method execute. No errors or exceptions either. Here is my code, I really don't know what's wrong with it:
Annotation:
#InterceptorBinding
#Retention(RetentionPolicy.RUNTIME)
#Target({ ElementType.TYPE, ElementType.METHOD })
public #interface Restricted {
#Nonbinding Role value() default Role.ADMIN; // ###
}
Interceptor:
#Interceptor
#Restricted
public class RoleBasedRestrictingInterceptor implements Serializable {
#Inject
ISecurityManager security;
#AroundInvoke
public Object intercept(final InvocationContext ctx) throws Exception {
final Restricted annotation = ctx.getClass().getAnnotation(Restricted.class);
log.info("Intercepted, required role is: {}", annotation.value()); // ###
log.info("User is logged in: {}", security.isLoggedIn());
return ctx.proceed();
}
}
Controller:
#Named("manageUsers")
#SessionScoped
public class ManageUsersBacking extends implements Serializable {
#Restricted(Role.ADMIN) // ###
public void testRestricted() {
log.info("testRestricted()");
}
}
The ### occurrences mark what has to be changed or removed to make it work again. The interceptor is properly defined in WEB-INF/beans.xml, since it works without the role parameter in my annotation.
16:04:33.772 [http-apr-8080-exec-11] INFO c.m.s.RoleBasedRestrictingInterceptor - User is logged in: true
16:04:33.772 [http-apr-8080-exec-11] INFO c.m.c.admin.ManageUsersBacking - testRestricted()
Today I revisited this particular problem and noticed it had nothing to do with CDI:
ctx.getClass().getAnnotation(Restricted.class)
Obviously, there is no class level annotation in my example. So getAnnotation() returns null. Instead I should have used the following:
ctx.getMethod().getAnnotation(Restricted.class)
Though I don't know why there where no exceptions whatsoever. Maybe some other things were going on, that I can no longer reproduce because I migrated my application to TomEE.
if you switch to TomEE you'll don't need to depend (maven) on implementations, just api (use org.apache.openejb:javaee-api:6.0-4 with a provided scope
It sounds like you have things setup correct (beans.xml and interceptor). Which CDI implementation are you using? If you're using Tomcat have you looked at using TomEE?