Use case: I want to have a REST API that will fire an event in the system using RabbitMQ/AMQP, and I want to have the SecurityContext of the currently logged in user propagated to that event, in order to use the audit annotations in my JPA Repository (lastModifiedBy) and annotations for checking roles for users in services.
I currently have this piece of code when declaring the AmqpTemplate bean:
rabbitTemplate.setBeforePublishPostProcessors(
MessagePostProcessor { message ->
val authentication = SecurityContextHolder.getContext().authentication
message.messageProperties.setHeader("x-user-id", objectMapper.writeValueAsString(authentication))
message
}
)
I want to serialize the Authentication object and then deserialize it on the side of the consumer.
I'm currently having it like this:
@RabbitListener(queues = [TrackingEventPublisher.QUEUE_NAME])
fun handleTrackingMessage(trackingEvent: TrackingEvent, headers: MessageHeaders) {
val xUserId = headers["x-user-id"].toString()
val propagatedAuthentication = try {
objectMapper.readValue(xUserId, AnonymousAuthenticationToken::class.java)
} catch (_: Exception) {
objectMapper.readValue(xUserId, OAuth2AuthenticationToken::class.java)
}
SecurityContextHolder.setContext(
SecurityContextHolder.createEmptyContext().apply {
authentication = propagatedAuthentication
}
)
logger.info { "Logging event: ${trackingEvent.uuid} started, headers: ${headers}" }
logger.info { "Logging event: security ${SecurityContextHolder.getContext().authentication}" }
trackingService.save(trackingEvent)
logger.info { "Logging event: ${trackingEvent.uuid} finished" }
}
The problem with this approach is that ObjectMapper can't actually deserialize the Authentication objects. It can't do it because the fields of that object are abstract classes or interfaces and they can't be instantiated (they also don't have a default constructor and Jackson is requiring it).
I wonder if there's some idiomatic way to do it, or maybe something that has been already done by someone in some library.
I know that there is a different approach that utilizes Spring Integration and it does have support for SecurityContext propagation, but I don't feel ready for learning about EIP, so I first want to do something simpler that is closer to the AMQP protocol, and not protocol-agnostic.
Do you have any ideas how to actually approach this?
I don't think it is the right approach trying to serialize an Authentication
. I suggest you to look into a storing Principal
or credentials instead into those AMQP headers. And then on the consumer side authenticate the received principal and so on.