Skip to main content
Blog

Testing Keycloak integration with Arquillian

By 28 augustus 2015juli 4th, 2017No Comments

In a previous blog post we discussed a bit on how you can integrate Keycloak with JAX-RS. In this blog post we’ll show an example on how to build an integration test to test if your REST endpoint is secured.

In this setup, we are using Keycloak 1.4.0 for our identity management, and we deploy our application in Wildfly 9. (Older versions of Keycloak might not work with this setup). To be able to use Keycloak from a client application (e.g. the JAX-RS endpoint) it needs to know where the Keycloak server lives, as well as its public key. You can either configure this using a keycloak.json packaged within your WAR file, or configure it in the container. (In Wildfly/JBoss terminology, define a subsystem for Keycloak). See the read more section for more information on configuring these adapters. In our setup, we want to be able to use the same WAR file in any part of our DTAP street. If we drop the WAR file in a development Wildfly container, it should connect to a locally running Keycloak server, for test, accept and production it will connect to a specific central Keycloak server for that environment. As such, we configure the security settings in the container and bind the war file to a security context.

As an example, we have a subsystem like this defined in our standalone.xml for a local (development) wildfly container:

  
    
      MY-REALM
      my-app
      MII...

http://localhost:8180/auth

      none
      xxxxx-xxx-xxx-xxx-xxxx
    
  
...

As you can see, it binds the my-app.war to a realm MY-REALM at a Keycloak server running on localhost:8180. This way, in our app we only have to define security constraints and it can be (mostly) ignorant of the fact that we use Keycloak and where it is running.

So how can we test this? Since most of the functionality is serviced by the container, we need a full stack to test against. Arquillian to the rescue.

The idea behind Arquillian is that you define a micro deployment, containing just enough to deploy the class you want to test. Arquillian creates a WAR file for it, deploys it in a running container (or starts a new one if necessary), runs the tests and tears everything down again.

Example controller

In our case, we just want to deploy our JAX-RS endpoint, the MyApiController. The controller in this example looks like this:

@Path("/")
@Stateless
public class MyApiController {
  @Inject
  private MyService myService;
  @Inject
  private Principal principal;
 
  @GET
  @Produces(MediaType.APPLICATION_JSON)
  @Path("/hello")
  public String hello() {
    return "hello " + principal;
  }
}

And it is protected with the following security constraints defined in web.xml:

    
        
            /rest/*
        
        
            owner
        
    

Arquillian Test Setup

To test this, we are going to build a micro deployment for this controller. We create a test class with a createDeployment function which looks something like this:

@RunWith(Arquillian.class)
public class MyApiControllerSecurityTest {
  @Deployment(testable = false)
  public static WebArchive createDeployment() throws IOException {
    WebArchive war = ShrinkWrap
       .create(WebArchive.class, "my-app.war")
       .addClasses(MyApiController.class, MyService.class,
         MyServiceStubImpl.class, JaxRsActivator.class)
       .addAsManifestResource("META-INF/persistence.xml",
         "persistence.xml")
       .addAsWebInfResource("test-secured-web.xml", "web.xml");
    return war;
  }
}

A few choices have been made in this setup. First, Arquillian is quite capable of making up a name for the war file. However, since we have bound the security subsystem in the container to a war file, we need to explicitly name the war file. This way, the test deployment will have the same configuration as the actual application. Next, we add the necessary classes. These contain of course the controller we want to test but also its dependencies. The MyService interface is one of them. We provide a stubbed implementation for it because we are not interested in the actual behaviour. Last but not least, we need to package our JAX-RS Application class to bootstrap the REST service, JaxRsActivator. 

Also note that in this case we also package a test version (test-secured-web.xml) instead of the actual web.xml. We could use the actual web.xml but that would include a lot more dependencies. Also, we do not want to be burden all our integration tests with security stuff so we created a small version of it. Note that we do have to keep both in sync with each other so for test purists, this might be a problem. In our setup, this is not really much of a concern since we do not use role-based authorisation much, instead most of our security relies on programmatic authorisation.

[box type=”info”] If you run these integration tests against your development Wildfly container, you might get errors from Arquillian that the application is already deployed. That would be your actual application which has the same name as your micro deployment. Simply undeploy the application or run the test again to solve that. (If you don’t force the name in the Arquillian test, you wont run into this, but then the configuration might be different as well.) [/box]

Testing without login

With this setup we can now create two tests, one for testing that if you are not logged in, you will be redirected to Keycloak to log in. Secondly, if you provide an invalid token, permission should be denied:

@ArquillianResource
URL deploymentUrl;

@Test
public void unauthenticatedShouldRedirect() throws Exception {
 Client client = ClientBuilder.newClient();
 Response response = client
 .target(deploymentUrl.toString() + RESOURCE_PREFIX + "/hello")
 .request(MediaType.APPLICATION_JSON).get();
 
 assertEquals(302, response.getStatus());
 assertTrue(response.getHeaderString("Location").contains("openid-connect"));
 assertTrue(response.getHeaderString("Location").contains("client_id=integration-test"));
}

@Test
public void invalidTokenShouldBeForbidden() throws Exception {
 Client client = ClientBuilder.newClient();
 Response response = client
 .target(deploymentUrl.toString() + RESOURCE_PREFIX + "/hello")
 .request(MediaType.APPLICATION_JSON).header("Authorization", "Bearer invalidtoken").get();
 
 assertEquals(401, response.getStatus());
}

You can see that we use a simple JAX-RS client to call our controller. To find out where the application is actually deployed, we inject the deployment URL delivered by Arquillian.

Testing with login

That was quite easy. Now for the slightly trickier part. We want to test if you can actually use the REST API if you have the proper credentials. For that, we need to create a well-known user that our tests can rely on. In this case, we have created a testuser-owner account in Keycloak to represent a user with the role owner. We know want our test to login as that user and use the access token to call our API.

The easiest way of doing that is to simply present a username and password to keycloak and get the token. Something like this, using Apache HttpClient would work:

private String login(String userName, String password) throws Exception {
  String url = getKeycloakUrl();
  HttpClient client = new DefaultHttpClient();
  HttpPost post = new HttpPost(url);
  post.setHeader("Content-Type", "application/x-www-form-urlencoded");

  List urlParameters = new ArrayList<>();
  urlParameters.add(new BasicNameValuePair("username", userName));
  urlParameters.add(new BasicNameValuePair("password", password));
  urlParameters.add(new BasicNameValuePair("grant_type", "password"));
  urlParameters.add(new BasicNameValuePair("client_id", "my-app"));
  post.setEntity(new UrlEncodedFormEntity(urlParameters));

  HttpResponse response = client.execute(post);
  JsonReader reader = Json.createReader(response.getEntity().getContent());
  String accessToken = reader.readObject().getJsonString("access_token").getString();
  return accessToken;
}

Notice that we do a simple POST to the Keycloak server, with a grant_type of type password, the username and the password as well as the client we are asking a token for. We get a JSon message back in which we can find an access-token. The login() function returns this one for use in tests.

[box type=”info”] There is one catch here. To be able to directly log in, the client must allow “Direct Access Grants”. If you don’t want to change your setup just for integration tests, you can add a new Client specifically for integration tests in the same realm. Use that clients id to log in and use the same access token on other clients. [/box]

With this, we can create the third, happy flow, test:

@Test
public void withTokenShouldSayHello() throws Exception {
  Client client = ClientBuilder.newClient();
  Response response = client
    .target(deploymentUrl.toString() + RESOURCE_PREFIX + "/hello")
    .request(MediaType.APPLICATION_JSON)
    .header("Authorization", "Bearer " + login("testuser-owner", "test123")).get();
  assertEquals(200, response.getStatus());
}

Happy testing!

Read more: