Howto implement Keycloak `AbstractOIDCProtocolMapper` with spring DI?

Viewed 207

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 AbstractOIDCProtocolMapper and implement OIDCAccessTokenMapper, 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());
    }
}
0 Answers
Related