🔸 TLDR
▪️ Java EE (Jakarta Security) lets you secure apps by combining Identity Stores (where users/roles come from) + Authentication Mechanisms (how users prove who they are).
▪️ Start simple (DB + BASIC/FORM), then evolve (LDAP, custom stores, custom auth flows) without rewriting your whole app. ✅

🔸 THE TWO BUILDING BLOCKS (MENTAL MODEL) 🧠
▪️ Identity Store = “Where are my users, passwords, and roles?” (DB, LDAP, custom…)
▪️ Auth Mechanism = “How does the user authenticate?” (BASIC, FORM, custom…)
▪️ Jakarta Security wires them together so your app code stays clean.
🔸 IDENTITY STORES: DATABASE (RELATIONAL) 🗄️
Typical setup: one table for users + one for roles (or a join table).
import jakarta.security.enterprise.identitystore.DatabaseIdentityStoreDefinition; import jakarta.security.enterprise.identitystore.PasswordHash; @DatabaseIdentityStoreDefinition( dataSourceLookup = "java:app/jdbc/securityDS", callerQuery = "SELECT password FROM app_user WHERE username = ?", groupsQuery = "SELECT role FROM app_user_role WHERE username = ?", hashAlgorithm = PasswordHash.class ) public class SecurityConfig {}
▪️ Your container executes the queries, verifies the password hash, and loads roles into the security context.
▪️ Bonus: this scales well for classic enterprise apps with existing schemas.
🔸 IDENTITY STORES: LDAP DIRECTORY 📇
Great when identities are centralized (company directory, SSO ecosystem, org-wide roles).
import jakarta.security.enterprise.identitystore.LdapIdentityStoreDefinition; @LdapIdentityStoreDefinition( url = "ldap://ldap.company.local:389", callerBaseDn = "ou=people,dc=company,dc=local", groupBaseDn = "ou=groups,dc=company,dc=local", callerNameAttribute = "uid", groupSearchFilter = "(&(member={0})(objectClass=groupOfNames))" ) public class SecurityConfig {}
▪️ LDAP becomes your source of truth for users + group membership.
▪️ Ideal for internal apps: less password management in your app.
🔸 IDENTITY STORES: CUSTOM (WHEN REAL LIFE GETS MESSY) 🧰
When you need API calls, multi-tenant rules, legacy systems, or non-standard credential checks.
import jakarta.security.enterprise.credential.UsernamePasswordCredential; import jakarta.security.enterprise.identitystore.CredentialValidationResult; import jakarta.security.enterprise.identitystore.IdentityStore; import java.util.Set; public class CustomIdentityStore implements IdentityStore { @Override public CredentialValidationResult validate(UsernamePasswordCredential cred) { // 1) load user from anywhere (API, DB, cache…) // 2) verify password/hash // 3) return roles boolean ok = check(cred.getCaller(), cred.getPasswordAsString()); return ok ? new CredentialValidationResult(cred.getCaller(), Set.of("USER", "ADMIN")) : CredentialValidationResult.INVALID_RESULT; } private boolean check(String username, String rawPassword) { // your custom verification return true; } }
▪️ Keep it small: validate credentials + return roles.
▪️ Put the heavy logic behind a service (so you can test it).
🔸 AUTHENTICATION MECHANISMS: BASIC (FASTEST TO START) ⚡
Perfect for APIs, internal tools, and quick prototypes.
import jakarta.security.enterprise.authentication.mechanism.http.BasicAuthenticationMechanismDefinition; @BasicAuthenticationMechanismDefinition( realmName = "my-realm" ) public class SecurityConfig {}
▪️ Works great over HTTPS.
▪️ Browser UX is basic (pun intended 😄), but API clients love it.
🔸 AUTHENTICATION MECHANISMS: FORM (CLASSIC WEB APPS) 🧑💻
For server-rendered apps where you want a login page.
import jakarta.security.enterprise.authentication.mechanism.http.FormAuthenticationMechanismDefinition; @FormAuthenticationMechanismDefinition( loginToContinue = @jakarta.security.enterprise.authentication.mechanism.http.LoginToContinue( loginPage = "/login.xhtml", errorPage = "/login-error.xhtml" ) ) public class SecurityConfig {}
▪️ You control the pages.
▪️ The container handles redirects + security context creation.
🔸 CUSTOM FORM AUTH (FULL CONTROL OVER THE FLOW) 🛠️
Use this when you want extra steps: CAPTCHA, MFA prompts, tenant selection, “magic link”, etc.
import jakarta.security.enterprise.authentication.mechanism.http.HttpAuthenticationMechanism; import jakarta.security.enterprise.authentication.mechanism.http.AuthenticationParameters; import jakarta.security.enterprise.SecurityContext; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import jakarta.enterprise.context.ApplicationScoped; import jakarta.inject.Inject; @ApplicationScoped public class CustomFormAuth implements HttpAuthenticationMechanism { @Inject SecurityContext securityContext; @Override public jakarta.security.enterprise.AuthenticationStatus validateRequest( HttpServletRequest request, HttpServletResponse response, jakarta.security.enterprise.authentication.mechanism.http.HttpMessageContext context) { if ("/login".equals(request.getServletPath()) && "POST".equalsIgnoreCase(request.getMethod())) { String user = request.getParameter("username"); String pass = request.getParameter("password"); return context.notifyContainerAboutLogin( securityContext.authenticate(request, response, AuthenticationParameters.withParams().credential(new jakarta.security.enterprise.credential.UsernamePasswordCredential(user, pass))) ); } return context.doNothing(); } }
▪️ You decide when/how to trigger authentication.
▪️ Still reuse Identity Stores for validation + role loading.
🔸 TAKEAWAYS ✅
▪️ Identity Store answers “who are you + what roles do you have?”
▪️ Auth Mechanism answers “how do you prove it?”
▪️ Start with DB + BASIC/FORM, then move to LDAP for centralized identities.
▪️ Use custom store for legacy/complex rules, and custom mechanism for advanced UX/security flows.
▪️ Keep security testable: push logic into services, keep the mechanism/store thin.
#Java #JavaEE #JakartaEE #JakartaSecurity #Security #Authentication #Authorization #LDAP #EnterpriseJava #SoftwareEngineering
Go further with Java certification:
Java👇
Spring👇
SpringBook👇
JavaBook👇