Securing a REST Service
Want to learn more about securing a REST service? Check out this post where we look in-depth at securing your REST service on different platforms.
Join the DZone community and get the full member experience.
Join For FreeIf you're building a REST service, then that REST service will expose some kind of data or will allow some kind of interactions with a server. For instance, consider a Facebook REST service that allows you to retrieve your chat history. Naturally, you don't want just anyone looking at that history, hence the need for security.
What Are the Concepts Involved With REST Service Security?
For Java EE security, there are a few basic concepts involved:
- Caller — A user or script that calls the REST Service.
- Role — An opaque attribute or ID associated with a specific caller. This is used to refer to a group of callers in an abstract way. For example, "all callers with the role 'xyz.'"
- Authenticating — Callers proving their identity, largely synonymous to "logging in." For example, "Is the caller claiming to be Sarah indeed Sarah?"
- Authorizing — Determining whether a caller has access to something. For example, "Is Sarah allowed to see Mike's photos?"
- Authentication Mechanism — This refers to the specific way in which the server interacts with the caller to authenticate that caller. For example, with the BASIC authentication mechanism, a caller sends a username/password combination via an HTTP header, but with the JWT authentication mechanism, a JWT token is sent.
- Identity Store — A logical unit that is capable of validating credentials and returning a caller name and zero or more roles associated with that caller. Often, but not necessarily, the identity store provides access to (external) storage containing the details of callers, their credentials, and their roles, for example, a database, file, or LDAP identity store. All this data can also be stored within the effective credential, which is the case for a JWT token.
How to Secure a REST Service
For Java EE Security/the Payara Platform, there are a couple of options:
- REST Services can be secured by defining constraints on the URL, endpoint (resource class), or method level.
- An authentication mechanism can be chosen from those defined by the Servlet API, by the Java EE Security API, or a custom (application provided) one can be used.
- An identity store can be left undefined by the application and left to be configured within Payara Server (for example, using the admin console or CLI), one can be chosen from those defined by the Java EE Security API, or a custom (application provided) one can be used.
Which Authentication Mechanisms Are Available in the Payara Platform?
The Payara Platform ships with the authentication mechanisms defined by the Servlet-, Java EE Security-, MicroProfile- and Payara APIs. These all can be used largely interchangeably. Note that not all of them are primarily useful for REST authentication, but are instead more suited for interactive websites (specifically the FORM based ones).
Servlet
- BASIC
- FORM
- DIGEST
- CERTIFICATE
Java EE Security
- BASIC
- FORM
- Custom FORM
MicroProfile
- JWT
Payara API
- OAuth2
- OpenID Connect (coming in 5.183)
- Yubikey
Which Identity Stores Are Available in the Payara Platform?
The Payara Platform ships with the identity stores defined by the Java EE Security API and its own internal ones (a JAAS LoginModule/GlassFish Realm combination) that can be configured via the admin console, CLI, or domain.xml. In Payara 5.182 and before identity stores from Java EE Security, we can be paired with authentication mechanisms from Java EE Security, MicroProfile, and the Payara API, but not with those from Servlet. Likewise, the internal ones can only be paired with the authentication mechanisms from Servlet. Payara 5.182 will likely make these all interchangeable. Note that MicroProfile JWT has its own identity store, but it can be paired if needed with additional identity stores if extra roles beyond those provided by the JWT token are needed.
Java EE Security
- LDAP
- DataBase
Payara Internal
- File
- LDAP
- JDBC (DataBase)
- Digest
- Solaris
Example
For our first example, we'll be demonstrating Java EE Security BASIC authentication with a custom (application provided) identity store.
We'll start with defining the REST Service (JAX-RS resource) itself as follows:
@Path("/resource")
@Produces(TEXT_PLAIN)
public class Resource {
@GET
@Path("hi")
@RolesAllowed("a")
public String hi() {
return "hi!";
}
}
In this simple example, we just have a single resource method returning the text "hi!" What is so special about this REST Service is the use of the @RolesAllowed
annotation, which causes the resource method to be secured, and thus only accessible for callers who are associated with the role "a." Note that this annotation is supported by out-of-the-box by Payara, but may not be supported directly on other servers or may have to be activated explicitly on those other servers.
Next, we'll create an activator class that activates JAX-RS itself, but most importantly, for this example, we will define what we want to use for the BASIC authentication mechanism from Java EE:
@ApplicationScoped
@ApplicationPath("/rest")
@DeclareRoles({ "a", "b" })
@BasicAuthenticationMechanismDefinition(realmName = "foo-ee")
public class JaxRsActivator extends Application {
}
The @BasicAuthenticationMechanismDefinition
annotation causes Payara to put a CDI bean implementing the BASIC authentication mechanism in service.
The last piece is the custom identity store, which contains the data for a single test caller:
@ApplicationScoped
public class TestIdentityStore implements IdentityStore {
public CredentialValidationResult validate(UsernamePasswordCredential usernamePasswordCredential) {
if (usernamePasswordCredential.compareTo("test", "secret")) {
return new CredentialValidationResult("test", new HashSet<>(asList("a", "b")));
}
return INVALID_RESULT;
}
}
In this minimum identity store, we recognize a caller with credentials and the username "test" and a password "secret." For this caller, we'll return the same username and the groups "a" and "b," which are mapped to roles of the same name by default, so we don't have to worry about groups/roles now.
When we bundle this together into a war called "test" and deploy it to a default Payara Server instance, we can request http://localhost:8080/test/rest/resource/hi, and the browser should respond with a dialog asking for the credentials. If we enter "test" and "secret," we'll see "hi!" on our screen. Otherwise, we'll see either a 401 or the browser will redisplay the dialog.
Demonstrating that much of our code stays the same when switching authentication mechanisms between EE Security and Servlet, we'll also show the conceptual same BASIC mechanism configured via Servlet. For that, the REST Service stays the same, but we remove the authentication mechanism annotation from the activator class:
@ApplicationScoped
@ApplicationPath("/rest")
@DeclareRoles({ "a", "b" })
public class JaxRsActivator extends Application {
}
In its place, we added a WEB-IN/web.xml file with the following contents:
<?xml version="1.0" encoding="UTF-8"?>
<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_3_1.xsd"
version="3.1">
<login-config>
<auth-method>BASIC</auth-method>
<realm-name>foo-servlet</realm-name>
</login-config>
</web-app>
Note that the realm name, contrary to a very persistent public belief, is not officially specified to indicate which identity store is being used. The string here ("foo-servlet") is supposed to be only used in the header that is sent to the caller (and which, for example, a browser renders as a dialog box). But, in Payara, it can optionally indicate which Payara Realm/LoginModule combo is used. If it's an unknown Payara Realm, it will still be sent to the browser, but the default Payara realm will be used.
The next thing to do is configure Payara's global identity store, which, in Payara terms, is called a realm. The default global realm is File, and since that's exactly the one that's easiest for this example, we don't have to actually select it. We do have to a user called "test" to it. With Payara running, execute the following command from [payara install dir]/bin:
./asadmin create-file-user --groups a test
When asked for the password enter "secret."
Now, when deploying this again and requesting http://localhost:8080/test/rest/resource/hi once more, we should see the same result as with the first variant of this application.
The full source of both variants can be found on GitHub, along with an automated test that demonstrates how the applications are supposed to be called:
Published at DZone with permission of Arjan Tijms. See the original article here.
Opinions expressed by DZone contributors are their own.
Comments