In a banking application where each individual customer has their own realm of data (e.g., account info, transaction history), the traditional role-based security mechanism in Spring Security (e.g., ROLE_USER
, ROLE_ADMIN
) is not enough on its own to enforce access control at the data level.
What Role-Based Security Does Well
Role-based access in Spring Security is great for:
- Granting access to functional areas (e.g., only admins can access the admin dashboard).
- Allowing or denying methods or endpoints based on the user’s role.
But your requirement needs ownership-based access control, or object-level security, not just role-level.
How to Satisfy “Only-Access-Own-Realm” in Spring Security?
You’ll need to combine role-based security with fine-grained, ownership-based authorization logic.
1. Standard Authentication & Role Setup
You still define roles like this:
@Bean
public UserDetailsService userDetailsService() {
UserDetails user = User.withUsername("john")
.password(passwordEncoder().encode("12345"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
So john
has ROLE_USER
.
2. Secure Controller/Service with Ownership Check
Let’s say you have a controller like:
@GetMapping("/accounts/{accountId}")
public ResponseEntity<Account> getAccount(@PathVariable Long accountId, Principal principal) {
Account account = accountService.findById(accountId);
// Ownership check
if (!account.getOwnerUsername().equals(principal.getName())) {
throw new AccessDeniedException("You don't have permission to view this account.");
}
return ResponseEntity.ok(account);
}
You manually compare the logged-in username (principal.getName()
) with the resource owner.
✅ This enforces individual realm security.
3. Optional: Use Spring Method Security with @PreAuthorize
You can move ownership check logic into a method-level security annotation using SpEL:
@PreAuthorize("#accountId == principal.username")
public Account getAccount(String accountId) {
...
}
But that only works if the ID is directly comparable, so in real apps you might need a service method:
@PreAuthorize("@accountSecurity.hasAccess(#accountId, principal.username)")
public Account getAccount(Long accountId) { ... }
In AccountSecurity.java
:
@Component
public class AccountSecurity {
public boolean hasAccess(Long accountId, String username) {
Account account = accountService.findById(accountId);
return account.getOwnerUsername().equals(username);
}
}
