Skip to main content
Blog

Securing JAX-RS: Keycloak, CDI and EJB confusion

By 10 augustus 2015januari 30th, 2017No Comments

Working with the latest technology can be quite painful. Not necessarily because the software is buggy or immature, in my experience it is mostly because googling for a problem gives solutions for older versions. In this bug hunt, I was working with a Wildfly 9.0.1 hooked up to a Keycloak 1.3.1 server. I was trying to secure our REST API. I was mostly interested in doing programmatic security (the business rules can be quite complex) but it would be nice to simply slap on some @RolesAllowed annotations for the trivial cases. Of course, this didn’t work even though RESTEasy supports it.

Let’s have a look at our very basic API controller:

@Path("/")
public class ApiController {

	@Inject
	private Principal principal;

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/hello")
	@RolesAllowed("admin")
	public String hello() {
		return "hello " + principal;
	}
}

In this setup, the @RolesAllowed does not do anything. The Principal is
If you have a JAX-RS bean it isn’t strictly a CDI or EJB bean, so not everything works out of the box. Most things work though so it can be confusing at times. This was one of those times. If you put a @RequestScoped on it, you promote it to a CDI bean and some quirkiness will disappear. In this case, we actually have to promote it to a full blown EJB.

@Path("/")
@Stateless
public class ApiController {

	@Inject
	private Principal principal;

	@GET
	@Produces(MediaType.APPLICATION_JSON)
	@Path("/hello")
	@RolesAllowed("admin")
	public String hello() {
		return "hello " + principal;
	}
}

If you put a @Stateless annotation on your JAX-RS bean, you make it a stateless session EJB. The @RolesAllowed will work. And all the CDI features will still work since all EJB’s are also CDI beans. Unfortunately, security worked a bit too aggressive, even if I had the role, I was refused.

Turns out, for JAX-RS support for @RolesAllowed to work, you need to explicitly list all the roles available. Using @DeclareRoles didn’t work, I had to explicitly list all the roles you use in web.xml:

<?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>
    <url-pattern>/rest/*</url-pattern>
   </web-resource-collection>
   <!--
     <user-data-constraint>
       <transport-guarantee>CONFIDENTIAL</transport-guarantee>
     </user-data-constraint> 
   -->
   <auth-constraint>
     <role-name>owner</role-name>
   </auth-constraint>
 </security-constraint>

 <security-role>
   <role-name>admin</role-name>
 </security-role>
</web-app>

Also note that if you annotate one function with @RolesAllowed, all other functions will be protected as well. Be careful with that if you are experimenting since it can be misleading.

Googling for these kind of problems will lead you across many different solutions, some XML based for very old versions of JEE, others only offer part of the solution. As always, it helps if you understand a bit of what is happening inside the containers. So, if you run into a similar problem, try making sure it is an EJB (add @Stateless or something) and list all the roles in your web.xml. I am not sure if this is caused by the Keycloak adapter or by RESTEasy, that’s for another blog post if I find out.

 

Read more: