Start HereOrdered learning track

Jakarta Security Authentication Model

Learn Java Authentication Pattern - Part 007

Jakarta Security Authentication Model untuk engineer Java: HttpAuthenticationMechanism, IdentityStore, IdentityStoreHandler, CredentialValidationResult, SecurityContext, built-in mechanisms, custom mechanism, role/group mapping, dan failure mode production-grade.

15 min read2811 words
PrevNext
Lesson 0740 lesson track01–08 Start Here
#java#authentication#jakarta-security#jakarta-ee+4 more

Part 007 — Jakarta Security Authentication Model

Target part ini: kita memahami model autentikasi standar Jakarta EE, bukan hanya "cara login di server tertentu". Setelah bagian ini, kamu harus bisa membaca sistem Jakarta Security seperti membaca state machine: request masuk, mechanism membaca credential, identity store memvalidasi, container membentuk caller principal, aplikasi mengakses SecurityContext, lalu response dikembalikan dengan boundary yang jelas.

Jakarta Security berguna ketika aplikasi berjalan di dunia Jakarta EE: Payara, WildFly, Open Liberty, GlassFish, TomEE, atau runtime enterprise lain yang ingin tetap portable di atas standar.

Spring Security sangat kuat dan dominan di ekosistem Spring. Tetapi di Jakarta EE, standar resminya adalah Jakarta Security. Mental model-nya berbeda:

Spring Security:
    application-owned security pipeline
    banyak keputusan ada di konfigurasi aplikasi Spring

Jakarta Security:
    container-integrated security pipeline
    aplikasi mendefinisikan mechanism/store/context hook
    container ikut mengelola caller identity dan role mapping

Yang penting: Jakarta Security bukan sekadar API login. Ia adalah kontrak antara aplikasi dan container untuk autentikasi HTTP, validasi credential, dan akses security context.


1. Masalah yang Diselesaikan Jakarta Security

Sebelum Jakarta Security, aplikasi Jakarta/Java EE sering punya beberapa jalur autentikasi yang tersebar:

AreaMasalah lama
web.xml login configdeclarative, tetapi sulit dikustomisasi secara application-native
Container realmkuat, tetapi vendor-specific
JAASlower-level, historis, tidak nyaman untuk HTTP web apps modern
Servlet filter customfleksibel, tetapi sering tidak terintegrasi dengan caller principal container
JAX-RS filterterlambat dalam pipeline untuk banyak use case web security
Framework sendiriportability rendah, risk inconsistent context

Jakarta Security memberi abstraksi standar untuk:

  1. HTTP authentication mechanism
    Bagaimana request membawa credential dan bagaimana response challenge dikirim.

  2. Identity store
    Bagaimana credential divalidasi dan group/caller attribute didapat.

  3. Security context
    Bagaimana aplikasi melihat caller yang sudah diautentikasi.

  4. Container integration
    Bagaimana principal dan roles masuk ke mekanisme security Jakarta EE.

Jika Spring Security bertanya:

Filter apa yang harus saya pasang?
AuthenticationProvider mana yang memvalidasi credential?
SecurityContextRepository mana yang menyimpan context?

Jakarta Security bertanya:

HttpAuthenticationMechanism apa yang membaca request?
IdentityStore mana yang memvalidasi caller?
SecurityContext apa yang terlihat oleh application code?
Bagaimana container mengikat identity itu ke request?

2. Arsitektur Besar

Model minimal:

HTTP request
  -> authentication mechanism extracts credential
  -> identity store validates credential
  -> validation result contains caller + groups
  -> container establishes authenticated caller
  -> application reads SecurityContext

Yang harus dipegang:

HttpAuthenticationMechanism bukan database user.
IdentityStore bukan HTTP filter.
SecurityContext bukan tempat validasi password.
Masing-masing punya boundary.


3. Komponen Inti

KomponenTugasJangan digunakan untuk
HttpAuthenticationMechanismMembaca request, membuat credential, mengirim challenge/failure/successQuery user database langsung kecuali sangat sederhana
CredentialRepresentasi credential yang dibawa requestMenyimpan identity permanen
IdentityStoreValidasi credential dan provide groupsMengatur HTTP response
IdentityStoreHandlerMenggabungkan satu atau banyak identity storeMenjadi business service
CredentialValidationResultHasil validasi callerMenyimpan session/token jangka panjang
SecurityContextAPI aplikasi untuk melihat caller dan roleMengganti access-control service kompleks
ContainerMengikat identity ke request dan role modelMenjadi domain identity source of truth

4. Lifecycle Request

Di sini ada beberapa titik keputusan:

TitikPertanyaan
Request tidak punya credentialPublic resource? challenge? continue?
Credential malformed400? 401? generic error?
Credential validCaller dibuat, group dipetakan
Credential invalidGeneric failure; jangan leak apakah user ada
Account disabled/lockedSecara eksternal tetap generic
Store unavailableFail closed untuk protected resource
Multiple mechanismsMechanism selection harus deterministic

5. Authentication Status: Jangan Semua Dianggap Boolean

HTTP authentication bukan true/false. Dalam Jakarta Security, mechanism mengembalikan status yang mewakili langkah pipeline.

Model konseptual:

StatusArti operasional
SUCCESSAuthentication berhasil dan container dapat melanjutkan request
SEND_CONTINUEMechanism sudah mengirim intermediate response/challenge; request belum lanjut ke app
SEND_FAILUREMechanism sudah mengirim failure response
NOT_DONEMechanism tidak menyelesaikan autentikasi; container/request handling lanjut sesuai konteks

Mental model:

Production bug umum muncul ketika engineer menyederhanakan semua ke boolean:

if authenticated -> continue
else -> 401

Padahal:

  • public endpoint boleh tidak authenticated;
  • login redirect butuh SEND_CONTINUE;
  • OIDC flow bisa multi-step;
  • preflight CORS tidak boleh dipaksa login;
  • invalid credential dan missing credential punya response strategy berbeda;
  • failure response harus tidak membocorkan detail account.

6. HttpAuthenticationMechanism

HttpAuthenticationMechanism adalah adapter HTTP.

Tanggung jawabnya:

  1. membaca credential dari request;
  2. memutuskan apakah request butuh authentication attempt;
  3. membuat Credential;
  4. meminta validasi ke IdentityStoreHandler atau store;
  5. memberitahu container jika login berhasil;
  6. mengirim challenge/failure jika perlu.

Pseudocode mental:

@ApplicationScoped
public class LoginAuthenticationMechanism implements HttpAuthenticationMechanism {

    @Inject
    private IdentityStoreHandler identityStoreHandler;

    @Override
    public AuthenticationStatus validateRequest(
            HttpServletRequest request,
            HttpServletResponse response,
            HttpMessageContext context) throws AuthenticationException {

        if (!isLoginSubmission(request)) {
            return context.doNothing();
        }

        UsernamePasswordCredential credential = new UsernamePasswordCredential(
                request.getParameter("username"),
                request.getParameter("password")
        );

        CredentialValidationResult result = identityStoreHandler.validate(credential);

        if (result.getStatus() == CredentialValidationResult.Status.VALID) {
            return context.notifyContainerAboutLogin(
                    result.getCallerPrincipal(),
                    result.getCallerGroups()
            );
        }

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return context.responseUnauthorized();
    }
}

Ini contoh konseptual. Dalam production, jangan langsung percaya request.getParameter() tanpa validasi size, encoding, method, content type, CSRF, rate limit, audit event, dan generic error semantics.

Boundary yang benar

HttpAuthenticationMechanism boleh tahu:

  • endpoint login;
  • HTTP method;
  • header/cookie/form field;
  • challenge response;
  • redirect target;
  • apakah credential dikirim;
  • apakah result valid/invalid.

HttpAuthenticationMechanism sebaiknya tidak tahu:

  • struktur kompleks domain account;
  • kebijakan password hash;
  • SQL detail;
  • risk scoring detail;
  • business role derivation yang kompleks;
  • provisioning identity.

Kenapa? Karena mechanism adalah transport layer authentication adapter. Begitu ia memuat banyak business logic, kamu sulit memakai credential yang sama dari Basic, Form, API key, atau OIDC.


7. IdentityStore

IdentityStore adalah boundary validasi credential.

Pertanyaan yang dijawab:

Apakah credential ini membuktikan caller X?
Jika valid, group apa yang melekat pada caller?

Identity store bukan selalu database. Bisa:

StoreUse case
Database identity storeusername/password internal
LDAP identity storeenterprise directory
Custom identity storeaccount service, external verifier, legacy IAM
Remember-me identity storetoken persistent untuk remember-me
OIDC-backed identityclaims dari provider federasi

Contoh custom store:

@ApplicationScoped
public class ApplicationIdentityStore implements IdentityStore {

    @Inject
    AccountCredentialRepository credentials;

    @Inject
    PasswordVerifier passwordVerifier;

    @Override
    public CredentialValidationResult validate(Credential credential) {
        if (!(credential instanceof UsernamePasswordCredential usernamePassword)) {
            return CredentialValidationResult.NOT_VALIDATED_RESULT;
        }

        String login = normalizeLogin(usernamePassword.getCaller());
        AccountCredentialRecord record = credentials.findActivePasswordCredential(login)
                .orElse(null);

        if (record == null) {
            // tetap lakukan dummy hash di production untuk mengurangi timing signal
            passwordVerifier.verifyAgainstDummyHash(usernamePassword.getPasswordAsString());
            return CredentialValidationResult.INVALID_RESULT;
        }

        boolean valid = passwordVerifier.verify(
                usernamePassword.getPasswordAsString(),
                record.passwordHash()
        );

        if (!valid) {
            return CredentialValidationResult.INVALID_RESULT;
        }

        Set<String> groups = credentials.findGroups(record.accountId());

        return new CredentialValidationResult(
                new CallerPrincipal(record.accountId().toString()),
                groups
        );
    }
}

Production notes:

  • Caller principal sebaiknya memakai stable opaque subject id, bukan email.
  • Login identifier boleh email/username/phone, tetapi principal internal sebaiknya immutable.
  • groups jangan otomatis dianggap permission granular.
  • Store harus membedakan internal reason, tetapi response eksternal tetap generic.
  • Store harus mendukung credential migration, misalnya rehash setelah successful login.

8. CredentialValidationResult

CredentialValidationResult adalah kontrak hasil validasi.

Secara konseptual:

VALID:
    caller principal diketahui
    optional groups diketahui

INVALID:
    credential dikenali sebagai credential type yang didukung
    tetapi proof gagal

NOT_VALIDATED:
    store tidak menangani credential type ini

Perbedaan INVALID dan NOT_VALIDATED penting ketika ada banyak store.

Contoh:

Store A: username/password database
Store B: API key
Store C: LDAP

Request membawa API key.
Store A -> NOT_VALIDATED
Store B -> VALID/INVALID
Store C -> NOT_VALIDATED

Jika semua store yang tidak mengenal credential mengembalikan INVALID, chain bisa gagal terlalu cepat.

Invariant

A store must not say INVALID merely because the credential is not its type.
A store says INVALID when the credential is its type, but proof is wrong.
A store says NOT_VALIDATED when it cannot evaluate that credential type.

9. IdentityStoreHandler: Komposisi Store

Dalam sistem enterprise, identity jarang berasal dari satu sumber.

Pertanyaan desain:

  1. Apakah satu store validasi credential, store lain provide groups?
  2. Apakah LDAP groups dimapping ke application roles?
  3. Apakah account lokal harus linked dengan external directory identity?
  4. Apakah failure di satu store harus fail closed atau coba store lain?
  5. Apakah ordering deterministic?

Contoh production rule:

ScenarioRecommended behavior
Password store downProtected login fail closed; jangan fallback ke weaker path
LDAP group store downLogin bisa fail closed jika role wajib; jangan beri default admin/user
API key malformedInvalid segera, tidak perlu cek LDAP
Credential type tidak cocokNOT_VALIDATED
Multiple valid storesHindari; identity linking harus eksplisit

10. SecurityContext

Aplikasi tidak seharusnya membaca session raw atau header raw untuk tahu siapa caller. Di Jakarta Security, aplikasi membaca SecurityContext.

Contoh:

@Path("/me")
@RequestScoped
public class MeResource {

    @Inject
    SecurityContext securityContext;

    @GET
    @Produces(MediaType.APPLICATION_JSON)
    public Response me() {
        Principal caller = securityContext.getCallerPrincipal();

        if (caller == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        return Response.ok(Map.of(
                "subject", caller.getName(),
                "employee", securityContext.isCallerInRole("employee"),
                "admin", securityContext.isCallerInRole("admin")
        )).build();
    }
}

Gunakan SecurityContext untuk:

  • mengambil caller principal;
  • mengecek role coarse-grained;
  • memicu authentication programmatically pada kasus tertentu;
  • integrasi container security.

Jangan gunakan SecurityContext sebagai satu-satunya authorization engine untuk domain kompleks seperti:

Can investigator A escalate case B from stage X to stage Y?
Can tenant admin invite user across legal entity boundary?
Can merchant support agent refund order above threshold?

Untuk itu, gunakan authorization layer terpisah. Authentication menjawab siapa caller, bukan semua keputusan apa yang boleh dilakukan.


11. Group vs Role vs Permission

Jakarta Security dan container security sering berbicara tentang groups dan roles.

Jangan samakan semuanya.

IstilahArti praktis
Caller principalidentity yang sudah diautentikasi
Groupmembership dari identity source, misalnya LDAP group atau application group
Roleabstraction yang dipakai container/application untuk access check
Permissionaksi granular di domain
Entitlementhak akses terstruktur, sering resource-aware
Scopedelegated access string, sering di OAuth/OIDC

Mapping yang sehat:

Anti-pattern:

LDAP group CN=Payroll-Europe-L2 langsung digunakan sebagai permission domain.

Lebih sehat:

External group -> normalized application role -> policy decision -> domain action

Contoh mapping:

External groupApplication roleDomain permission
ldap:corp-support-l1support_agentview ticket, add note
ldap:corp-support-l2support_supervisorassign ticket, escalate ticket
ldap:corp-security-adminsecurity_admindisable account, revoke sessions

Authentication layer cukup membawa caller + normalized group/role claim. Authorization layer memutuskan aksi.


12. Built-in Authentication Mechanisms

Jakarta Security menyediakan mechanism standar melalui annotation, tergantung versi/platform.

Kategori penting:

MechanismCocok untukCatatan
Basic authenticationinternal API sederhana, test, machine constrained environmentjangan pakai untuk browser user login modern tanpa alasan kuat
Form authenticationaplikasi web klasikbutuh CSRF, session rotation, generic error, rate limit
Custom form authenticationketika form flow perlu customizationhati-hati redirect dan saved request
OpenID Connect authenticationfederated login / SSOperlu state, nonce, issuer validation, session binding
Custom mechanismAPI key, HMAC, legacy SSO, special tenant flowharus menjaga invariant pipeline

Contoh declarative form:

@FormAuthenticationMechanismDefinition(
    loginToContinue = @LoginToContinue(
        loginPage = "/login.xhtml",
        errorPage = "/login-error.xhtml"
    )
)
@ApplicationScoped
public class SecurityConfiguration {
}

Contoh database store declarative:

@DatabaseIdentityStoreDefinition(
    dataSourceLookup = "java:global/jdbc/AuthDS",
    callerQuery = "select password_hash from auth_user where login = ? and status = 'ACTIVE'",
    groupsQuery = "select group_name from auth_user_group where login = ?",
    hashAlgorithm = Pbkdf2PasswordHash.class,
    priority = 10
)
@ApplicationScoped
public class IdentityStoreConfiguration {
}

Catatan: annotation-based store cocok untuk kasus sederhana. Untuk production account lifecycle yang kaya, custom IdentityStore sering lebih jelas karena kamu perlu:

  • account status state machine;
  • credential version;
  • password hash migration;
  • risk signal;
  • lockout dan rate limit;
  • tenant-aware lookup;
  • audit event;
  • recovery state;
  • passkey/MFA binding.

13. Custom Mechanism: API Key Contoh Minimal

Kita akan bahas API key detail di Part 019. Di sini cukup untuk memahami Jakarta Security boundary.

Credential object:

public final class ApiKeyCredential implements Credential {
    private final String keyPrefix;
    private final String secret;

    public ApiKeyCredential(String keyPrefix, String secret) {
        this.keyPrefix = keyPrefix;
        this.secret = secret;
    }

    public String keyPrefix() {
        return keyPrefix;
    }

    public String secret() {
        return secret;
    }
}

Mechanism:

@ApplicationScoped
public class ApiKeyAuthenticationMechanism implements HttpAuthenticationMechanism {

    @Inject
    IdentityStoreHandler identityStoreHandler;

    @Override
    public AuthenticationStatus validateRequest(
            HttpServletRequest request,
            HttpServletResponse response,
            HttpMessageContext context) throws AuthenticationException {

        String header = request.getHeader("X-API-Key");

        if (header == null || header.isBlank()) {
            return context.doNothing();
        }

        ApiKeyCredential credential = parseApiKey(header);
        CredentialValidationResult result = identityStoreHandler.validate(credential);

        if (result.getStatus() == CredentialValidationResult.Status.VALID) {
            return context.notifyContainerAboutLogin(
                    result.getCallerPrincipal(),
                    result.getCallerGroups()
            );
        }

        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
        return context.responseUnauthorized();
    }

    private ApiKeyCredential parseApiKey(String header) {
        int dot = header.indexOf('.');
        if (dot <= 0 || dot == header.length() - 1) {
            throw new IllegalArgumentException("Malformed API key");
        }
        return new ApiKeyCredential(header.substring(0, dot), header.substring(dot + 1));
    }
}

Store:

@ApplicationScoped
public class ApiKeyIdentityStore implements IdentityStore {

    @Inject
    ApiKeyRepository apiKeys;

    @Inject
    SecretVerifier secretVerifier;

    @Override
    public CredentialValidationResult validate(Credential credential) {
        if (!(credential instanceof ApiKeyCredential apiKey)) {
            return CredentialValidationResult.NOT_VALIDATED_RESULT;
        }

        ApiKeyRecord record = apiKeys.findByPrefix(apiKey.keyPrefix()).orElse(null);
        if (record == null) {
            secretVerifier.verifyAgainstDummySecret(apiKey.secret());
            return CredentialValidationResult.INVALID_RESULT;
        }

        if (!record.active()) {
            return CredentialValidationResult.INVALID_RESULT;
        }

        if (!secretVerifier.verify(apiKey.secret(), record.secretHash())) {
            return CredentialValidationResult.INVALID_RESULT;
        }

        return new CredentialValidationResult(
                new CallerPrincipal("service:" + record.serviceAccountId()),
                Set.of("service_client", "api_key_client")
        );
    }
}

Important: jangan simpan API key plaintext. Simpan hash secret, gunakan prefix untuk lookup, dan lakukan constant-time verification sebisa mungkin.


14. Jakarta Security dengan JAX-RS

JAX-RS resource biasanya berada setelah container authentication.

Di resource:

@Path("/orders")
public class OrderResource {

    @Inject
    SecurityContext securityContext;

    @Inject
    OrderApplicationService orders;

    @POST
    @Consumes(MediaType.APPLICATION_JSON)
    public Response create(CreateOrderRequest request) {
        Principal principal = securityContext.getCallerPrincipal();
        if (principal == null) {
            return Response.status(Response.Status.UNAUTHORIZED).build();
        }

        OrderId orderId = orders.createOrder(
                SubjectId.parse(principal.getName()),
                request
        );

        return Response.status(Response.Status.CREATED)
                .entity(Map.of("orderId", orderId.value()))
                .build();
    }
}

Pattern yang benar:

SecurityContext -> SubjectId -> Domain Command -> Authorization/Policy -> Business execution

Anti-pattern:

SecurityContext.isCallerInRole("admin") tersebar di setiap resource method.

Untuk domain kompleks, buat adapter:

@RequestScoped
public class CurrentSubject {

    @Inject
    SecurityContext securityContext;

    public Optional<SubjectId> subjectId() {
        Principal principal = securityContext.getCallerPrincipal();
        if (principal == null) {
            return Optional.empty();
        }
        return Optional.of(SubjectId.parse(principal.getName()));
    }

    public boolean hasContainerRole(String role) {
        return securityContext.isCallerInRole(role);
    }
}

Lalu resource tidak tahu detail container:

SubjectId subject = currentSubject.subjectId()
        .orElseThrow(UnauthenticatedException::new);

15. Session, Stateless, dan Jakarta Security

Jakarta Security tidak memaksa semua sistem stateful atau stateless. Tetapi mechanism tertentu punya konsekuensi.

PatternStateCocok untuk
Form loginbiasanya stateful sessionbrowser app server-rendered
Basicstateless per requestinternal/simple API; harus TLS
API keystateless per requestmachine-to-machine
OIDC loginbrowser session + IdP token lifecycleSSO web app
Bearer token customstateless validation atau opaque introspectionAPI/resource server

Stateful session flow:

Stateless credential flow:

Production decision:

RequirementPrefer
Browser server-side appsession cookie
SPA with BFFBFF session cookie; token hidden from browser JS
Public APIOAuth/OIDC access token
Internal servicemTLS, workload identity, client credentials, HMAC depending constraints
Legacy partner APIAPI key/HMAC with rotation and replay defense

16. Failure Mode: Context Tidak Konsisten

Salah satu bug paling mahal adalah identity terlihat berbeda di layer berbeda.

Contoh:

HTTP header says user A
SecurityContext says user B
Domain command says user C
Audit log says user D

Ini biasanya terjadi karena:

  • custom filter membuat sendiri principal tetapi tidak notify container;
  • JAX-RS filter membaca JWT sendiri tanpa sinkron dengan container;
  • ThreadLocal tidak dibersihkan;
  • async task membawa identity lama;
  • reverse proxy menambahkan header identity yang dipercaya app;
  • session fixation setelah login;
  • tenant id berasal dari path, principal berasal dari token, tetapi tidak divalidasi relasinya.

Invariant:

For one request, there must be exactly one authenticated subject view.
All downstream layers must derive from the same canonical subject source.

Praktik sehat:

HTTP credential -> container auth -> SecurityContext -> CurrentSubject -> domain command metadata -> audit event

Jangan:

Controller membaca X-User-Id header langsung.
Service membaca session langsung.
Repository membaca tenant context global.
Audit membaca MDC userId yang di-set filter lain.

17. Failure Mode: Group Drift

Group/role sering berubah lebih cepat daripada session.

Scenario:

08:00 user login, mendapat group "admin"
09:00 admin privilege dicabut di directory
09:05 user masih punya session lama
09:10 user melakukan destructive action

Pilihan desain:

StrategyKelebihanKekurangan
Store groups in sessioncepatstale privilege
Reload groups per requestfreshlatency tinggi, dependency store
Cache groups short TTLbalancecomplexity invalidation
Token short expirylimits stale windowrefresh complexity
Back-channel revocationkuatoperational complexity

Production-grade system biasanya menggabungkan:

  • short session/role cache TTL untuk role sensitif;
  • explicit privilege version di account;
  • session invalidation saat role berubah;
  • audit event untuk role change;
  • step-up auth untuk destructive actions.

18. Failure Mode: Mechanism Ambiguity

Jakarta Security 4.0 memperkenalkan dukungan lebih baik untuk multiple HTTP authentication mechanisms melalui handler. Tetapi dari sudut desain, multiple mechanisms tetap berbahaya jika tidak punya routing jelas.

Contoh buruk:

Form login, Basic auth, API key, OIDC, dan custom header semua aktif untuk semua path.

Risiko:

  • credential downgrade;
  • unexpected auth path;
  • API client mendapat HTML login page;
  • browser request diterima via Basic;
  • machine credential diterima di user endpoint;
  • audit tidak tahu mechanism mana yang dipakai.

Desain lebih sehat:

PathMechanism
/ui/**form/OIDC session
/api/public/**no auth or optional auth
/api/user/**bearer/session depending architecture
/api/partner/**HMAC/API key
/internal/**mTLS/service identity
/admin/**OIDC + MFA/step-up

Mermaid:

Invariant:

Authentication mechanism selection must be explicit, deterministic, observable, and testable.

19. Failure Mode: Login Error Leaks

Jakarta Security memberi API, tetapi tidak otomatis menyelamatkan desain error kamu.

Login response yang buruk:

"User not found"
"Password incorrect"
"Account is disabled"
"Your LDAP account exists but has no group"

Ini mempercepat account enumeration dan targeted attack.

Response eksternal yang lebih sehat:

"Invalid username or password."

Internal audit tetap detail:

{
  "eventType": "AUTH_LOGIN_FAILED",
  "reason": "ACCOUNT_DISABLED",
  "subjectCandidateHash": "...",
  "ip": "203.0.113.10",
  "userAgentHash": "...",
  "mechanism": "FORM",
  "tenantId": "tenant_123"
}

Boundary:

AudienceDetail
User responsegeneric
Security logstructured and specific
Metricsaggregate reason codes
Alertinganomaly/rate based
Support toolcontrolled visibility with audit

20. Failure Mode: Jakarta API Dipakai untuk Semua IAM

Jakarta Security bukan full IAM platform.

Ia tidak otomatis menyediakan:

  • full user lifecycle management;
  • account registration policy;
  • password reset design;
  • MFA orchestration lengkap;
  • device management;
  • passkey management;
  • identity proofing;
  • risk scoring;
  • tenant lifecycle;
  • consent management;
  • SCIM provisioning;
  • advanced token exchange;
  • enterprise federation governance.

Gunakan Jakarta Security sebagai application authentication integration layer.

Untuk sistem besar, architecture-nya sering:

Jakarta app tidak harus menjadi identity platform. Ia bisa menjadi relying party/resource server yang menerima identity dari IdP.


21. Testing Jakarta Security

Minimal test matrix:

TestTujuan
valid logincaller principal terbentuk
invalid passwordresponse generic
unknown userresponse sama dengan invalid password
disabled accountresponse generic, audit specific
public endpointtidak memaksa login
protected endpointchallenge/401
role checkgroup mapping benar
group changestale privilege behavior terdefinisi
store downfail closed
multiple mechanismspath routing deterministic
logoutsession/context cleared
async executionidentity tidak bocor

Contoh black-box test:

@Test
void invalidUserAndWrongPasswordHaveSameExternalShape() {
    HttpResponse<String> unknownUser = postLogin("unknown@example.com", "whatever");
    HttpResponse<String> wrongPassword = postLogin("alice@example.com", "wrong");

    assertEquals(401, unknownUser.statusCode());
    assertEquals(401, wrongPassword.statusCode());
    assertEquals(normalize(unknownUser.body()), normalize(wrongPassword.body()));
}

Contoh role assertion:

@Test
void authenticatedCallerReceivesExpectedRole() {
    Session session = login("operator@example.com", "correct-password");

    HttpResponse<String> response = get("/ops/dashboard", session.cookie());

    assertEquals(200, response.statusCode());
}

22. Production Checklist

Mechanism

  • mechanism selection explicit by route/client type;
  • public endpoints do not trigger login loops;
  • API endpoints do not return HTML login unexpectedly;
  • malformed credentials handled safely;
  • all failed login responses are generic;
  • credential extraction has size limits;
  • CORS preflight handled before auth challenge where appropriate;
  • CSRF enforced for browser form/session flows;
  • redirect targets validated;
  • audit event includes mechanism name.

Store

  • store returns NOT_VALIDATED for unsupported credential type;
  • store returns INVALID for supported but wrong credential;
  • no plaintext secret stored;
  • dummy verification used where timing leak matters;
  • account status handled as internal reason only;
  • password hash migration planned;
  • groups normalized before use;
  • tenant boundary included in lookup;
  • store unavailable behavior is fail closed;
  • security-relevant changes emit audit events.

Context

  • application reads canonical identity from SecurityContext or a wrapper;
  • no controller trusts X-User-Id from external clients;
  • subject id is stable and opaque;
  • tenant id and subject relationship validated;
  • audit subject matches SecurityContext subject;
  • async execution does not leak caller;
  • logout clears session/context;
  • role/group staleness is bounded.

23. Design Drill

Desain Jakarta Security untuk aplikasi case management internal:

Requirement:

- UI browser memakai enterprise OIDC.
- Admin endpoint butuh step-up MFA di IdP.
- Partner API memakai HMAC request signing.
- Internal service endpoint memakai mTLS.
- Semua audit event harus punya subject, tenant, mechanism, assurance level.
- Role external dari IdP harus dimapping ke role internal.

Jawaban yang diharapkan bukan satu class besar.

Model yang sehat:

Key invariant:

Every mechanism must normalize into one canonical subject model.

24. Apa yang Harus Diingat

Jakarta Security membuat autentikasi Jakarta EE menjadi lebih terstruktur, tetapi ia tidak menggantikan desain identity yang benar.

Ringkasnya:

HttpAuthenticationMechanism = HTTP adapter
Credential = proof container
IdentityStore = proof verifier + group provider
CredentialValidationResult = result contract
SecurityContext = application view of authenticated caller
Container = binding layer between auth result and request execution

Production engineer harus menjaga invariant:

  1. Satu request punya satu canonical subject.
  2. Credential extraction tidak dicampur dengan domain identity lifecycle.
  3. Store membedakan unsupported credential vs invalid credential.
  4. Error eksternal generic, log internal specific.
  5. Role/group mapping explicit dan tidak stale tanpa batas.
  6. Multi-mechanism routing deterministic.
  7. Jakarta Security dipakai sebagai integration layer, bukan seluruh IAM platform.

25. Referensi

  • Jakarta Security 4.0 Specification — https://jakarta.ee/specifications/security/4.0/jakarta-security-spec-4.0
  • Jakarta Security 4.0 Release Page — https://jakarta.ee/specifications/security/4.0/
  • Jakarta Security API: OpenIdAuthenticationMechanismDefinitionhttps://jakarta.ee/specifications/security/4.0/apidocs/jakarta.security/jakarta/security/enterprise/authentication/mechanism/http/openidauthenticationmechanismdefinition
  • Jakarta Security API: FormAuthenticationMechanismDefinitionhttps://jakarta.ee/specifications/security/4.0/apidocs/jakarta.security/jakarta/security/enterprise/authentication/mechanism/http/formauthenticationmechanismdefinition
  • Jakarta Security Identity Store package — https://javadoc.io/static/jakarta.security.enterprise/jakarta.security.enterprise-api/4.0.0-M2/jakarta.security/jakarta/security/enterprise/identitystore/package-summary.html
  • Jakarta Security UsernamePasswordCredential API — https://jakarta.ee/specifications/security/3.0/apidocs/jakarta.security/jakarta/security/enterprise/credential/usernamepasswordcredential
  • OWASP Authentication Cheat Sheet — https://cheatsheetseries.owasp.org/cheatsheets/Authentication_Cheat_Sheet.html
Lesson Recap

You just completed lesson 07 in start here. Use the series map if you want to review the broader track, or continue directly into the next lesson while the context is still warm.

Continue The Track

Keep the momentum while the lesson is still fresh. Move backward for review or continue forward into the next concept.