Getting Started with Security using Basic authentication and Jakarta Persistence
Get started with Quarkus Security by securing your Quarkus application endpoints with the built-in Quarkus Basic authentication and the Jakarta Persistence identity provider and enabling role-based access control.
The Jakarta Persistence IdentityProvider
verifies and converts a
Basic authentication user name and
password pair to a SecurityIdentity
instance which is used to authorize
access requests, making your Quarkus application secure.
For more information about Jakarta Persistence, see the Quarkus Security with Jakarta Persistence guide.
This tutorial prepares you for implementing more advanced security mechanisms in Quarkus, for example, how to use the OpenID Connect (OIDC) authentication mechanism.
准备
要完成本指南,您需要:
-
Roughly 15 minutes
-
An IDE
-
JDK 11+ installed with
JAVA_HOME
configured appropriately -
Apache Maven 3.9.6
-
Optionally the Quarkus CLI if you want to use it
-
Optionally Mandrel or GraalVM installed and configured appropriately if you want to build a native executable (or Docker if you use a native container build)
What you will build
To demonstrate different authorization policies, the steps in this tutorial guide you through building an application that provides the following endpoints:
Endpoint | Description |
---|---|
|
The |
|
The |
|
The |
1. Create and verify the Maven project
For Quarkus Security to be able to map your security source to Jakarta
Persistence entities, ensure that the Maven project that is used in this
tutorial includes the security-jpa
or the security-jpa-reactive
extension.
Hibernate ORM with Panache is used to store
your user identities but you can also use Hibernate
ORM with the You must also add your preferred database connector library. The instructions in this example tutorial use a PostgreSQL database for the identity store. |
1.1. Create the Maven project
You can either create a new Maven project with the Security Jakarta Persistence extension or you can add the extension to an existing Maven project. You can use either Hibernate ORM or Hibernate Reactive.
-
To create a new Maven project with the Jakarta Persistence extension, complete one of the following steps:
-
To create the Maven project with Hibernate ORM, use the following command:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=security-jpa-quickstart"
-
-
To create the Maven project with Hibernate Reactive, use the following command:
For Windows users:
-
If using cmd, (don’t use backward slash
\
and put everything on the same line) -
If using Powershell, wrap
-D
parameters in double quotes e.g."-DprojectArtifactId=security-jpa-reactive-quickstart"
-
-
-
To add the Jakarta Persistence extension to an existing Maven project, complete one of the following steps:
-
To add the Security Jakarta Persistence extension to an existing Maven project with Hibernate ORM, run the following command from your project base directory:
CLIquarkus extension add security-jpa
Maven./mvnw quarkus:add-extension -Dextensions='security-jpa'
Gradle./gradlew addExtension --extensions='security-jpa'
-
To add the Security Jakarta Persistence extension to an existing Maven project with Hibernate Reactive, run the following command from your project base directory:
CLIquarkus extension add security-jpa-reactive
Maven./mvnw quarkus:add-extension -Dextensions='security-jpa-reactive'
Gradle./gradlew addExtension --extensions='security-jpa-reactive'
-
1.2. Verify the quarkus-security-jpa dependency
After you have run either of the preceding commands to create the Maven
project, verify that the security-jpa
dependency was added to your project
build XML file.
-
To verify the
security-jpa
extension, check for the following configuration:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa</artifactId> </dependency>
build.gradleimplementation("io.quarkus:quarkus-security-jpa")
-
To verify the
security-jpa-reactive
extension, check for the following configuration:pom.xml<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa-reactive</artifactId> </dependency>
build.gradleimplementation("io.quarkus:quarkus-security-jpa-reactive")
2. Write the application
-
Secure the API endpoint to determine who can access the application by using one of the following approaches:
-
Implement the
/api/public
endpoint to allow all users access to the application. Add a regular Jakarta REST resource to your Java source code, as shown in the following code snippet:package org.acme.security.jpa; import jakarta.annotation.security.PermitAll; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path("/api/public") public class PublicResource { @GET @PermitAll @Produces(MediaType.TEXT_PLAIN) public String publicResource() { return "public"; } }
-
Implement the
/api/public
endpoint to allow all users to access the application. The source code for the/api/admin
endpoint is similar, but instead, you use a@RolesAllowed
annotation to ensure that only users granted theadmin
role can access the endpoint. Add a Jakarta REST resource with the following@RolesAllowed
annotation:package org.acme.security.jpa; import jakarta.annotation.security.RolesAllowed; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.Produces; import jakarta.ws.rs.core.MediaType; @Path("/api/admin") public class AdminResource { @GET @RolesAllowed("admin") @Produces(MediaType.TEXT_PLAIN) public String adminResource() { return "admin"; } }
-
Implement an
/api/users/me
endpoint that can only be accessed by users who have theuser
role. UseSecurityContext
to get access to the currently authenticatedPrincipal
user and to return their username, all of which is retrieved from the database.package org.acme.security.jpa; import jakarta.annotation.security.RolesAllowed; import jakarta.inject.Inject; import jakarta.ws.rs.GET; import jakarta.ws.rs.Path; import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.SecurityContext; @Path("/api/users") public class UserResource { @GET @RolesAllowed("user") @Path("/me") public String me(@Context SecurityContext securityContext) { return securityContext.getUserPrincipal().getName(); } }
-
2.1. Define the user entity
-
You can now describe how you want security information to be stored in the model by adding annotations to the
user
entity, as outlined in the following code snippet:
package org.acme.security.jpa;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.security.jpa.Password;
import io.quarkus.security.jpa.Roles;
import io.quarkus.security.jpa.UserDefinition;
import io.quarkus.security.jpa.Username;
@Entity
@Table(name = "test_user")
@UserDefinition (1)
public class User extends PanacheEntity {
@Username (2)
public String username;
@Password (3)
public String password;
@Roles (4)
public String role;
/**
* Adds a new user to the database
* @param username the username
* @param password the unencrypted password (it will be encrypted with bcrypt)
* @param role the comma-separated roles
*/
public static void add(String username, String password, String role) { (5)
User user = new User();
user.username = username;
user.password = BcryptUtil.bcryptHash(password);
user.role = role;
user.persist();
}
}
The security-jpa
extension initializes only if a single entity is
annotated with @UserDefinition
.
1 | The @UserDefinition annotation must be present on a single entity and can
be either a regular Hibernate ORM entity or a Hibernate ORM with Panache
entity. |
2 | Indicates the field used for the username. |
3 | Indicates the field used for the password. By default, it uses bcrypt-hashed passwords. You can configure it to use plain text or custom passwords. |
4 | Indicates the comma-separated list of roles added to the target principal representation attributes. |
5 | Allows us to add users while hashing passwords with the proper bcrypt hash. |
Hibernate Reactive Panache uses
io.quarkus.hibernate.reactive.panache.PanacheEntity instead of
io.quarkus.hibernate.orm.panache.PanacheEntity . For more information, see
User
file.
|
2.2. Configure the application
-
Enable the built-in Quarkus Basic authentication mechanism by setting the
quarkus.http.auth.basic
property totrue
:quarkus.http.auth.basic
=true`When secure access is required and no other authentication mechanisms are enabled, the built-in Basic authentication of Quarkus is the fallback authentication mechanism. Therefore, in this tutorial, you do not need to set the property
quarkus.http.auth.basic
totrue
. -
Configure at least one data source in the
application.properties
file so thesecurity-jpa
extension can access your database. For example:quarkus.http.auth.basic=true quarkus.datasource.db-kind=postgresql quarkus.datasource.username=quarkus quarkus.datasource.password=quarkus quarkus.datasource.jdbc.url=jdbc:postgresql:security_jpa quarkus.hibernate-orm.database.generation=drop-and-create
-
To initialize the database with users and roles, implement the
Startup
class, as outlined in the following code snippet:
|
package org.acme.security.jpa;
import jakarta.enterprise.event.Observes;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;
import io.quarkus.runtime.StartupEvent;
@Singleton
public class Startup {
@Transactional
public void loadUsers(@Observes StartupEvent evt) {
// reset and load all test users
User.deleteAll();
User.add("admin", "admin", "admin");
User.add("user", "user", "user");
}
}
The preceding example demonstrates how the application can be protected and identities provided by the specified database.
In a production environment, do not store plain text passwords. As a
result, the |
3. Test your application by using Dev Services for PostgreSQL
Complete the integration testing of your application in JVM and native modes by using Dev Services for PostgreSQL before you run your application in production mode.
-
To run your application in dev mode:
quarkus dev
./mvnw quarkus:dev
./gradlew --console=plain quarkusDev
-
The following properties configuration demonstrates how you can enable PostgreSQL testing to run in production (
prod
) mode only. In this scenario,Dev Services for PostgreSQL
launches and configures aPostgreSQL
test container.
%prod.quarkus.datasource.db-kind=postgresql
%prod.quarkus.datasource.username=quarkus
%prod.quarkus.datasource.password=quarkus
%prod.quarkus.datasource.jdbc.url=jdbc:postgresql:elytron_security_jpa
quarkus.hibernate-orm.database.generation=drop-and-create
-
If you add the
%prod.
profile prefix, data source properties are not visible toDev Services for PostgreSQL
and are only observed by an application running in production mode. -
To write the integration test, use the following code sample:
package org.acme.elytron.security.jpa;
import static io.restassured.RestAssured.get;
import static io.restassured.RestAssured.given;
import static org.hamcrest.core.Is.is;
import org.apache.http.HttpStatus;
import org.junit.jupiter.api.Test;
import io.quarkus.test.junit.QuarkusTest;
@QuarkusTest
public class JpaSecurityRealmTest {
@Test
void shouldAccessPublicWhenAnonymous() {
get("/api/public")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessAdminWhenAnonymous() {
get("/api/admin")
.then()
.statusCode(HttpStatus.SC_UNAUTHORIZED);
}
@Test
void shouldAccessAdminWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/admin")
.then()
.statusCode(HttpStatus.SC_OK);
}
@Test
void shouldNotAccessUserWhenAdminAuthenticated() {
given()
.auth().preemptive().basic("admin", "admin")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_FORBIDDEN);
}
@Test
void shouldAccessUserAndGetIdentityWhenUserAuthenticated() {
given()
.auth().preemptive().basic("user", "user")
.when()
.get("/api/users/me")
.then()
.statusCode(HttpStatus.SC_OK)
.body(is("user"));
}
}
As you can see in this code sample, you do not need to start the test container from the test code.
When you start your application in dev mode, |
3.1. Use Curl or a browser to test your application
-
Use the following example to start the PostgreSQL server:
docker run --rm=true --name security-getting-started -e POSTGRES_USER=quarkus \ -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=elytron_security_jpa \ -p 5432:5432 postgres:14.1
3.2. Compile and run the application
-
Compile and run your Quarkus application by using one of the following methods:
-
JVM mode
-
Compile the application:
CLIquarkus build
Maven./mvnw install
Gradle./gradlew build
-
Run the application:
java -jar target/quarkus-app/quarkus-run.jar
-
-
Native mode
-
Compile the application:
CLIquarkus build --native
Maven./mvnw install -Dnative
Gradle./gradlew build -Dquarkus.package.type=native
-
Run the application:
./target/security-jpa-quickstart-runner
-
-
3.3. Access and test the application security
When your application is running, you can access its endpoints by using one of the following Curl commands.
-
Connect to a protected endpoint anonymously:
$ 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
-
Connect to a protected endpoint anonymously:
$ curl -i -X GET http://localhost:8080/api/admin HTTP/1.1 401 Unauthorized Content-Length: 14 Content-Type: text/html;charset=UTF-8 WWW-Authenticate: Basic Not authorized
-
Connect to a protected endpoint as an authorized 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
You can also access the same endpoint URLs by using a browser.
If you use a browser to anonymously connect to a protected resource, a Basic authentication form displays, prompting you to enter credentials. |
3.4. Results
When you provide the credentials of an authorized user, for example,
admin:admin
, the Jakarta Persistence security extension authenticates and
loads the roles of the user. The admin
user is authorized to access the
protected resources.
If a resource is protected with @RolesAllowed("user")
, the user admin
is
not authorized to access the resource because it is not assigned to the
"user" role, as shown in the following shell example:
$ 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
Forbidden
Finally, the user named user
is authorized and the security context
contains the principal details, for example, the username.
$ 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
What’s next
Congratulations! You have learned how to create and test a secure Quarkus application by combining the built-in Basic authentication in Quarkus with the Jakarta Persistence identity provider.
After completing this tutorial, you can explore more advanced security
mechanisms in Quarkus. The following information shows you how to use
OpenID Connect
for secure single sign-on access to your Quarkus endpoints: