A SCIM 2.0 server backed by an LDAP directory, built on Apache Directory SCIMple and the Apache Directory LDAP API.
This module implements Repository<ScimUser> and Repository<ScimGroup> against LDAP, translating SCIM operations into LDAP reads and writes. It is a reference implementation intended to be adapted for production deployments with real LDAP directories.
Build the full SCIMple project (the LDAP module depends on other SCIMple modules):
./mvnw package -DskipTests
This produces an executable uber JAR at reference-projects/scim-server-ldap/target/scim-server-ldap-1.0.0-SNAPSHOT-exec.jar.
Note: The uber JAR is built with the Spring Boot Maven Plugin for its JAR repackaging capability only — the project has no runtime dependency on Spring. The JAR uses Spring Boot's
JarLauncheras the entry point, which delegates toLdapApplication.main(). If you inspect the manifest, that is why you will see Spring Boot references.
Run the server with an embedded Apache DS instance (no external LDAP required):
java -Dldap.embedded=true -jar reference-projects/scim-server-ldap/target/scim-server-ldap-1.0.0-SNAPSHOT-exec.jar
Or, during development, run directly via Maven without building the uber JAR first:
./mvnw exec:java -pl reference-projects/scim-server-ldap -Dldap.embedded=true
The embedded mode seeds the directory with sample users and a group. Test it:
curl http://localhost:8080/Users curl http://localhost:8080/Groups curl http://localhost:8080/ServiceProviderConfig
SCIM endpoints expect Content-Type: application/scim+json for write operations (POST, PUT, PATCH).
The server port defaults to 8080 and can be changed with -Dserver.port=9090.
To connect to an external LDAP server instead, configure src/main/resources/scim-ldap.yml and run without the embedded flag.
All configuration lives in a single file: scim-ldap.yml on the classpath.
ldap: embedded: false # set to true to start an in-memory ApacheDS (ignores host/port/bind settings) host: localhost port: 389 bindDn: cn=admin,dc=example,dc=com bindPassword: secret useTls: false userBaseDn: ou=users,dc=example,dc=com groupBaseDn: ou=groups,dc=example,dc=com user: objectClasses: [inetOrgPerson, organizationalPerson, person, top] rdnAttribute: uid attributes: userName: uid "name.givenName": givenName "name.familyName": sn displayName: displayName "emails.value": mail # ... full mapping in scim-ldap.yml group: objectClasses: [groupOfNames, top] rdnAttribute: cn attributes: displayName: cn "members.value": member
LDAP connection settings can be overridden at runtime via system properties, which is useful for containerized deployments where secrets come from the environment:
java -Dldap.host=ldap.corp.example.com \ -Dldap.port=636 \ -Dldap.bind.dn=cn=scim,ou=services,dc=corp \ -Dldap.bind.password=$LDAP_PASSWORD \ -Dldap.use.tls=true \ -jar reference-projects/scim-server-ldap/target/scim-server-ldap-1.0.0-SNAPSHOT-exec.jar
| System Property | YAML Key | Default |
|---|---|---|
ldap.embedded | ldap.embedded | false |
ldap.host | ldap.host | localhost |
ldap.port | ldap.port | 389 |
ldap.bind.dn | ldap.bindDn | cn=admin,dc=example,dc=com |
ldap.bind.password | ldap.bindPassword | secret |
ldap.use.tls | ldap.useTls | false |
ldap.base.dn.users | ldap.userBaseDn | ou=users,dc=example,dc=com |
ldap.base.dn.groups | ldap.groupBaseDn | ou=groups,dc=example,dc=com |
server.port | - | 8080 |
SCIM Request
|
v
+-------------------+ +------------------+ +---------------------+
| LdapUserRepository|---->| AttributeMapper |---->| LdapDao |
| LdapGroupRepository | (SCIM <-> LDAP) | | (CRUD operations) |
+-------------------+ +------------------+ +---------------------+
| | |
| +------------------+ +---------------------+
+--------------->| FilterTranslator | | LdapConnectionManager
| (SCIM -> LDAP) | | (connection pool) |
+------------------+ +---------------------+
service/)LdapUserRepository and LdapGroupRepository implement SCIMple's Repository<T> SPI. They handle:
FilterTranslator then executes against LDAPentryUUID operational attribute (immutable, server-assigned)mapping/FilterTranslator)SCIM filter expressions are translated into LDAP filter strings. All SCIM comparison operators are supported:
| SCIM | LDAP | Example |
|---|---|---|
eq | (attr=value) | userName eq "jdoe" -> (uid=jdoe) |
co | (attr=*value*) | displayName co "test" -> (displayName=*test*) |
sw | (attr=value*) | userName sw "j" -> (uid=j*) |
ew | (attr=*value) | emails.value ew "@example.com" -> (mail=*@example.com) |
pr | (attr=*) | title pr -> (title=*) |
gt, ge, lt, le | Ordering filters | Mapped to LDAP >=/<= combinations |
and, or, not | (&...), (|...), (!...) | Logical composition |
Attribute names are resolved through the configurable SCIM-to-LDAP mapping (e.g., userName -> uid). Filter values are escaped per RFC 4515.
mapping/AttributeMapper)Converts between SCIM resources and LDAP entries using the user and group sections of scim-ldap.yml. The mapping is a simple key-value structure:
# SCIM attribute path -> LDAP attribute name userName: uid "name.givenName": givenName "emails.value": mail
To adapt for a different LDAP schema (e.g., Active Directory), change the attribute mapping and objectClasses in scim-ldap.yml — no code changes required.
The module uses the SCIMple compliance test suite (scim-compliance-tests) with an embedded Apache DS server. Run the tests with:
./mvnw verify -pl reference-projects/scim-server-ldap
The test configuration in src/test/resources/scim-ldap.yml adds extensibleObject to the user objectClasses so that custom attributes (scimActive, scimPhoneTypes) are accepted by the Apache DS schema.