Getting started with security
Abstract
Providing feedback on Red Hat build of Quarkus documentation
To report an error or to improve our documentation, log in to your Red Hat Jira account and submit an issue. If you do not have a Red Hat Jira account, then you will be prompted to create an account.
Procedure
- Click the following link to create a ticket.
- Enter a brief description of the issue in the Summary.
- Provide a detailed description of the issue or enhancement in the Description. Include a URL to where the issue occurs in the documentation.
- Clicking Submit creates and routes the issue to the appropriate documentation team.
Making open source more inclusive
Red Hat is committed to replacing problematic language in our code, documentation, and web properties. We are beginning with these four terms: master, slave, blacklist, and whitelist. Because of the enormity of this endeavor, these changes will be implemented gradually over several upcoming releases. For more details, see our CTO Chris Wright’s message.
Chapter 1. Getting started with Security by 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, 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 to implement more advanced security mechanisms in Quarkus, for example, how to use the OpenID Connect (OIDC) authentication mechanism.
1.1. Prerequisites
To complete this guide, you need:
- Roughly 15 minutes
- An IDE
-
JDK 17+ installed with
JAVA_HOME
configured appropriately - Apache Maven 3.8.6 or later
- 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)
1.2. Building your application
This tutorial gives detailed steps for creating an application with endpoints that illustrate various authorization policies:
Endpoint | Description |
---|---|
| Accessible without authentication, this endpoint allows anonymous access. |
|
Secured with role-based access control (RBAC), this endpoint is accessible only to users with the |
|
Also secured by RBAC, this endpoint is accessible only to users with the |
1.3. 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 in this tutorial includes the quarkus-security-jpa
extension.
Hibernate ORM with Panache is used to store your user identities, but you can also use Hibernate ORM with the quarkus-security-jpa
extension.
You must also add your preferred database connector library. The instructions in this example tutorial use a PostgreSQL database for the identity store.
1.3.1. Create the Maven project
You can create a new Maven project with the Security Jakarta Persistence extension or add the extension to an existing Maven project. You can use either Hibernate ORM or Hibernate Reactive.
1.3.1.1. Creating new Maven project
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:
Using the Quarkus CLI:
quarkus create app org.acme:security-jpa-quickstart \ --extension='security-jpa,jdbc-postgresql,rest,hibernate-orm-panache' \ --no-code cd security-jpa-quickstart
To create a Gradle project, add the
--gradle
or--gradle-kotlin-dsl
option.For more information about how to install and use the Quarkus CLI, see the Quarkus CLI guide.
Using Maven:
mvn com.redhat.quarkus.platform:quarkus-maven-plugin:3.15.1:create \ -DprojectGroupId=org.acme \ -DprojectArtifactId=security-jpa-quickstart \ -Dextensions='security-jpa,jdbc-postgresql,rest,hibernate-orm-panache' \ -DnoCode cd security-jpa-quickstart
To create a Gradle project, add the
-DbuildTool=gradle
or-DbuildTool=gradle-kotlin-dsl
option.
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"
1.3.1.2. Adding Jakarta Persistence extension to existing project
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:
Using the Quarkus CLI:
quarkus extension add security-jpa
Using Maven:
./mvnw quarkus:add-extension -Dextensions='security-jpa'
Using Gradle:
./gradlew addExtension --extensions='security-jpa'
1.3.2. Verify the quarkus-security-jpa dependency
After you have run either of the preceding commands to create the Maven project, verify that the quarkus-security-jpa
dependency was added to your project build XML file.
To verify the
quarkus-security-jpa
extension, check for the following configuration:Using Maven:
<dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-security-jpa</artifactId> </dependency>
Using Gradle:
implementation("io.quarkus:quarkus-security-jpa")
1.4. 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 an /api/admin endpoint that can only be accessed by users who have the admin role. 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(); } }
1.5. 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 is 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 quarkus-security-jpa
extension only initializes if a single entity is annotated with @UserDefinition
.
- 1
- The
@UserDefinition
annotation must be present on a single entity, 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.
Don’t forget to set up the Panache and PostgreSQL JDBC driver, please see Setting up and configuring Hibernate ORM with Panache for more information.
1.6. 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
NoteWhen 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 thequarkus-security-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 quarkus-security-jpa
defaults to using bcrypt-hashed passwords.
1.7. 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.
Start by adding the following dependencies to your test project:
Using Maven:
<dependency> <groupId>io.rest-assured</groupId> <artifactId>rest-assured</artifactId> <scope>test</scope> </dependency>
Using Gradle:
testImplementation("io.rest-assured:rest-assured")
To run your application in dev mode:
Using the Quarkus CLI:
quarkus dev
Using Maven:
./mvnw quarkus:dev
Using Gradle:
./gradlew --console=plain quarkusDev
The following properties configuration demonstrates how to enable PostgreSQL testing to run only in production (prod
) mode. In this scenario, Dev Services for PostgreSQL
launches and configures a PostgreSQL
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://localhost/quarkus quarkus.hibernate-orm.database.generation=drop-and-create
If you add the %prod.
profile prefix, data source properties are not visible to Dev 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.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, Dev Services for PostgreSQL launches a PostgreSQL dev mode container so that you can start developing your application. While developing your application, you can add and run tests individually by using the Continuous Testing feature. Dev Services for PostgreSQL supports testing while you develop by providing a separate PostgreSQL test container that does not conflict with the dev mode container.
1.8. Test your application using Curl or browser
To test your application using Curl or the browser, you must first start a PostgreSQL server, then compile and run your application either in JVM or native mode.
1.8.1. Start the PostgreSQL server
docker run --rm=true --name security-getting-started -e POSTGRES_USER=quarkus \ -e POSTGRES_PASSWORD=quarkus -e POSTGRES_DB=quarkus \ -p 5432:5432 postgres:14.1
1.8.2. Compile and run the application
Compile and run your Quarkus application by using one of the following methods:
JVM mode
Compile the application:
Using the Quarkus CLI:
quarkus build
Using Maven:
./mvnw install
Using Gradle:
./gradlew build
Run the application:
java -jar target/quarkus-app/quarkus-run.jar
Native mode
Compile the application:
Using the Quarkus CLI:
quarkus build --native
Using Maven:
./mvnw install -Dnative
Using Gradle:
./gradlew build -Dquarkus.native.enabled=true
Run the application:
./target/security-jpa-quickstart-1.0.0-SNAPSHOT-runner
1.8.3. Access and test the application security with Curl
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.
1.8.4. Access and test the application security with the browser
If you use a browser to connect to a protected resource anonymously, a Basic authentication form displays, prompting you to enter credentials.
1.8.5. Results
When you provide the credentials of an authorized user, for example, admin:admin
, the Jakarta Persistence security extension authenticates and loads the user’s roles. 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 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
1.9. What’s next
You have successfully learned how to create and test a secure Quarkus application. This was achieved by integrating 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:
1.10. References
Chapter 2. Quarkus Security with Jakarta Persistence
You can configure your application to use Jakarta Persistence to store users' identities.
Quarkus provides a Jakarta Persistence identity provider, similar to the JDBC identity provider. Jakarta Persistence is suitable for use with the Basic and Form-based Quarkus Security mechanisms, which require username and password credentials.
The Jakarta Persistence IdentityProvider
creates a SecurityIdentity
instance. During user authentication, this instance is used to verify and authorize access requests.
For a practical example, see the Getting started with Security using Basic authentication and Jakarta Persistence tutorial.
2.1. Jakarta Persistence entity specification
Quarkus security offers a Jakarta Persistence integration to collect usernames, passwords, and roles and store them into Jakarta Persistence database entities.
The following Jakarta Persistence entity specification demonstrates how users' information needs to be stored in a Jakarta Persistence entity and correctly mapped so that Quarkus can retrieve this information from a database.
-
The
@UserDefinition
annotation must be present on a Jakarta Persistence entity, regardless of whether simplified Hibernate ORM with Panache is used or not. -
The
@Username
and@Password
field types are alwaysString
. -
The
@Roles
field must either beString
,Collection<String>
, or aCollection<X>
, whereX
is an entity class with a singleString
field annotated as@RolesValue
. -
Each
String
role element type is parsed as a comma-separated list of roles.
The following example demonstrates storing security information by adding annotations to the user
entity:
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 is 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 quarkus-security-jpa
extension initializes only if a single entity is annotated with @UserDefinition
.
- 1
- The
@UserDefinition
annotation must be present on a single entity, 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,
quarkus-security-jpa
uses bcrypt-hashed passwords, or you can configure plain text or custom passwords instead. - 4
- This indicates the comma-separated list of roles added to the target principal representation attributes.
- 5
- This method lets you add users while hashing passwords with the proper
bcrypt
hash.
2.2. Jakarta Persistence entity as storage of roles
Use the following example to store roles inside another Jakarta Persistence entity:
@UserDefinition @Table(name = "test_user") @Entity public class User extends PanacheEntity { @Username public String name; @Password public String pass; @ManyToMany @Roles public List<Role> roles = new ArrayList<>(); } @Entity public class Role extends PanacheEntity { @ManyToMany(mappedBy = "roles") public List<User> users; @RolesValue public String role; }
This example demonstrates storing and accessing roles. To update an existing user or create a new one, annotate public List<Role> roles
with @Cascade(CascadeType.ALL)
or choose a specific CascadeType
.
2.3. Password storage and hashing
When developing applications with Quarkus, you can decide how to manage password storage and hashing. You can keep the default password and hashing settings of Quarkus, or you can hash passwords manually.
With the default option, passwords are stored and hashed with bcrypt under the Modular Crypt Format (MCF). While using MCF, the hashing algorithm, iteration count, and salt are stored as a part of the hashed value. As such, we do not need dedicated columns to keep them.
In cryptography, a salt is a name for random data used as an additional input to a one-way function that hashes data, a password, or a passphrase.
To represent passwords stored in the database that were hashed by different algorithms, create a class that implements org.wildfly.security.password.PasswordProvider
as shown in the following example.
The following snippet shows how to set a custom password provider that represents a password that was hashed with the SHA256 hashing algorithm.
import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; import jakarta.persistence.Table; import io.quarkus.security.jpa.Password; import io.quarkus.security.jpa.PasswordType; import io.quarkus.security.jpa.Roles; import io.quarkus.security.jpa.UserDefinition; import io.quarkus.security.jpa.Username; @UserDefinition @Table(name = "test_user") @Entity public class CustomPasswordUserEntity { @Id @GeneratedValue public Long id; @Column(name = "username") @Username public String name; @Column(name = "password") @Password(value = PasswordType.CUSTOM, provider = CustomPasswordProvider.class) public String pass; @Roles public String role; }
import jakarta.xml.bind.DatatypeConverter; import org.wildfly.security.password.Password; import org.wildfly.security.password.interfaces.SimpleDigestPassword; import io.quarkus.security.jpa.PasswordProvider; public class CustomPasswordProvider implements PasswordProvider { @Override public Password getPassword(String passwordInDatabase) { byte[] digest = DatatypeConverter.parseHexBinary(passwordInDatabase); // Let the security runtime know that this passwordInDatabase is hashed by using the SHA256 hashing algorithm return SimpleDigestPassword.createRaw(SimpleDigestPassword.ALGORITHM_SIMPLE_DIGEST_SHA_256, digest); } }
To quickly create a hashed password, use String BcryptUtil.bcryptHash(String password)
, which defaults to creating a random salt and hashing in ten iterations. This method also allows specifying the number of iterations and salt used.
For applications running in a production environment, do not store passwords as plain text.
However, it is possible to store passwords as plain text with the @Password(PasswordType.CLEAR)
annotation when operating in a test environment.
The Hibernate Multitenancy is supported, and you can store the user entity in a persistence unit with enabled multitenancy. However, if your io.quarkus.hibernate.orm.runtime.tenant.TenantResolver
must access the io.vertx.ext.web.RoutingContext
to resolve request details, you must disable proactive authentication. For more information about proactive authentication, see the Quarkus Proactive authentication guide.
Configuration property fixed at build time - All other configuration properties are overridable at runtime
Configuration property | Type | Default |
Selects the Hibernate ORM persistence unit. Default persistence unit is used when no value is specified.
Environment variable: | string |
|