IAM Client SDK Usage Patterns
Common patterns and best practices for authentication and authorization.
🔑 Authentication Patterns
JWT Token Validation
@Component
class JwtAuthenticationFilter(
private val authenticationService: AuthenticationService
) : OncePerRequestFilter() {
override fun doFilterInternal(
request: HttpServletRequest,
response: HttpServletResponse,
filterChain: FilterChain
) {
val token = extractToken(request)
if (token != null) {
try {
val authentication = authenticationService.authenticate(token)
SecurityContextHolder.getContext().authentication = authentication
} catch (e: AuthenticationException) {
response.status = HttpStatus.UNAUTHORIZED.value()
return
}
}
filterChain.doFilter(request, response)
}
}
Service-to-Service Authentication
@Component
class ServiceAuthenticatedWebClient(
private val iamClient: IAMClient,
private val webClientBuilder: WebClient.Builder
) {
private val webClient = webClientBuilder
.filter(addServiceTokenFilter())
.build()
private fun addServiceTokenFilter(): ExchangeFilterFunction {
return ExchangeFilterFunction.ofRequestProcessor { request ->
iamClient.getServiceToken()
.map { token ->
ClientRequest.from(request)
.header("Authorization", "Bearer $token")
.build()
}
}
}
}
🛡️ Authorization Patterns
Method-Level Security
@RestController
class OrderController {
@PreAuthorize("hasPermission('orders', 'CREATE')")
@PostMapping("/orders")
fun createOrder(@RequestBody order: CreateOrderRequest): OrderResponse {
return orderService.createOrder(order)
}
@PreAuthorize("hasPermission(#orderId, 'Order', 'READ')")
@GetMapping("/orders/{orderId}")
fun getOrder(@PathVariable orderId: String): OrderResponse {
return orderService.getOrder(OrderId(orderId))
}
}
Programmatic Authorization
@Service
class OrderService(
private val authorizationService: AuthorizationService
) {
fun getOrdersForCustomer(customerId: CustomerId): List<Order> {
if (!authorizationService.hasPermission("orders:read:all")) {
// User can only see their own orders
val currentUserId = authorizationService.getCurrentUserId()
require(customerId.userId == currentUserId) { "Access denied" }
}
return orderRepository.findByCustomerId(customerId)
}
}
🏢 Multi-Tenant Patterns
Tenant-Aware Repository
@Repository
class TenantAwareOrderRepository(
private val tenantContext: TenantContext,
private val jpaRepository: OrderJpaRepository
) : OrderRepository {
override fun findById(id: OrderId): Order? {
val tenantId = tenantContext.getCurrentTenant()
return jpaRepository.findByIdAndTenantId(id.value, tenantId.value)
?.let { orderMapper.toDomain(it) }
}
override fun save(order: Order): Order {
val tenantId = tenantContext.getCurrentTenant()
val entity = orderMapper.toEntity(order).copy(tenantId = tenantId.value)
val savedEntity = jpaRepository.save(entity)
return orderMapper.toDomain(savedEntity)
}
}
Tenant Context Propagation
@Component
class TenantContextPropagator(
private val tenantContext: TenantContext
) {
@EventListener
fun propagateTenantContext(event: DomainEvent) {
val currentTenant = tenantContext.getCurrentTenant()
// Add tenant information to event metadata
event.metadata["tenantId"] = currentTenant.value
}
@Async
fun processAsync(task: Runnable) {
val tenant = tenantContext.getCurrentTenant()
CompletableFuture.runAsync {
try {
tenantContext.setTenant(tenant)
task.run()
} finally {
tenantContext.clear()
}
}
}
}
🔄 Error Handling
Authentication Error Handling
@ControllerAdvice
class AuthenticationExceptionHandler {
@ExceptionHandler(AuthenticationException::class)
fun handleAuthenticationError(ex: AuthenticationException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(HttpStatus.UNAUTHORIZED)
.body(ErrorResponse("AUTHENTICATION_FAILED", ex.message))
}
@ExceptionHandler(AuthorizationException::class)
fun handleAuthorizationError(ex: AuthorizationException): ResponseEntity<ErrorResponse> {
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorResponse("ACCESS_DENIED", "Insufficient permissions"))
}
}
Token Refresh Pattern
@Component
class TokenManager(
private val iamClient: IAMClient
) {
private var currentToken: String? = null
private var refreshToken: String? = null
private var tokenExpiry: Instant? = null
suspend fun getValidToken(): String {
if (isTokenExpired()) {
refreshToken()
}
return currentToken ?: throw IllegalStateException("No valid token available")
}
private suspend fun refreshToken() {
val refreshToken = this.refreshToken ?: throw IllegalStateException("No refresh token")
try {
val response = iamClient.refreshToken(refreshToken)
this.currentToken = response.accessToken
this.refreshToken = response.refreshToken
this.tokenExpiry = response.expiresAt
} catch (e: Exception) {
// Handle refresh failure, might need to re-authenticate
handleTokenRefreshFailure(e)
}
}
}
Best practices for secure authentication and authorization with the EAF IAM Client SDK.