To main content

Securing JAX-RS endpoints using Apache Shiro

Published by Benjamin Marwell on

Apache Shiro is a powerful Java Security Toolkit, used by Java developers around the world to secure their applications. While Apache Shiro’s focus is on broad usability, it does excel in specific use cases. This article will give you a broad overview of what Apache Shiro is used for and where it excels, especially compared to other frameworks.

Apache Shiro: The Java Library

If you ask yourself »What is Apache Shiro?«, well, this is too broad of a question. Apache Shiro is a Java library, or more specifically a collection of Java libraries you can include into your application. You can include it in all kind of applications: Java Web Applications (.war), Java Enterprise Applications (.ear) and even standalone applications (.jar)!

So, »what is Apache Shiro«? — A Java Library to secure any Java Application using permissions, roles and authentication of ussers. Read the next paragraph to see how to use it in your JAX-RS application.

Knowledge and Prerequisites for this Apache Shiro JAX-RS tutorial

You need to know about the following things:

  • How to configure your project using Apache Maven (or any similar java build tool like Gradle)

  • How to set up a .war project

  • How to set up a JAX-RS application using annotations

  • How JAX-RS endpoints work

  • How to run your project on your favourite application serer

  • How to query your endpoints (e.g. using curl, Insomnia, Postman etc.)

Shiro for JAX-RS: Overview

The first thing you will need in any case is a file shiro.ini. This file defines some basic rules for Shiro and can be extended later.

To be able to use Apache Shiro in your Maven project, you will need a few dependencies:

  <dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-core</artifactId>
      <version>${dependency.shiro.version}</version>
    </dependency>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-web</artifactId>
      <version>${dependency.shiro.version}</version>
    </dependency>
  </dependencies>

For the start, the first thing we case about is to tell Apache Shiro which web paths to secure. Big surprise: You want it to (potentially) secure ALL paths, even your login! The fine-grained »what does Shiro allow the user to do« will be set up later.

The following file is located in the classpath root.

A shiro.ini starter
[main]


[urls]
/** = noSessionCreation,authcBasic[permissive]

Now, we allow to check for permissions to request a specific resource. As with any modern application, we do not create a session (more on that later). The next thing to look out for is authcBasic. It tells Shiro you can authenticate (tell Shiro who you are) using a HTTP header. But if we stopped there, Shiro would require this header, even for displaying a login page or the login resource, which does not make any sense. Hence we added the [permissive] parameter, which tells Shiro that a login is not mandatory by default. On the downside, we have to tell Shiro for each resource that an authenticated user is necessary, when applicable.

The very basics of Shiro

While the paragraph above contains a lot of dense information, be sure to read it carefully. It contains a lot of valuable information which is needed to understand the following sections.

Repeat it and try to explain it in easy words to someone else in simple English. If you cannot do it, read it over and try again until you succeed.

But to be able to test Apache Shiro with JAX-RS, we might want to add a user. For sake of easiness, let’s start with a hard-coded user. In practice later on, you will want to add authentication against another source, e.g. a Database or LDAP or Active Directory.

Shiro for JAX-RS

JAX-RS is the number one web technology for Java right now (in my opinion). It allows you to create simple and predictable endpoints that output only data, but no HTML code. The data can be encoded in any form you like. While JSON is the most common data encoding, JAX-RS (Restful endpoints) are not restricted to it. Other valid options are TOML, YAML, XML and even Google Protobuf would work!

All set? Let’s dive in right into the code!

Preparing a Shiro JAX-RS endpoint

Let’s update our dependencies first. Shiro’s JAX-RS-dependency contains the other dependencies I mentioned earlier:

  <dependencies>
    <dependency>
      <groupId>org.apache.shiro</groupId>
      <artifactId>shiro-jaxrs</artifactId>
      <version>${dependency.shiro.version}</version>
    </dependency>
  </dependencies>

To make Shiro being picked up, you will need three additional things:

A ShiroServletFilter

A class extending ShiroFilter which will implement the servlet filter. All JAX-RS endpoints are servlets in disguise, so a servlet filter will be executed before the execution is handed over to the JAX-RS class. We also define which events to listen to.

A Shiro EnvironmentListener

This class will be a listener on application startup which initializes Shiro once. Actually, this Servlet Listener will invoke the loading of the shiro.ini file.

A ShiroJaxRsFeature

JAX-RS annotations need to be registered as a feature. While the shiro-jaxrs dependency ships such a class, we need to extend it, so it will be picked up.

There are alternative approaches which do the same:

  • Adding the filter to the web.xml or importing the shiro-servlet dependency, which contains this very same snippet.

  • Adding the listener to the web.xml or importing the shiro-servlet dependency, which contains this very same snippet.

  • Adding Shiro’s feature class (org.apache.shiro.web.jaxrs.ShiroFeature) to your Java class extending javax.ws.rs.core.Application. But this would force you to declare all JAX-RS classes manually, since a non-empty Set would disable auto-discovery.

The implementations are quite simple, as they only extend and annotate Shiro’s existing classes:

Implementing a simple JAX-RS endpoint using Shiro

Now that the setup is done, let’s create an endpoint and add our Shiro annotations. Let us consider your existing endpoint which can output all Storm Troopers in your database:

For Shiro to do it’s work, you will need two annotations.

  1. Since all your endpoints in this class are only available to registered users, add the annotation @org.apache.shiro.authz.annotation.RequiresUser to the class.

  2. Not every registered user can read the known storm troopers. Only users with the permission troopers:read may do this. Imagine this is a permission implicitly granted to all admins and stormtrooper managers. We need to add @org.apache.shiro.authz.annotation.RequiresPermissions("troopers:read") for this to work.

The modified snippet looks like this:

If you now try to read this resource, you will get a 401 Unauthorized response, indicating the need for authentication as set up by adding the user requirement.

We do not have a user yet, so let us extend our shiro.ini file.

Creating an admin user

As mentioned previously, let us consider a simple and hard-coded authentication. Authentication by other means (LDAP, AD, DB) will be covered in another article.

Extend your shiro.ini by two sections:

A shiro.ini with an admin user
[main]

[users]
# format: username = password, role1, role2, ..., roleN
# root:Start123
root = "$shiro1$SHA-256$500000$MD1ILiY3dLHYa5n4Ys7FCg==$asN2AWGqceSJgo7AjpzlDgEVC9XB4Q/sbHRG17TTY+4=",admin
guest = guest,guest

[roles]
# format: roleName = permission1, permission2, ..., permissionN
admin = *

[urls]
/** = noSessionCreation,authcBasic[permissive]

Try to curl your endpoint again with the user 'root' and the password 'Start123' – it is now available. This is also the end of this basic tutorial. You successfully secured your JAX-RS endpoint!

Testing your Shiro Endpoints

We test our endpoints in the main branch of Shiro which is the Shiro 2 branch. You can see our unit tests here:

This should give you enough resources on how to start a real application server without Arquillian or Docker (TestContainers). The tests are using mostly standards where applicable, e.g. the JAX-RS client for quering the data. Apache Johnzon is used for JSON parsing, which is also a valid JSON-B implementation.

Conclusions

In this tutorial you learned what Apache Shiro is and how you can use it to secure your JAX-RS endpoints. Apache Shiro allows granular permissions, which are subject to another tutorial.

This simple setup is used as an integration test for Apache Shiro 2 and is therefore a valid real world example.

Alternatives to Apache Shiro

Jakarta EE Security

While Apache Shiro does not implement the Jakarta Security API, you can use it with an implementation like Soteria. Soteria however does not allow you to use annotations on your JAX-RS endpoint and only implements roles, but no permissions. Use it if you do not want to ship an implementation library to keep your archive small and when you do not need fine-grained control over your user’s permissions.

Spring Security

While Apache Shiro does integrate with Spring-Boot and the Spring framework, the Spring team put a lot of work into Spring Security.

Further reading

There is some documentation you will want to read and which was used to create this tutorial: