May 8, 2017

Java EE 6 Security

Background

A general introduction can be found at Oracle Java EE 6 Tutorial Security. Here follow a summary with code examples and some pitfalls for JBoss EAP 6.

Web

URL security is done WEB-INF/web.xml, as which authentication technique you want - BASIC, FORM, DIGEST or CLIENT-CERT. Only one factor authentication is supported in EE 6.

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd" version="3.0">

    <security-constraint>
        <web-resource-collection>
            <web-resource-name>Secure Content</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ROLE_GUEST</role-name>
        </auth-constraint>
    </security-constraint>

    <login-config>
        <auth-method>BASIC</auth-method>
    </login-config>

    <security-role>
        <role-name>ROLE_GUEST</role-name>
    </security-role>
    <security-role>
        <role-name>ROLE_ADMIN</role-name>
    </security-role>
</web-app>

Choosing security domain is done in the container specific web deployment descriptor. For jboss it is in WEB-INF/jboss-web.xml.

<?xml version="1.0" encoding="UTF-8"?>
<jboss-web xmlns="http://www.jboss.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.jboss.com/xml/ns/javaee http://www.jboss.org/j2ee/schema/jboss-web_7_0.xsd" version="7.0">

    <context-root>/java-ee6-security</context-root>
    <security-domain>java-ee6-security</security-domain>
</jboss-web>

In JBoss EAP 6 you do not need the security domain prefix - java:/jaas/. Do not use since it will be consistent when using security domain in jboss-ejb3.xml, where jndi prefix is not supported. It is only kept in jboss-web.xml for backward compatibility.

JAX-RS (REST)

JAX-RS annotated classes is technically a servlet, so use URL security in your web.xml. You can add EJB annotation (@javax.ejb.Stateless or @javax.ejb.Stateful) to your JAX-RS class for monitoring reason and even add EJB security annotations, but I would recommend to separate logic into separate EJB class.

JAX-WS (Web Service)

The same as for JAX-RS goes for JAX-WS. A JAX-WS annotated class is a servlet, use standard web.xml security and keep things easy and easy to understand, by putting all logic into separate EJB.

EJB

All EJB security annotations are in the javax.annotation.security package. Defines all roles at the class level and method specific role restrictions to methods.

package se.magnuskkarlsson.example.ejb;

import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.annotation.security.DeclareRoles;
import javax.annotation.security.RolesAllowed;
import javax.ejb.SessionContext;
import javax.ejb.Stateless;

@Stateless
@DeclareRoles(value = { "ROLE_ADMIN", "ROLE_GUEST" })
// @org.jboss.ejb3.annotation.SecurityDomain("java-ee6-security") vs [WEB-INF|META-INF]/jboss-ejb3.xml
public class HelloSLSB {

    private Logger log = Logger.getLogger(HelloSLSB.class.getName());

    @Resource
    private SessionContext sessionContext;

    @RolesAllowed(value = { "ROLE_GUEST" })
    public String helloGuest() {
        log.info(" *** helloGuest from EJB : " + sessionContext.getCallerPrincipal());
        return "helloGuest from EJB : " + sessionContext.getCallerPrincipal() + ", guest : "
                + sessionContext.isCallerInRole("ROLE_GUEST") + ", admin : "
                + sessionContext.isCallerInRole("ROLE_ADMIN");
    }

    @RolesAllowed(value = { "ROLE_ADMIN" })
    public String helloAdmin() {
        log.info(" *** helloAdmin from EJB : " + sessionContext.getCallerPrincipal());
        return "helloAdmin from EJB : " + sessionContext.getCallerPrincipal() + ", guest : "
                + sessionContext.isCallerInRole("ROLE_GUEST") + ", admin : "
                + sessionContext.isCallerInRole("ROLE_ADMIN");
    }
}

In JBoss EAP 6 there are EJB security annotations, but I discourage you to use them, to keep your code so vendor neutral as possible. Use vendor deployment descriptor instead.

jboss-ejb3.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss:jboss xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="urn:security:1.1" version="3.1" impl-version="2.0">

    <assembly-descriptor>
        <s:security>
            <ejb-name>*</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
        </s:security>
        ...
    </assembly-descriptor>
</jboss:jboss>

MDB

MDB is a special case. Java EE 6 does not support security on MDB, you can add security to your connection factory and queues, but not MDB. What you can do is add @javax.annotation.security.RunAs("role_name"), to enable MDB call a secured EJB.

package se.magnuskkarlsson.example.ejb;

import java.security.Principal;
import java.util.logging.Logger;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.annotation.security.RunAs;
import javax.ejb.ActivationConfigProperty;
import javax.ejb.MessageDriven;
import javax.ejb.MessageDrivenContext;
import javax.inject.Inject;
import javax.jms.JMSException;
import javax.jms.Message;
import javax.jms.MessageListener;
import javax.jms.TextMessage;
import javax.security.auth.Subject;
import javax.security.jacc.PolicyContext;

@MessageDriven(activationConfig = {
        @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
        @ActivationConfigProperty(propertyName = "destination", propertyValue = "jms/queue/javaEE6SecurityQueue") })
@RunAs("ROLE_ADMIN")
// @org.jboss.ejb3.annotation.RunAsPrincipal("admin") vs [WEB-INF|META-INF]/jboss-ejb3.xml
// @org.jboss.ejb3.annotation.SecurityDomain("java-ee6-security") vs [WEB-INF|META-INF]/jboss-ejb3.xml
public class EchoMDB implements MessageListener {

    private Logger log = Logger.getLogger(EchoMDB.class.getName());

    @Resource
    private MessageDrivenContext messageDrivenContext;

    @Inject
    private HelloSLSB hello;

    @PermitAll
    @Override
    public void onMessage(Message message) {
        try {
            if (message instanceof TextMessage) {
                log.info("MESSAGE BEAN: Message received: " + ((TextMessage) message).getText());

                // will always return "anonymous" when placed inside the MDB
                Principal principal = messageDrivenContext.getCallerPrincipal();
                log.info("Principal : " + principal);

                // will always return "anonymous" when placed inside the MDB
                Subject caller = (Subject) PolicyContext.getContext("javax.security.auth.Subject.container");
                log.info("Caller : " + caller);

                log.info("Hello from HelloSLSB : " + hello.helloAdmin());
            } else {
                log.warning("Message of wrong type: " + message.getClass().getName());
            }
        } catch (JMSException e) {
            e.printStackTrace();
            messageDrivenContext.setRollbackOnly();
        } catch (Throwable te) {
            te.printStackTrace();
        }
    }
}

And ones again we don't use vendor specific annotations and instead use external xml file configuration.

jboss-ejb3.xml

<?xml version="1.0" encoding="UTF-8"?>
<jboss:jboss xmlns="http://java.sun.com/xml/ns/javaee" xmlns:jboss="http://www.jboss.com/xml/ns/javaee"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:s="urn:security:1.1" version="3.1" impl-version="2.0">

    <assembly-descriptor>
        <s:security>
            <ejb-name>*</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
        </s:security>
        <s:security>
            <ejb-name>EchoMDB</ejb-name>
            <s:security-domain>java-ee6-security</s:security-domain>
            <s:run-as-principal>admin</s:run-as-principal>
        </s:security>
    </assembly-descriptor>
</jboss:jboss>

No comments: