Extensions et SPI Keycloak 30 min de lecture

REST endpoints custom et Protocol Mappers

Custom REST Endpoints

Le RealmResourceProvider SPI permet d'ajouter des endpoints REST custom a Keycloak, utiles pour integrer des logiques metier specifiques.

Creer un endpoint REST custom

public class CustomResourceProvider
    implements RealmResourceProvider {

    private final KeycloakSession session;

    public CustomResourceProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public Object getResource() {
        return new CustomResource(session);
    }

    @Override
    public void close() {}
}

public class CustomResource {

    private final KeycloakSession session;

    public CustomResource(KeycloakSession session) {
        this.session = session;
    }

    @GET
    @Path("/user-stats")
    @Produces(MediaType.APPLICATION_JSON)
    public Response getUserStats() {
        RealmModel realm = session.getContext().getRealm();
        long userCount = session.users()
            .getUsersCount(realm);
        long activeCount = session.sessions()
            .getActiveUserSessions(realm,
                session.getContext().getClient());

        Map<String, Long> stats = Map.of(
            "totalUsers", userCount,
            "activeSessions", activeCount
        );
        return Response.ok(stats).build();
    }

    @POST
    @Path("/bulk-assign-role")
    @Consumes(MediaType.APPLICATION_JSON)
    public Response bulkAssignRole(BulkRoleRequest req) {
        RealmModel realm = session.getContext().getRealm();
        RoleModel role = realm.getRole(req.getRoleName());

        for (String userId : req.getUserIds()) {
            UserModel user = session.users()
                .getUserById(realm, userId);
            if (user != null) {
                user.grantRole(role);
            }
        }
        return Response.ok().build();
    }
}

Custom Protocol Mapper

Les Protocol Mappers ajoutent des claims personnalises aux tokens.

public class DepartmentMapper
    extends AbstractOIDCProtocolMapper
    implements OIDCAccessTokenMapper, OIDCIDTokenMapper {

    public static final String PROVIDER_ID = "department-mapper";

    @Override
    protected void setClaim(
        IDToken token,
        ProtocolMapperModel model,
        UserSessionModel userSession,
        KeycloakSession session,
        ClientSessionContext ctx
    ) {
        UserModel user = userSession.getUser();
        String dept = user.getFirstAttribute("department");
        if (dept != null) {
            token.getOtherClaims().put("department", dept);
        }

        // Ajouter des claims complexes
        Map<String, Object> orgInfo = Map.of(
            "department", dept != null ? dept : "unknown",
            "manager", user.getFirstAttribute("manager"),
            "costCenter", user.getFirstAttribute("cost_center")
        );
        token.getOtherClaims().put("org_info", orgInfo);
    }

    @Override
    public String getDisplayType() {
        return "Department Mapper";
    }

    @Override
    public String getId() { return PROVIDER_ID; }
}

Compilation et deploiement

# pom.xml (Maven)
<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-core</artifactId>
  <version>${keycloak.version}</version>
  <scope>provided</scope>
</dependency>
<dependency>
  <groupId>org.keycloak</groupId>
  <artifactId>keycloak-server-spi</artifactId>
  <version>${keycloak.version}</version>
  <scope>provided</scope>
</dependency>

# Compiler et deployer
mvn clean package
cp target/mon-extension.jar /opt/keycloak/providers/
/opt/keycloak/bin/kc.sh build
Important : Toutes les dependances Keycloak doivent etre en scope provided car elles sont deja presentes dans le serveur. N'incluez que vos dependances specifiques.