Quarkus Security 2 - HTTP Basic Auth dengan JDBC
Kita akan membahas bagaimana Quarkus dapat menggunakan database untuk menyimpan provider identitas pengguna dari database ke HTTP Basic Auth.
1. Persiapan
Untuk menyelesaikan panduan ini, kita memerlukan:
- Waktu sekitar 15 menit
- IDE IntelliJ IDEA
- JDK 17+ yang terinstal dengan JAVA_HOME yang dikonfigurasi dengan benar
- Apache Maven 3.9.9
- Opsional: Quarkus CLI jika ingin menggunakannya
- Opsional: Mandrel atau GraalVM yang terinstal dan dikonfigurasi dengan benar jika ingin membangun executable native (atau Docker jika menggunakan build kontainer native)
2. Arsitektur
Dalam contoh ini, kita membangun sebuah mikroservis yang sangat sederhana yang menawarkan tiga endpoint:
/api/public
/api/users/me
/api/admin
Endpoint /api/public
dapat diakses secara anonim. Endpoint /api/admin
dilindungi dengan RBAC (Role-Based Access Control) di mana hanya
pengguna yang diberikan peran admin yang dapat mengaksesnya. Pada
endpoint ini, kita menggunakan anotasi @RolesAllowed
untuk secara deklaratif menegakkan batasan akses. Endpoint /api/users/me
juga dilindungi dengan RBAC, di mana hanya pengguna yang diberikan
peran user yang dapat mengaksesnya. Sebagai respons, endpoint ini
mengembalikan dokumen JSON yang berisi detail tentang pengguna.
3. Solusi
Kita dapat langsung menggunakan contoh yang telah selesai dengan clone repositori Git berikut dengan perintah:
git clone https://github.com/quarkusio/quarkus-quickstarts.git
Clone via IntelliJ IDEA |
Project setelah di Load ke IDE |
Loaded Projects |
4. Persiapkan Database Table
CREATE TABLE test_user (
id INT,
username VARCHAR(255),
password VARCHAR(255),
role VARCHAR(255)
);
INSERT INTO test_user (id, username, password, role) VALUES (1, 'admin', '$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'admin');
INSERT INTO test_user (id, username, password, role) VALUES (2, 'user', '$2a$10$Uc.SZ0hvGJQlYdsAp7be1.lFjmOnc7aAr4L0YY3/VN3oK.F8zJHRG', 'user');
SQL Query data Users |
Data users di database |
Kita menggunakan PostgreSQL sebagai penyimpanan data, dan kita menginisialisasi basis data dengan pengguna dan role. Kita akan menggunakan versi kata sandi yang di-hash dan disalin sebagai kata sandi dalam contoh ini. Kita dapat menggunakan kelas BcryptUtil untuk menghasilkan kata sandi dalam format Modular Crypt Format (MCF).
5. Hasil di Browser
User: admin/user, pass: password.
User login user di database |
User berhasil Login |
Konfigurasi Keamanan JDBC Quarkus
Properti Utama
Properti | Tipe | Default | Deskripsi |
---|---|---|---|
quarkus.security.jdbc.realm-name | string | Quarkus | Nama realm untuk otentikasi |
quarkus.security.jdbc.enabled | boolean | false | Mengaktifkan penyimpanan properti |
quarkus.security.jdbc.principal-query.sql | string | - | Kueri SQL untuk menemukan password |
quarkus.security.jdbc.principal-query.datasource | string | - | Sumber data untuk kueri |
Pemetaan Password Clear Text
Properti | Tipe | Default | Deskripsi |
---|---|---|---|
quarkus.security.jdbc.principal-query.clear-password-mapper.enabled | boolean | false | Aktifkan pemetaan password clear text |
quarkus.security.jdbc.principal-query.clear-password-mapper.password-index | int | 1 | Indeks kolom password |
Pemetaan Bcrypt Password
Properti | Tipe | Default | Deskripsi |
---|---|---|---|
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled | boolean | false | Aktifkan pemetaan password bcrypt |
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index | int | 0 | Indeks kolom hash password |
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.hash-encoding | string | base64 | Encoding hash (BASE64/HEX) |
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index | int | -1 | Indeks kolom salt |
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-encoding | string | base64 | Encoding salt (BASE64/HEX) |
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index | int | -1 | Indeks jumlah iterasi |
Code dari Applikasi Diatas
1. Class PublicResource (Controller)
Kode ini mendefinisikan dua endpoint: satu untuk mengakses informasi publik dan satu lagi untuk mendapatkan nama pengguna yang sedang masuk. Endpoint ini dapat digunakan dalam aplikasi yang memerlukan otentikasi dan otorisasi, dengan memberikan informasi yang sesuai berdasarkan status login pengguna.
package org.acme.security.jdbc;
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";
}
}
Penjelasan Kode Class PublicResource:
-
Class Declaration:
@Path("/api/public")
: Menentukan bahwa kelas ini akan menangani permintaan yang dimulai dengan/api/public
.
-
Endpoint
/api/public
:@GET
: Menandakan bahwa metode ini akan menangani permintaan HTTP GET.@Produces(MediaType.TEXT_PLAIN)
: Menentukan bahwa metode ini akan mengembalikan respons dalam format teks biasa.public String publicResource()
: Metode ini mengembalikan string "public" ketika endpoint ini diakses.
-
Endpoint
/api/public/me
:@GET
: Menandakan bahwa metode ini juga akan menangani permintaan HTTP GET.@Path("/me")
: Menentukan path tambahan untuk endpoint ini.@Produces(MediaType.TEXT_PLAIN)
: Menentukan bahwa metode ini juga akan mengembalikan respons dalam format teks biasa.public String me(@Context SecurityContext securityContext)
: Metode ini menerima objekSecurityContext
sebagai parameter, yang memberikan informasi tentang keamanan saat ini.Principal user = securityContext.getUser Principal();
: Mengambil objekPrincipal
yang mewakili pengguna yang sedang masuk.return user != null ? user.getName() : "<not logged in>";
: Mengembalikan nama pengguna jika pengguna terautentikasi, atau string "<not logged in>" jika tidak ada pengguna yang terautentikasi.
2. Kode Class AdminResource (Controller)
package org.acme.security.jdbc;
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";
}
}
Penjelasan Kode AdminResource:
-
Anotasi Kelas:
@Path("/api/admin")
- Menentukan base path untuk semua endpoint di kelas ini artinya semua endpoint akan diawali dengan/api/admin
-
Endpoint Admin:
Hanya user dengan role "admin" yang bisa mengakses endpoint ini Jika user tanpa role "admin" mencoba akses akan mendapatkan response HTTP 403 Forbidden, dan jika user belum login mencoba akses akan mendapatkan response HTTP 401 Unauthorized Detail Implementasi:
@GET
- Menandakan ini endpoint metode HTTP GET@RolesAllowed("admin")
- Membatasi akses hanya untuk user dengan role "admin"- Ini adalah bagian dari RBAC (Role-Based Access Control)
@Produces(MediaType.TEXT_PLAIN)
- Menentukan response berupa teks plain (bukan JSON/XML)- Method mengembalikan string sederhana "admin"
3. Kode Class UserResource (Controller)
package org.acme.security.jdbc;
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.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
@Path("/api/users")
public class UserResource {
@GET
@RolesAllowed("user")
@Path("/me")
@Produces(MediaType.TEXT_PLAIN)
public String me(@Context SecurityContext securityContext) {
return securityContext.getUserPrincipal().getName();
}
}
Penjelasan Mendetail:
-
Endpoint
/me
:@GET
- Endpoint HTTP GET@RolesAllowed("user")
- Hanya boleh diakses oleh user dengan role "user"@Path("/me")
- Membentuk endpoint lengkap/api/users/me
- Parameter
SecurityContext
- Memberikan akses ke informasi keamanan Fungsi Utama: - Mengembalikan nama user yang sedang login
- Menggunakan getUserPrincipal().getName() untuk mendapatkan identitas user
- Tidak perlu pengecekan null karena: Anotasi @RolesAllowed sudah memastikan user terautentikasi
- Jika belum login, akan otomatis mengembalikan 401 Unauthorized
Alur Kerja:
- Client mengakses /api/users/me
- Sistem memverifikasi: Apakah user sudah login?
- Apakah user memiliki role "user"?
- Jika valid: Mengembalikan nama user
- Jika tidak valid: Mengembalikan kode error (401 atau 403)
Best Practice yang Diterapkan:
- Pembatasan akses dengan
@RolesAllowed
- Penggunaan
SecurityContext
untuk mendapatkan info user - Organisasi path API yang konsisten
- Nama endpoint yang deskriptif (
/me
untuk data user yang login)
4. Kode Class User (Entity Model)
Model entitas User
ini menyediakan cara yang efisien untuk
mengelola pengguna dalam aplikasi Quarkus dengan menggunakan Elytron
untuk keamanan dan Hibernate ORM untuk interaksi basis data. Dengan
metode add
, kita dapat dengan mudah menambahkan pengguna
baru dengan kata sandi yang aman. Endpoint REST yang disediakan
memungkinkan pengguna untuk mendaftar dengan mudah.
package org.acme.elytron.security.jpa;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import io.quarkus.elytron.security.common.BcryptUtil;
import io.quarkus.hibernate.orm.panache.PanacheEntity;
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
public class User extends PanacheEntity {
@Username
public String username;
@Password
public String password;
@Roles
public String role;
/**
* Adds a new user in the database
* @param username the user name
* @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) {
User user = new User();
user.username = username;
user.password = BcryptUtil.bcryptHash(password);
user.role = role;
user.persist();
}
}
Penjelasan Kode:
-
Paket dan Impor:
- Kode ini berada dalam paket
org.acme.elytron.security.jpa
. - Mengimpor berbagai kelas dari Jakarta Persistence, Quarkus Elytron, dan Hibernate ORM.
- Kode ini berada dalam paket
-
Anotasi Entitas:
@Entity
: Menandakan bahwa kelas ini adalah entitas JPA yang akan dipetakan ke tabel dalam basis data.@Table(name = "test_user")
: Menentukan nama tabel dalam basis data yang akan digunakan untuk menyimpan entitas ini.@User Definition
: Menandakan bahwa kelas ini mendefinisikan pengguna untuk keperluan otentikasi.
-
Atribut Kelas:
@Username
: Menandakan bahwa atributusername
adalah nama pengguna yang digunakan untuk otentikasi.@Password
: Menandakan bahwa atributpassword
adalah kata sandi yang akan di-hash.@Roles
: Menandakan bahwa atributrole
menyimpan peran pengguna.
-
Metode Statis
add
:- Metode ini digunakan untuk menambahkan pengguna baru ke dalam basis data.
- Menerima parameter
username
,password
, danrole
. - Kata sandi yang diterima akan di-hash menggunakan
BcryptUtil.bcryptHash(password)
. - Setelah itu, objek
User
baru akan disimpan ke dalam basis data dengan memanggiluser.persist()
.
Konfigurasi dari Aplikasi
1. Mengonfigurasi Aplikasi
Ekstensi elytron-security-jdbc
memerlukan setidaknya satu datasource untuk mengakses basis data.
Berikut adalah konfigurasi yang diperlukan untuk menggunakan PostgreSQL sebagai penyimpanan identitas (application.properties):
quarkus.datasource.db-kind=postgresql
quarkus.datasource.username=postgres
quarkus.datasource.password=postgres
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/elytron_security_jdbc
Penjelasan Konfigurasi:
-
quarkus.datasource.db-kind=postgresql
- Menentukan jenis basis data yang digunakan, dalam hal ini PostgreSQL.
-
quarkus.datasource.username=quarkus
- Menentukan nama pengguna yang digunakan untuk mengakses basis data.
-
quarkus.datasource.password=quarkus
- Menentukan kata sandi untuk pengguna yang ditentukan.
-
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/elytron_security_jdbc
- Menentukan URL JDBC untuk menghubungkan aplikasi ke basis data PostgreSQL. Dalam hal ini, nama basis data yang digunakan adalah
elytron-security-jdbc
.
- Menentukan URL JDBC untuk menghubungkan aplikasi ke basis data PostgreSQL. Dalam hal ini, nama basis data yang digunakan adalah
2. Konteks Penggunaan
Dalam konteks ini, kita menggunakan PostgreSQL
sebagai penyimpanan identitas, dan kita menginisialisasi basis data
dengan pengguna dan peran. Kita akan menggunakan versi kata sandi yang
di-hash dan disalin sebagai kata sandi dalam contoh ini. Kita dapat
menggunakan kelas BcryptUtil
untuk menghasilkan kata sandi dalam format Modular Crypt Format (MCF).
3. Contoh Penggunaan BcryptUtil
Untuk menghasilkan kata sandi yang aman, dapat menggunakan kelas BcryptUtil
seperti berikut:
import org.jboss.security.bcrypt.BcryptUtil;...
Penjelasan:
- Kode di atas menggunakan
BcryptUtil
untuk menghasilkan hash dari kata sandi yang diberikan. - Hash ini dapat disimpan dalam basis data sebagai kata sandi yang aman.
Dengan konfigurasi ini, kita siap untuk menggunakan PostgreSQL sebagai penyimpanan identitas dengan dukungan untuk otentikasi dan otorisasi berbasis peran. Pastikan untuk menginisialisasi basis data dengan pengguna dan peran yang sesuai agar aplikasi dapat berfungsi dengan baik.
Saat ini kita bisa mengonfigurasi Elytron JDBC Realm.
Konfigurasi Elytron - application.properties
quarkus.security.jdbc.enabled=true
quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM test_user u WHERE u.username=?
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index=-1
quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index=-1
quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2
quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups
Berikut adalah terjemahan dan susunan dari konfigurasi yang Anda berikan:
quarkus.security.jdbc.enabled=true
: Mengaktifkan realm JDBC untuk keamanan.quarkus.security.jdbc.principal-query.sql=SELECT u.password, u.role FROM test_user u WHERE u.username=?
: Mendefinisikan kueri SQL utama untuk otentikasi. Kueri ini mengambil kata sandi (password
) dan peran (role
) dari tabeltest_user
berdasarkanusername
.quarkus.security.jdbc.principal-query.bcrypt-password-mapper.enabled=true
: Mengaktifkan pemetaan kata sandi dengan algoritma bcrypt. Ini penting untuk membandingkan kata sandi yang dimasukkan pengguna dengan hash kata sandi yang tersimpan.quarkus.security.jdbc.principal-query.bcrypt-password-mapper.password-index=1
: Menunjukkan bahwa kolom kata sandi (u.password
) berada pada indeks 1 dari hasil kueri SQL.quarkus.security.jdbc.principal-query.bcrypt-password-mapper.salt-index=-1
: Menunjukkan bahwa salt (nilai acak yang ditambahkan ke kata sandi sebelum di-hash) tidak disimpan di kolom terpisah. Nilai -1 berarti salt disimpan sebagai bagian dari hash kata sandi dalam format Modular Crypt Format (MCF).quarkus.security.jdbc.principal-query.bcrypt-password-mapper.iteration-count-index=-1
: Menunjukkan bahwa jumlah iterasi tidak disimpan di kolom terpisah. Nilai -1 berarti nilai ini juga tersimpan di dalam hash kata sandi MCF.quarkus.security.jdbc.principal-query.attribute-mappings.0.index=2
: Memetakan kolom pada indeks 2 dari hasil kueri (u.role
).quarkus.security.jdbc.principal-query.attribute-mappings.0.to=groups
: Mengaitkan kolom peran (u.role
) ke atributgroups
yang digunakan dalam representasi user. Ini memungkinkan Elytron untuk memuat peran pengguna setelah otentikasi berhasil.
Penjelasan dan Konteks Tambahan
Ekstensi elytron-security-jdbc
memerlukan setidaknya satu kueri utama (principal query
) untuk mengotentikasi dan memuat identitas pengguna. Kueri ini harus berupa pernyataan SQL berparameter (menggunakan ?
) dan mengembalikan kata sandi pengguna beserta informasi tambahan yang Anda butuhkan (seperti peran).
Password Mapper dikonfigurasi untuk menunjukkan posisi kolom kata sandi di hasil kueri. Karena nilai salt-index
dan iteration-count-index
diatur ke -1, hash kata sandi disimpan dalam format Modular Crypt Format (MCF). Anda bisa mengubahnya jika salt dan jumlah iterasi disimpan di kolom yang berbeda.
Properti attribute-mappings
digunakan untuk menghubungkan kolom-kolom yang dipilih dalam kueri (u.role
) dengan atribut yang akan diwakili dalam objek user (groups
).
Penting: Dalam konfigurasi principal-query
, properti index
dimulai dari 1, bukan 0.
Sumber Belajar
https://quarkus.io/guides/security-jdbc
Comments
Post a Comment