Spring Security โ Ensuring Each Customer Accesses Only Their Realm
Q: In a bank online app, every individual customer has their own sole realm. How can this requirement be satisfied with Spring’s role-based security mechanism?
Spring’s role-based mechanism controls access to functionality like user vs. admin, but doesn’t inherently control access to specific data objects (e.g. ensuring a user can only view their account).
To enforce this “realm” behavior, you need object-level security in addition to role checks.
โ Approach Overview
- Use roles for functional access (`ROLE_USER`, `ROLE_ADMIN`).
- Manually or declaratively enforce ownership checks on object access.
- Leverage `@PreAuthorize` with a custom service.
// Example manual ownership check
if (!account.getOwnerUsername().equals(principal.getName())) {
throw new AccessDeniedException("Unauthorized");
}
// Using @PreAuthorize with SpEL
@PreAuthorize("@accountSecurity.hasAccess(#accountId, principal.username)")
public Account getAccount(Long accountId) { ... }
This approach satisfies the individual realm isolation requirement.
Q: Can I see a full working Spring Boot example?
โ Full Spring Boot Example: Per-Customer Access Control
๐ Project Structure
bank-app/
โโโ Account.java
โโโ AccountController.java
โโโ AccountService.java
โโโ AccountSecurity.java
โโโ SecurityConfig.java
โโโ BankAppApplication.java
1. Account.java
public class Account {
private Long id;
private String ownerUsername;
private String details;
public Account(Long id, String ownerUsername, String details) {
this.id = id;
this.ownerUsername = ownerUsername;
this.details = details;
}
public Long getId() { return id; }
public String getOwnerUsername() { return ownerUsername; }
public String getDetails() { return details; }
}
2. AccountService.java
@Service
public class AccountService {
private static final Map<Long, Account> accounts = new HashMap<>();
static {
accounts.put(1L, new Account(1L, "alice", "Alice's savings"));
accounts.put(2L, new Account(2L, "bob", "Bob's checking"));
}
public Account findById(Long id) {
return accounts.get(id);
}
}
3. SecurityConfig.java
@Configuration
@EnableMethodSecurity
public class SecurityConfig {
@Bean
public UserDetailsService userDetailsService() {
UserDetails alice = User.withUsername("alice")
.password(passwordEncoder().encode("123"))
.roles("USER")
.build();
UserDetails bob = User.withUsername("bob")
.password(passwordEncoder().encode("456"))
.roles("USER")
.build();
return new InMemoryUserDetailsManager(alice, bob);
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeHttpRequests(auth -> auth
.anyRequest().authenticated())
.formLogin();
return http.build();
}
}
4. AccountSecurity.java
@Component
public class AccountSecurity {
@Autowired
private AccountService accountService;
public boolean hasAccess(Long accountId, String username) {
Account account = accountService.findById(accountId);
return account != null && account.getOwnerUsername().equals(username);
}
}
5. AccountController.java
@RestController
@RequestMapping("/accounts")
public class AccountController {
@Autowired
private AccountService accountService;
@PreAuthorize("@accountSecurity.hasAccess(#accountId, authentication.name)")
@GetMapping("/{accountId}")
public Account getAccount(@PathVariable Long accountId) {
return accountService.findById(accountId);
}
}
6. BankAppApplication.java
@SpringBootApplication
public class BankAppApplication {
public static void main(String[] args) {
SpringApplication.run(BankAppApplication.class, args);
}
}
โ Test It
- Login as
alice / 123
โ Access/accounts/1
โ - Login as
bob / 456
โ Access/accounts/1
โ