Tuesday, March 13, 2012

 

Java EE 6 Security


Every time that a new major release of the JBoss application server comes out it is always a challenge to see how the new security framework features could fit our needs for the construction of highly secured web applications. In this article I will walk you through every layer of your application and show you how to enable the new security features that JBoss AS 7.1 brings.

Securing the view pages

It all starts with securing the JSF pages of your web application. This can easily be done using JBoss Seam 3.1. The first thing that you want to do is to define the roles that authenticated users can have within your system. In this example we construct a simple @User role as follows.

import org.jboss.seam.security.annotations.SecurityBindingType;
import org.jboss.seam.faces.security.RestrictAtPhase;
import org.jboss.seam.faces.event.PhaseIdType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.annotation.ElementType;

@SecurityBindingType
@RestrictAtPhase(PhaseIdType.RESTORE_VIEW)
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD, ElementType.TYPE})
public @interface User {
}

Next we instruct Seam which pages should be protected under the custom defined role. For this we use JBoss Seam 3.1 Faces. In the following example we protect pages under /user/ so that they require an authenticated and @User authorized user.

import org.jboss.seam.faces.view.config.ViewConfig;
import org.jboss.seam.faces.view.config.ViewPattern;
import org.jboss.seam.faces.security.LoginView;
import org.jboss.seam.faces.security.AccessDeniedView;

@ViewConfig
public interface MyViewConfig {

    static enum MyPages {

        @ViewPattern("/user/*")
        @User
        USER_PAGES,

        @ViewPattern("/*")
        @LoginView("/login.xhtml")
        @AccessDeniedView("/denied.xhtml")
        ALL
    }
}

Via the login.xhtml page you somehow gather the user's credentials within the PicketBox credentials bean. The #{identity.login} action will trigger the Seam Security framework to authenticate the user. This requires an authenticator component to be present. The authenticator will simply use the credentials and set the identity after authenticating the user. Here you probably want to use some EJB3 component defined within your model to perform the actual authentication/authorization. In the following example this is done via the MyAuthenticationBean EJB3 session bean.

import org.jboss.seam.security.BaseAuthenticator;
import org.jboss.seam.security.Authenticator;
import javax.inject.Inject;
import org.jboss.seam.security.Credentials;
import org.jboss.seam.security.Identity;
import javax.ejb.EJB;
import java.util.Set;

public class MyAuthenticator extends BaseAuthenticator implements Authenticator {

    @Inject
    private Credentials credentials;

    @Inject
    private Identity identity;

    @EJB
    private MyAuthenticationBean myAuthenticationBean;

    @Override
    public void authenticate() {
        String username = this.credentials.getUsername();
        ... credential = credentials.getCredential();
        Set<String> roles = this.myAuthenticationBean.authenticate(username, credential);
        if (null == roles) {
            setStatus(AuthenticationStatus.FAILURE);
            return;
        }
        for (String role : roles) {
            this.identity.addRole(role, "USERS", "GROUP");
        }
        setUser(new SimpleUser(username));
        setStatus(AuthenticationStatus.SUCCESS);
    }
}

As you can see the authenticator also adds the roles to the identity.

Securing the controllers

The CDI controllers can now simply use the custom @User annotations to protect their methods,
...

import javax.inject.Named;

@Named
public class MyController {

    @User
    public String myAction() {
        ...
        return "...";
    }
}

Of course we still need to tell Seam Security how to map from the roles to the custom defined @User role annotation. For this we define a new CDI component that manages the actual authorizations.

import org.jboss.seam.security.Identity;
import org.jboss.seam.security.annotations.Secures;

public class MyAuthorization {

    @Secures
    @User
    public boolean authorizeUser(Identity identity) {
        return identity.hasRole("user", "USERS", "GROUP");
    }
}

So whenever Seam (CDI) needs a @User authorization, it will call the authorizeUser producer method.

Securing the model

For most applications you want to have the EJB3 business logic completely separate from the view/controllers. For example, because you have multiple web applications, or you want to have different interfaces towards the business logic (web application, SOAP web services, JSON). Eventually you want to have the same notion of authenticated/authorized users within your model's EJB3 session beans. An important design principle here is that you never can trust the outer layers of your application's architecture. So you want to have the model to re-verify the user's credentials. Or in a more advanced scheme you can generate a custom token (some HMAC or so) as part of the call to MyAuthenticationBean that the view can use afterwards towards the model. This is where we need to activate some security domain on your EJB session beans. First of all we have to propagate the user credentials towards the model. For this we need to define a custom security domain within the JBoss AS 7.1 configuration.

<subsystem xmlns="urn:jboss:domain:security:1.1">
    ...
    <security-domains>
        ...
        <security-domain name="my-security-domain-client" cache-type="default">
            <authentication>
                <login-module code="org.jboss.security.ClientLoginModule" flag="required">
                    <module-option name="multi-threaded" value="true"/>
                    <module-option name="restore-login-identity" value="true"/>
                </login-module>
            </authentication>
        </security-domain>
    </security-domains>
</subsystem>

Via the ClientLoginModule you can pass the user's credentials from the servlet container to the EJB3 container within the JBoss application server. There are several ways possible to login into the my-security-domain-client security domain. One method is to use a custom servlet filter as shown in the following example.

import javax.servlet.annotation.WebFilter;
import javax.servlet.*;
import javax.inject.Inject;
import org.jboss.seam.security.Identity;
import javax.security.auth.login.LoginContext;
import javax.security.auth.login.LoginException;

@WebFilter(urlPatterns = "/*")
public class LoginFilter implements Filter {

    @Inject
    private Identity identity;

    @Override
    public void init(FilterConfig filterConfig) throws ServletException {
    }

    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain)
        throws IOException, ServletException {
        LoginContext loginContext;
        if (this.identity.isLoggedIn()) {
            ... user = (...) this.identity.getUser();
            UsernamePasswordHandler usernamePasswordHandler = new UsernamePasswordHandler(user.getId(), user.getCredential());
            try {
                loginContext = new LoginContext("my-security-domain-client", usernamePasswordHandler);
                loginContext.login();
            } catch (LoginException e) {
                throw new ServletException(e.getMessage());
            }
        } else {
            loginContext = null;
        }
        try {
            filterChain.doFilter(servletRequest, servletResponse);
        } finally {
            if (null != loginContext) {
                try {
                    loginContext.logout();
                } catch (LoginException e) {
                    throw new ServletException(e.getMessage());
                }
            }
        }
    }

    @Override
    public void destroy() {
    }
}

Now that the front-end performed a JAAS based authentication we need to use the passed credentials somehow within a security domain dedicated to our EJB3 model. For this we define a custom JAAS login module, which we configure again within the JBoss AS as follows.

<subsystem xmlns="urn:jboss:domain:security:1.1">
    ...
    <security-domains>
        ...
        <security-domain name="my-security-domain" cache-type="default">
            <authentication>
                <login-module code="my.package.MyLoginModule" flag="required"/>
            </authentication>
        </security-domain>
    </security-domains>
</subsystem>

Despite the fact that we defined the security domain globally, the custom JAAS login module can live within our EJB3 model itself. This custom JAAS login module looks as follows.

import javax.security.auth.spi.LoginModule;
import javax.security.auth.callback.CallbackHandler;
import javax.security.auth.Subject;
import javax.security.auth.login.LoginException;
import javax.security.auth.callback.NameCallback;
import javax.security.auth.callback.PasswordCallback;
import javax.security.auth.callback.Callback;
import org.jboss.security.SimplePrincipal;
import org.jboss.security.SimpleGroup;

public class MyLoginModule implements LoginModule {

    private CallbackHandler callbackHandler;

    private Subject subject;

    private String authenticatedUsername;

    private Set<String> authorizedRoles;

    @Override
    public void initialize(Subject subject, CallbackHandler callbackHandler,
        Map<String, ?> sharedState, Map<String, ?> options) {
        this.subject = subject;
        this.callbackHandler = callbackHandler;
    }

    @Override
    public boolean login() throws LoginException {
        NameCallback nameCallback = new NameCallback("username");
        PasswordCallback passwordCallback = new PasswordCallback("password", false);
        Callback[] callbacks = new Callback[]{nameCallback, passwordCallback};
        try {
            this.callbackHandler.handle(callbacks);
        } catch (Exception e) {
            throw new LoginException(e.getMessage());
        }
        String username = nameCallback.getName();
        byte[] credential = passwordCallback.getPassword();
        Set<String> roles = ... retrieve via some EJB3 session bean via InitialContext ...
        if (null == roles) {
            throw new LoginException("invalid login");
        }
        this.authenticatedUsername = username;
        this.authorizedRoles = roles;
        return true;
    }

    @Override
    public boolean commit() throws LoginException {
        if (null != this.authenticatedUsername) {
            this.subject.getPrincipals().add(new SimplePrincipal(this.authenticatedUsername));
            SimpleGroup rolesGroup = new SimpleGroup("Roles");
            for (String role : this.authorizedRoles) {
                rolesGroup.add(new SimplePrincipal(role));
            }
            this.subject.getPrincipals().add(rolesGroup);
        }
        return true;
    }

    @Override
    public boolean logout() throws LoginException {
        this.subject.getPrincipals().clear();
        return true;
    }

    ...
}

As you can see a JAAS login module is using a two-phase commit design. The task here is to populate the subject using the callbackHandler. During the login we authenticate/authorize the user. This operation may fail if the user's credentials are invalid for example. During the commit we simple commit the authentication transaction. In JBoss AS the EJB3 roles are passed via a Group which is named Roles.

Now we can finally use our security domain to protected the EJB3 session beans within our model. An example of such a protected session bean is given below.

import javax.ejb.Stateless;
import org.jboss.ejb3.annotation.SecurityDomain;
import javax.annotation.security.RolesAllowed;

@Stateless
@SecurityDomain("my-security-domain")
public class MyProtectedBean {

    @RolesAllowed("my-model-role");
    public void myProtectedBusinessMethod() {
    }
}

You can get the caller principal within the session beans via the SessionContext as shown in the following example code.

import javax.annotation.Resource;
import javax.ejb.SessionContext;
import java.security.Principal;

    ...
    @Resource
    private SessionContext sessionContext;

    ... {
        Principal callerPrincipal = this.sessionContext.getCallerPrincipal();
        String callerName = callerPrincipal.getName();
        ...
    }


Context aware input validation


Since Bean Validation is part of Java EE 6, input validation never has been easier before. The only thing that you have to keep in mind is that you have to do the activation of the Bean Validation yourself. As we're interested in input validation on our model session beans, we have to use a custom EJB3 interceptor to activate the Bean Validation on the methods. This is basically a copy of the BeanValidationAppendixInterceptor of OpenEJB.

import javax.annotation.Resource;
import javax.validation.Validator;
import javax.interceptor.AroundInvoke;
import javax.ejb.SessionContext;
import java.lang.reflect.Method;
import org.hibernate.validator.method.MethodValidator;
import java.util.Set;
import javax.validation.ConstraintViolationException;
import javax.validation.ConstraintViolation;

public class BeanValidationAppendixInterceptor {

    @Resource
    private Validator validator;

    @Resource
    private SessionContext sessionContext;

    @AroundInvoke
    public Object aroundInvoke(final InvocationContext ejbContext) throws Exception {
        Class<?> bvalClazzToValidate = ejbContext.getTarget().getClass();
        if (this.sessionContext != null && ejbContext.getTarget().getClass().getInterfaces().length > 0) {
            bvalClazzToValidate = this.sessionContext.getInvokedBusinessInterface();
        }
        Method method = ejbContext.getMethod();
        if (!bvalClazzToValidate.equals(ejbContext.getTarget().getClass())) {
            method = bvalClazzToValidate.getMethod(method.getName(), method.getParameterTypes());
        }

        MethodValidator methodValidator = this.validator.unwrap(MethodValidator.class);

        Set<?> violations = methodValidator.validateAllParameters(ejbContext.getTarget(),
            ejbContext.getMethod(), ejbContext.getParameters(), new Class[0]);
        if (violations.size() > 0) {
            throw new ConstraintViolationException((Set<ConstraintViolation<?>>) violations);
        }

        Object returnedValue = ejbContext.proceed();

        violations = methodValidator.validateReturnValue(ejbContext.getTarget(),
            ejbContext.getMethod(), returnedValue, new Class[0]);
        if (violations.size() > 0) {
            throw new ConstraintViolationException((Set<ConstraintViolation<?>>) violations);
        }

        return returnedValue;
    }
}

Funny that JBoss AS 7.1 doesn't provide this out-of-the-box. This would probably not look good on their benchmarks I guess.

So now we can use the Bean Validation framework to do input validation on our model session beans as follows.

import javax.ejb.Stateless;
import javax.interceptor.Interceptors;
import javax.validation.constraints.NotNull;

@Stateless
@Interceptors(BeanValidationAppendixInterceptor.class)
public class MySessionBean {

    public void myMethod(@NotNull String message) {
        ...
    }
}

Besides the validation annotations that are defined as part of the Bean Validation specification, we can of course also define our own validation annotations and corresponding validators. What we would like to do here is to make our custom validators EJB context aware. So for example, we can do something like:

import javax.ejb.Stateless;
import javax.interceptor.Interceptors;

@Stateless
@Interceptors(BeanValidationAppendixInterceptor.class)
public class MySessionBean {

    public void myMethod(@CheckOwnership MyEntity myEntity) {
        ...
    }
}

Where the semantics of the @CheckOwnership annotation is to check whether the caller principal owns the MyEntity object. The validator corresponding to @CheckOwnership will need to get access to the caller principal so it can indeed check whether the MyEntity object is owned by the current caller. So let's define this validation annotation.

import java.lang.annotation.*;
import javax.validation.Payload;
import javax.validation.Constraint;

@Target({ElementType.PARAMETER})
@Retention(RetentionPolicy.RUNTIME)
@Constraint(validatedBy = CheckOwnershipValidatorBean.class)
public @interface CheckOwnership {
    String message() default "ownership error";
    Class<?>[] groups() default {};
    Class<? extends Payload>[] payload() default {};
}

Via the @Constraint annotation the Bean Validation framework knows which validator class it must use for validating parameters annotated with @CheckOwnership. Here the validator is actually an EJB3 session bean as shown in the following example.

import javax.ejb.Stateless;
import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext;
import javax.persistence.PersistenceContext;
import javax.persistence.EntityManager;
import javax.annotation.Resource;
import javax.ejb.SessionContext;

@Stateless
public class CheckOwnershipValidatorBean implements ConstraintValidator<CheckOwnership, MyEntity> {

    @PersistenceContext
    private EntityManager entityManager;

    @Resource
    private SessionContext sessionContext;

    @Override
    public void initialize(CheckOwnership checkOwnership) {
    }

    @Override
    public boolean isValid(MyEntity myEntityParam, ConstraintValidatorContext constraintValidatorContext) {
        MyEntity myEntity = this.entityManager.find(MyEntity.class, myEntityParam.getId());
        String username = this.sessionContext.getCallerPrincipal().getName();
        return username.equals(myEntity.getOwnerUsername());
    }
}

Of course the default validator factory does not support EJB3 session beans for validators.
So we need to define a custom ConstraintValidatorFactory that is also capable of handling EJB3 session bean validators. In the following example we omitted the exception handling to ease reading.

import javax.validation.ConstraintValidatorFactory;
import javax.validation.ConstraintValidator;
import javax.ejb.Stateless;
import javax.ejb.Stateful;
import javax.naming.InitialContext;

public class SessionBeanConstraintValidatorFactory implements ConstraintValidatorFactory {

    @Override
    public <T extends ConstraintValidator<?, ?>> T getInstance(Class<T> tClass) {
        Stateless statelessAnnotation = tClass.getAnnotation(Stateless.class);
        Stateful statefulAnnotation = tClass.getAnnotation(Stateful.class);
        if (null != statelessAnnotation || null != statefulAnnotation) {
            InitialContext initialContext = new InitialContext();
            T validator = (T) initialContext.lookup("java:module/" + tClass.getSimpleName());
            return validator;
        }
        return tClass.newInstance();
    }
}

This custom validator factory can be configured within the META-INF/validation.xml file as follows.

<validation-config xmlns="http://jboss.org/xml/ns/javax/validation/configuration">
    <constraint-validator-factory>my.package.SessionBeanConstraintValidatorFactory</constraint-validator-factory>
</validation-config>

This page is powered by Blogger. Isn't yours?