How to Satisfy “Only-Access-Own-Realm” in Spring Security

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);
    }
}

Leave a Comment

Your email address will not be published. Required fields are marked *