Careeroza
Real World Example
A Wallet App (Paytm / PhonePe Style)
Money-moving services get the strictest rules. A typical layered setup:
| Layer | Control |
|---|---|
| Edge | WAF + rate limits per device and per user; JWT validated at the gateway; device binding |
| wallet-service | Needs scope wallet:debit AND a fresh step-up auth claim (UPI PIN verified in the last 2 min) |
| Between services | mTLS by the service mesh; client-credentials tokens for background jobs |
| Data | Card/bank data in a separate PCI-DSS zone; secrets from Vault; audit log of every debit |
wallet-service Security Configuration
@Configuration
@EnableMethodSecurity
public class WalletSecurityConfig {
@Bean
SecurityFilterChain wallet(HttpSecurity http) throws Exception {
return http
.csrf(AbstractHttpConfigurer::disable)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers("/actuator/health/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/wallet/balance").hasAuthority("SCOPE_wallet:read")
.requestMatchers(HttpMethod.POST, "/api/wallet/debit").hasAuthority("SCOPE_wallet:debit")
.anyRequest().denyAll()) // deny by default
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.build();
}
}
@RestController
@RequestMapping("/api/wallet")
@RequiredArgsConstructor
public class WalletController {
private final WalletService wallet;
@PostMapping("/debit")
@PreAuthorize("@stepUp.isRecent(authentication, T(java.time.Duration).ofMinutes(2))")
public DebitResponse debit(@RequestHeader("Idempotency-Key") String key,
@Valid @RequestBody DebitRequest req,
@AuthenticationPrincipal Jwt jwt) {
return wallet.debit(jwt.getSubject(), req, key); // subject from the token, never from the body
}
}Tip: Notice two habits: deny by default (anyRequest().denyAll()), and take the user ID from the verified token (jwt.getSubject()), never from a request field that the client controls.