Definition
- RBAC (Role-Based Access Control) – grants permissions to named roles such as CUSTOMER, TELLER, or ADMIN; users inherit permissions by being assigned roles.
- ABAC (Attribute-Based Access Control) – evaluates dynamic policies over attributes of the subject, the resource, the action, and the environment at decision time.
- RBAC decision – reduces to
does this user hold a role that carries this permission? - ABAC decision – evaluates
given these attribute values, does the policy allow this action? - Role explosion – the RBAC anti-pattern where encoding context-dependent rules requires an unmanageable number of separate roles.
In RBAC, National Bank of India gives every teller the same master key that opens all teller counters: your key depends only on your job title. In ABAC, the door reads a live rule each time: open only if this employee works at this branch, it is between 9am and 5pm, and the customer is not flagged. The ABAC door looks at many live facts, not just the job title. RBAC asks what is your role; ABAC asks given everything true right now, should this specific action be allowed.
Understanding the Concepts
What is RBAC?
Role-Based Access Control assigns permissions to named roles, and users get permissions by holding roles. The access decision is simple: does the user have a role that carries the required permission?
In Spring Security, RBAC is implemented with hasRole or hasAuthority checks. Roles are coarse labels like CUSTOMER, TELLER, and ADMIN. Permissions are fine-grained actions tied to roles.
package com.bank.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableMethodSecurity
public class RbacSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(auth -> auth
.requestMatchers("/api/v1/accounts/**").hasAnyRole("CUSTOMER", "TELLER", "ADMIN")
.requestMatchers("/api/v1/transfers/approve").hasRole("TELLER")
.requestMatchers("/api/v1/admin/**").hasRole("ADMIN")
.anyRequest().authenticated()
);
return http.build();
}
}
Rajesh (CUSTOMER) --> GET /api/v1/accounts/SAV2001 --> 200 OK
Rajesh (CUSTOMER) --> POST /api/v1/transfers/approve --> 403 Forbidden
Priya (TELLER) --> POST /api/v1/transfers/approve --> 200 OK
What is ABAC?
Attribute-Based Access Control evaluates a policy expression against attributes of the subject (user), the resource (data), the action (operation), and the environment (time, location). The decision is not just about role but about the full context.
In Spring Security, simple ABAC can be expressed with SpEL in @PreAuthorize. For richer policies, teams use a policy engine such as Open Policy Agent (OPA).
package com.bank.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class TransferApprovalService {
// ABAC: teller's branch must match transfer's branch,
// amount must be under teller's daily limit,
// and action must happen during business hours.
@PreAuthorize(
"hasRole('TELLER') " +
"and #transfer.branch == authentication.principal.branch " +
"and #transfer.amount.compareTo(authentication.principal.dailyLimit) <= 0"
)
public void approveTransfer(TransferRequest transfer) {
System.out.println("Transfer approved: " + transfer.getAmount() + " INR");
}
}
Priya (TELLER, branch=Mumbai, dailyLimit=100000)
approveTransfer(branch=Mumbai, amount=50000) --> approved
approveTransfer(branch=Delhi, amount=50000) --> 403 (branch mismatch)
approveTransfer(branch=Mumbai, amount=200000) --> 403 (over daily limit)
How is RBAC different from ABAC?
| Aspect | RBAC | ABAC |
|---|---|---|
| Decision basis | Role membership only | Subject + resource + action + environment attributes |
| Granularity | Coarse (job title level) | Fine (specific record, time, location) |
| Complexity | Simple to implement and audit | Complex policies, needs policy engine |
| Flexibility | Static role-to-permission mapping | Dynamic, context-aware decisions |
| Scalability | Role explosion with many conditions | Scales with attributes, not roles |
| Example | "Is user a TELLER?" | "Is user a TELLER in this branch, during hours, under limit?" |
| Spring Security | hasRole('TELLER') | SpEL expressions or OPA integration |
package com.bank.security;
import java.math.BigDecimal;
import java.time.LocalTime;
public class RbacVsAbacDemo {
// RBAC: simple role check
public boolean canApproveRbac(String role) {
return "TELLER".equals(role);
}
// ABAC: attribute-based check
public boolean canApproveAbac(String role, String userBranch,
String transferBranch, BigDecimal amount,
BigDecimal dailyLimit, LocalTime currentTime) {
return "TELLER".equals(role)
&& userBranch.equals(transferBranch)
&& amount.compareTo(dailyLimit) <= 0
&& currentTime.isAfter(LocalTime.of(9, 0))
&& currentTime.isBefore(LocalTime.of(17, 0));
}
}
What is role explosion?
Role explosion is the RBAC anti-pattern where encoding context-dependent rules requires creating a separate role for every combination. To encode "teller in Mumbai branch during business hours with a 100K limit" you would need TELLER_MUMBAI_DAY_100K, TELLER_DELHI_DAY_100K, TELLER_MUMBAI_NIGHT_200K, and so on.
With ABAC, these conditions are separate attributes evaluated at runtime, so you need only one role (TELLER) and the policy handles the rest.
// Role explosion in RBAC:
// TELLER_MUMBAI_DAY_100K
// TELLER_MUMBAI_NIGHT_200K
// TELLER_DELHI_DAY_100K
// TELLER_DELHI_NIGHT_200K
// ... 50+ roles for 5 branches x 2 shifts x varying limits
// ABAC eliminates this:
// Role: TELLER
// Attributes: branch=Mumbai, shift=day, dailyLimit=100000
// Policy: allow if role=TELLER AND branch matches AND shift matches AND amount <= limit
When should you use RBAC vs ABAC?
Use RBAC when permissions depend only on job title and are stable. Examples: only admins can close accounts, only tellers can approve transfers, customers can only view their own data. RBAC is clean and easy to audit.
Use ABAC when you need multi-tenant data isolation, time-based access, location restrictions, or per-record limits. Examples: a teller may only approve transfers for accounts in their own branch, during business hours, under their daily limit.
Most banks use a hybrid: RBAC for the coarse gate and ABAC for the contextual conditions.
package com.bank.service;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
@Service
public class HybridAccessService {
// Coarse RBAC gate: must be a TELLER
// Fine ABAC conditions: branch match and amount limit
@PreAuthorize(
"hasRole('TELLER') " +
"and #request.branch == authentication.principal.branch " +
"and #request.amount.compareTo(authentication.principal.dailyLimit) <= 0"
)
public void approveTransfer(TransferRequest request) {
System.out.println("Hybrid check passed. Transfer approved.");
}
// Pure RBAC: only admins can close accounts
@PreAuthorize("hasRole('ADMIN')")
public void closeAccount(String accountId) {
System.out.println("Account " + accountId + " closed.");
}
}
How does Open Policy Agent (OPA) help with ABAC?
OPA is an external policy engine that evaluates policies written in Rego. The application sends a policy query with attributes, and OPA returns an allow or deny decision. This keeps policy logic outside the application code, making it easier to test, audit, and change without redeployment.
package com.bank.security;
import org.springframework.web.client.RestTemplate;
import java.util.Map;
public class OpaClient {
private final RestTemplate restTemplate = new RestTemplate();
private static final String OPA_URL = "http://opa:8181/v1/data/bank/authz";
public boolean isAllowed(String user, String branch,
String action, String resource) {
Map<String, Object> input = Map.of(
"user", user,
"branch", branch,
"action", action,
"resource", resource
);
Map<String, Object> body = Map.of("input", input);
Map response = restTemplate.postForObject(OPA_URL, body, Map.class);
Map result = (Map) response.get("result");
return Boolean.TRUE.equals(result.get("allow"));
}
}
What are common mistakes?
A common mistake is using RBAC for everything and ending up with role explosion. When a team creates TELLER_MUMBAI_DAY, TELLER_DELHI_NIGHT, and dozens of similar roles, the system becomes unmanageable.
Another mistake is implementing ABAC without centralized policy management. When ABAC rules are scattered across service code, they become impossible to audit.
// WRONG: role explosion
@PreAuthorize("hasRole('TELLER_MUMBAI_DAY_100K')")
public void approveTransfer(TransferRequest request) { }
// CORRECT: hybrid RBAC + ABAC
@PreAuthorize(
"hasRole('TELLER') and #request.branch == authentication.principal.branch"
)
public void approveTransfer(TransferRequest request) { }
What should a senior engineer know?
Senior engineers know that the choice between RBAC and ABAC is not binary. The practical approach is a hybrid where RBAC handles the coarse gate (are you a TELLER at all?) and ABAC handles fine conditions (your branch, your limit, the time). The cost of ABAC is complexity, harder testing, and a policy engine to operate.
In large banking systems, ABAC policies are often managed by a dedicated security team and stored in a policy engine like OPA, while the application code only calls the engine. This separation of concerns makes policies auditable and changeable without code deployments.
Visual Overview
RBAC Decision:
┌───────────┐ ┌──────────────┐
│ Request │────►│ Role Check │
│ user=priya │ │ │
│ action= │ │ Has TELLER? │
│ approve │ │ │ │
└───────────┘ │ ┌──┴──┐ │
│ Yes No │
│ │ │ │
│ ▼ ▼ │
│ ALLOW DENY │
└──────────────┘
ABAC Decision:
┌───────────┐ ┌──────────────────────────────┐
│ Request │────►│ Policy Evaluation │
│ user=priya │ │ │
│ branch=MUM │ │ role=TELLER? ✓ │
│ amount=50K │ │ branch=Mumbai match? ✓ │
│ time=10:30 │ │ amount <= dailyLimit? ✓ │
└───────────┘ │ time in 9am-5pm? ✓ │
│ │
│ ALL attributes pass? ──► ALLOW │
│ Any fails? ──► DENY │
└──────────────────────────────┘
Hybrid RBAC + ABAC:
┌──────────────┐
│ Request │
└──────┬───────┘
│
▼
┌──────────────┐
│ RBAC Gate │── No role? ──► 403 DENY
│ hasRole(T)? │
└──────┬───────┘
│ Yes
▼
┌──────────────┐
│ ABAC Policy │── Attributes fail? ──► 403 DENY
│ branch match │
│ time check │
│ amount limit │
└──────┬───────┘
│ All pass
▼
200 ALLOW
Key Takeaway
RBAC, role-based access control, grants permissions to named roles such as CUSTOMER, TELLER, and ADMIN, and users get permissions by holding roles. The decision is simply whether the user holds a role that carries the permission. ABAC, attribute-based access control, evaluates a dynamic policy over attributes of the subject, the resource, the action, and the environment, such as allow only if the teller's branch matches the account's branch, during business hours, and under the teller's daily limit.
In the National Bank of India project, RBAC handles coarse decisions where title is enough: only tellers approve transfers and only admins close accounts. That is clean and easy to audit. But the moment the bank added rules like a teller may only approve transfers for accounts in their own branch, during business hours, and below their daily limit, pure RBAC would have forced a separate role for every branch, shift, and limit combination: the classic role explosion.
So the fine-grained rules moved to ABAC. The RBAC gate first confirms Priya is a teller, then the ABAC policy evaluates branch match, business hours, and amount against her limit. An in-branch daytime transfer under her limit is allowed, while an out-of-branch request or an after-hours request is denied, each denial traceable to a specific attribute.
The rule of thumb is: use RBAC when permissions depend only on job title and are stable, and reach for ABAC when you need multi-tenant data isolation, time-based access, location restrictions, or per-record limits. In large systems the combination works best: RBAC as the coarse gate and ABAC, often via a policy engine like Open Policy Agent, for the contextual conditions.
Remember one sentence: RBAC checks your title, ABAC checks your context, and most banks use both together.
---