🏰 The Castle of Identity: Jakarta EE Security
Imagine your application is a magical castle. Not just anyone can walk in! You need guards, secret passwords, special badges, and clever rules to keep the bad guys out and let the good guys in.
🎭 What is Identity and Authorization?
Think of it like a birthday party:
- Identity = Knowing WHO is at the door (Is that really Timmy?)
- Authentication = Proving it’s really them (Timmy shows his invitation)
- Authorization = Deciding what they can do (Timmy can eat cake, but only the birthday kid can open presents!)
In Jakarta EE, we have powerful tools to do all of this!
🗄️ Identity Stores: The Guest Book
An Identity Store is like a magical guest book that remembers everyone who’s allowed in the castle.
graph TD A["User Arrives"] --> B{Check Identity Store} B --> C["Database Store"] B --> D["Custom Store"] C --> E["User Found?"] D --> E E -->|Yes| F["Welcome In!"] E -->|No| G["Go Away!"]
Simple Idea:
- Someone knocks on the door
- You check your guest book
- If their name is there with the right password, they can enter!
📊 Database Identity Store
This is like keeping your guest list in a notebook (database).
Real Example: Imagine a table in your notebook:
| Username | Password (hashed) | Groups |
|---|---|---|
| alice | abc123hash | admin |
| bob | xyz789hash | user |
Jakarta EE Code:
@DatabaseIdentityStoreDefinition(
dataSourceLookup = "java:comp/DefaultDataSource",
callerQuery =
"SELECT password FROM users WHERE username = ?",
groupsQuery =
"SELECT role FROM user_roles WHERE username = ?"
)
@ApplicationScoped
public class AppConfig {
// Configuration class
}
What’s happening here?
- We tell the system WHERE to find users (database)
- HOW to check passwords
- WHAT groups/roles they belong to
🎨 Custom Identity Store
Sometimes the notebook (database) isn’t enough. What if users log in with their fingerprint? Or a magic spell?
You build your OWN guest book!
@ApplicationScoped
public class MyCustomStore
implements IdentityStore {
@Override
public CredentialValidationResult validate(
Credential credential) {
UsernamePasswordCredential upc =
(UsernamePasswordCredential) credential;
String username = upc.getCaller();
String password = upc.getPasswordAsString();
// Your custom magic here!
if (checkMySpecialWay(username, password)) {
return new CredentialValidationResult(
username,
Set.of("user", "premium")
);
}
return CredentialValidationResult.INVALID_RESULT;
}
}
Think of it like:
- The default guest book checks names in a list
- YOUR custom guest book might check if they know a secret handshake!
👑 Role-Based Access Control (RBAC)
Imagine your castle has different rooms:
- Kitchen → Only cooks can enter
- Throne Room → Only kings and queens
- Garden → Everyone can visit!
graph TD A["User: Alice"] --> B{What Role?} B -->|admin| C["Can Access Everything"] B -->|user| D["Can Access Garden + Library"] B -->|guest| E["Can Only See Garden"]
The Magic Idea: Instead of checking “Is this Alice? Is this Bob?” every time, we ask: “Are they an ADMIN?”
Benefits:
- ✅ Easy to manage (change role, not 100 rules)
- ✅ Clear and simple
- ✅ Same rules for everyone in a group
📜 Declarative Security: The Easy Way
“Declarative” means you DECLARE the rules with simple labels (annotations).
It’s like putting signs on doors:
- 🚪 “ADMINS ONLY” sign on the Treasury
- 🚪 “USERS WELCOME” sign on the Library
@Path("/admin")
@RolesAllowed("admin")
public class AdminResource {
@GET
@Path("/secrets")
public String getSecrets() {
return "Top secret admin stuff!";
}
}
@Path("/public")
@PermitAll
public class PublicResource {
@GET
public String hello() {
return "Hello everyone!";
}
}
See how simple?
@RolesAllowed("admin")= Only admins allowed here!@PermitAll= Everyone can come in!
🔧 Programmatic Security: The Flexible Way
Sometimes signs on doors aren’t enough. What if:
- “Admins can see everything, but users can only see THEIR OWN stuff”
- “Premium users get extra features”
You write code to check!
@Path("/profile")
public class ProfileResource {
@Context
private SecurityContext securityContext;
@GET
@Path("/{userId}")
public String getProfile(
@PathParam("userId") String userId) {
// Get who's asking
String currentUser = securityContext
.getCallerPrincipal().getName();
// Admins see anyone's profile
if (securityContext.isCallerInRole("admin")) {
return getAnyProfile(userId);
}
// Users only see their own
if (currentUser.equals(userId)) {
return getAnyProfile(userId);
}
// Otherwise, no way!
throw new ForbiddenException("Not yours!");
}
}
The Power:
- You make decisions WHILE the code runs
- More control, more flexibility!
🏷️ Security Annotations: The Label Collection
Jakarta EE gives you a toolbox of labels:
| Annotation | What It Does |
|---|---|
@RolesAllowed |
Only these roles enter |
@PermitAll |
Everyone welcome! |
@DenyAll |
Nobody allowed (maybe for testing) |
@RunAs |
Pretend to be another role |
Example with all of them:
@Path("/api")
public class MixedResource {
@GET
@PermitAll
@Path("/welcome")
public String welcome() {
return "Hi friend!";
}
@GET
@RolesAllowed({"admin", "manager"})
@Path("/reports")
public String reports() {
return "Sales: $1,000,000";
}
@DELETE
@RolesAllowed("admin")
@Path("/everything")
public String deleteAll() {
return "Everything deleted!";
}
@GET
@DenyAll
@Path("/forbidden")
public String forbidden() {
return "You'll never see this!";
}
}
🛡️ CSRF Protection: Stop the Tricksters!
CSRF = Cross-Site Request Forgery
Imagine this sneaky trick:
- You’re logged into your bank
- A bad website secretly makes YOUR browser send money to the bad guy
- The bank thinks it’s really you!
graph TD A["You're Logged In] --> B[Bad Website] B -->|Secret Request| C[Your Bank] C -->|Thinks It's You!| D[Money Gone!"]
The Solution: Secret Tokens!
Like a secret handshake that changes every time:
<!-- In your form -->
<form method="POST" action="/transfer">
<input type="hidden"
name="csrf_token"
value="abc123xyz789">
<input type="text" name="amount">
<button>Send Money</button>
</form>
// Server checks the token
@POST
@Path("/transfer")
public Response transfer(
@FormParam("csrf_token") String token,
@FormParam("amount") int amount) {
if (!isValidCsrfToken(token)) {
return Response.status(403)
.entity("Nice try, trickster!")
.build();
}
// Safe to proceed!
return doTransfer(amount);
}
How tokens work:
- 🎫 Server gives you a SECRET token
- 📝 Your form includes this token
- ✅ Server checks: “Is this the token I gave YOU?”
- 🚫 Bad websites can’t guess your token!
🎯 Putting It All Together
Let’s build a mini secure castle:
// 1. Define where users come from
@DatabaseIdentityStoreDefinition(
dataSourceLookup = "java:comp/DefaultDataSource",
callerQuery = "SELECT password FROM users WHERE name = ?",
groupsQuery = "SELECT role FROM roles WHERE name = ?"
)
// 2. Set up authentication
@BasicAuthenticationMechanismDefinition(
realmName = "MyCastle"
)
@ApplicationScoped
public class SecuritySetup {}
// 3. Protect your resources
@Path("/castle")
public class CastleAPI {
@GET
@Path("/gate")
@PermitAll
public String gate() {
return "Welcome to the castle gates!";
}
@GET
@Path("/treasury")
@RolesAllowed("king")
public String treasury() {
return "Gold coins everywhere!";
}
@GET
@Path("/kitchen")
@RolesAllowed({"cook", "king"})
public String kitchen() {
return "Delicious food cooking!";
}
}
✨ Key Takeaways
- Identity Stores = Your guest list (database or custom)
- Database Store = Names in a notebook/database
- Custom Store = Your own special way to check
- RBAC = Group people by roles, not names
- Declarative = Put signs on doors (@RolesAllowed)
- Programmatic = Write code to decide
- Annotations = Your label toolbox
- CSRF Protection = Secret tokens stop tricksters
🚀 You Did It!
Now you know how to:
- ✅ Know WHO is knocking (Identity Stores)
- ✅ Group them smartly (Roles)
- ✅ Put up signs (Declarative)
- ✅ Make smart decisions (Programmatic)
- ✅ Stop sneaky tricks (CSRF)
Your castle is SECURE! 🏰🔐
