Hint: If you are using features described by this page and the CDI container you are using is Weld (or OpenWebBeans in BDA mode), you have to enable the security interceptor in your beans.xml file:
<beans> <interceptors> <class>org.apache.deltaspike.security.impl.extension.SecurityInterceptor</class> </interceptors> </beans>
This feature of the security module functions by intercepting method calls, and performing a security check before invocation is allowed to proceed.
In order to use the DeltaSpike security module, you must first have installed the proper dependencies into your POM file. Once this is complete, you may proceed to create a security parameter binding annotation. This is what we will use to add security behavior to our business classes and methods.
Create the SecurityBinding:
@Retention(value = RUNTIME) @Target({TYPE, METHOD}) @Documented @SecurityBindingType public @interface CustomSecurityBinding { }
Next, we must define an Authorizer class to implement behavior for our custom SecurityBindingType. This class is simply a CDI bean which declares a @Secures method, qualified with the security binding annotation we created in the first step.
This method has access to the InvocationContext of the method call, so if we need to access parameter arguments, we can do so using the given context. Note that we may also inject other beans into the parameter list of our @Secures method.
Create the Authorizer:
@ApplicationScoped public class CustomAuthorizer { @Secures @CustomSecurityBinding public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, @LoggedIn User user) throws Exception { return user.isLoggedIn(); // perform security check } }
We can then use our new annotation to secure business or bean methods. This binding annotation may be placed on the entire class (securing all methods,) or on individual methods that you wish to secure.
Secure a bean method:
@ApplicationScoped public class SecuredBean1 { @CustomSecurityBinding public void doSomething(Thing thing) { thing.doSomething(); } }
Next, we may access parameter values from the method invocation directly in our authorizer bean by creating custom @SecurityParameterBinding types; this is a simple step once we have completed the work above:
Create a parameter binding annotation:
@Retention(value = RUNTIME) @Target({PARAMETER}) @Documented @SecurityParameterBinding public @interface CurrentThing { }
Now, when a secured method is invoked, we can inject actual parameter values as arguments into our authorizer method, providing domain-level security in our applications:
Update the Authorizer to use parameter binding:
@ApplicationScoped public class CustomAuthorizer { @Secures @CustomSecurityBinding public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, @LoggedIn User user, @CurrentThing Thing thing) throws Exception { return thing.hasMember(user); // perform security check against our method parameter } }
Note that our business method must also be annotated.
Complete the parameter binding:
@ApplicationScoped public class SecuredBean1 { @CustomSecurityBinding public void doSomething(@CurrentThing Thing thing) { thing.doSomething(); } }
Our method is now secured, and we are able to use given parameter values as part of our security authorizer!
There may be cases where you may want to base your authorization logic on the result of the secured method and do the security check after the method invocation. Just use the same security binding type for that case:
@ApplicationScoped public class SecuredBean1 { @CustomSecurityBinding public Thing loadSomething() { return thingLoader.load(); } }
Now you need to access the return value in the authorizer method. You can inject it using the @SecuredReturn annotation. Update the Authorizer to use a secured return value:
@ApplicationScoped public class CustomAuthorizer { @Secures @CustomSecurityBinding public boolean doSecuredCheck(@SecuredReturn Thing thing, @LoggedIn User user) throws Exception { return thing.hasMember(user); // perform security check against the return value }
Now the authorization will take place after the method invocation using the return value of the business method.
Complete the parameter binding:
@ApplicationScoped public class SecuredBean1 { @CustomSecurityBinding public void doSomething(@CurrentThing Thing thing) { thing.doSomething(); } }
Our method is now secured, and we are able to use given parameter values as part of our security authorizer!
@Secured is build on @SecurityBindingType and a very simple alternative to the rest of the security module.
It's a basic hook to integrate a custom security concept, 3rd party frameworks,... . It doesn't provide a full blown security concept like the rest of the security module, but other DeltaSpike modules ensure that the security concepts are integrated properly (e.g. correct behaviour within custom scope implementations,...). It just allows to integrate other security frameworks easily.
(In MyFaces CODI it was originally a CDI interceptor. This part changed a bit, because between the interceptor and @Secured is the @SecurityBindingType concept which triggers @Secured as on possible approach. Therefore the basic behaviour remains the same and you can think about it like an interceptor.)
Securing all intercepted methods of a CDI bean:
//... @Secured(CustomAccessDecisionVoter.class) public class SecuredBean { //... }
or
Securing specific methods:
//... public class SecuredBean { @Secured(CustomAccessDecisionVoter.class) public String getResult() { //... } }
This interface is (besides the Secured annotation) the most important part of the concept. Both artifact types are also the only required parts:
public class CustomAccessDecisionVoter implements AccessDecisionVoter { @Override public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext accessDecisionVoterContext) { Method method = accessDecisionVoterContext.<InvocationContext>getSource().getMethod(); //... } }
[TODO] hint about the changed parameter/s
In case of a detected violation a SecurityViolation has to be added to the result returned by the AccessDecisionVoter.
[TODO] AbstractAccessDecisionVoter
If there are multiple AccessDecisionVoter and maybe in different constellations, it's easier to provide an expressive CDI stereotypes for it. Later on that also allows to change the behaviour in a central place.
Stereotype support of @Secured:
@Named @Admin public class MyBean implements Serializable { //... } //... @Stereotype @Secured(RoleAccessDecisionVoter.class) public @interface Admin { }
Furthermore, it's possible to provide custom meta-data easily.
Stereotype of @Secured with custom meta-data:
@Named @Admin(securityLevel=3) public class MyBean implements Serializable { //... } //... @Stereotype @Secured(RoleAccessDecisionVoter.class) public @interface Admin { int securityLevel(); } @ApplicationScoped public class RoleAccessDecisionVoter implements AccessDecisionVoter { private static final long serialVersionUID = -8007511215776345835L; public Set<SecurityViolation> checkPermission(AccessDecisionVoterContext voterContext) { Admin admin = voterContext.getMetaDataFor(Admin.class.getName(), Admin.class); int level = admin.securityLevel(); //... } }
[TODO]
[TODO]