├── .gitattributes ├── .github └── FUNDING.yml ├── .gitignore ├── .mvn └── wrapper │ └── maven-wrapper.properties ├── README.md ├── build-docker-images.sh ├── docker-compose.yml ├── documentation ├── phpldapadmin.jpeg ├── project-diagram.excalidraw ├── project-diagram.jpeg └── simple-service-swagger.jpeg ├── import-openldap-users.sh ├── init-keycloak.sh ├── ldap ├── ldap-config.json └── ldap-mycompany-com.ldif ├── mvnw ├── mvnw.cmd ├── pom.xml ├── remove-docker-images.sh └── simple-service ├── pom.xml └── src ├── main ├── java │ └── com │ │ └── ivanfranchin │ │ └── simpleservice │ │ ├── SimpleServiceApplication.java │ │ ├── config │ │ └── SwaggerConfig.java │ │ ├── controller │ │ └── SimpleServiceController.java │ │ └── security │ │ ├── JwtAuthenticationTokenConverter.java │ │ └── SecurityConfig.java └── resources │ ├── application.properties │ └── banner.txt └── test └── java └── com └── ivanfranchin └── simpleservice └── SimpleServiceApplicationTests.java /.gitattributes: -------------------------------------------------------------------------------- 1 | /mvnw text eol=lf 2 | *.cmd text eol=crlf 3 | -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | github: ivangfr 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | !.mvn/wrapper/maven-wrapper.jar 3 | !**/src/main/** 4 | !**/src/test/** 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | 21 | ### NetBeans ### 22 | /nbproject/private/ 23 | /nbbuild/ 24 | /dist/ 25 | /nbdist/ 26 | /.nb-gradle/ 27 | build/ 28 | 29 | ### VS Code ### 30 | .vscode/ 31 | 32 | ### MAC OS ### 33 | *.DS_store 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | # Licensed to the Apache Software Foundation (ASF) under one 2 | # or more contributor license agreements. See the NOTICE file 3 | # distributed with this work for additional information 4 | # regarding copyright ownership. The ASF licenses this file 5 | # to you under the Apache License, Version 2.0 (the 6 | # "License"); you may not use this file except in compliance 7 | # with the License. You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, 12 | # software distributed under the License is distributed on an 13 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 14 | # KIND, either express or implied. See the License for the 15 | # specific language governing permissions and limitations 16 | # under the License. 17 | wrapperVersion=3.3.2 18 | distributionType=only-script 19 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.9/apache-maven-3.9.9-bin.zip 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # springboot-keycloak-openldap 2 | 3 | The goal of this project is to create a simple [Spring Boot](https://docs.spring.io/spring-boot/index.html) REST API, called `simple-service`, and secure it with [`Keycloak`](https://www.keycloak.org). Furthermore, the API users will be loaded into `Keycloak` from [`OpenLDAP`](https://www.openldap.org) server. 4 | 5 | > **Note**: In the [`springboot-react-keycloak`](https://github.com/ivangfr/springboot-react-keycloak) repository, we have implemented a `movies-app` using `Keycloak` (with `PKCE`). This application consists of two services: the backend that was implemented using `Spring Boot` and the frontend implemented with `ReactJS`. 6 | 7 | ## Proof-of-Concepts & Articles 8 | 9 | On [ivangfr.github.io](https://ivangfr.github.io), I have compiled my Proof-of-Concepts (PoCs) and articles. You can easily search for the technology you are interested in by using the filter. Who knows, perhaps I have already implemented a PoC or written an article about what you are looking for. 10 | 11 | ## Additional Readings 12 | 13 | - \[**Medium**\] [**Implementing and Securing a Simple Spring Boot REST API with Keycloak**](https://medium.com/@ivangfr/how-to-secure-a-spring-boot-app-with-keycloak-5a931ee12c5a) 14 | - \[**Medium**\] [**Implementing and Securing a Simple Spring Boot UI (Thymeleaf + RBAC) with Keycloak**](https://medium.com/@ivangfr/how-to-secure-a-simple-spring-boot-ui-thymeleaf-rbac-with-keycloak-ba9f30b9cb2b) 15 | - \[**Medium**\] [**Implementing and Securing a Spring Boot GraphQL API with Keycloak**](https://medium.com/@ivangfr/implementing-and-securing-a-spring-boot-graphql-api-with-keycloak-c461c86e3972) 16 | - \[**Medium**\] [**Setting Up OpenLDAP With Keycloak For User Federation**](https://medium.com/@ivangfr/setting-up-openldap-with-keycloak-for-user-federation-82c643b3a0e6) 17 | - \[**Medium**\] [**Integrating GitHub as a Social Identity Provider in Keycloak**](https://medium.com/@ivangfr/integrating-github-as-a-social-identity-provider-in-keycloak-982f521a622f) 18 | - \[**Medium**\] [**Integrating Google as a Social Identity Provider in Keycloak**](https://medium.com/@ivangfr/integrating-google-as-a-social-identity-provider-in-keycloak-c905577ec499) 19 | - \[**Medium**\] [**Building a Single Spring Boot App with Keycloak or Okta as IdP: Introduction**](https://medium.com/@ivangfr/building-a-single-spring-boot-app-with-keycloak-or-okta-as-idp-introduction-2814a4829aed) 20 | - \[**Medium**\] [**Implementing a Full Stack Web App using Spring-Boot and React**](https://medium.com/@ivangfr/implementing-a-full-stack-web-app-using-spring-boot-and-react-7db598df4452) 21 | - \[**Medium**\] [**Using Keycloak to secure a Full Stack Web App implemented with Spring-Boot and React**](https://medium.com/@ivangfr/using-keycloak-to-secure-a-full-stack-web-app-implemented-with-spring-boot-and-react-6b2d80fc5c12) 22 | 23 | ## Project Diagram 24 | 25 | ![project-diagram](documentation/project-diagram.jpeg) 26 | 27 | ## Application 28 | 29 | - ### simple-service 30 | 31 | `Spring Boot` Web Java application that exposes the following endpoints: 32 | - `GET /api/public`: it's a not secured endpoint, everybody can access it; 33 | - `GET /api/private`: it's a secured endpoint, only accessible by users that provide a `JWT` access token issued by `Keycloak` and the token must contain the role `USER`. 34 | 35 | ## Prerequisites 36 | 37 | - [`Java 21`](https://www.oracle.com/java/technologies/downloads/#java21) or higher; 38 | - A containerization tool (e.g., [`Docker`](https://www.docker.com), [`Podman`](https://podman.io), etc.) 39 | - [`jq`](https://jqlang.github.io/jq/) 40 | 41 | ## Start Environment 42 | 43 | - Open a terminal and inside the `springboot-keycloak-openldap` root folder run: 44 | ```bash 45 | docker compose up -d 46 | ``` 47 | 48 | - Just wait for the Docker containers to start running. The Keycloak Docker container usually takes longer. You can check its progress by running this command: 49 | ```bash 50 | docker logs -f keycloak 51 | ``` 52 | > Press `Ctrl+C` to exit 53 | 54 | Once you see the following log, Keycloak has started: 55 | ```text 56 | INFO [io.quarkus] (main) Keycloak 26.1.3 on JVM (powered by Quarkus 3.15.3.1) started in 44.567s. Listening on: http://0.0.0.0:8080. Management interface listening on http://0.0.0.0:9000. 57 | ``` 58 | 59 | ## Import OpenLDAP Users 60 | 61 | The `LDIF` file that we will use, `springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif`, contains a predefined structure for `mycompany.com`. Basically, it has 2 groups (`developers` and `admin`) and 4 users (`Bill Gates`, `Steve Jobs`, `Mark Cuban` and `Ivan Franchin`). Additionally, it is defined that `Bill Gates`, `Steve Jobs` and `Mark Cuban` belong to `developers` group and `Ivan Franchin` belongs to `admin` group. 62 | ```text 63 | Bill Gates > username: bgates, password: 123 64 | Steve Jobs > username: sjobs, password: 123 65 | Mark Cuban > username: mcuban, password: 123 66 | Ivan Franchin > username: ifranchin, password: 123 67 | ``` 68 | 69 | There are two ways to import those users: by running a script or by using [`phpLDAPadmin`](https://github.com/leenooks/phpLDAPadmin). 70 | 71 | ### Running a script 72 | 73 | - In a terminal and inside the `springboot-keycloak-openldap` root folder run: 74 | ```bash 75 | ./import-openldap-users.sh 76 | ``` 77 | 78 | - The command below can be used to check the imported users: 79 | ```bash 80 | ldapsearch -x -D "cn=admin,dc=mycompany,dc=com" \ 81 | -w admin -H ldap://localhost:389 \ 82 | -b "ou=users,dc=mycompany,dc=com" \ 83 | -s sub "(uid=*)" 84 | ``` 85 | 86 | ### Using phpLDAPadmin website 87 | 88 | - Access https://localhost:6443 89 | 90 | - Login with the credentials: 91 | ```text 92 | Login DN: cn=admin,dc=mycompany,dc=com 93 | Password: admin 94 | ``` 95 | 96 | - Import the file `springboot-keycloak-openldap/ldap/ldap-mycompany-com.ldif`. 97 | 98 | - You should see a tree like the one shown in the picture below: 99 | 100 | ![phpldapadmin](documentation/phpldapadmin.jpeg) 101 | 102 | ## Configure Keycloak 103 | 104 | There are two ways: running a script or using `Keycloak` website. 105 | 106 | ### Running a script 107 | 108 | - In a terminal, make sure you are inside the `springboot-keycloak-openldap` root folder. 109 | 110 | - Run the script below to configure `Keycloak` for `simple-service` application: 111 | ```bash 112 | ./init-keycloak.sh 113 | ``` 114 | 115 | It creates `company-services` realm, `simple-service` client, `USER` client role, `ldap` federation and the users `bgates` and `sjobs` with the role `USER` assigned. 116 | 117 | - Copy `SIMPLE_SERVICE_CLIENT_SECRET` value that is shown at the end of the script. It will be needed whenever we call `Keycloak` to get a `JWT` access token to access `simple-service`. 118 | 119 | ### Using Keycloak website 120 | 121 | Please have a look at this **Medium** article, [**Setting Up OpenLDAP With Keycloak For User Federation**](https://medium.com/@ivangfr/setting-up-openldap-with-keycloak-for-user-federation-82c643b3a0e6) 122 | 123 | ## Run simple-service using Maven 124 | 125 | - Open a new terminal and make sure you are in the `springboot-keycloak-openldap` root folder. 126 | 127 | - Start the application by running the following command: 128 | ```bash 129 | ./mvnw clean spring-boot:run --projects simple-service -Dspring-boot.run.jvmArguments="-Dserver.port=9080" 130 | ``` 131 | 132 | ## Test using curl 133 | 134 | 1. Open a new terminal. 135 | 136 | 2. Call the endpoint `GET /api/public`: 137 | ```bash 138 | curl -i http://localhost:9080/api/public 139 | ``` 140 | 141 | It should return: 142 | ```text 143 | HTTP/1.1 200 144 | It is public. 145 | ``` 146 | 147 | 3. Try to call the endpoint `GET /api/private` without authentication: 148 | ```bash 149 | curl -i http://localhost:9080/api/private 150 | ``` 151 | 152 | It should return: 153 | ```text 154 | HTTP/1.1 401 155 | ``` 156 | 157 | 4. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `simple-service` at [Configure Keycloak](#configure-keycloak) step: 158 | ```bash 159 | SIMPLE_SERVICE_CLIENT_SECRET=... 160 | ``` 161 | 162 | 5. Run the command below to get an access token for `bgates` user: 163 | ```bash 164 | BGATES_ACCESS_TOKEN=$(curl -s -X POST \ 165 | "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \ 166 | -H "Content-Type: application/x-www-form-urlencoded" \ 167 | -d "username=bgates" \ 168 | -d "password=123" \ 169 | -d "grant_type=password" \ 170 | -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \ 171 | -d "client_id=simple-service" | jq -r .access_token) 172 | 173 | echo $BGATES_ACCESS_TOKEN 174 | ``` 175 | > **Note**: In [jwt.io](https://jwt.io), you can decode and verify the `JWT` access token 176 | 177 | 6. Call the endpoint `GET /api/private`: 178 | ```bash 179 | curl -i http://localhost:9080/api/private -H "Authorization: Bearer $BGATES_ACCESS_TOKEN" 180 | ``` 181 | 182 | It should return: 183 | ```text 184 | HTTP/1.1 200 185 | bgates, it is private. 186 | ``` 187 | 188 | 7. The access token default expiration period is `5 minutes`. So, wait for this time and, using the same access token, try to call the private endpoint. 189 | 190 | It should return: 191 | ```text 192 | HTTP/1.1 401 193 | WWW-Authenticate: Bearer realm="company-services", error="invalid_token", error_description="Token is not active" 194 | ``` 195 | 196 | ## Test using Swagger 197 | 198 | 1. Access http://localhost:9080/swagger-ui.html 199 | 200 | ![simple-service-swagger](documentation/simple-service-swagger.jpeg) 201 | 202 | 2. Click `GET /api/public` to open it. Then, click `Try it out` button and, finally, click `Execute` button. 203 | 204 | It should return: 205 | ```text 206 | Code: 200 207 | Response Body: It is public. 208 | ``` 209 | 210 | 3. Now click `GET /api/private` secured endpoint. Let's try it without authentication. Then, click `Try it out` button and, finally, click `Execute` button. 211 | 212 | It should return: 213 | ```text 214 | Code: 401 215 | Details: Error: response status is 401 216 | ``` 217 | 218 | 4. In order to access the private endpoint, you need an access token. So, open a terminal. 219 | 220 | 5. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `simple-service` at [Configure Keycloak](#configure-keycloak) step: 221 | ```bash 222 | SIMPLE_SERVICE_CLIENT_SECRET=... 223 | ``` 224 | 225 | 6. Run the following commands: 226 | ```bash 227 | BGATES_ACCESS_TOKEN=$(curl -s -X POST \ 228 | "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \ 229 | -H "Content-Type: application/x-www-form-urlencoded" \ 230 | -d "username=bgates" \ 231 | -d "password=123" \ 232 | -d "grant_type=password" \ 233 | -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \ 234 | -d "client_id=simple-service" | jq -r .access_token) 235 | 236 | echo $BGATES_ACCESS_TOKEN 237 | ``` 238 | 239 | 7. Copy the token generated and go back to `Swagger`. 240 | 241 | 8. Click `Authorize` button and paste the access token in the `Value` field. Then, click `Authorize` button and, to finalize, click `Close`. 242 | 243 | 9. Go to `GET /api/private` and call this endpoint again, now with authentication. 244 | 245 | It should return: 246 | ```text 247 | Code: 200 248 | Response Body: bgates, it is private. 249 | ``` 250 | 251 | ## Using client_id and client_secret to get access token 252 | 253 | You can get an access token for `simple-service` using `client_id` and `client_secret` 254 | 255 | ### Configuration 256 | 257 | - Access http://localhost:8080; 258 | - Click the dropdown button that contains `Keycloak` and select `company-services`; 259 | - On the left menu, click `Clients`; 260 | - Select `simple-service` client; 261 | - In `Settings` tab: 262 | - Go to `Capability config` and check `Service accounts roles` checkbox; 263 | - Click `Save` button; 264 | - In `Service account roles` tab: 265 | - Click `service-account-simple-service` link present in the info message; 266 | > "To manage detail and group mappings, click on the username service-account-simple-service" 267 | - In `Role mapping` tab: 268 | - Click `Assign role` button; 269 | - Make sure the `Filter by clients` is selected in the first dropdown button; 270 | - In `Search by role name`, type `simple-service` and press `Enter`; 271 | - Select `[simple-service] USER` name and click `Assign` button; 272 | - Now, `service-account-simple-service` has the role `USER` of the `simple-service` assigned. 273 | 274 | ### Test 275 | 276 | 1. Open a terminal. 277 | 278 | 2. Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `simple-service` at [Configure Keycloak](#configure-keycloak) step. 279 | ```bash 280 | SIMPLE_SERVICE_CLIENT_SECRET=... 281 | ``` 282 | 283 | 3. Run the following command: 284 | ```bash 285 | CLIENT_ACCESS_TOKEN=$(curl -s -X POST \ 286 | "http://localhost:8080/realms/company-services/protocol/openid-connect/token" \ 287 | -H "Content-Type: application/x-www-form-urlencoded" \ 288 | -d "grant_type=client_credentials" \ 289 | -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \ 290 | -d "client_id=simple-service" | jq -r .access_token) 291 | 292 | echo $CLIENT_ACCESS_TOKEN 293 | ``` 294 | 295 | 4. Try to call the endpoint `GET /api/private`: 296 | ```bash 297 | curl -i http://localhost:9080/api/private -H "Authorization: Bearer $CLIENT_ACCESS_TOKEN" 298 | ``` 299 | 300 | It should return: 301 | ```text 302 | HTTP/1.1 200 303 | service-account-simple-service, it is private. 304 | ``` 305 | 306 | ## Running simple-service as a Docker container 307 | 308 | - In a terminal, make sure you are in the `springboot-keycloak-openldap` root folder. 309 | 310 | - Build Docker Image: 311 | - JVM 312 | ```bash 313 | ./build-docker-images.sh 314 | ``` 315 | - Native 316 | ```bash 317 | ./build-docker-images.sh native 318 | ``` 319 | 320 | | Environment Variable | Description | 321 | |----------------------|-------------------------------------------------------------| 322 | | `KEYCLOAK_HOST` | Specify host of the `Keycloak` to use (default `localhost`) | 323 | | `KEYCLOAK_PORT` | Specify port of the `Keycloak` to use (default `8080`) | 324 | 325 | - Run Docker Container: 326 | ```bash 327 | docker run --rm --name simple-service \ 328 | -p 9080:8080 \ 329 | -e KEYCLOAK_HOST=keycloak \ 330 | --network=springboot-keycloak-openldap_default \ 331 | ivanfranchin/simple-service:1.0.0 332 | ``` 333 | 334 | - Open a new terminal. 335 | 336 | - Create an environment variable that contains the `Client Secret` generated by `Keycloak` to `simple-service` at [Configure Keycloak](#configure-keycloak) step. 337 | ```bash 338 | SIMPLE_SERVICE_CLIENT_SECRET=... 339 | ``` 340 | 341 | - Run the commands below to get an access token for `bgates` user: 342 | ```bash 343 | BGATES_TOKEN=$( 344 | docker run -t --rm -e CLIENT_SECRET=$SIMPLE_SERVICE_CLIENT_SECRET --network springboot-keycloak-openldap_default alpine/curl:latest sh -c ' 345 | curl -s -X POST http://keycloak:8080/realms/company-services/protocol/openid-connect/token \ 346 | -H "Content-Type: application/x-www-form-urlencoded" \ 347 | -d "username=bgates" \ 348 | -d "password=123" \ 349 | -d "grant_type=password" \ 350 | -d "client_secret=$CLIENT_SECRET" \ 351 | -d "client_id=simple-service"') 352 | 353 | BGATES_ACCESS_TOKEN=$(echo $BGATES_TOKEN | jq -r .access_token) 354 | ``` 355 | 356 | - Call the endpoint `GET /api/private`: 357 | ```bash 358 | curl -i http://localhost:9080/api/private -H "Authorization: Bearer $BGATES_ACCESS_TOKEN" 359 | ``` 360 | 361 | It should return: 362 | ```text 363 | HTTP/1.1 200 364 | bgates, it is private. 365 | ``` 366 | 367 | ## Shutdown 368 | 369 | - To stop the `simple-service` application, go to the terminal where it is running and press `Ctrl+C`; 370 | - To stop and remove Docker Compose containers, network, and volumes, go to a terminal and, inside the `springboot-keycloak-openldap` root folder, run the following command: 371 | ```bash 372 | docker compose down -v 373 | ``` 374 | 375 | ## Cleanup 376 | 377 | To remove the Docker image create by this project, go to a terminal and, inside the `springboot-keycloak-openldap` root folder, run the following script: 378 | ```bash 379 | ./remove-docker-images.sh 380 | ``` 381 | 382 | ## References 383 | 384 | - https://www.keycloak.org/docs/latest/server_admin/ 385 | -------------------------------------------------------------------------------- /build-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | DOCKER_IMAGE_PREFIX="ivanfranchin" 4 | APP_NAME="simple-service" 5 | APP_VERSION="1.0.0" 6 | DOCKER_IMAGE_NAME="${DOCKER_IMAGE_PREFIX}/${APP_NAME}:${APP_VERSION}" 7 | SKIP_TESTS="true" 8 | 9 | if [ "$1" = "native" ]; 10 | then 11 | ./mvnw -Pnative clean spring-boot:build-image \ 12 | --projects "$APP_NAME" \ 13 | -DskipTests="$SKIP_TESTS" \ 14 | -Dspring-boot.build-image.imageName="$DOCKER_IMAGE_NAME" 15 | else 16 | ./mvnw clean spring-boot:build-image \ 17 | --projects "$APP_NAME" \ 18 | -DskipTests="$SKIP_TESTS" \ 19 | -Dspring-boot.build-image.imageName="$DOCKER_IMAGE_NAME" 20 | fi 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | 3 | mysql: 4 | image: 'mysql:9.1.0' 5 | container_name: 'mysql' 6 | ports: 7 | - '3306:3306' 8 | environment: 9 | - 'MYSQL_DATABASE=keycloak' 10 | - 'MYSQL_USER=keycloak' 11 | - 'MYSQL_PASSWORD=password' 12 | - 'MYSQL_ROOT_PASSWORD=root_password' 13 | healthcheck: 14 | test: 'mysqladmin ping -u root -p$${MYSQL_ROOT_PASSWORD}' 15 | 16 | keycloak: 17 | image: 'quay.io/keycloak/keycloak:26.1.3' 18 | container_name: 'keycloak' 19 | environment: 20 | - 'KC_BOOTSTRAP_ADMIN_USERNAME=admin' 21 | - 'KC_BOOTSTRAP_ADMIN_PASSWORD=admin' 22 | - 'KC_DB=mysql' 23 | - 'KC_DB_URL_HOST=mysql' 24 | - 'KC_DB_URL_DATABASE=keycloak' 25 | - 'KC_DB_USERNAME=keycloak' 26 | - 'KC_DB_PASSWORD=password' 27 | - 'KC_HEALTH_ENABLED=true' 28 | ports: 29 | - '8080:8080' 30 | command: 'start-dev' 31 | depends_on: 32 | - 'mysql' 33 | healthcheck: 34 | test: [ "CMD", "sh", "-c", "[ -z \"$(echo \"\" > /dev/tcp/localhost/9081)\" ] || exit 1" ] 35 | 36 | openldap: 37 | image: 'osixia/openldap:1.5.0' 38 | container_name: 'openldap' 39 | environment: 40 | - 'LDAP_ORGANISATION="MyCompany Inc."' 41 | - 'LDAP_DOMAIN=mycompany.com' 42 | ports: 43 | - '389:389' 44 | 45 | phpldapadmin: 46 | image: 'osixia/phpldapadmin:0.9.0' 47 | container_name: 'phpldapadmin' 48 | environment: 49 | - 'PHPLDAPADMIN_LDAP_HOSTS=openldap' 50 | ports: 51 | - '6443:443' 52 | depends_on: 53 | - 'openldap' 54 | -------------------------------------------------------------------------------- /documentation/phpldapadmin.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-keycloak-openldap/b7e3e3eb905ffd14f30b38b6c6f0cd48bee8f640/documentation/phpldapadmin.jpeg -------------------------------------------------------------------------------- /documentation/project-diagram.excalidraw: -------------------------------------------------------------------------------- 1 | { 2 | "type": "excalidraw", 3 | "version": 2, 4 | "source": "https://excalidraw.com", 5 | "elements": [ 6 | { 7 | "type": "rectangle", 8 | "version": 360, 9 | "versionNonce": 536770157, 10 | "isDeleted": false, 11 | "id": "LF8GW9IPL_GFv-IztvS9e", 12 | "fillStyle": "hachure", 13 | "strokeWidth": 1, 14 | "strokeStyle": "solid", 15 | "roughness": 1, 16 | "opacity": 100, 17 | "angle": 0, 18 | "x": 695.5566932166084, 19 | "y": 123.35027431362596, 20 | "strokeColor": "#000000", 21 | "backgroundColor": "#4c6ef5", 22 | "width": 209.18356323242188, 23 | "height": 99.67071533203125, 24 | "seed": 1491655448, 25 | "groupIds": [], 26 | "roundness": { 27 | "type": 3 28 | }, 29 | "boundElements": [ 30 | { 31 | "type": "text", 32 | "id": "_OetYLRC4Z5TgAYd9XEfr" 33 | }, 34 | { 35 | "id": "gxDaAxiK9vlXNqvOjW4hR", 36 | "type": "arrow" 37 | }, 38 | { 39 | "id": "h0Mz6bwLORO7JPnOyVVPH", 40 | "type": "arrow" 41 | } 42 | ], 43 | "updated": 1682154542328, 44 | "link": null, 45 | "locked": false 46 | }, 47 | { 48 | "type": "text", 49 | "version": 351, 50 | "versionNonce": 609141558, 51 | "isDeleted": false, 52 | "id": "_OetYLRC4Z5TgAYd9XEfr", 53 | "fillStyle": "hachure", 54 | "strokeWidth": 1, 55 | "strokeStyle": "solid", 56 | "roughness": 1, 57 | "opacity": 100, 58 | "angle": 0, 59 | "x": 710.2685157263741, 60 | "y": 156.38563197964157, 61 | "strokeColor": "#000000", 62 | "backgroundColor": "transparent", 63 | "width": 179.75991821289062, 64 | "height": 33.6, 65 | "seed": 542041697, 66 | "groupIds": [], 67 | "roundness": null, 68 | "boundElements": [], 69 | "updated": 1682154217277, 70 | "link": null, 71 | "locked": false, 72 | "fontSize": 28, 73 | "fontFamily": 1, 74 | "text": "simple-service", 75 | "textAlign": "center", 76 | "verticalAlign": "middle", 77 | "containerId": "LF8GW9IPL_GFv-IztvS9e", 78 | "originalText": "simple-service", 79 | "lineHeight": 1.2, 80 | "baseline": 24 81 | }, 82 | { 83 | "type": "rectangle", 84 | "version": 752, 85 | "versionNonce": 1042035427, 86 | "isDeleted": false, 87 | "id": "htH4DvpAlw_lK0WCfkn_y", 88 | "fillStyle": "hachure", 89 | "strokeWidth": 1, 90 | "strokeStyle": "solid", 91 | "roughness": 1, 92 | "opacity": 100, 93 | "angle": 0, 94 | "x": 296.5864574026842, 95 | "y": 125.26187133789062, 96 | "strokeColor": "#000000", 97 | "backgroundColor": "#be4bdb", 98 | "width": 209.18356323242188, 99 | "height": 99.67071533203125, 100 | "seed": 575518744, 101 | "groupIds": [], 102 | "roundness": { 103 | "type": 3 104 | }, 105 | "boundElements": [ 106 | { 107 | "type": "text", 108 | "id": "2X2Ld8TDu-NPJPkdyJl5g" 109 | }, 110 | { 111 | "id": "gxDaAxiK9vlXNqvOjW4hR", 112 | "type": "arrow" 113 | }, 114 | { 115 | "id": "dmRTXBbC_6Lu0gbofjs5F", 116 | "type": "arrow" 117 | }, 118 | { 119 | "id": "LLf5MpEVXMYg3R9iJaUnk", 120 | "type": "arrow" 121 | }, 122 | { 123 | "id": "ifTxAzOAWyGvwh2vK-vh4", 124 | "type": "arrow" 125 | }, 126 | { 127 | "id": "QJqJSWNOeX5UUg7YQxJ9u", 128 | "type": "arrow" 129 | }, 130 | { 131 | "id": "9clvjtopspLFkfIEUdpAA", 132 | "type": "arrow" 133 | } 134 | ], 135 | "updated": 1682154542328, 136 | "link": null, 137 | "locked": false 138 | }, 139 | { 140 | "type": "text", 141 | "version": 740, 142 | "versionNonce": 524086390, 143 | "isDeleted": false, 144 | "id": "2X2Ld8TDu-NPJPkdyJl5g", 145 | "fillStyle": "hachure", 146 | "strokeWidth": 1, 147 | "strokeStyle": "solid", 148 | "roughness": 1, 149 | "opacity": 100, 150 | "angle": 0, 151 | "x": 343.75026813266464, 152 | "y": 158.29722900390624, 153 | "strokeColor": "#000000", 154 | "backgroundColor": "transparent", 155 | "width": 114.85594177246094, 156 | "height": 33.6, 157 | "seed": 200323745, 158 | "groupIds": [], 159 | "roundness": null, 160 | "boundElements": [], 161 | "updated": 1682154217277, 162 | "link": null, 163 | "locked": false, 164 | "fontSize": 28, 165 | "fontFamily": 1, 166 | "text": "Keycloak", 167 | "textAlign": "center", 168 | "verticalAlign": "middle", 169 | "containerId": "htH4DvpAlw_lK0WCfkn_y", 170 | "originalText": "Keycloak", 171 | "lineHeight": 1.2, 172 | "baseline": 24 173 | }, 174 | { 175 | "type": "rectangle", 176 | "version": 925, 177 | "versionNonce": 1533454902, 178 | "isDeleted": false, 179 | "id": "NKmNZxYxWMCKh3prRiPwX", 180 | "fillStyle": "hachure", 181 | "strokeWidth": 1, 182 | "strokeStyle": "solid", 183 | "roughness": 1, 184 | "opacity": 100, 185 | "angle": 0, 186 | "x": -12.177137074514462, 187 | "y": 121.84813808315721, 188 | "strokeColor": "#000000", 189 | "backgroundColor": "#82c91e", 190 | "width": 209.18356323242188, 191 | "height": 99.67071533203125, 192 | "seed": 84866328, 193 | "groupIds": [], 194 | "roundness": { 195 | "type": 3 196 | }, 197 | "boundElements": [ 198 | { 199 | "type": "text", 200 | "id": "C9YTYTHGgwpszLfgIFDTi" 201 | }, 202 | { 203 | "id": "9clvjtopspLFkfIEUdpAA", 204 | "type": "arrow" 205 | } 206 | ], 207 | "updated": 1682154240202, 208 | "link": null, 209 | "locked": false 210 | }, 211 | { 212 | "type": "text", 213 | "version": 937, 214 | "versionNonce": 85192118, 215 | "isDeleted": false, 216 | "id": "C9YTYTHGgwpszLfgIFDTi", 217 | "fillStyle": "hachure", 218 | "strokeWidth": 1, 219 | "strokeStyle": "solid", 220 | "roughness": 1, 221 | "opacity": 100, 222 | "angle": 0, 223 | "x": 48.35666877265351, 224 | "y": 154.88349574917282, 225 | "strokeColor": "#000000", 226 | "backgroundColor": "transparent", 227 | "width": 88.11595153808594, 228 | "height": 33.6, 229 | "seed": 672921103, 230 | "groupIds": [], 231 | "roundness": null, 232 | "boundElements": [], 233 | "updated": 1682154217277, 234 | "link": null, 235 | "locked": false, 236 | "fontSize": 28, 237 | "fontFamily": 1, 238 | "text": "MySQL", 239 | "textAlign": "center", 240 | "verticalAlign": "middle", 241 | "containerId": "NKmNZxYxWMCKh3prRiPwX", 242 | "originalText": "MySQL", 243 | "lineHeight": 1.2, 244 | "baseline": 24 245 | }, 246 | { 247 | "type": "rectangle", 248 | "version": 1317, 249 | "versionNonce": 543402038, 250 | "isDeleted": false, 251 | "id": "-fBgLVr8TGW8aaNMK731H", 252 | "fillStyle": "hachure", 253 | "strokeWidth": 1, 254 | "strokeStyle": "solid", 255 | "roughness": 1, 256 | "opacity": 100, 257 | "angle": 0, 258 | "x": 296.5864574026842, 259 | "y": 316.07691171581234, 260 | "strokeColor": "#000000", 261 | "backgroundColor": "#fd7e14", 262 | "width": 209.18356323242188, 263 | "height": 99.67071533203125, 264 | "seed": 1453363736, 265 | "groupIds": [], 266 | "roundness": { 267 | "type": 3 268 | }, 269 | "boundElements": [ 270 | { 271 | "type": "text", 272 | "id": "ibIuQlYWMa9dg_rumvKxK" 273 | }, 274 | { 275 | "id": "yNOTVOp1OXW9sHr6z9HKG", 276 | "type": "arrow" 277 | }, 278 | { 279 | "id": "8bPcv9HlAk3aTlx83Mhkx", 280 | "type": "arrow" 281 | } 282 | ], 283 | "updated": 1682154287388, 284 | "link": null, 285 | "locked": false 286 | }, 287 | { 288 | "type": "text", 289 | "version": 1309, 290 | "versionNonce": 855164662, 291 | "isDeleted": false, 292 | "id": "ibIuQlYWMa9dg_rumvKxK", 293 | "fillStyle": "hachure", 294 | "strokeWidth": 1, 295 | "strokeStyle": "solid", 296 | "roughness": 1, 297 | "opacity": 100, 298 | "angle": 0, 299 | "x": 304.74628558871933, 300 | "y": 349.11226938182796, 301 | "strokeColor": "#000000", 302 | "backgroundColor": "transparent", 303 | "width": 192.86390686035156, 304 | "height": 33.6, 305 | "seed": 781345565, 306 | "groupIds": [], 307 | "roundness": null, 308 | "boundElements": [], 309 | "updated": 1682154217277, 310 | "link": null, 311 | "locked": false, 312 | "fontSize": 28, 313 | "fontFamily": 1, 314 | "text": "PhpLDAPAdmin", 315 | "textAlign": "center", 316 | "verticalAlign": "middle", 317 | "containerId": "-fBgLVr8TGW8aaNMK731H", 318 | "originalText": "PhpLDAPAdmin", 319 | "lineHeight": 1.2, 320 | "baseline": 24 321 | }, 322 | { 323 | "type": "ellipse", 324 | "version": 632, 325 | "versionNonce": 722717546, 326 | "isDeleted": false, 327 | "id": "flM-p46Jb3LUEX1XCxWf1", 328 | "fillStyle": "hachure", 329 | "strokeWidth": 2, 330 | "strokeStyle": "solid", 331 | "roughness": 1, 332 | "opacity": 100, 333 | "angle": 0, 334 | "x": 601.1402609967746, 335 | "y": -105.02635976856266, 336 | "strokeColor": "#000000", 337 | "backgroundColor": "transparent", 338 | "width": 26.930389404296875, 339 | "height": 27.545562744140625, 340 | "seed": 336283672, 341 | "groupIds": [ 342 | "k93ihkNXC8u2CtHzxSadx" 343 | ], 344 | "roundness": { 345 | "type": 2 346 | }, 347 | "boundElements": [], 348 | "updated": 1682154217277, 349 | "link": null, 350 | "locked": false 351 | }, 352 | { 353 | "type": "line", 354 | "version": 653, 355 | "versionNonce": 426801206, 356 | "isDeleted": false, 357 | "id": "EbiGQ1Bsju1j3fJU9VTt0", 358 | "fillStyle": "hachure", 359 | "strokeWidth": 2, 360 | "strokeStyle": "solid", 361 | "roughness": 1, 362 | "opacity": 100, 363 | "angle": 0, 364 | "x": 612.578096690134, 365 | "y": -77.02571889942203, 366 | "strokeColor": "#000000", 367 | "backgroundColor": "transparent", 368 | "width": 0.473419189453125, 369 | "height": 40.3687744140625, 370 | "seed": 144344344, 371 | "groupIds": [ 372 | "k93ihkNXC8u2CtHzxSadx" 373 | ], 374 | "roundness": { 375 | "type": 2 376 | }, 377 | "boundElements": [], 378 | "updated": 1682154217277, 379 | "link": null, 380 | "locked": false, 381 | "startBinding": null, 382 | "endBinding": null, 383 | "lastCommittedPoint": null, 384 | "startArrowhead": null, 385 | "endArrowhead": null, 386 | "points": [ 387 | [ 388 | 0, 389 | 0 390 | ], 391 | [ 392 | -0.473419189453125, 393 | 40.3687744140625 394 | ] 395 | ] 396 | }, 397 | { 398 | "type": "line", 399 | "version": 604, 400 | "versionNonce": 1310379562, 401 | "isDeleted": false, 402 | "id": "1ekBPFngOlVFkHRQdx_fs", 403 | "fillStyle": "hachure", 404 | "strokeWidth": 2, 405 | "strokeStyle": "solid", 406 | "roughness": 1, 407 | "opacity": 100, 408 | "angle": 0, 409 | "x": 612.2825949811496, 410 | "y": -35.22206899707828, 411 | "strokeColor": "#000000", 412 | "backgroundColor": "transparent", 413 | "width": 17.21380615234375, 414 | "height": 33.91400146484375, 415 | "seed": 37364248, 416 | "groupIds": [ 417 | "k93ihkNXC8u2CtHzxSadx" 418 | ], 419 | "roundness": { 420 | "type": 2 421 | }, 422 | "boundElements": [], 423 | "updated": 1682154217277, 424 | "link": null, 425 | "locked": false, 426 | "startBinding": null, 427 | "endBinding": null, 428 | "lastCommittedPoint": null, 429 | "startArrowhead": null, 430 | "endArrowhead": null, 431 | "points": [ 432 | [ 433 | 0, 434 | 0 435 | ], 436 | [ 437 | -17.21380615234375, 438 | 33.91400146484375 439 | ] 440 | ] 441 | }, 442 | { 443 | "type": "line", 444 | "version": 623, 445 | "versionNonce": 1348469110, 446 | "isDeleted": false, 447 | "id": "1uBWzjtbCU7nyF1hJT3-Q", 448 | "fillStyle": "hachure", 449 | "strokeWidth": 2, 450 | "strokeStyle": "solid", 451 | "roughness": 1, 452 | "opacity": 100, 453 | "angle": 0, 454 | "x": 612.3984397077121, 455 | "y": -35.10189077442203, 456 | "strokeColor": "#000000", 457 | "backgroundColor": "transparent", 458 | "width": 12.9422607421875, 459 | "height": 35.16510009765625, 460 | "seed": 1071521560, 461 | "groupIds": [ 462 | "k93ihkNXC8u2CtHzxSadx" 463 | ], 464 | "roundness": { 465 | "type": 2 466 | }, 467 | "boundElements": [], 468 | "updated": 1682154217277, 469 | "link": null, 470 | "locked": false, 471 | "startBinding": null, 472 | "endBinding": null, 473 | "lastCommittedPoint": null, 474 | "startArrowhead": null, 475 | "endArrowhead": null, 476 | "points": [ 477 | [ 478 | 0, 479 | 0 480 | ], 481 | [ 482 | 12.9422607421875, 483 | 35.16510009765625 484 | ] 485 | ] 486 | }, 487 | { 488 | "type": "line", 489 | "version": 639, 490 | "versionNonce": 1723037930, 491 | "isDeleted": false, 492 | "id": "68rxeiTLOJcmisjdHZN_M", 493 | "fillStyle": "hachure", 494 | "strokeWidth": 2, 495 | "strokeStyle": "solid", 496 | "roughness": 1, 497 | "opacity": 100, 498 | "angle": 0, 499 | "x": 613.5942710065402, 500 | "y": -59.52773305957828, 501 | "strokeColor": "#000000", 502 | "backgroundColor": "transparent", 503 | "width": 29.445220947265625, 504 | "height": 20.990234375, 505 | "seed": 33192984, 506 | "groupIds": [ 507 | "k93ihkNXC8u2CtHzxSadx" 508 | ], 509 | "roundness": { 510 | "type": 2 511 | }, 512 | "boundElements": [], 513 | "updated": 1682154217277, 514 | "link": null, 515 | "locked": false, 516 | "startBinding": null, 517 | "endBinding": null, 518 | "lastCommittedPoint": null, 519 | "startArrowhead": null, 520 | "endArrowhead": null, 521 | "points": [ 522 | [ 523 | 0, 524 | 0 525 | ], 526 | [ 527 | 29.445220947265625, 528 | -20.990234375 529 | ] 530 | ] 531 | }, 532 | { 533 | "type": "line", 534 | "version": 678, 535 | "versionNonce": 722231990, 536 | "isDeleted": false, 537 | "id": "86PwlDW0bQsfE8A6yj0ns", 538 | "fillStyle": "hachure", 539 | "strokeWidth": 2, 540 | "strokeStyle": "solid", 541 | "roughness": 1, 542 | "opacity": 100, 543 | "angle": 0, 544 | "x": 612.7033713483371, 545 | "y": -60.25789663379703, 546 | "strokeColor": "#000000", 547 | "backgroundColor": "transparent", 548 | "width": 25.4169921875, 549 | "height": 9.85821533203125, 550 | "seed": 1518549272, 551 | "groupIds": [ 552 | "k93ihkNXC8u2CtHzxSadx" 553 | ], 554 | "roundness": { 555 | "type": 2 556 | }, 557 | "boundElements": [], 558 | "updated": 1682154217277, 559 | "link": null, 560 | "locked": false, 561 | "startBinding": null, 562 | "endBinding": null, 563 | "lastCommittedPoint": null, 564 | "startArrowhead": null, 565 | "endArrowhead": null, 566 | "points": [ 567 | [ 568 | 0, 569 | 0 570 | ], 571 | [ 572 | -25.4169921875, 573 | -9.85821533203125 574 | ] 575 | ] 576 | }, 577 | { 578 | "type": "text", 579 | "version": 609, 580 | "versionNonce": 610847658, 581 | "isDeleted": false, 582 | "id": "ehCDjz26ruGMW6Q7TDldG", 583 | "fillStyle": "hachure", 584 | "strokeWidth": 2, 585 | "strokeStyle": "solid", 586 | "roughness": 1, 587 | "opacity": 100, 588 | "angle": 0, 589 | "x": 590.2163718366184, 590 | "y": -134.44591543262516, 591 | "strokeColor": "#000000", 592 | "backgroundColor": "transparent", 593 | "width": 50.87995910644531, 594 | "height": 24, 595 | "seed": 2095863320, 596 | "groupIds": [ 597 | "k93ihkNXC8u2CtHzxSadx" 598 | ], 599 | "roundness": null, 600 | "boundElements": [], 601 | "updated": 1682154217277, 602 | "link": null, 603 | "locked": false, 604 | "fontSize": 20, 605 | "fontFamily": 1, 606 | "text": "Admin", 607 | "textAlign": "left", 608 | "verticalAlign": "top", 609 | "containerId": null, 610 | "originalText": "Admin", 611 | "lineHeight": 1.2, 612 | "baseline": 17 613 | }, 614 | { 615 | "type": "ellipse", 616 | "version": 713, 617 | "versionNonce": 1276812278, 618 | "isDeleted": false, 619 | "id": "zYllgBlgP7S7-phqNnnEr", 620 | "fillStyle": "hachure", 621 | "strokeWidth": 2, 622 | "strokeStyle": "solid", 623 | "roughness": 1, 624 | "opacity": 100, 625 | "angle": 0, 626 | "x": 527.1802695416965, 627 | "y": -108.07631704395328, 628 | "strokeColor": "#000000", 629 | "backgroundColor": "transparent", 630 | "width": 26.930389404296875, 631 | "height": 27.545562744140625, 632 | "seed": 237622040, 633 | "groupIds": [ 634 | "4D1ojplACrlVIaNZ7P0FH" 635 | ], 636 | "roundness": { 637 | "type": 2 638 | }, 639 | "boundElements": [], 640 | "updated": 1682154217277, 641 | "link": null, 642 | "locked": false 643 | }, 644 | { 645 | "type": "line", 646 | "version": 734, 647 | "versionNonce": 224773738, 648 | "isDeleted": false, 649 | "id": "iW_3iMfYwsgECYYtnEK33", 650 | "fillStyle": "hachure", 651 | "strokeWidth": 2, 652 | "strokeStyle": "solid", 653 | "roughness": 1, 654 | "opacity": 100, 655 | "angle": 0, 656 | "x": 538.6181052350559, 657 | "y": -80.07567617481266, 658 | "strokeColor": "#000000", 659 | "backgroundColor": "transparent", 660 | "width": 0.473419189453125, 661 | "height": 40.3687744140625, 662 | "seed": 1957670936, 663 | "groupIds": [ 664 | "4D1ojplACrlVIaNZ7P0FH" 665 | ], 666 | "roundness": { 667 | "type": 2 668 | }, 669 | "boundElements": [], 670 | "updated": 1682154217277, 671 | "link": null, 672 | "locked": false, 673 | "startBinding": null, 674 | "endBinding": null, 675 | "lastCommittedPoint": null, 676 | "startArrowhead": null, 677 | "endArrowhead": null, 678 | "points": [ 679 | [ 680 | 0, 681 | 0 682 | ], 683 | [ 684 | -0.473419189453125, 685 | 40.3687744140625 686 | ] 687 | ] 688 | }, 689 | { 690 | "type": "line", 691 | "version": 685, 692 | "versionNonce": 965717302, 693 | "isDeleted": false, 694 | "id": "8JTNvN86yjteIVYunsqxA", 695 | "fillStyle": "hachure", 696 | "strokeWidth": 2, 697 | "strokeStyle": "solid", 698 | "roughness": 1, 699 | "opacity": 100, 700 | "angle": 0, 701 | "x": 538.3226035260715, 702 | "y": -38.272026272468906, 703 | "strokeColor": "#000000", 704 | "backgroundColor": "transparent", 705 | "width": 17.21380615234375, 706 | "height": 33.91400146484375, 707 | "seed": 678554904, 708 | "groupIds": [ 709 | "4D1ojplACrlVIaNZ7P0FH" 710 | ], 711 | "roundness": { 712 | "type": 2 713 | }, 714 | "boundElements": [], 715 | "updated": 1682154217277, 716 | "link": null, 717 | "locked": false, 718 | "startBinding": null, 719 | "endBinding": null, 720 | "lastCommittedPoint": null, 721 | "startArrowhead": null, 722 | "endArrowhead": null, 723 | "points": [ 724 | [ 725 | 0, 726 | 0 727 | ], 728 | [ 729 | -17.21380615234375, 730 | 33.91400146484375 731 | ] 732 | ] 733 | }, 734 | { 735 | "type": "line", 736 | "version": 704, 737 | "versionNonce": 712337706, 738 | "isDeleted": false, 739 | "id": "hMliKg9HCYu-lEplUg1Y6", 740 | "fillStyle": "hachure", 741 | "strokeWidth": 2, 742 | "strokeStyle": "solid", 743 | "roughness": 1, 744 | "opacity": 100, 745 | "angle": 0, 746 | "x": 538.438448252634, 747 | "y": -38.151848049812656, 748 | "strokeColor": "#000000", 749 | "backgroundColor": "transparent", 750 | "width": 12.9422607421875, 751 | "height": 35.16510009765625, 752 | "seed": 48531992, 753 | "groupIds": [ 754 | "4D1ojplACrlVIaNZ7P0FH" 755 | ], 756 | "roundness": { 757 | "type": 2 758 | }, 759 | "boundElements": [], 760 | "updated": 1682154217277, 761 | "link": null, 762 | "locked": false, 763 | "startBinding": null, 764 | "endBinding": null, 765 | "lastCommittedPoint": null, 766 | "startArrowhead": null, 767 | "endArrowhead": null, 768 | "points": [ 769 | [ 770 | 0, 771 | 0 772 | ], 773 | [ 774 | 12.9422607421875, 775 | 35.16510009765625 776 | ] 777 | ] 778 | }, 779 | { 780 | "type": "line", 781 | "version": 720, 782 | "versionNonce": 1892718198, 783 | "isDeleted": false, 784 | "id": "pnA0DA25tJxDKgaSQnlkY", 785 | "fillStyle": "hachure", 786 | "strokeWidth": 2, 787 | "strokeStyle": "solid", 788 | "roughness": 1, 789 | "opacity": 100, 790 | "angle": 0, 791 | "x": 539.6342795514621, 792 | "y": -62.577690334968906, 793 | "strokeColor": "#000000", 794 | "backgroundColor": "transparent", 795 | "width": 29.445220947265625, 796 | "height": 20.990234375, 797 | "seed": 251365144, 798 | "groupIds": [ 799 | "4D1ojplACrlVIaNZ7P0FH" 800 | ], 801 | "roundness": { 802 | "type": 2 803 | }, 804 | "boundElements": [], 805 | "updated": 1682154217277, 806 | "link": null, 807 | "locked": false, 808 | "startBinding": null, 809 | "endBinding": null, 810 | "lastCommittedPoint": null, 811 | "startArrowhead": null, 812 | "endArrowhead": null, 813 | "points": [ 814 | [ 815 | 0, 816 | 0 817 | ], 818 | [ 819 | 29.445220947265625, 820 | -20.990234375 821 | ] 822 | ] 823 | }, 824 | { 825 | "type": "line", 826 | "version": 759, 827 | "versionNonce": 855815146, 828 | "isDeleted": false, 829 | "id": "HhvpXqS4JXS1iu_1aiVep", 830 | "fillStyle": "hachure", 831 | "strokeWidth": 2, 832 | "strokeStyle": "solid", 833 | "roughness": 1, 834 | "opacity": 100, 835 | "angle": 0, 836 | "x": 538.743379893259, 837 | "y": -63.307853909187656, 838 | "strokeColor": "#000000", 839 | "backgroundColor": "transparent", 840 | "width": 25.4169921875, 841 | "height": 9.85821533203125, 842 | "seed": 1495983128, 843 | "groupIds": [ 844 | "4D1ojplACrlVIaNZ7P0FH" 845 | ], 846 | "roundness": { 847 | "type": 2 848 | }, 849 | "boundElements": [], 850 | "updated": 1682154217278, 851 | "link": null, 852 | "locked": false, 853 | "startBinding": null, 854 | "endBinding": null, 855 | "lastCommittedPoint": null, 856 | "startArrowhead": null, 857 | "endArrowhead": null, 858 | "points": [ 859 | [ 860 | 0, 861 | 0 862 | ], 863 | [ 864 | -25.4169921875, 865 | -9.85821533203125 866 | ] 867 | ] 868 | }, 869 | { 870 | "type": "text", 871 | "version": 785, 872 | "versionNonce": 2057459638, 873 | "isDeleted": false, 874 | "id": "T0m-48lm7wA_Uw99BXSmk", 875 | "fillStyle": "hachure", 876 | "strokeWidth": 2, 877 | "strokeStyle": "solid", 878 | "roughness": 1, 879 | "opacity": 100, 880 | "angle": 0, 881 | "x": 516.5344870709934, 882 | "y": -140.02175161426578, 883 | "strokeColor": "#000000", 884 | "backgroundColor": "transparent", 885 | "width": 44.679962158203125, 886 | "height": 24, 887 | "seed": 1063690520, 888 | "groupIds": [ 889 | "4D1ojplACrlVIaNZ7P0FH" 890 | ], 891 | "roundness": null, 892 | "boundElements": [], 893 | "updated": 1682154217278, 894 | "link": null, 895 | "locked": false, 896 | "fontSize": 20, 897 | "fontFamily": 1, 898 | "text": "User", 899 | "textAlign": "left", 900 | "verticalAlign": "top", 901 | "containerId": null, 902 | "originalText": "User", 903 | "lineHeight": 1.2, 904 | "baseline": 17 905 | }, 906 | { 907 | "type": "arrow", 908 | "version": 204, 909 | "versionNonce": 1968210541, 910 | "isDeleted": false, 911 | "id": "gxDaAxiK9vlXNqvOjW4hR", 912 | "fillStyle": "hachure", 913 | "strokeWidth": 1, 914 | "strokeStyle": "solid", 915 | "roughness": 1, 916 | "opacity": 100, 917 | "angle": 0, 918 | "x": 515.4852927350557, 919 | "y": 180.14856698924984, 920 | "strokeColor": "#000000", 921 | "backgroundColor": "transparent", 922 | "width": 168.14727783203136, 923 | "height": 1.14910888671875, 924 | "seed": 1311854616, 925 | "groupIds": [], 926 | "roundness": { 927 | "type": 2 928 | }, 929 | "boundElements": [], 930 | "updated": 1682154548450, 931 | "link": null, 932 | "locked": false, 933 | "startBinding": { 934 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 935 | "focus": 0.11538062583729973, 936 | "gap": 9.715272099949686 937 | }, 938 | "endBinding": { 939 | "elementId": "LF8GW9IPL_GFv-IztvS9e", 940 | "focus": -0.09925914930998067, 941 | "gap": 11.924122649521337 942 | }, 943 | "lastCommittedPoint": null, 944 | "startArrowhead": "arrow", 945 | "endArrowhead": "arrow", 946 | "points": [ 947 | [ 948 | 0, 949 | 0 950 | ], 951 | [ 952 | 168.14727783203136, 953 | -1.14910888671875 954 | ] 955 | ] 956 | }, 957 | { 958 | "type": "ellipse", 959 | "version": 1052, 960 | "versionNonce": 1508663862, 961 | "isDeleted": false, 962 | "id": "s6b0o3EXLE89MvqfGkUns", 963 | "fillStyle": "hachure", 964 | "strokeWidth": 2, 965 | "strokeStyle": "solid", 966 | "roughness": 1, 967 | "opacity": 100, 968 | "angle": 0, 969 | "x": 748.5876486920871, 970 | "y": 308.9952161591717, 971 | "strokeColor": "#000000", 972 | "backgroundColor": "transparent", 973 | "width": 26.930389404296875, 974 | "height": 27.545562744140625, 975 | "seed": 1092795928, 976 | "groupIds": [ 977 | "u7g1JzQZ0WL6fcn3IY7co" 978 | ], 979 | "roundness": { 980 | "type": 2 981 | }, 982 | "boundElements": [], 983 | "updated": 1682154217278, 984 | "link": null, 985 | "locked": false 986 | }, 987 | { 988 | "type": "line", 989 | "version": 1073, 990 | "versionNonce": 1395295274, 991 | "isDeleted": false, 992 | "id": "2uTa355SnJV7AGa7mH2Yi", 993 | "fillStyle": "hachure", 994 | "strokeWidth": 2, 995 | "strokeStyle": "solid", 996 | "roughness": 1, 997 | "opacity": 100, 998 | "angle": 0, 999 | "x": 760.0254843854465, 1000 | "y": 336.99585702831234, 1001 | "strokeColor": "#000000", 1002 | "backgroundColor": "transparent", 1003 | "width": 0.473419189453125, 1004 | "height": 40.3687744140625, 1005 | "seed": 1330854680, 1006 | "groupIds": [ 1007 | "u7g1JzQZ0WL6fcn3IY7co" 1008 | ], 1009 | "roundness": { 1010 | "type": 2 1011 | }, 1012 | "boundElements": [], 1013 | "updated": 1682154217278, 1014 | "link": null, 1015 | "locked": false, 1016 | "startBinding": null, 1017 | "endBinding": null, 1018 | "lastCommittedPoint": null, 1019 | "startArrowhead": null, 1020 | "endArrowhead": null, 1021 | "points": [ 1022 | [ 1023 | 0, 1024 | 0 1025 | ], 1026 | [ 1027 | -0.473419189453125, 1028 | 40.3687744140625 1029 | ] 1030 | ] 1031 | }, 1032 | { 1033 | "type": "line", 1034 | "version": 1024, 1035 | "versionNonce": 768519030, 1036 | "isDeleted": false, 1037 | "id": "oM6ILnVKaA2gGkNcBhjZN", 1038 | "fillStyle": "hachure", 1039 | "strokeWidth": 2, 1040 | "strokeStyle": "solid", 1041 | "roughness": 1, 1042 | "opacity": 100, 1043 | "angle": 0, 1044 | "x": 759.7299826764621, 1045 | "y": 378.7995069306561, 1046 | "strokeColor": "#000000", 1047 | "backgroundColor": "transparent", 1048 | "width": 17.21380615234375, 1049 | "height": 33.91400146484375, 1050 | "seed": 476846104, 1051 | "groupIds": [ 1052 | "u7g1JzQZ0WL6fcn3IY7co" 1053 | ], 1054 | "roundness": { 1055 | "type": 2 1056 | }, 1057 | "boundElements": [], 1058 | "updated": 1682154217278, 1059 | "link": null, 1060 | "locked": false, 1061 | "startBinding": null, 1062 | "endBinding": null, 1063 | "lastCommittedPoint": null, 1064 | "startArrowhead": null, 1065 | "endArrowhead": null, 1066 | "points": [ 1067 | [ 1068 | 0, 1069 | 0 1070 | ], 1071 | [ 1072 | -17.21380615234375, 1073 | 33.91400146484375 1074 | ] 1075 | ] 1076 | }, 1077 | { 1078 | "type": "line", 1079 | "version": 1043, 1080 | "versionNonce": 1549561578, 1081 | "isDeleted": false, 1082 | "id": "k6SVayfoANqiSMRE9yF5M", 1083 | "fillStyle": "hachure", 1084 | "strokeWidth": 2, 1085 | "strokeStyle": "solid", 1086 | "roughness": 1, 1087 | "opacity": 100, 1088 | "angle": 0, 1089 | "x": 759.8458274030246, 1090 | "y": 378.91968515331234, 1091 | "strokeColor": "#000000", 1092 | "backgroundColor": "transparent", 1093 | "width": 12.9422607421875, 1094 | "height": 35.16510009765625, 1095 | "seed": 1818318104, 1096 | "groupIds": [ 1097 | "u7g1JzQZ0WL6fcn3IY7co" 1098 | ], 1099 | "roundness": { 1100 | "type": 2 1101 | }, 1102 | "boundElements": [], 1103 | "updated": 1682154217278, 1104 | "link": null, 1105 | "locked": false, 1106 | "startBinding": null, 1107 | "endBinding": null, 1108 | "lastCommittedPoint": null, 1109 | "startArrowhead": null, 1110 | "endArrowhead": null, 1111 | "points": [ 1112 | [ 1113 | 0, 1114 | 0 1115 | ], 1116 | [ 1117 | 12.9422607421875, 1118 | 35.16510009765625 1119 | ] 1120 | ] 1121 | }, 1122 | { 1123 | "type": "line", 1124 | "version": 1059, 1125 | "versionNonce": 2031471798, 1126 | "isDeleted": false, 1127 | "id": "vR2qNnN5GZahQ6PZgXKLz", 1128 | "fillStyle": "hachure", 1129 | "strokeWidth": 2, 1130 | "strokeStyle": "solid", 1131 | "roughness": 1, 1132 | "opacity": 100, 1133 | "angle": 0, 1134 | "x": 761.0416587018527, 1135 | "y": 354.4938428681561, 1136 | "strokeColor": "#000000", 1137 | "backgroundColor": "transparent", 1138 | "width": 29.445220947265625, 1139 | "height": 20.990234375, 1140 | "seed": 1688662552, 1141 | "groupIds": [ 1142 | "u7g1JzQZ0WL6fcn3IY7co" 1143 | ], 1144 | "roundness": { 1145 | "type": 2 1146 | }, 1147 | "boundElements": [], 1148 | "updated": 1682154217278, 1149 | "link": null, 1150 | "locked": false, 1151 | "startBinding": null, 1152 | "endBinding": null, 1153 | "lastCommittedPoint": null, 1154 | "startArrowhead": null, 1155 | "endArrowhead": null, 1156 | "points": [ 1157 | [ 1158 | 0, 1159 | 0 1160 | ], 1161 | [ 1162 | 29.445220947265625, 1163 | -20.990234375 1164 | ] 1165 | ] 1166 | }, 1167 | { 1168 | "type": "line", 1169 | "version": 1098, 1170 | "versionNonce": 629342634, 1171 | "isDeleted": false, 1172 | "id": "uwlaBbyWa_J2CP7wHadaY", 1173 | "fillStyle": "hachure", 1174 | "strokeWidth": 2, 1175 | "strokeStyle": "solid", 1176 | "roughness": 1, 1177 | "opacity": 100, 1178 | "angle": 0, 1179 | "x": 760.1507590436496, 1180 | "y": 353.76367929393734, 1181 | "strokeColor": "#000000", 1182 | "backgroundColor": "transparent", 1183 | "width": 25.4169921875, 1184 | "height": 9.85821533203125, 1185 | "seed": 1546710808, 1186 | "groupIds": [ 1187 | "u7g1JzQZ0WL6fcn3IY7co" 1188 | ], 1189 | "roundness": { 1190 | "type": 2 1191 | }, 1192 | "boundElements": [], 1193 | "updated": 1682154217278, 1194 | "link": null, 1195 | "locked": false, 1196 | "startBinding": null, 1197 | "endBinding": null, 1198 | "lastCommittedPoint": null, 1199 | "startArrowhead": null, 1200 | "endArrowhead": null, 1201 | "points": [ 1202 | [ 1203 | 0, 1204 | 0 1205 | ], 1206 | [ 1207 | -25.4169921875, 1208 | -9.85821533203125 1209 | ] 1210 | ] 1211 | }, 1212 | { 1213 | "type": "text", 1214 | "version": 1034, 1215 | "versionNonce": 694601206, 1216 | "isDeleted": false, 1217 | "id": "GRFVRUsIgnm6E4hNYNpue", 1218 | "fillStyle": "hachure", 1219 | "strokeWidth": 2, 1220 | "strokeStyle": "solid", 1221 | "roughness": 1, 1222 | "opacity": 100, 1223 | "angle": 0, 1224 | "x": 737.6637595319309, 1225 | "y": 279.5756604951092, 1226 | "strokeColor": "#000000", 1227 | "backgroundColor": "transparent", 1228 | "width": 50.87995910644531, 1229 | "height": 24, 1230 | "seed": 728976408, 1231 | "groupIds": [ 1232 | "u7g1JzQZ0WL6fcn3IY7co" 1233 | ], 1234 | "roundness": null, 1235 | "boundElements": [ 1236 | { 1237 | "id": "dmRTXBbC_6Lu0gbofjs5F", 1238 | "type": "arrow" 1239 | } 1240 | ], 1241 | "updated": 1682154217278, 1242 | "link": null, 1243 | "locked": false, 1244 | "fontSize": 20, 1245 | "fontFamily": 1, 1246 | "text": "Admin", 1247 | "textAlign": "left", 1248 | "verticalAlign": "top", 1249 | "containerId": null, 1250 | "originalText": "Admin", 1251 | "lineHeight": 1.2, 1252 | "baseline": 17 1253 | }, 1254 | { 1255 | "type": "rectangle", 1256 | "version": 1318, 1257 | "versionNonce": 1858022051, 1258 | "isDeleted": false, 1259 | "id": "aGQshIal6oOX3KkC7gGyi", 1260 | "fillStyle": "hachure", 1261 | "strokeWidth": 1, 1262 | "strokeStyle": "solid", 1263 | "roughness": 1, 1264 | "opacity": 100, 1265 | "angle": 0, 1266 | "x": -12.177137074514462, 1267 | "y": 315.2311475556561, 1268 | "strokeColor": "#000000", 1269 | "backgroundColor": "#868e96", 1270 | "width": 209.18356323242188, 1271 | "height": 99.67071533203125, 1272 | "seed": 502697752, 1273 | "groupIds": [], 1274 | "roundness": { 1275 | "type": 3 1276 | }, 1277 | "boundElements": [ 1278 | { 1279 | "type": "text", 1280 | "id": "YW4e-MmLeAlOJSJMQgxmy" 1281 | }, 1282 | { 1283 | "id": "QJqJSWNOeX5UUg7YQxJ9u", 1284 | "type": "arrow" 1285 | }, 1286 | { 1287 | "id": "8bPcv9HlAk3aTlx83Mhkx", 1288 | "type": "arrow" 1289 | } 1290 | ], 1291 | "updated": 1682154519855, 1292 | "link": null, 1293 | "locked": false 1294 | }, 1295 | { 1296 | "type": "text", 1297 | "version": 1291, 1298 | "versionNonce": 801543990, 1299 | "isDeleted": false, 1300 | "id": "YW4e-MmLeAlOJSJMQgxmy", 1301 | "fillStyle": "hachure", 1302 | "strokeWidth": 1, 1303 | "strokeStyle": "solid", 1304 | "roughness": 1, 1305 | "opacity": 100, 1306 | "angle": 0, 1307 | "x": 23.408678355173038, 1308 | "y": 348.2665052216717, 1309 | "strokeColor": "#000000", 1310 | "backgroundColor": "transparent", 1311 | "width": 138.01193237304688, 1312 | "height": 33.6, 1313 | "seed": 976563229, 1314 | "groupIds": [], 1315 | "roundness": null, 1316 | "boundElements": [], 1317 | "updated": 1682154217278, 1318 | "link": null, 1319 | "locked": false, 1320 | "fontSize": 28, 1321 | "fontFamily": 1, 1322 | "text": "OpenLDAP", 1323 | "textAlign": "center", 1324 | "verticalAlign": "middle", 1325 | "containerId": "aGQshIal6oOX3KkC7gGyi", 1326 | "originalText": "OpenLDAP", 1327 | "lineHeight": 1.2, 1328 | "baseline": 24 1329 | }, 1330 | { 1331 | "type": "arrow", 1332 | "version": 804, 1333 | "versionNonce": 241666346, 1334 | "isDeleted": false, 1335 | "id": "dmRTXBbC_6Lu0gbofjs5F", 1336 | "fillStyle": "hachure", 1337 | "strokeWidth": 1, 1338 | "strokeStyle": "solid", 1339 | "roughness": 1, 1340 | "opacity": 100, 1341 | "angle": 0, 1342 | "x": 723.8208077466159, 1343 | "y": 299.0972021993553, 1344 | "strokeColor": "#000000", 1345 | "backgroundColor": "#4c6ef5", 1346 | "width": 217.01685437933065, 1347 | "height": 65.31029902846484, 1348 | "seed": 1618881048, 1349 | "groupIds": [], 1350 | "roundness": { 1351 | "type": 2 1352 | }, 1353 | "boundElements": [ 1354 | { 1355 | "type": "text", 1356 | "id": "BlCW2DPyuGQXuVwfoWBqW" 1357 | } 1358 | ], 1359 | "updated": 1682154272483, 1360 | "link": null, 1361 | "locked": false, 1362 | "startBinding": { 1363 | "elementId": "GRFVRUsIgnm6E4hNYNpue", 1364 | "focus": -1.0085405808715424, 1365 | "gap": 13.842951785314995 1366 | }, 1367 | "endBinding": { 1368 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 1369 | "focus": 0.39238353746845955, 1370 | "gap": 8.854316500968594 1371 | }, 1372 | "lastCommittedPoint": null, 1373 | "startArrowhead": null, 1374 | "endArrowhead": "arrow", 1375 | "points": [ 1376 | [ 1377 | 0, 1378 | 0 1379 | ], 1380 | [ 1381 | -106.72565173031, 1382 | -35.88109980971484 1383 | ], 1384 | [ 1385 | -217.01685437933065, 1386 | -65.31029902846484 1387 | ] 1388 | ] 1389 | }, 1390 | { 1391 | "type": "text", 1392 | "version": 43, 1393 | "versionNonce": 1483483434, 1394 | "isDeleted": false, 1395 | "id": "BlCW2DPyuGQXuVwfoWBqW", 1396 | "fillStyle": "hachure", 1397 | "strokeWidth": 1, 1398 | "strokeStyle": "solid", 1399 | "roughness": 0, 1400 | "opacity": 100, 1401 | "angle": 0, 1402 | "x": 563.0751975202121, 1403 | "y": 239.21610238964047, 1404 | "strokeColor": "#000000", 1405 | "backgroundColor": "#4c6ef5", 1406 | "width": 108.0399169921875, 1407 | "height": 48, 1408 | "seed": 1292401875, 1409 | "groupIds": [], 1410 | "roundness": null, 1411 | "boundElements": [], 1412 | "updated": 1682154259779, 1413 | "link": null, 1414 | "locked": false, 1415 | "fontSize": 20, 1416 | "fontFamily": 1, 1417 | "text": "username /\npassword", 1418 | "textAlign": "center", 1419 | "verticalAlign": "middle", 1420 | "containerId": "dmRTXBbC_6Lu0gbofjs5F", 1421 | "originalText": "username /\npassword", 1422 | "lineHeight": 1.2, 1423 | "baseline": 41 1424 | }, 1425 | { 1426 | "type": "arrow", 1427 | "version": 675, 1428 | "versionNonce": 1327827894, 1429 | "isDeleted": false, 1430 | "id": "yNOTVOp1OXW9sHr6z9HKG", 1431 | "fillStyle": "hachure", 1432 | "strokeWidth": 1, 1433 | "strokeStyle": "solid", 1434 | "roughness": 1, 1435 | "opacity": 100, 1436 | "angle": 0, 1437 | "x": 742.0419943952121, 1438 | "y": 366.24964120799984, 1439 | "strokeColor": "#000000", 1440 | "backgroundColor": "#4c6ef5", 1441 | "width": 221.52130126953125, 1442 | "height": 3.85479736328125, 1443 | "seed": 1214326552, 1444 | "groupIds": [], 1445 | "roundness": { 1446 | "type": 2 1447 | }, 1448 | "boundElements": [ 1449 | { 1450 | "type": "text", 1451 | "id": "0bFiWihxywFrCxZdYXHDS" 1452 | } 1453 | ], 1454 | "updated": 1682154277270, 1455 | "link": null, 1456 | "locked": false, 1457 | "startBinding": null, 1458 | "endBinding": { 1459 | "elementId": "-fBgLVr8TGW8aaNMK731H", 1460 | "focus": 0.12136006739371531, 1461 | "gap": 14.7506724905748 1462 | }, 1463 | "lastCommittedPoint": null, 1464 | "startArrowhead": null, 1465 | "endArrowhead": "arrow", 1466 | "points": [ 1467 | [ 1468 | 0, 1469 | 0 1470 | ], 1471 | [ 1472 | -221.52130126953125, 1473 | 3.85479736328125 1474 | ] 1475 | ] 1476 | }, 1477 | { 1478 | "type": "text", 1479 | "version": 173, 1480 | "versionNonce": 409279478, 1481 | "isDeleted": false, 1482 | "id": "0bFiWihxywFrCxZdYXHDS", 1483 | "fillStyle": "hachure", 1484 | "strokeWidth": 1, 1485 | "strokeStyle": "solid", 1486 | "roughness": 0, 1487 | "opacity": 100, 1488 | "angle": 0, 1489 | "x": 577.2613852643527, 1490 | "y": 344.17703988964047, 1491 | "strokeColor": "#000000", 1492 | "backgroundColor": "#4c6ef5", 1493 | "width": 108.0399169921875, 1494 | "height": 48, 1495 | "seed": 400348989, 1496 | "groupIds": [], 1497 | "roundness": null, 1498 | "boundElements": [], 1499 | "updated": 1682154331138, 1500 | "link": null, 1501 | "locked": false, 1502 | "fontSize": 20, 1503 | "fontFamily": 1, 1504 | "text": "username /\npassword", 1505 | "textAlign": "center", 1506 | "verticalAlign": "middle", 1507 | "containerId": "yNOTVOp1OXW9sHr6z9HKG", 1508 | "originalText": "username /\npassword", 1509 | "lineHeight": 1.2, 1510 | "baseline": 41 1511 | }, 1512 | { 1513 | "type": "arrow", 1514 | "version": 227, 1515 | "versionNonce": 1877919734, 1516 | "isDeleted": false, 1517 | "id": "LLf5MpEVXMYg3R9iJaUnk", 1518 | "fillStyle": "hachure", 1519 | "strokeWidth": 1, 1520 | "strokeStyle": "solid", 1521 | "roughness": 1, 1522 | "opacity": 100, 1523 | "angle": 0, 1524 | "x": 500.08743329516733, 1525 | "y": -80.19342405108142, 1526 | "strokeColor": "#000000", 1527 | "backgroundColor": "#4c6ef5", 1528 | "width": 183, 1529 | "height": 192.50384521484375, 1530 | "seed": 2086799640, 1531 | "groupIds": [], 1532 | "roundness": { 1533 | "type": 2 1534 | }, 1535 | "boundElements": [ 1536 | { 1537 | "type": "text", 1538 | "id": "r2gIDzoPpv31_wtuoWqUP" 1539 | } 1540 | ], 1541 | "updated": 1682154301657, 1542 | "link": null, 1543 | "locked": false, 1544 | "startBinding": null, 1545 | "endBinding": { 1546 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 1547 | "focus": -0.8220345412109226, 1548 | "gap": 12.951450174128297 1549 | }, 1550 | "lastCommittedPoint": null, 1551 | "startArrowhead": null, 1552 | "endArrowhead": "arrow", 1553 | "points": [ 1554 | [ 1555 | 0, 1556 | 0 1557 | ], 1558 | [ 1559 | -168.03648594494223, 1560 | 19.495531432080714 1561 | ], 1562 | [ 1563 | -183, 1564 | 192.50384521484375 1565 | ] 1566 | ] 1567 | }, 1568 | { 1569 | "type": "text", 1570 | "version": 83, 1571 | "versionNonce": 291157866, 1572 | "isDeleted": false, 1573 | "id": "r2gIDzoPpv31_wtuoWqUP", 1574 | "fillStyle": "hachure", 1575 | "strokeWidth": 1, 1576 | "strokeStyle": "solid", 1577 | "roughness": 0, 1578 | "opacity": 100, 1579 | "angle": 0, 1580 | "x": 274.4310056387993, 1581 | "y": -120.69789261900071, 1582 | "strokeColor": "#000000", 1583 | "backgroundColor": "#4c6ef5", 1584 | "width": 115.23988342285156, 1585 | "height": 120, 1586 | "seed": 262363667, 1587 | "groupIds": [], 1588 | "roundness": null, 1589 | "boundElements": [], 1590 | "updated": 1682154217278, 1591 | "link": null, 1592 | "locked": false, 1593 | "fontSize": 20, 1594 | "fontFamily": 1, 1595 | "text": "1.\nusername /\npassword /\nclientId /\nclientSecret", 1596 | "textAlign": "center", 1597 | "verticalAlign": "middle", 1598 | "containerId": "LLf5MpEVXMYg3R9iJaUnk", 1599 | "originalText": "1.\nusername /\npassword /\nclientId /\nclientSecret", 1600 | "lineHeight": 1.2, 1601 | "baseline": 113 1602 | }, 1603 | { 1604 | "type": "arrow", 1605 | "version": 526, 1606 | "versionNonce": 1904684982, 1607 | "isDeleted": false, 1608 | "id": "ifTxAzOAWyGvwh2vK-vh4", 1609 | "fillStyle": "hachure", 1610 | "strokeWidth": 1, 1611 | "strokeStyle": "solid", 1612 | "roughness": 1, 1613 | "opacity": 100, 1614 | "angle": 0, 1615 | "x": 410.29730445380585, 1616 | "y": 110.9037549775311, 1617 | "strokeColor": "#000000", 1618 | "backgroundColor": "#4c6ef5", 1619 | "width": 99.058349609375, 1620 | "height": 160.17752075195312, 1621 | "seed": 661320552, 1622 | "groupIds": [], 1623 | "roundness": { 1624 | "type": 2 1625 | }, 1626 | "boundElements": [ 1627 | { 1628 | "type": "text", 1629 | "id": "aIq6yevn8-5qNMCF1S2B_" 1630 | } 1631 | ], 1632 | "updated": 1682154298833, 1633 | "link": null, 1634 | "locked": false, 1635 | "startBinding": { 1636 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 1637 | "focus": -0.006284524215784527, 1638 | "gap": 14.358116360359531 1639 | }, 1640 | "endBinding": null, 1641 | "lastCommittedPoint": null, 1642 | "startArrowhead": null, 1643 | "endArrowhead": "arrow", 1644 | "points": [ 1645 | [ 1646 | 0, 1647 | 0 1648 | ], 1649 | [ 1650 | 13.2613525390625, 1651 | -86.65164184570312 1652 | ], 1653 | [ 1654 | 99.058349609375, 1655 | -160.17752075195312 1656 | ] 1657 | ] 1658 | }, 1659 | { 1660 | "type": "text", 1661 | "version": 30, 1662 | "versionNonce": 1229605622, 1663 | "isDeleted": false, 1664 | "id": "aIq6yevn8-5qNMCF1S2B_", 1665 | "fillStyle": "hachure", 1666 | "strokeWidth": 1, 1667 | "strokeStyle": "solid", 1668 | "roughness": 0, 1669 | "opacity": 100, 1670 | "angle": 0, 1671 | "x": 357.03871375556366, 1672 | "y": -11.747886868172031, 1673 | "strokeColor": "#000000", 1674 | "backgroundColor": "#4c6ef5", 1675 | "width": 133.03988647460938, 1676 | "height": 72, 1677 | "seed": 1423337939, 1678 | "groupIds": [], 1679 | "roundness": null, 1680 | "boundElements": [], 1681 | "updated": 1682154294186, 1682 | "link": null, 1683 | "locked": false, 1684 | "fontSize": 20, 1685 | "fontFamily": 1, 1686 | "text": "2.\nAccess Token\n(JWT)", 1687 | "textAlign": "center", 1688 | "verticalAlign": "middle", 1689 | "containerId": "ifTxAzOAWyGvwh2vK-vh4", 1690 | "originalText": "2.\nAccess Token\n(JWT)", 1691 | "lineHeight": 1.2, 1692 | "baseline": 65 1693 | }, 1694 | { 1695 | "type": "arrow", 1696 | "version": 228, 1697 | "versionNonce": 1319559862, 1698 | "isDeleted": false, 1699 | "id": "h0Mz6bwLORO7JPnOyVVPH", 1700 | "fillStyle": "hachure", 1701 | "strokeWidth": 1, 1702 | "strokeStyle": "solid", 1703 | "roughness": 1, 1704 | "opacity": 100, 1705 | "angle": 0, 1706 | "x": 644.9364035748996, 1707 | "y": -78.98714468067203, 1708 | "strokeColor": "#000000", 1709 | "backgroundColor": "#4c6ef5", 1710 | "width": 153.41180419921875, 1711 | "height": 187.87030029296875, 1712 | "seed": 443622936, 1713 | "groupIds": [], 1714 | "roundness": { 1715 | "type": 2 1716 | }, 1717 | "boundElements": [ 1718 | { 1719 | "type": "text", 1720 | "id": "tifYlJVMYkKvmzGQODwfF" 1721 | } 1722 | ], 1723 | "updated": 1682154304560, 1724 | "link": null, 1725 | "locked": false, 1726 | "startBinding": null, 1727 | "endBinding": { 1728 | "elementId": "LF8GW9IPL_GFv-IztvS9e", 1729 | "focus": 0.062020326630055946, 1730 | "gap": 14.467118701329241 1731 | }, 1732 | "lastCommittedPoint": null, 1733 | "startArrowhead": null, 1734 | "endArrowhead": "arrow", 1735 | "points": [ 1736 | [ 1737 | 0, 1738 | 0 1739 | ], 1740 | [ 1741 | 135.2490234375, 1742 | 53.71282958984375 1743 | ], 1744 | [ 1745 | 153.41180419921875, 1746 | 187.87030029296875 1747 | ] 1748 | ] 1749 | }, 1750 | { 1751 | "type": "text", 1752 | "version": 55, 1753 | "versionNonce": 19883242, 1754 | "isDeleted": false, 1755 | "id": "tifYlJVMYkKvmzGQODwfF", 1756 | "fillStyle": "hachure", 1757 | "strokeWidth": 1, 1758 | "strokeStyle": "solid", 1759 | "roughness": 0, 1760 | "opacity": 100, 1761 | "angle": 0, 1762 | "x": 712.3254874372043, 1763 | "y": -73.27431509082828, 1764 | "strokeColor": "#000000", 1765 | "backgroundColor": "#4c6ef5", 1766 | "width": 135.71987915039062, 1767 | "height": 96, 1768 | "seed": 466887123, 1769 | "groupIds": [], 1770 | "roundness": null, 1771 | "boundElements": [], 1772 | "updated": 1682154217279, 1773 | "link": null, 1774 | "locked": false, 1775 | "fontSize": 20, 1776 | "fontFamily": 1, 1777 | "text": "3. uses\nAccess Token\nto call\nAPI endpoints", 1778 | "textAlign": "center", 1779 | "verticalAlign": "middle", 1780 | "containerId": "h0Mz6bwLORO7JPnOyVVPH", 1781 | "originalText": "3. uses\nAccess Token\nto call\nAPI endpoints", 1782 | "lineHeight": 1.2, 1783 | "baseline": 89 1784 | }, 1785 | { 1786 | "type": "arrow", 1787 | "version": 342, 1788 | "versionNonce": 601505197, 1789 | "isDeleted": false, 1790 | "id": "QJqJSWNOeX5UUg7YQxJ9u", 1791 | "fillStyle": "hachure", 1792 | "strokeWidth": 1, 1793 | "strokeStyle": "solid", 1794 | "roughness": 1, 1795 | "opacity": 100, 1796 | "angle": 0, 1797 | "x": 286.79474097724335, 1798 | "y": 236.3439405244061, 1799 | "strokeColor": "#000000", 1800 | "backgroundColor": "#4c6ef5", 1801 | "width": 90.4102783203125, 1802 | "height": 76.27899169921875, 1803 | "seed": 1871201896, 1804 | "groupIds": [], 1805 | "roundness": { 1806 | "type": 2 1807 | }, 1808 | "boundElements": [], 1809 | "updated": 1682154529798, 1810 | "link": null, 1811 | "locked": false, 1812 | "startBinding": { 1813 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 1814 | "focus": 0.2553493085262571, 1815 | "gap": 11.411353854484219 1816 | }, 1817 | "endBinding": { 1818 | "elementId": "aGQshIal6oOX3KkC7gGyi", 1819 | "focus": 0.25547340545208447, 1820 | "gap": 2.60821533203125 1821 | }, 1822 | "lastCommittedPoint": null, 1823 | "startArrowhead": "arrow", 1824 | "endArrowhead": "arrow", 1825 | "points": [ 1826 | [ 1827 | 0, 1828 | 0 1829 | ], 1830 | [ 1831 | -90.4102783203125, 1832 | 76.27899169921875 1833 | ] 1834 | ] 1835 | }, 1836 | { 1837 | "type": "arrow", 1838 | "version": 62, 1839 | "versionNonce": 1241753322, 1840 | "isDeleted": false, 1841 | "id": "9clvjtopspLFkfIEUdpAA", 1842 | "fillStyle": "hachure", 1843 | "strokeWidth": 1, 1844 | "strokeStyle": "solid", 1845 | "roughness": 1, 1846 | "opacity": 100, 1847 | "angle": 0, 1848 | "x": 203.81720421850792, 1849 | "y": 177.5523908417889, 1850 | "strokeColor": "#000000", 1851 | "backgroundColor": "transparent", 1852 | "width": 87.2376708984375, 1853 | "height": 0, 1854 | "seed": 677145782, 1855 | "groupIds": [], 1856 | "roundness": { 1857 | "type": 2 1858 | }, 1859 | "boundElements": [], 1860 | "updated": 1682154242090, 1861 | "link": null, 1862 | "locked": false, 1863 | "startBinding": { 1864 | "elementId": "NKmNZxYxWMCKh3prRiPwX", 1865 | "focus": 0.11776568620110987, 1866 | "gap": 6.810778060600512 1867 | }, 1868 | "endBinding": { 1869 | "elementId": "htH4DvpAlw_lK0WCfkn_y", 1870 | "focus": -0.049265460365240085, 1871 | "gap": 5.53158228573875 1872 | }, 1873 | "lastCommittedPoint": null, 1874 | "startArrowhead": null, 1875 | "endArrowhead": null, 1876 | "points": [ 1877 | [ 1878 | 0, 1879 | 0 1880 | ], 1881 | [ 1882 | 87.2376708984375, 1883 | 0 1884 | ] 1885 | ] 1886 | }, 1887 | { 1888 | "type": "arrow", 1889 | "version": 81, 1890 | "versionNonce": 209147843, 1891 | "isDeleted": false, 1892 | "id": "8bPcv9HlAk3aTlx83Mhkx", 1893 | "fillStyle": "hachure", 1894 | "strokeWidth": 1, 1895 | "strokeStyle": "solid", 1896 | "roughness": 1, 1897 | "opacity": 100, 1898 | "angle": 0, 1899 | "x": 205.97491906225792, 1900 | "y": 369.84554269725766, 1901 | "strokeColor": "#000000", 1902 | "backgroundColor": "transparent", 1903 | "width": 83.415771484375, 1904 | "height": 0.6298828125, 1905 | "seed": 1256928566, 1906 | "groupIds": [], 1907 | "roundness": { 1908 | "type": 2 1909 | }, 1910 | "boundElements": [], 1911 | "updated": 1682154493465, 1912 | "link": null, 1913 | "locked": false, 1914 | "startBinding": { 1915 | "elementId": "aGQshIal6oOX3KkC7gGyi", 1916 | "focus": 0.11133884883632172, 1917 | "gap": 8.968492904350512 1918 | }, 1919 | "endBinding": { 1920 | "elementId": "-fBgLVr8TGW8aaNMK731H", 1921 | "focus": -0.0485780135823891, 1922 | "gap": 7.19576685605125 1923 | }, 1924 | "lastCommittedPoint": null, 1925 | "startArrowhead": "arrow", 1926 | "endArrowhead": "arrow", 1927 | "points": [ 1928 | [ 1929 | 0, 1930 | 0 1931 | ], 1932 | [ 1933 | 83.415771484375, 1934 | -0.6298828125 1935 | ] 1936 | ] 1937 | } 1938 | ], 1939 | "appState": { 1940 | "gridSize": null, 1941 | "viewBackgroundColor": "#ffffff" 1942 | }, 1943 | "files": {} 1944 | } -------------------------------------------------------------------------------- /documentation/project-diagram.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-keycloak-openldap/b7e3e3eb905ffd14f30b38b6c6f0cd48bee8f640/documentation/project-diagram.jpeg -------------------------------------------------------------------------------- /documentation/simple-service-swagger.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ivangfr/springboot-keycloak-openldap/b7e3e3eb905ffd14f30b38b6c6f0cd48bee8f640/documentation/simple-service-swagger.jpeg -------------------------------------------------------------------------------- /import-openldap-users.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | LDAP_HOST=${1:-localhost} 4 | 5 | ldapadd -x -D "cn=admin,dc=mycompany,dc=com" -w admin -H ldap://$LDAP_HOST -f ldap/ldap-mycompany-com.ldif -------------------------------------------------------------------------------- /init-keycloak.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | KEYCLOAK_HOST_PORT=${1:-"localhost:8080"} 4 | echo 5 | echo "KEYCLOAK_HOST_PORT: $KEYCLOAK_HOST_PORT" 6 | echo 7 | 8 | echo "Getting admin access token" 9 | echo "==========================" 10 | 11 | ADMIN_TOKEN=$(curl -s -X POST "http://$KEYCLOAK_HOST_PORT/realms/master/protocol/openid-connect/token" \ 12 | -H "Content-Type: application/x-www-form-urlencoded" \ 13 | -d "username=admin" \ 14 | -d 'password=admin' \ 15 | -d 'grant_type=password' \ 16 | -d 'client_id=admin-cli' | jq -r '.access_token') 17 | 18 | echo "ADMIN_TOKEN=$ADMIN_TOKEN" 19 | echo 20 | 21 | echo "Creating realm" 22 | echo "==============" 23 | 24 | curl -i -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms" \ 25 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 26 | -H "Content-Type: application/json" \ 27 | -d '{"realm": "company-services", "enabled": true}' 28 | 29 | echo "Get Required Action Verify Profile" 30 | echo "----------------------------------" 31 | 32 | VERIFY_PROFILE_REQUIRED_ACTION=$(curl -s "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/authentication/required-actions/VERIFY_PROFILE" \ 33 | -H "Authorization: Bearer $ADMIN_TOKEN" | jq) 34 | 35 | echo $VERIFY_PROFILE_REQUIRED_ACTION 36 | echo 37 | 38 | echo "Disable Required Action Verify Profile" 39 | echo "--------------------------------------" 40 | 41 | NEW_VERIFY_PROFILE_REQUIRED_ACTION=$(echo "$VERIFY_PROFILE_REQUIRED_ACTION" | jq '.enabled = false') 42 | 43 | echo $NEW_VERIFY_PROFILE_REQUIRED_ACTION 44 | echo 45 | 46 | curl -i -X PUT "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/authentication/required-actions/VERIFY_PROFILE" \ 47 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 48 | -H "Content-Type: application/json" \ 49 | -d "$NEW_VERIFY_PROFILE_REQUIRED_ACTION" 50 | 51 | echo "Creating client" 52 | echo "===============" 53 | 54 | CLIENT_ID=$(curl -si -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/clients" \ 55 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 56 | -H "Content-Type: application/json" \ 57 | -d '{"clientId": "simple-service", "directAccessGrantsEnabled": true, "redirectUris": ["http://localhost:9080/*"]}' \ 58 | | grep -oE '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}') 59 | 60 | echo "CLIENT_ID=$CLIENT_ID" 61 | echo 62 | 63 | echo "Getting client secret" 64 | echo "=====================" 65 | 66 | SIMPLE_SERVICE_CLIENT_SECRET=$(curl -s -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/clients/$CLIENT_ID/client-secret" \ 67 | -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.value') 68 | 69 | echo "SIMPLE_SERVICE_CLIENT_SECRET=$SIMPLE_SERVICE_CLIENT_SECRET" 70 | echo 71 | 72 | echo "Creating client role" 73 | echo "====================" 74 | 75 | curl -i -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/clients/$CLIENT_ID/roles" \ 76 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 77 | -H "Content-Type: application/json" \ 78 | -d '{"name": "USER"}' 79 | 80 | ROLE_ID=$(curl -s "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/clients/$CLIENT_ID/roles" \ 81 | -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[0].id') 82 | 83 | echo "ROLE_ID=$ROLE_ID" 84 | echo 85 | 86 | echo "Configuring LDAP" 87 | echo "================" 88 | 89 | LDAP_ID=$(curl -si -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/components" \ 90 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 91 | -H "Content-Type: application/json" \ 92 | -d '@ldap/ldap-config.json' \ 93 | | grep -oE '[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}') 94 | 95 | echo "LDAP_ID=$LDAP_ID" 96 | echo 97 | 98 | echo "Sync LDAP Users" 99 | echo "===============" 100 | 101 | curl -i -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/user-storage/$LDAP_ID/sync?action=triggerFullSync" \ 102 | -H "Authorization: Bearer $ADMIN_TOKEN" 103 | 104 | echo 105 | echo 106 | echo "Get bgates id" 107 | echo "=============" 108 | 109 | BGATES_ID=$(curl -s "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/users?username=bgates" \ 110 | -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[0].id') 111 | 112 | echo "BGATES_ID=$BGATES_ID" 113 | echo 114 | 115 | echo "Setting client role to bgates" 116 | echo "=============================" 117 | 118 | curl -i -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/users/$BGATES_ID/role-mappings/clients/$CLIENT_ID" \ 119 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 120 | -H "Content-Type: application/json" \ 121 | -d '[{"id":"'"$ROLE_ID"'","name":"USER"}]' 122 | 123 | echo "Get sjobs id" 124 | echo "============" 125 | 126 | SJOBS_ID=$(curl -s "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/users?username=sjobs" \ 127 | -H "Authorization: Bearer $ADMIN_TOKEN" | jq -r '.[0].id') 128 | 129 | echo "SJOBS_ID=$SJOBS_ID" 130 | echo 131 | 132 | echo "Setting client role to sjobs" 133 | echo "============================" 134 | 135 | curl -i -X POST "http://$KEYCLOAK_HOST_PORT/admin/realms/company-services/users/$SJOBS_ID/role-mappings/clients/$CLIENT_ID" \ 136 | -H "Authorization: Bearer $ADMIN_TOKEN" \ 137 | -H "Content-Type: application/json" \ 138 | -d '[{"id":"'"$ROLE_ID"'","name":"USER"}]' 139 | 140 | echo "Getting bgates access token" 141 | echo "===========================" 142 | 143 | curl -s -X POST "http://$KEYCLOAK_HOST_PORT/realms/company-services/protocol/openid-connect/token" \ 144 | -H "Content-Type: application/x-www-form-urlencoded" \ 145 | -d "username=bgates" \ 146 | -d "password=123" \ 147 | -d "grant_type=password" \ 148 | -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \ 149 | -d "client_id=simple-service" | jq -r .access_token 150 | echo 151 | 152 | echo "Getting sjobs access token" 153 | echo "==========================" 154 | 155 | curl -s -X POST "http://$KEYCLOAK_HOST_PORT/realms/company-services/protocol/openid-connect/token" \ 156 | -H "Content-Type: application/x-www-form-urlencoded" \ 157 | -d "username=sjobs" \ 158 | -d "password=123" \ 159 | -d "grant_type=password" \ 160 | -d "client_secret=$SIMPLE_SERVICE_CLIENT_SECRET" \ 161 | -d "client_id=simple-service" | jq -r .access_token 162 | 163 | echo 164 | echo "============================" 165 | echo "SIMPLE_SERVICE_CLIENT_SECRET=$SIMPLE_SERVICE_CLIENT_SECRET" 166 | echo "============================" 167 | -------------------------------------------------------------------------------- /ldap/ldap-config.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ldap", 3 | "providerId": "ldap", 4 | "providerType": "org.keycloak.storage.UserStorageProvider", 5 | "config": { 6 | "fullSyncPeriod": [ 7 | "-1" 8 | ], 9 | "pagination": [ 10 | "true" 11 | ], 12 | "usersDn": [ 13 | "ou=users,dc=mycompany,dc=com" 14 | ], 15 | "connectionPooling": [ 16 | "true" 17 | ], 18 | "cachePolicy": [ 19 | "DEFAULT" 20 | ], 21 | "useKerberosForPasswordAuthentication": [ 22 | "false" 23 | ], 24 | "importEnabled": [ 25 | "true" 26 | ], 27 | "editMode": [ 28 | "READ_ONLY" 29 | ], 30 | "enabled": [ 31 | "true" 32 | ], 33 | "changedSyncPeriod": [ 34 | "-1" 35 | ], 36 | "bindCredential": [ 37 | "admin" 38 | ], 39 | "bindDn": [ 40 | "cn=admin,dc=mycompany,dc=com" 41 | ], 42 | "usernameLDAPAttribute": [ 43 | "uid" 44 | ], 45 | "lastSync": [ 46 | "1575220979" 47 | ], 48 | "vendor": [ 49 | "other" 50 | ], 51 | "uuidLDAPAttribute": [ 52 | "entryUUID" 53 | ], 54 | "connectionUrl": [ 55 | "ldap://openldap" 56 | ], 57 | "allowKerberosAuthentication": [ 58 | "false" 59 | ], 60 | "syncRegistrations": [ 61 | "false" 62 | ], 63 | "authType": [ 64 | "simple" 65 | ], 66 | "customUserSearchFilter": [ 67 | "(gidnumber=500)" 68 | ], 69 | "debug": [ 70 | "false" 71 | ], 72 | "searchScope": [ 73 | "1" 74 | ], 75 | "useTruststoreSpi": [ 76 | "ldapsOnly" 77 | ], 78 | "priority": [ 79 | "0" 80 | ], 81 | "userObjectClasses": [ 82 | "inetOrgPerson, organizationalPerson" 83 | ], 84 | "rdnLDAPAttribute": [ 85 | "uid" 86 | ], 87 | "validatePasswordPolicy": [ 88 | "false" 89 | ], 90 | "batchSizeForSync": [ 91 | "1000" 92 | ] 93 | } 94 | } -------------------------------------------------------------------------------- /ldap/ldap-mycompany-com.ldif: -------------------------------------------------------------------------------- 1 | # LDIF Export for dc=mycompany,dc=com 2 | # Server: openldap (openldap) 3 | # Search Scope: sub 4 | # Search Filter: (objectClass=*) 5 | # Total Entries: 10 6 | # 7 | # Generated by phpLDAPadmin (http://phpldapadmin.sourceforge.net) on March 6, 2018 5:56 pm 8 | # Version: 1.2.3 9 | 10 | version: 1 11 | 12 | ## Entry 1: dc=mycompany,dc=com 13 | #dn: dc=mycompany,dc=com 14 | #dc: mycompany 15 | #o: "MyCompany Inc." 16 | #objectclass: top 17 | #objectclass: dcObject 18 | #objectclass: organization 19 | 20 | ## Entry 2: cn=admin,dc=mycompany,dc=com 21 | #dn: cn=admin,dc=mycompany,dc=com 22 | #cn: admin 23 | #description: LDAP administrator 24 | #objectclass: simpleSecurityObject 25 | #objectclass: organizationalRole 26 | #userpassword: {SSHA}iGmwNRRDcGxmafPklI1kcKivNq8cCb4j 27 | 28 | # Entry 3: ou=groups,dc=mycompany,dc=com 29 | dn: ou=groups,dc=mycompany,dc=com 30 | objectclass: organizationalUnit 31 | objectclass: top 32 | ou: groups 33 | 34 | # Entry 4: cn=admin,ou=groups,dc=mycompany,dc=com 35 | dn: cn=admin,ou=groups,dc=mycompany,dc=com 36 | cn: admin 37 | gidnumber: 501 38 | memberuid: 1003 39 | objectclass: posixGroup 40 | objectclass: top 41 | 42 | # Entry 5: cn=developers,ou=groups,dc=mycompany,dc=com 43 | dn: cn=developers,ou=groups,dc=mycompany,dc=com 44 | cn: developers 45 | gidnumber: 500 46 | memberuid: 1000 47 | memberuid: 1001 48 | memberuid: 1002 49 | objectclass: posixGroup 50 | objectclass: top 51 | 52 | # Entry 6: ou=users,dc=mycompany,dc=com 53 | dn: ou=users,dc=mycompany,dc=com 54 | objectclass: organizationalUnit 55 | objectclass: top 56 | ou: users 57 | 58 | # Entry 7: cn=Bill Gates,ou=users,dc=mycompany,dc=com 59 | dn: cn=Bill Gates,ou=users,dc=mycompany,dc=com 60 | cn: Bill Gates 61 | gidnumber: 500 62 | givenname: bill 63 | homedirectory: /home/users/bgates 64 | objectclass: inetOrgPerson 65 | objectclass: posixAccount 66 | objectclass: top 67 | sn: gates 68 | uid: bgates 69 | uidnumber: 1000 70 | userpassword: {MD5}ICy5YqxZB1uWSwcVLSNLcA== 71 | 72 | # Entry 8: cn=Ivan Franchin,ou=users,dc=mycompany,dc=com 73 | dn: cn=Ivan Franchin,ou=users,dc=mycompany,dc=com 74 | cn: Ivan Franchin 75 | gidnumber: 501 76 | givenname: Ivan 77 | homedirectory: /home/users/ifranchin 78 | objectclass: inetOrgPerson 79 | objectclass: posixAccount 80 | objectclass: top 81 | sn: Franchin 82 | uid: ifranchin 83 | uidnumber: 1003 84 | userpassword: {MD5}ICy5YqxZB1uWSwcVLSNLcA== 85 | 86 | # Entry 9: cn=Mark Cuban,ou=users,dc=mycompany,dc=com 87 | dn: cn=Mark Cuban,ou=users,dc=mycompany,dc=com 88 | cn: Mark Cuban 89 | gidnumber: 500 90 | givenname: Mark 91 | homedirectory: /home/users/mcuban 92 | objectclass: inetOrgPerson 93 | objectclass: posixAccount 94 | objectclass: top 95 | sn: Cuban 96 | uid: mcuban 97 | uidnumber: 1002 98 | userpassword: {MD5}ICy5YqxZB1uWSwcVLSNLcA== 99 | 100 | # Entry 10: cn=Steve Jobs,ou=users,dc=mycompany,dc=com 101 | dn: cn=Steve Jobs,ou=users,dc=mycompany,dc=com 102 | cn: Steve Jobs 103 | gidnumber: 500 104 | givenname: Steve 105 | homedirectory: /home/users/sjobs 106 | objectclass: inetOrgPerson 107 | objectclass: posixAccount 108 | objectclass: top 109 | sn: Jobs 110 | uid: sjobs 111 | uidnumber: 1001 112 | userpassword: {MD5}ICy5YqxZB1uWSwcVLSNLcA== -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # http://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Apache Maven Wrapper startup batch script, version 3.3.2 23 | # 24 | # Optional ENV vars 25 | # ----------------- 26 | # JAVA_HOME - location of a JDK home dir, required when download maven via java source 27 | # MVNW_REPOURL - repo url base for downloading maven distribution 28 | # MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 29 | # MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output 30 | # ---------------------------------------------------------------------------- 31 | 32 | set -euf 33 | [ "${MVNW_VERBOSE-}" != debug ] || set -x 34 | 35 | # OS specific support. 36 | native_path() { printf %s\\n "$1"; } 37 | case "$(uname)" in 38 | CYGWIN* | MINGW*) 39 | [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" 40 | native_path() { cygpath --path --windows "$1"; } 41 | ;; 42 | esac 43 | 44 | # set JAVACMD and JAVACCMD 45 | set_java_home() { 46 | # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched 47 | if [ -n "${JAVA_HOME-}" ]; then 48 | if [ -x "$JAVA_HOME/jre/sh/java" ]; then 49 | # IBM's JDK on AIX uses strange locations for the executables 50 | JAVACMD="$JAVA_HOME/jre/sh/java" 51 | JAVACCMD="$JAVA_HOME/jre/sh/javac" 52 | else 53 | JAVACMD="$JAVA_HOME/bin/java" 54 | JAVACCMD="$JAVA_HOME/bin/javac" 55 | 56 | if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then 57 | echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 58 | echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 59 | return 1 60 | fi 61 | fi 62 | else 63 | JAVACMD="$( 64 | 'set' +e 65 | 'unset' -f command 2>/dev/null 66 | 'command' -v java 67 | )" || : 68 | JAVACCMD="$( 69 | 'set' +e 70 | 'unset' -f command 2>/dev/null 71 | 'command' -v javac 72 | )" || : 73 | 74 | if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then 75 | echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 76 | return 1 77 | fi 78 | fi 79 | } 80 | 81 | # hash string like Java String::hashCode 82 | hash_string() { 83 | str="${1:-}" h=0 84 | while [ -n "$str" ]; do 85 | char="${str%"${str#?}"}" 86 | h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) 87 | str="${str#?}" 88 | done 89 | printf %x\\n $h 90 | } 91 | 92 | verbose() { :; } 93 | [ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } 94 | 95 | die() { 96 | printf %s\\n "$1" >&2 97 | exit 1 98 | } 99 | 100 | trim() { 101 | # MWRAPPER-139: 102 | # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. 103 | # Needed for removing poorly interpreted newline sequences when running in more 104 | # exotic environments such as mingw bash on Windows. 105 | printf "%s" "${1}" | tr -d '[:space:]' 106 | } 107 | 108 | # parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties 109 | while IFS="=" read -r key value; do 110 | case "${key-}" in 111 | distributionUrl) distributionUrl=$(trim "${value-}") ;; 112 | distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; 113 | esac 114 | done <"${0%/*}/.mvn/wrapper/maven-wrapper.properties" 115 | [ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in ${0%/*}/.mvn/wrapper/maven-wrapper.properties" 116 | 117 | case "${distributionUrl##*/}" in 118 | maven-mvnd-*bin.*) 119 | MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ 120 | case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in 121 | *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; 122 | :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; 123 | :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; 124 | :Linux*x86_64*) distributionPlatform=linux-amd64 ;; 125 | *) 126 | echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 127 | distributionPlatform=linux-amd64 128 | ;; 129 | esac 130 | distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" 131 | ;; 132 | maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; 133 | *) MVN_CMD="mvn${0##*/mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; 134 | esac 135 | 136 | # apply MVNW_REPOURL and calculate MAVEN_HOME 137 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 138 | [ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" 139 | distributionUrlName="${distributionUrl##*/}" 140 | distributionUrlNameMain="${distributionUrlName%.*}" 141 | distributionUrlNameMain="${distributionUrlNameMain%-bin}" 142 | MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" 143 | MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" 144 | 145 | exec_maven() { 146 | unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : 147 | exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" 148 | } 149 | 150 | if [ -d "$MAVEN_HOME" ]; then 151 | verbose "found existing MAVEN_HOME at $MAVEN_HOME" 152 | exec_maven "$@" 153 | fi 154 | 155 | case "${distributionUrl-}" in 156 | *?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; 157 | *) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; 158 | esac 159 | 160 | # prepare tmp dir 161 | if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then 162 | clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } 163 | trap clean HUP INT TERM EXIT 164 | else 165 | die "cannot create temp dir" 166 | fi 167 | 168 | mkdir -p -- "${MAVEN_HOME%/*}" 169 | 170 | # Download and Install Apache Maven 171 | verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 172 | verbose "Downloading from: $distributionUrl" 173 | verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 174 | 175 | # select .zip or .tar.gz 176 | if ! command -v unzip >/dev/null; then 177 | distributionUrl="${distributionUrl%.zip}.tar.gz" 178 | distributionUrlName="${distributionUrl##*/}" 179 | fi 180 | 181 | # verbose opt 182 | __MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' 183 | [ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v 184 | 185 | # normalize http auth 186 | case "${MVNW_PASSWORD:+has-password}" in 187 | '') MVNW_USERNAME='' MVNW_PASSWORD='' ;; 188 | has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; 189 | esac 190 | 191 | if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then 192 | verbose "Found wget ... using wget" 193 | wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" 194 | elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then 195 | verbose "Found curl ... using curl" 196 | curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" 197 | elif set_java_home; then 198 | verbose "Falling back to use Java to download" 199 | javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" 200 | targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" 201 | cat >"$javaSource" <<-END 202 | public class Downloader extends java.net.Authenticator 203 | { 204 | protected java.net.PasswordAuthentication getPasswordAuthentication() 205 | { 206 | return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); 207 | } 208 | public static void main( String[] args ) throws Exception 209 | { 210 | setDefault( new Downloader() ); 211 | java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); 212 | } 213 | } 214 | END 215 | # For Cygwin/MinGW, switch paths to Windows format before running javac and java 216 | verbose " - Compiling Downloader.java ..." 217 | "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" 218 | verbose " - Running Downloader.java ..." 219 | "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" 220 | fi 221 | 222 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 223 | if [ -n "${distributionSha256Sum-}" ]; then 224 | distributionSha256Result=false 225 | if [ "$MVN_CMD" = mvnd.sh ]; then 226 | echo "Checksum validation is not supported for maven-mvnd." >&2 227 | echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 228 | exit 1 229 | elif command -v sha256sum >/dev/null; then 230 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c >/dev/null 2>&1; then 231 | distributionSha256Result=true 232 | fi 233 | elif command -v shasum >/dev/null; then 234 | if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then 235 | distributionSha256Result=true 236 | fi 237 | else 238 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 239 | echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 240 | exit 1 241 | fi 242 | if [ $distributionSha256Result = false ]; then 243 | echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 244 | echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 245 | exit 1 246 | fi 247 | fi 248 | 249 | # unzip and move 250 | if command -v unzip >/dev/null; then 251 | unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" 252 | else 253 | tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" 254 | fi 255 | printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/mvnw.url" 256 | mv -- "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" 257 | 258 | clean || : 259 | exec_maven "$@" 260 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | <# : batch portion 2 | @REM ---------------------------------------------------------------------------- 3 | @REM Licensed to the Apache Software Foundation (ASF) under one 4 | @REM or more contributor license agreements. See the NOTICE file 5 | @REM distributed with this work for additional information 6 | @REM regarding copyright ownership. The ASF licenses this file 7 | @REM to you under the Apache License, Version 2.0 (the 8 | @REM "License"); you may not use this file except in compliance 9 | @REM with the License. You may obtain a copy of the License at 10 | @REM 11 | @REM http://www.apache.org/licenses/LICENSE-2.0 12 | @REM 13 | @REM Unless required by applicable law or agreed to in writing, 14 | @REM software distributed under the License is distributed on an 15 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | @REM KIND, either express or implied. See the License for the 17 | @REM specific language governing permissions and limitations 18 | @REM under the License. 19 | @REM ---------------------------------------------------------------------------- 20 | 21 | @REM ---------------------------------------------------------------------------- 22 | @REM Apache Maven Wrapper startup batch script, version 3.3.2 23 | @REM 24 | @REM Optional ENV vars 25 | @REM MVNW_REPOURL - repo url base for downloading maven distribution 26 | @REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven 27 | @REM MVNW_VERBOSE - true: enable verbose log; others: silence the output 28 | @REM ---------------------------------------------------------------------------- 29 | 30 | @IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) 31 | @SET __MVNW_CMD__= 32 | @SET __MVNW_ERROR__= 33 | @SET __MVNW_PSMODULEP_SAVE=%PSModulePath% 34 | @SET PSModulePath= 35 | @FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( 36 | IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) 37 | ) 38 | @SET PSModulePath=%__MVNW_PSMODULEP_SAVE% 39 | @SET __MVNW_PSMODULEP_SAVE= 40 | @SET __MVNW_ARG0_NAME__= 41 | @SET MVNW_USERNAME= 42 | @SET MVNW_PASSWORD= 43 | @IF NOT "%__MVNW_CMD__%"=="" (%__MVNW_CMD__% %*) 44 | @echo Cannot start maven from wrapper >&2 && exit /b 1 45 | @GOTO :EOF 46 | : end batch / begin powershell #> 47 | 48 | $ErrorActionPreference = "Stop" 49 | if ($env:MVNW_VERBOSE -eq "true") { 50 | $VerbosePreference = "Continue" 51 | } 52 | 53 | # calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties 54 | $distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl 55 | if (!$distributionUrl) { 56 | Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" 57 | } 58 | 59 | switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { 60 | "maven-mvnd-*" { 61 | $USE_MVND = $true 62 | $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" 63 | $MVN_CMD = "mvnd.cmd" 64 | break 65 | } 66 | default { 67 | $USE_MVND = $false 68 | $MVN_CMD = $script -replace '^mvnw','mvn' 69 | break 70 | } 71 | } 72 | 73 | # apply MVNW_REPOURL and calculate MAVEN_HOME 74 | # maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ 75 | if ($env:MVNW_REPOURL) { 76 | $MVNW_REPO_PATTERN = if ($USE_MVND) { "/org/apache/maven/" } else { "/maven/mvnd/" } 77 | $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace '^.*'+$MVNW_REPO_PATTERN,'')" 78 | } 79 | $distributionUrlName = $distributionUrl -replace '^.*/','' 80 | $distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' 81 | $MAVEN_HOME_PARENT = "$HOME/.m2/wrapper/dists/$distributionUrlNameMain" 82 | if ($env:MAVEN_USER_HOME) { 83 | $MAVEN_HOME_PARENT = "$env:MAVEN_USER_HOME/wrapper/dists/$distributionUrlNameMain" 84 | } 85 | $MAVEN_HOME_NAME = ([System.Security.Cryptography.MD5]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' 86 | $MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" 87 | 88 | if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { 89 | Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" 90 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 91 | exit $? 92 | } 93 | 94 | if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { 95 | Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" 96 | } 97 | 98 | # prepare tmp dir 99 | $TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile 100 | $TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" 101 | $TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null 102 | trap { 103 | if ($TMP_DOWNLOAD_DIR.Exists) { 104 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 105 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 106 | } 107 | } 108 | 109 | New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null 110 | 111 | # Download and Install Apache Maven 112 | Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." 113 | Write-Verbose "Downloading from: $distributionUrl" 114 | Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" 115 | 116 | $webclient = New-Object System.Net.WebClient 117 | if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { 118 | $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) 119 | } 120 | [Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 121 | $webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null 122 | 123 | # If specified, validate the SHA-256 sum of the Maven distribution zip file 124 | $distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum 125 | if ($distributionSha256Sum) { 126 | if ($USE_MVND) { 127 | Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." 128 | } 129 | Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash 130 | if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { 131 | Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." 132 | } 133 | } 134 | 135 | # unzip and move 136 | Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null 137 | Rename-Item -Path "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" -NewName $MAVEN_HOME_NAME | Out-Null 138 | try { 139 | Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null 140 | } catch { 141 | if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { 142 | Write-Error "fail to move MAVEN_HOME" 143 | } 144 | } finally { 145 | try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } 146 | catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } 147 | } 148 | 149 | Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" 150 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.4.3 9 | 10 | 11 | com.ivanfranchin 12 | springboot-keycloak-openldap 13 | 1.0.0 14 | pom 15 | springboot-keycloak-openldap 16 | Demo project for Spring Boot 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 21 32 | 33 | 34 | simple-service 35 | 36 | 37 | -------------------------------------------------------------------------------- /remove-docker-images.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | docker rmi ivanfranchin/simple-service:1.0.0 4 | -------------------------------------------------------------------------------- /simple-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | com.ivanfranchin 7 | springboot-keycloak-openldap 8 | 1.0.0 9 | ../pom.xml 10 | 11 | simple-service 12 | simple-service 13 | Demo project for Spring Boot 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 2.8.5 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-oauth2-resource-server 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-starter-validation 38 | 39 | 40 | org.springframework.boot 41 | spring-boot-starter-web 42 | 43 | 44 | org.springdoc 45 | springdoc-openapi-starter-webmvc-ui 46 | ${springdoc-openapi.version} 47 | 48 | 49 | 50 | org.springframework.boot 51 | spring-boot-starter-test 52 | test 53 | 54 | 55 | 56 | 57 | 58 | 59 | org.graalvm.buildtools 60 | native-maven-plugin 61 | 62 | 63 | org.springframework.boot 64 | spring-boot-maven-plugin 65 | 66 | 67 | 68 | 69 | 70 | -------------------------------------------------------------------------------- /simple-service/src/main/java/com/ivanfranchin/simpleservice/SimpleServiceApplication.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class SimpleServiceApplication { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(SimpleServiceApplication.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /simple-service/src/main/java/com/ivanfranchin/simpleservice/config/SwaggerConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice.config; 2 | 3 | import io.swagger.v3.oas.models.Components; 4 | import io.swagger.v3.oas.models.OpenAPI; 5 | import io.swagger.v3.oas.models.info.Info; 6 | import io.swagger.v3.oas.models.security.SecurityScheme; 7 | import org.springdoc.core.models.GroupedOpenApi; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.Configuration; 11 | 12 | @Configuration 13 | public class SwaggerConfig { 14 | 15 | @Value("${spring.application.name}") 16 | private String applicationName; 17 | 18 | @Bean 19 | OpenAPI customOpenAPI() { 20 | return new OpenAPI() 21 | .components( 22 | new Components().addSecuritySchemes(BEARER_KEY_SECURITY_SCHEME, 23 | new SecurityScheme().type(SecurityScheme.Type.HTTP).scheme("bearer").bearerFormat("JWT"))) 24 | .info(new Info().title(applicationName)); 25 | } 26 | 27 | @Bean 28 | GroupedOpenApi customApi() { 29 | return GroupedOpenApi.builder().group("api").pathsToMatch("/api/**").build(); 30 | } 31 | 32 | public static final String BEARER_KEY_SECURITY_SCHEME = "bearer-key"; 33 | } 34 | -------------------------------------------------------------------------------- /simple-service/src/main/java/com/ivanfranchin/simpleservice/controller/SimpleServiceController.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice.controller; 2 | 3 | import com.ivanfranchin.simpleservice.config.SwaggerConfig; 4 | import io.swagger.v3.oas.annotations.Operation; 5 | import io.swagger.v3.oas.annotations.security.SecurityRequirement; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | import java.security.Principal; 11 | 12 | @RestController 13 | @RequestMapping("/api") 14 | public class SimpleServiceController { 15 | 16 | @Operation(summary = "Get string from public endpoint") 17 | @GetMapping("/public") 18 | public String getPublicString() { 19 | return "It is public."; 20 | } 21 | 22 | @Operation( 23 | summary = "Get string from private/secured endpoint", 24 | security = {@SecurityRequirement(name = SwaggerConfig.BEARER_KEY_SECURITY_SCHEME)}) 25 | @GetMapping("/private") 26 | public String getPrivateString(Principal principal) { 27 | return "%s, it is private.".formatted(principal.getName()); 28 | } 29 | } -------------------------------------------------------------------------------- /simple-service/src/main/java/com/ivanfranchin/simpleservice/security/JwtAuthenticationTokenConverter.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice.security; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.core.convert.converter.Converter; 5 | import org.springframework.security.authentication.AbstractAuthenticationToken; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.oauth2.jwt.Jwt; 9 | import org.springframework.security.oauth2.jwt.JwtClaimNames; 10 | import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; 11 | import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.Collection; 15 | import java.util.Collections; 16 | import java.util.Map; 17 | import java.util.stream.Collectors; 18 | import java.util.stream.Stream; 19 | 20 | @Component 21 | public class JwtAuthenticationTokenConverter implements Converter { 22 | 23 | private static final JwtGrantedAuthoritiesConverter jwtGrantedAuthoritiesConverter = new JwtGrantedAuthoritiesConverter(); 24 | 25 | @Value("${jwt.auth.converter.resource-id}") 26 | private String resourceId; 27 | 28 | @Value("${jwt.auth.converter.principal-attribute}") 29 | private String principalAttribute; 30 | 31 | @Override 32 | public AbstractAuthenticationToken convert(Jwt jwt) { 33 | Collection authorities = 34 | Stream.concat(jwtGrantedAuthoritiesConverter.convert(jwt).stream(), extractResourceRoles(jwt).stream()) 35 | .collect(Collectors.toSet()); 36 | String claimName = principalAttribute == null ? JwtClaimNames.SUB : principalAttribute; 37 | return new JwtAuthenticationToken(jwt, authorities, jwt.getClaim(claimName)); 38 | } 39 | 40 | private Collection extractResourceRoles(Jwt jwt) { 41 | Map resourceAccess = jwt.getClaim("resource_access"); 42 | Map resource; 43 | Collection resourceRoles; 44 | if (resourceAccess == null 45 | || (resource = (Map) resourceAccess.get(resourceId)) == null 46 | || (resourceRoles = (Collection) resource.get("roles")) == null) { 47 | return Collections.emptySet(); 48 | } 49 | return resourceRoles.stream() 50 | .map(role -> new SimpleGrantedAuthority("ROLE_" + role)) 51 | .collect(Collectors.toSet()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /simple-service/src/main/java/com/ivanfranchin/simpleservice/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice.security; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.http.HttpMethod; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.http.SessionCreationPolicy; 8 | import org.springframework.security.web.SecurityFilterChain; 9 | 10 | @Configuration 11 | public class SecurityConfig { 12 | 13 | private final JwtAuthenticationTokenConverter jwtAuthenticationTokenConverter; 14 | 15 | public SecurityConfig(JwtAuthenticationTokenConverter jwtAuthenticationTokenConverter) { 16 | this.jwtAuthenticationTokenConverter = jwtAuthenticationTokenConverter; 17 | } 18 | 19 | @Bean 20 | SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { 21 | return http 22 | .authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests 23 | .requestMatchers(HttpMethod.GET, "/api/private").hasRole("USER") 24 | .requestMatchers(HttpMethod.GET, "/api/public").permitAll() 25 | .requestMatchers("/swagger-ui.html", "/swagger-ui/**", "/v3/api-docs", "/v3/api-docs/**").permitAll() 26 | .anyRequest().authenticated()) 27 | .oauth2ResourceServer(oauth2ResourceServer -> oauth2ResourceServer 28 | .jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationTokenConverter))) 29 | .sessionManagement(sessionManagement -> sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)) 30 | .build(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /simple-service/src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | spring.application.name=simple-service 2 | 3 | spring.security.oauth2.resourceserver.jwt.issuer-uri=http://${KEYCLOAK_HOST:localhost}:${KEYCLOAK_PORT:8080}/realms/company-services 4 | 5 | jwt.auth.converter.resource-id=${spring.application.name} 6 | jwt.auth.converter.principal-attribute=preferred_username 7 | 8 | springdoc.swagger-ui.disable-swagger-default-url=true 9 | springdoc.enable-native-support=true 10 | 11 | logging.level.org.springframework.security=DEBUG -------------------------------------------------------------------------------- /simple-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | _ _ _ 2 | ___(_)_ __ ___ _ __ | | ___ ___ ___ _ ____ _(_) ___ ___ 3 | / __| | '_ ` _ \| '_ \| |/ _ \_____/ __|/ _ \ '__\ \ / / |/ __/ _ \ 4 | \__ \ | | | | | | |_) | | __/_____\__ \ __/ | \ V /| | (_| __/ 5 | |___/_|_| |_| |_| .__/|_|\___| |___/\___|_| \_/ |_|\___\___| 6 | |_| 7 | :: Spring Boot :: ${spring-boot.formatted-version} 8 | -------------------------------------------------------------------------------- /simple-service/src/test/java/com/ivanfranchin/simpleservice/SimpleServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.ivanfranchin.simpleservice; 2 | 3 | import org.junit.jupiter.api.Disabled; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | 7 | @Disabled 8 | @SpringBootTest 9 | public class SimpleServiceApplicationTests { 10 | 11 | @Test 12 | public void contextLoads() { 13 | } 14 | } 15 | --------------------------------------------------------------------------------