November 12, 2020
This article demonstrates how to enabled a JavaEE scheduled operation to access a Role Based Access Control (RBAC) enabled EJB. The RolesAllowed annotation is used to secure the EJB. A scheduled operation uses the EJB and is outfitted with the RunAs annotation. Unlike the RESTful web service interacting with the EJB, there is no user identity coming through the authentication subsystem.
The JavaEE code described this article is on GitHub.
The demonstration is a form processor. An initial RESTful web service POST saves a form. A process later awakens and processes the form. This is a pattern used when the data intake stage of an application is separated for performance or other reasons from processing. For instance, the data intake can be quick insert into an RDBMS which is followed up with a process stage that can include time consuming operations. The operation can even include a manual review.
The following UML class diagram shows the classes involved in the demonstration. FormResource is a JAX-RS endpoint that receives POST-ed form data and provides a GET that will verify the submitted forms. FormBean is a data access object that interacts with an in-memory datasource that ships with WildFly "H2". ProcessorBean has a scheduled operation that will use FormBean to mark any received forms as "PROCESSED" and also applies a timestamp.
In this sequence diagram, the flow starts with a POST of a form. The form is saved to the in-memory database by FormBean's create() call. Every two minutes, the ProcessorBean calls FormBean. FormBean checks for any RECEIVED forms. If any are found, their state is automatically changed to PROCESSED.
Java Persistence Architecture (JPA) is not a focus for this article, but a few notes are in order. Firstly, H2 is an in-memory datasource that ships with WildFly. So for this demo, there are no special management console operations. Secondly, we're relying on JPA to create the DB objects automatically.
In GitHub, look for the src/main/resources/META-INF folder's persistence.xml. It relates the WAR file to the out-of-the-box H2 datasource.
"Form" is a JPA entity with fields for id, data, and processing. It's purpose in the demo is to show the data intake stage which results in a Form record being written out with the submitted data, a generated id, a RECEIVED state, and a receivedOn timestamp set. Later processing will update the state to PROCESSED and write out a second timestamp processedOn.
The JAX-RS class is secured in the web.xml file. This is in additional security to the RolesAllowed placed on FormBean. It's important because the security-constraint will make sure that a 401 Unauthorized is returned to the API caller rather than an ugly 500 error. While the 500 does secure the app, it makes developing against the API more difficult.
<?xml version="1.0"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
version="4.0">
<security-constraint>
<web-resource-collection>
<web-resource-name>Protected pages</web-resource-name>
<url-pattern>/api/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>user</role-name>
</auth-constraint>
</security-constraint>
<security-role>
<role-name>user</role-name>
</security-role>
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>ApplicationRealm</realm-name>
</login-config>
</web-app>
FormResource delegates immediately to FormBean. On the GET call to return a list of Forms, it's using the Form Entity object as a transport.
@Path("/forms")
public class FormResource {
@Inject
private FormBean formBean;
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Form> getAllForms() { return formBean.findAllForms(); }
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public void postForm(@FormParam("mydata") String mydata) {
formBean.receiveForm(new Form(mydata));
}
}
FormBean does most of the work for the application. It writes the data out to the H2 database and contains the business logic for how to mark RECEIVED items and how to process RECEIVED items. It's configured to allow calls from identities with the role "user" and also the role "sys".
@Stateless
@RolesAllowed({"user","sys"})
public class FormBean {
@PersistenceContext
private EntityManager em;
public void create(Form form) {
em.persist(form);
}
public void update(Form form) {}
public List<Form> findAllForms() {
Query q = em.createQuery("SELECT f FROM Form f ORDER BY f.processedOn DESC", Form.class);
return q.getResultList();
}
public List<Form> findReceivedForms() {
Query q = em.createQuery("SELECT f FROM Form f WHERE f.formStateType = 'RECEIVED'", Form.class);
return q.getResultList();
}
public void receiveForm(Form form) {
form.setFormStateType(FormStateType.RECEIVED);
form.setReceivedOn(LocalDateTime.now());
create(form);
}
public void processForms() {
findReceivedForms()
.stream()
.forEach( f -> {
f.setFormStateType(FormStateType.PROCESSED);
f.setProcessedOn(LocalDateTime.now());
update(f);
});
}
}
Role "user" is set up in the Basic Authentication scheme as described in this article. The add-user.sh command was run to add a user with the role to application-users.properties and application-roles.properties. These are files and a Realm "ApplicationRealm" that ships with WildFly.
Role "sys" is not defined in ApplicationRealm. Rather, it's a role that will be used by ProcessorBean. The RunAs annotation asserts that these functions are running as the specified role. If the annotation is left off, when the scheduled operation comes due, the call into FormBean will fail because of RBAC.
@Singleton
@RunAs("sys")
public class ProcessorBean {
@Inject
private FormBean formBean;
@Lock(LockType.READ)
@Schedule(minute = "*/2", hour = "*", persistent = false)
public void processForms() throws InterruptedException {
formBean.processForms();
}
}
This demonstration showed how a scheduled operation can use the RunAs annotation to assume a role. That allows access to EJBs secured with RBAC. RunAs is important when there is a later reaper process that's executing some functions or business logic at a later stage. This later stage won't have a user providing an identity and it's up to RunAs to provide one.
By Carl Walker
President and Principal Consultant of Bekwam, Inc