Quarkus - Using Security with a JDBC Realm
To complete this guide, you need:
less than 15 minutes
an IDE
JDK 1.8+ installed with configured appropriately
Apache Maven 3.5.3+
Architecture
In this example, we build a very simple microservice which offers three endpoints:
/api/public
/api/users/me
/api/admin
The /api/public
endpoint can be accessed anonymously.The /api/admin
endpoint is protected with RBAC (Role-Based Access Control) where only users granted with the admin
role can access. At this endpoint, we use the @RolesAllowed
annotation to declaratively enforce the access constraint.The /api/users/me
endpoint is also protected with RBAC (Role-Based Access Control) where only users granted with the user
role can access. As a response, it returns a JSON document with details about the user.
Solution
Clone the Git repository: git clone https://github.com/quarkusio/quarkus-quickstarts.git
, or download an .
The solution is located in the security-jdbc-quickstart
directory.
First, we need a new project. Create a new project with the following command:
This command generates a Maven project, importing the elytron-security-jdbc
extensionwhich is an adapter for Quarkus applications.
Writing the application
Let’s start by implementing the /api/public
endpoint. As you can see from the source code below, it is just a regular JAX-RS resource:
package org.acme.elytron.security.jdbc;
import javax.annotation.security.PermitAll;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/api/public")
public class PublicResource {
@GET
@PermitAll
@Produces(MediaType.TEXT_PLAIN)
public String publicResource() {
return "public";
}
}
The source code for the /api/admin
endpoint is also very simple. The main difference here is that we are using a @RolesAllowed
annotation to make sure that only users granted with the admin
role can access the endpoint:
package org.acme.elytron.security.jdbc;
import javax.annotation.security.RolesAllowed;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
@Path("/api/admin")
public class AdminResource {
@GET
@RolesAllowed("admin")
@Produces(MediaType.TEXT_PLAIN)
public String adminResource() {
return "admin";
}
}
Finally, let’s consider the /api/users/me
endpoint. As you can see from the source code below, we are trusting only users with the user
role.We are using SecurityContext
to get access to the current authenticated Principal and we return the user’s name. This information is loaded from the database.
package org.acme.elytron.security.jdbc;
import javax.annotation.security.RolesAllowed;
import javax.inject.Inject;
import javax.ws.rs.GET;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.SecurityContext;
@Path("/api/users")
public class UserResource {
@GET
@RolesAllowed("user")
@Path("/me")
@Produces(MediaType.APPLICATION_JSON)
public String me(@Context SecurityContext securityContext) {
return securityContext.getUserPrincipal().getName();
}
}
The elytron-security-jdbc
extension requires at least one datasource to access to your database.
In our context, we are using PostgreSQL as identity store and we init the database with users and roles.
CREATE TABLE test_user (
id INT,
password VARCHAR(255),
role VARCHAR(255)
);
INSERT INTO test_user (id, username, password, role) VALUES (1, 'admin', 'admin', 'admin');
INSERT INTO test_user (id, username, password, role) VALUES (2, 'user','user', 'user');
We can now configure the Elytron JDBC Realm.
quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM test_user u WHERE u.username=? (1)
quarkus.security.jdbc.principal-query.clear-password-mapper.enabled=true (2)
quarkus.security.jdbc.principal-query.clear-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2 (3)
quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups
Testing the Application
The application is now protected and the identities are provided by our database.The very first thing to check is to ensure the anonymous access works.
$ curl -i -X GET http://localhost:8080/api/public
HTTP/1.1 200 OK
Content-Length: 6
Content-Type: text/plain;charset=UTF-8
public%
Now, let’s try a to hit a protected resource anonymously.
So far so good, now let’s try with an allowed user.
$ curl -i -X GET -u admin:admin http://localhost:8080/api/admin
HTTP/1.1 200 OK
Content-Length: 5
Content-Type: text/plain;charset=UTF-8
admin%
By providing the admin:admin
credentials, the extension authenticated the user and loaded their roles.The admin
user is authorized to access to the protected resources.
The user admin
should be forbidden to access a resource protected with @RolesAllowed("user")
because it doesn’t have this role.
$ curl -i -X GET -u admin:admin http://localhost:8080/api/users/me
HTTP/1.1 403 Forbidden
Content-Length: 34
Content-Type: text/html;charset=UTF-8
Finally, using the user user
works and the security context contains the principal details (username for instance).
curl -i -X GET -u user:user http://localhost:8080/api/users/me
HTTP/1.1 200 OK
Content-Length: 4
Content-Type: text/plain;charset=UTF-8
user%
This guide only covered an easy use case, the extension offers multiple datasources, multiple principal queries configuration as well as a bcrypt password mapper.
Configuration Reference
Configuration property fixed at build time - ️ Configuration property overridable at runtime
Future Work
Propose more password mappers.
Provide an opinionated configuration.