I was able to pass MDC values by using temporal ContextPropagator. Is there a way to pass SecurityContext to workflow and activities? If not would you recommend passing it as workflow or activity input?
import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
final SecurityContext context = SecurityContextHolder.getContext();
Whats use case where you need this in workflow code? For activities you should be able to autowire the security context as activity impl can be Component (see sample here if it helps).
final SecurityContext context = SecurityContextHolder.getContext();
This code is already inside Spring class annotated with @Service and this service is being called by activity. But context value is null. Only reason I can think of is that context is not propagated to activities. This also happens if we use @Async or create a new thread in which we can this service class. To resolve that we need to delegate security context. See https://www.baeldung.com/spring-security-async-principal-propagation .
Do we have similar thing for temporal by which we can delegate it to activies
Here is scenario
User makes API request → Start workflow → Start activity → Calls spring service class → update/insert db records
In our current logic while updating db record we have security context by which we get information about user who made request (Decoded JWT) e.g email, roles, name etc. Now while persisting we also persist user information corresponding to each record which we update or insert (For Audit)
This works well as of now without using temporal by delegating context and using spring async methods. Since now I have moved workflow to temporal this context is null.
As inbuilt authentication does not have default constructor. I think it is achievable just need to refactor some code. But for now I am using MDC as I just need userId from security context. This was easy fix for me.
You don’t have to use Temporal DataConverter to serialize/deserialize header fields. How is the SecurityContext serialized for HTTP/gRPC calls sent over the wire?
Not sure what do you mean by I don’t have to use DataConvertor. This is what I have in ContextPropagator
import io.temporal.api.common.v1.Payload;
import io.temporal.common.context.ContextPropagator;
import io.temporal.common.converter.DataConverter;
import java.util.HashMap;
import java.util.Map;
import org.slf4j.MDC;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextImpl;
public class TemporalMdcContextPropagator implements ContextPropagator {
private static final String MDC_KEY = "MDC";
private static final String SECURITY_CONTEXT_KEY = "SECURITY-CONTEXT";
@Override
public String getName() {
return this.getClass().getName();
}
@Override
public Object getCurrentContext() {
final Map<String, Object> contextMap = new HashMap<>();
contextMap.put(MDC_KEY, MDC.getCopyOfContextMap());
contextMap.put(SECURITY_CONTEXT_KEY, SecurityContextHolder.getContext());
return contextMap;
}
@Override
public void setCurrentContext(Object context) {
Map<String, Object> contextMap = (Map<String, Object>) context;
SecurityContextHolder.setContext((SecurityContextImpl) contextMap.get(SECURITY_CONTEXT_KEY));
MDC.setContextMap((Map<String, String>)contextMap.get(MDC_KEY));
}
@Override
public Map<String, Payload> serializeContext(Object context) {
Map<String, Object> contextMap = (Map<String, Object>) context;
Map<String, Payload> serializedContext = new HashMap<>();
for (Map.Entry<String, Object> entry : contextMap.entrySet()) {
serializedContext.put(
entry.getKey(), DataConverter.getDefaultInstance().toPayload(entry.getValue()).get());
}
return serializedContext;
}
@Override
public Object deserializeContext(Map<String, Payload> context) {
Map<String, Object> contextMap = new HashMap<>();
for (Map.Entry<String, Payload> entry : context.entrySet()) {
Class cls = MDC_KEY.equals(entry.getKey()) ? Map.class : SecurityContextImpl.class;
contextMap.put(
entry.getKey(),
DataConverter.getDefaultInstance()
.fromPayload(entry.getValue(), cls, cls));
}
return contextMap;
}
}
And I am getting following error
Caused by: io.temporal.common.converter.DataConverterException: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `org.springframework.security.core.Authentication` (no Creators, like default constructor, exist): abstract types either need to be mapped to concrete types, have custom deserializer, or contain additional type information
at [Source: (byte[])"{"authentication":{"authorities":[{"role":"READ_ROLE","authority":"READ_ROLE"},{"role":"MANAGE_USER","authority":"MANAGE_CUSTOMER"},{"role":"CREATE_CONFIG","authority":"CREATE_CONFIG"},CREATE_"[truncated 17700 bytes]; line: 1, column: 19] (through reference chain: org.springframework.security.core.context.SecurityContextImpl["authentication"])
at io.temporal.common.converter.JacksonJsonPayloadConverter.fromData(JacksonJsonPayloadConverter.java:101) ~[temporal-sdk-1.19.1.jar:na]
at io.temporal.common.converter.PayloadAndFailureDataConverter.fromPayload(PayloadAndFailureDataConverter.java:95) ~[temporal-sdk-1.19.1.jar:na]
at io.temporal.common.converter.DefaultDataConverter.fromPayload(DefaultDataConverter.java:33) ~[temporal-sdk-1.19.1.jar:na]
You decided to use the DataConverter to serialize context. It looks like SecurityContextImpl is not serializable using it. I would use another way to convert SecrityContext to bytes.
Map<String, Payload> serializeContext(Object context);
/** Turn the serialized header data into context object(s) */
Object deserializeContext(Map<String, Payload> header);
Accepts and returns Payload.
Are you saying first I should serialize using bytes and then convert it to Map<String, Payload> or Payload to bytes for deserialize