Security Module

Overview

The Security module provides APIs for authorization of method invocations.

There are two different APIs provided for two different approaches — one simple interceptor-style API and another for more complex scenarios.

  • Simple interceptor-style API: the method that is to be secured is loosely coupled to a predicate method (called authorizer method) which decides whether the secured method invocation should proceed. Similarly to CDI interceptors, the secured method and the authorizer are tied together using a binding annotation — @SecurityBindingType in this case.

  • Advanced API: this API offers fine-grained control over the authorization process. Multiple independent voters can participate in making the authorization decision and possibly return security violations and thus prevent the method invocation. The voters share a common context. This API is suitable for integration with third-party security frameworks. Also, this API can be used to secure JSF view access when using the DeltaSpike JSF module.

Project Setup

The configuration information provided here is for Maven-based projects and it assumes that you have already declared the DeltaSpike version and DeltaSpike Core module for your projects, as detailed in Configure DeltaSpike in Your Projects. For Maven-independent projects, see Configure DeltaSpike in Maven-independent Projects.

1. Declare Security Module Dependencies

Add the Security module to the list of dependencies in the project pom.xml file using this code snippet:

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-security-module-api</artifactId>
    <version>${deltaspike.version}</version>
    <scope>compile</scope>
</dependency>

<dependency>
    <groupId>org.apache.deltaspike.modules</groupId>
    <artifactId>deltaspike-security-module-impl</artifactId>
    <version>${deltaspike.version}</version>
    <scope>runtime</scope>
</dependency>

Or if you’re using Gradle, add these dependencies to your build.gradle:

     runtime 'org.apache.deltaspike.modules:deltaspike-security-module-impl'
     compile 'org.apache.deltaspike.modules:deltaspike-security-module-api'

2. Enable the SecurityInterceptor

For CDI 1.0 (or DeltaSpike v1.1.0 and earlier together with CDI 1.1+), you must enable the security interceptor in the project beans.xml file:

<beans>
    <!-- Not needed with CDI 1.1+ and DeltaSpike v1.1.1+ -->
    <interceptors>
        <class>org.apache.deltaspike.security.impl.extension.SecurityInterceptor</class>
    </interceptors>
</beans>

Simple interceptor-style authorization

This feature of the Security module intercepts method calls and performs a security check before invocation is allowed to proceed.

The first piece of code required to use this API is a security binding annotation. This is what we will use to add security behavior to our business classes and methods.

Create the security binding annotation
@Retention(value = RUNTIME)
@Target({TYPE, METHOD})
@Documented
@SecurityBindingType
public @interface UserLoggedIn {}

Next, we must define an authorizer class to implement behavior for our custom security binding type. This class is simply a CDI bean which declares a method annotated @Secures, 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 authorizer method.

Create the authorizer
@ApplicationScoped
public class LoggedInAuthorizer
{
    @Secures
    @UserLoggedIn
    public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, Identity identity) throws Exception
    {
        return identity.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
{
    @UserLoggedIn
    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
    @UserLoggedIn
    public boolean doSecuredCheck(InvocationContext invocationContext, BeanManager manager, Identity identity, @CurrentThing Thing thing) throws Exception
    {
        return thing.hasMember(identity); // perform security check against our method parameter
    }
}

Note that our business method must also be annotated.

Complete the Parameter Binding
@ApplicationScoped
public class SecuredBean1
{
    @UserLoggedIn
    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
{
    @UserLoggedIn
    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
    @UserLoggedIn
    public boolean doSecuredCheck(@SecuredReturn Thing thing, Identity identity) throws Exception
    {
        return thing.hasMember(identity); // 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.

Advanced authorization

This is an alternative to the simple annotation-based interceptor-style API. This API uses the annotation @Secured and is mainly a hook for integration of custom security concepts and third-party frameworks. The DeltaSpike Security module is not a full application security solution, but some of the other DeltaSpike modules are security-enabled and use this API (e.g. correct behaviour within custom scope implementations,…​). Internally, this @Secured API uses the @Secures/@SecurityBindingType API.

(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.)

The entry point to this API is the @Secured annotation placed either on the whole class — enabling security for all methods — or on individual methods. The only other prerequisite is at least one AccessDecisionVoter implementation, explained in the next section.

Securing All Intercepted Methods of a CDI Bean
//...
@Secured(CustomAccessDecisionVoter.class)
public class SecuredBean
{
    //...
}
Securing Specific Methods
//...
public class SecuredBean
{
    @Secured(CustomAccessDecisionVoter.class)
    public String getResult()
    {
        //...
    }
}

AccessDecisionVoter

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();

        //...
    }
}

SecurityViolation

In case of a detected violation a SecurityViolation has to be added to the result returned by the AccessDecisionVoter.

AbstractAccessDecisionVoter

You can also implement the abstract class AbstractAccessDecisionVoter. This is a convenience class which allows an easier usage:

public class CustomAccessDecisionVoter extends AbstractAccessDecisionVoter
{

    @Override
    protected void checkPermission(AccessDecisionVoterContext accessDecisionVoterContext,
            Set<SecurityViolation> violations)
    {
        // check for violations
        violations.add(newSecurityViolation("access not allowed due to ..."));
    }
}

@Secured and stereotypes with custom metadata

If there are multiple AccessDecisionVoter and maybe in different constellations, it is 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 is possible to provide custom metadata easily.

Stereotype of @Secured with custom metadata
@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();
        //...
    }
}

AccessDecisionVoterContext

Because the AccessDecisionVoter can be chained, AccessDecisionVoterContext allows to get the current state as well as the results of the security check.

There are several methods that can be useful

  • getState() - Exposes the current state : INITIAL, VOTE_IN_PROGRESS, VIOLATION_FOUND, NO_VIOLATION_FOUND

  • getViolations() - Exposes the found violations

  • getSource() - Exposes, for example, the current instance of javax.interceptor.InvocationContext in combination with @Secured used as interceptor.

  • getMetaData() - Exposes the found meta-data, for example the view-config-class if @Secured is used in combination with type-safe view-configs

  • getMetaDataFor(String, Class<T>) - Exposes meta-data for the given key

SecurityStrategy SPI

The SecurityStrategy interface allows to provide a custom implementation which should be used for @Secured. Provide a custom implementation as bean-class in combination with @Alternative or @Specializes (or as global-alternative).

In case of global-alternatives an additional configuration needs to be added to /META-INF/apache-deltaspike.properties.

Example
globalAlternatives.org.apache.deltaspike.security.spi.authorization.SecurityStrategy=mypackage.CustomSecurityStrategy
The configuration for global alternatives is following the pattern: globalAlternatives.<interface-name>=<implementation-class-name>

Examples

Redirect to requested page after login

DeltaSpike can be combined with pure CDI or with any other security frameworks (like PicketLink) to track the denied page and make it available after user logs in.

An example of this use case is available in the examples module in the DeltaSpike repository:

The relevant classes are AuthenticationListener and LoggedInAccessDecisionVoter.