I have implemented a Keycloak mapper and it works but without security.
Now, I'd like to use spring-boot to use dependency injection and make it an OAuth2 client (use spring-security and spring-boot properties to authenticate using OAuth2 client-crediential flow).
Here is what I did so far
- maven project with maven shade plugin
- META-INF/jboss-deployment-structure.xml
<jboss-deployment-structure>
<deployment>
<dependencies>
<module name="org.keycloak.keycloak-services" />
</dependencies>
</deployment>
</jboss-deployment-structure>
- META-INF/services/org.keycloak.protocol.ProtocolMapper
com.c4_soft.ProxiesMapper
- extend
AbstractOIDCProtocolMapperand implementOIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper
public class ProxiesMapper extends AbstractOIDCProtocolMapper implements OIDCAccessTokenMapper, OIDCIDTokenMapper, UserInfoTokenMapper {
private static final String PROVIDER_ID = "c4-soft.com";
private static final String PROXIES_SERVICE_BASE_URI = "proxies-service.base-uri";
private static Logger logger = Logger.getLogger(ProxiesMapper.class);
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(PROXIES_SERVICE_BASE_URI);
property.setLabel("Proxies service base URI");
property.setHelpText("Base URI for REST service to fetch proxies from");
property.setType(ProviderConfigProperty.STRING_TYPE);
property.setDefaultValue("https://localhost:4204");
configProperties.add(property);
}
private final Map<String, WebClient> webClientByBaseUri = new HashMap<>();
@Override
public String getDisplayCategory() {
return TOKEN_MAPPER_CATEGORY;
}
@Override
public String getDisplayType() {
return "User proxies mapper";
}
@Override
public String getId() {
return PROVIDER_ID;
}
@Override
public String getHelpText() {
return "Adds a \"proxies\" private claim containing a map of authorizations the user has to act on behalf of other users (one collection of grant IDs per user subject)";
}
@Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
@Override
public AccessToken transformAccessToken(
AccessToken token,
ProtocolMapperModel mappingModel,
KeycloakSession keycloakSession,
UserSessionModel userSession,
ClientSessionContext clientSessionCtx) {
Map<String, Collection<String>> grantsByProxiedUserSubject;
try {
@SuppressWarnings("unchecked")
final List<ProxyDto> dtos =
getWebClient(mappingModel).get().uri("/users/{userSubject}/proxies/granted", token.getSubject()).retrieve().bodyToMono(List.class).block();
grantsByProxiedUserSubject = dtos.stream().collect(Collectors.toMap(ProxyDto::getGrantingUserSubject, ProxyDto::getGrants));
} catch (final WebClientResponseException e) {
grantsByProxiedUserSubject = null;
logger.warn("Failed to fetch user proxies", e);
}
token.getOtherClaims().put("proxies", grantsByProxiedUserSubject);
setClaim(token, mappingModel, userSession, keycloakSession, clientSessionCtx);
return token;
}
public static ProtocolMapperModel create() {
final var mapper = new ProtocolMapperModel();
mapper.setProtocolMapper(PROVIDER_ID);
mapper.setProtocol(OIDCLoginProtocol.LOGIN_PROTOCOL);
final Map<String, String> config = new HashMap<>();
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ACCESS_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_ID_TOKEN, "true");
config.put(OIDCAttributeMapperHelper.INCLUDE_IN_USERINFO, "true");
mapper.setConfig(config);
return mapper;
}
private WebClient getWebClient(ProtocolMapperModel mappingModel) {
final var baseUri = mappingModel.getConfig().get(PROXIES_SERVICE_BASE_URI);
return webClientByBaseUri.computeIfAbsent(baseUri, (String k) -> WebClient.builder().baseUrl(baseUri).build());
}
}