├── .gitignore ├── Dockerfile ├── LICENSE ├── README.md ├── containers.sh ├── docker ├── ELK │ ├── docker-compose.yml │ └── logstash │ │ └── logstash.conf ├── jaeger │ └── docker-compose.yml ├── keycloak │ ├── docker-compose.yml │ └── realm-export.json └── postgres │ ├── docker-compose.yml │ └── init.sql ├── k8s ├── java-deployment-svc.yml └── postgres-deployment-svc.yml ├── pom.xml ├── ships ├── get.ship ├── get_paged.ship ├── keycloak_token.ship └── post.ship └── src ├── main ├── java │ └── com │ │ └── javi │ │ ├── Application.java │ │ ├── adapter │ │ ├── in │ │ │ └── DummyController.java │ │ └── out │ │ │ ├── DummyPersistenceAdapter.java │ │ │ ├── entities │ │ │ └── DummyEntity.java │ │ │ ├── mappers │ │ │ └── DummyMapper.java │ │ │ └── repositories │ │ │ └── DummyEntityRepository.java │ │ ├── application │ │ ├── in │ │ │ ├── DummyUseCase.java │ │ │ ├── request │ │ │ │ └── DummyRequest.java │ │ │ └── response │ │ │ │ └── DummyResponse.java │ │ └── out │ │ │ └── DummyPersistence.java │ │ ├── common │ │ ├── configuration │ │ │ └── BeanConfiguration.java │ │ └── exception │ │ │ └── NotFoundException.java │ │ └── domain │ │ ├── model │ │ └── Dummy.java │ │ └── service │ │ └── DummyService.java └── resources │ └── application.yml └── test ├── java └── com │ └── javi │ ├── adapter │ ├── in │ │ └── DummyControllerTest.java │ └── out │ │ └── DummyPersistenceTest.java │ └── domain │ └── service │ └── DummyServiceTest.java └── resources └── com └── javi └── adapter └── out └── db.sql /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | !**/src/main/**/build/ 6 | !**/src/test/**/build/ 7 | 8 | ### STS ### 9 | .apt_generated 10 | .classpath 11 | .factorypath 12 | .project 13 | .settings 14 | .springBeans 15 | .sts4-cache 16 | bin/ 17 | !**/src/main/**/bin/ 18 | !**/src/test/**/bin/ 19 | 20 | ### IntelliJ IDEA ### 21 | .idea 22 | *.iws 23 | *.iml 24 | *.ipr 25 | out/ 26 | !**/src/main/**/out/ 27 | !**/src/test/**/out/ 28 | 29 | ### NetBeans ### 30 | /nbproject/private/ 31 | /nbbuild/ 32 | /dist/ 33 | /nbdist/ 34 | /.nb-gradle/ 35 | 36 | ### VS Code ### 37 | .vscode/ 38 | 39 | ### Schema SQL ### 40 | schema.sql 41 | 42 | ### Maven ### 43 | /target 44 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:21-jre-alpine 2 | 3 | RUN addgroup -S spring && adduser -S spring -G spring 4 | 5 | COPY target/*.jar /opt/app.jar 6 | 7 | USER spring:spring 8 | 9 | WORKDIR /opt 10 | 11 | ENTRYPOINT ["java", "-jar", "app.jar"] 12 | 13 | EXPOSE 8080 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Javier Orfo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # java-spring3-microservice 2 | *Java archetype oriented to Microservices.* 3 | 4 | Clean architecture, Java 21, Spring Boot 3, distributed tracing, log centralization and Keycloak. 5 | 6 | ## Dependencies 7 | Java 21, Docker, Maven 8 | 9 | ## Features 10 | - Clean Architecture 11 | - Exception Controller 12 | - Custom Messages and Exceptions 13 | - Pagination and Ordering 14 | - Java 21 15 | - OpenJDK or GraalVM integration 16 | - Spring Boot 3 17 | - Spring Web 18 | - Spring Data JPA 19 | - Spring OAuth2 Resource Server 20 | - Spring Security 21 | - Spring Devtools 22 | - Spring Actuator 23 | - Keycloak as Auth Server 24 | - Distributed tracing 25 | - OpenTelemetry, Micrometer and Jaeger 26 | - Log Centralization 27 | - Logstash, ElasticSearch and Kibana 28 | - Swagger 29 | - OpenApi 30 | - Auditory 31 | - JPA auditing 32 | - Database 33 | - Postgres for the app 34 | - H2 for Test 35 | - Schema generation (schema.sql) 36 | 37 | ## Files 38 | - [Docker files](https://github.com/javiorfo/java-spring3-microservice/tree/master/docker) 39 | - [Kubernetes files](https://github.com/javiorfo/java-spring3-microservice/tree/master/k8s) 40 | - [Ship files](https://github.com/javiorfo/java-spring3-microservice/tree/master/ships) 41 | - For those using Neovim and [this plugin](https://github.com/javiorfo/nvim-ship) 42 | 43 | ## Usage 44 | - Create the containers executing `./containers.sh` 45 | - Download and compile [this library](https://github.com/javiorfo/java-spring3-microservice-lib) 46 | - Start the application with the command `mvn spring-boot:run -Pdev` 47 | - To delete all the containers: `./containers.sh d` 48 | 49 | ## MongoDB instead of Postgres 50 | - [MongoDB repo](https://github.com/javiorfo/java-spring3-microservice-mongo) contains version with MongoDB 51 | --- 52 | 53 | ### Donate 54 | - **Bitcoin** [(QR)](https://raw.githubusercontent.com/javiorfo/img/master/crypto/bitcoin.png) `1GqdJ63RDPE4eJKujHi166FAyigvHu5R7v` 55 | - [Paypal](https://www.paypal.com/donate/?hosted_button_id=FA7SGLSCT2H8G) 56 | -------------------------------------------------------------------------------- /containers.sh: -------------------------------------------------------------------------------- 1 | #! /bin/bash 2 | 3 | if [ "$1" = "d" ]; then 4 | action="down" 5 | label="Deleting" 6 | else 7 | action="up" 8 | label="Creating" 9 | fi 10 | 11 | echo "$action" 12 | 13 | echo "" 14 | echo "=========================================================" 15 | echo "= Docker Container Creation =" 16 | echo "=========================================================" 17 | echo "" 18 | 19 | echo "$label Postgres container for java-spring3-microservice..." 20 | docker-compose -f docker/postgres/docker-compose.yml "$action" -d 21 | 22 | echo "$label Postgres and Keycloak..." 23 | docker-compose -f docker/keycloak/docker-compose.yml "$action" -d 24 | 25 | echo "$label Jaeger container..." 26 | docker-compose -f docker/jaeger/docker-compose.yml "$action" -d 27 | 28 | echo "$label Logstash, Elasticsearch and Kibana container..." 29 | docker-compose -f docker/ELK/docker-compose.yml "$action" -d 30 | 31 | echo "Done!" 32 | 33 | 34 | -------------------------------------------------------------------------------- /docker/ELK/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | elasticsearch: 3 | image: elasticsearch:8.13.0 4 | container_name: elasticsearch 5 | environment: 6 | ES_JAVA_OPTS: "-Xmx256m -Xms256m" 7 | discovery.type: single-node 8 | xpack.security.enabled: false 9 | ports: 10 | - 9200:9200 11 | - 9300:9300 12 | volumes: 13 | - elastic_data:/usr/share/elasticsearch/data/ 14 | networks: 15 | - elk 16 | 17 | kibana: 18 | image: kibana:8.13.0 19 | container_name: kibana 20 | ports: 21 | - 5601:5601 22 | environment: 23 | ELASTICSEARCH_URL: http://elasticsearch:9200 24 | ELASTICSEARCH_HOSTS: '["http://elasticsearch:9200"]' 25 | depends_on: 26 | - elasticsearch 27 | networks: 28 | - elk 29 | 30 | logstash: 31 | image: logstash:8.13.0 32 | container_name: logstash 33 | volumes: 34 | - ./logstash/:/logstash_dir 35 | command: logstash -f /logstash_dir/logstash.conf 36 | depends_on: 37 | - elasticsearch 38 | ports: 39 | - 5000:5000 40 | - 9600:9600 41 | environment: 42 | LS_JAVA_OPTS: "-Xmx256m -Xms256m" 43 | networks: 44 | - elk 45 | 46 | volumes: 47 | elastic_data: {} 48 | 49 | networks: 50 | elk: 51 | driver: bridge 52 | -------------------------------------------------------------------------------- /docker/ELK/logstash/logstash.conf: -------------------------------------------------------------------------------- 1 | input { 2 | # Listen for logs on port 5000 using the TCP protocol 3 | tcp { 4 | port => 5000 5 | codec => json 6 | } 7 | } 8 | 9 | filter { 10 | # Parse the timestamp field as a date 11 | date { 12 | match => [ "timestamp", "ISO8601" ] 13 | } 14 | } 15 | 16 | output { 17 | # Send the logs to Elasticsearch 18 | elasticsearch { 19 | hosts => ["elasticsearch:9200"] 20 | index => "app-%{+YYYY.MM.dd}" 21 | } 22 | # Print the logs to the standard output (optional) 23 | stdout { 24 | codec => rubydebug 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /docker/jaeger/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | jaeger: 3 | container_name: jaeger 4 | image: jaegertracing/all-in-one:latest 5 | restart: always 6 | ports: 7 | - 4318:4318 8 | - 16686:16686 9 | environment: 10 | - COLLECTOR_OTLP_ENABLED=true 11 | -------------------------------------------------------------------------------- /docker/keycloak/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | postgres: 3 | container_name: postgres_keycloak 4 | image: postgres 5 | restart: always 6 | environment: 7 | POSTGRES_PASSWORD: admin 8 | POSTGRES_USER: admin 9 | POSTGRES_DB: keycloakdb 10 | ports: 11 | - 5433:5432 12 | # networks: 13 | # - backend 14 | keycloak: 15 | container_name: keycloak 16 | image: quay.io/keycloak/keycloak:21.1.1 17 | restart: always 18 | volumes: 19 | ./realm-export.json:/opt/jboss/keycloak/imports/realm-export.json 20 | environment: 21 | KEYCLOAK_ADMIN: admin 22 | KEYCLOAK_ADMIN_PASSWORD: admin 23 | KC_DB: postgres 24 | KC_DB_URL: jdbc:postgresql://postgres/keycloakdb 25 | KC_DB_USERNAME: admin 26 | KC_DB_PASSWORD: admin 27 | KEYCLOAK_IMPORT: /opt/jboss/keycloak/imports/realm-export.json 28 | depends_on: 29 | - postgres 30 | ports: 31 | - 8081:8080 32 | command: 33 | - start-dev 34 | # networks: 35 | # - backend 36 | 37 | # networks: 38 | # backend: 39 | # name: backend 40 | # driver: bridge 41 | -------------------------------------------------------------------------------- /docker/keycloak/realm-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5b1be921-15e4-4abb-9770-ff34129a5991", 3 | "realm": "javi", 4 | "notBefore": 0, 5 | "defaultSignatureAlgorithm": "RS256", 6 | "revokeRefreshToken": false, 7 | "refreshTokenMaxReuse": 0, 8 | "accessTokenLifespan": 300, 9 | "accessTokenLifespanForImplicitFlow": 900, 10 | "ssoSessionIdleTimeout": 1800, 11 | "ssoSessionMaxLifespan": 36000, 12 | "ssoSessionIdleTimeoutRememberMe": 0, 13 | "ssoSessionMaxLifespanRememberMe": 0, 14 | "offlineSessionIdleTimeout": 2592000, 15 | "offlineSessionMaxLifespanEnabled": false, 16 | "offlineSessionMaxLifespan": 5184000, 17 | "clientSessionIdleTimeout": 0, 18 | "clientSessionMaxLifespan": 0, 19 | "clientOfflineSessionIdleTimeout": 0, 20 | "clientOfflineSessionMaxLifespan": 0, 21 | "accessCodeLifespan": 60, 22 | "accessCodeLifespanUserAction": 300, 23 | "accessCodeLifespanLogin": 1800, 24 | "actionTokenGeneratedByAdminLifespan": 43200, 25 | "actionTokenGeneratedByUserLifespan": 300, 26 | "oauth2DeviceCodeLifespan": 600, 27 | "oauth2DevicePollingInterval": 5, 28 | "enabled": true, 29 | "sslRequired": "external", 30 | "registrationAllowed": false, 31 | "registrationEmailAsUsername": false, 32 | "rememberMe": false, 33 | "verifyEmail": false, 34 | "loginWithEmailAllowed": true, 35 | "duplicateEmailsAllowed": false, 36 | "resetPasswordAllowed": false, 37 | "editUsernameAllowed": false, 38 | "bruteForceProtected": false, 39 | "permanentLockout": false, 40 | "maxFailureWaitSeconds": 900, 41 | "minimumQuickLoginWaitSeconds": 60, 42 | "waitIncrementSeconds": 60, 43 | "quickLoginCheckMilliSeconds": 1000, 44 | "maxDeltaTimeSeconds": 43200, 45 | "failureFactor": 30, 46 | "roles": { 47 | "realm": [ 48 | { 49 | "id": "7a9038eb-969b-42be-ba85-e9c6aac9cc86", 50 | "name": "offline_access", 51 | "description": "${role_offline-access}", 52 | "composite": false, 53 | "clientRole": false, 54 | "containerId": "5b1be921-15e4-4abb-9770-ff34129a5991", 55 | "attributes": {} 56 | }, 57 | { 58 | "id": "16346e15-3937-4fa8-8809-a8b8cf4eaafe", 59 | "name": "uma_authorization", 60 | "description": "${role_uma_authorization}", 61 | "composite": false, 62 | "clientRole": false, 63 | "containerId": "5b1be921-15e4-4abb-9770-ff34129a5991", 64 | "attributes": {} 65 | }, 66 | { 67 | "id": "ae1ce714-097b-4ae4-954f-9406fa982093", 68 | "name": "ADMIN", 69 | "description": "", 70 | "composite": true, 71 | "composites": { 72 | "client": { 73 | "srv-client": [ 74 | "CLIENT_ADMIN" 75 | ] 76 | } 77 | }, 78 | "clientRole": false, 79 | "containerId": "5b1be921-15e4-4abb-9770-ff34129a5991", 80 | "attributes": {} 81 | }, 82 | { 83 | "id": "c838f9d5-a8c3-4b6f-a24c-96e259db9121", 84 | "name": "default-roles-javi", 85 | "description": "${role_default-roles}", 86 | "composite": true, 87 | "composites": { 88 | "realm": [ 89 | "offline_access", 90 | "uma_authorization" 91 | ], 92 | "client": { 93 | "account": [ 94 | "view-profile", 95 | "manage-account" 96 | ] 97 | } 98 | }, 99 | "clientRole": false, 100 | "containerId": "5b1be921-15e4-4abb-9770-ff34129a5991", 101 | "attributes": {} 102 | } 103 | ], 104 | "client": { 105 | "realm-management": [ 106 | { 107 | "id": "ca8adc5b-68f4-4dfe-af7d-393a206f589b", 108 | "name": "query-users", 109 | "description": "${role_query-users}", 110 | "composite": false, 111 | "clientRole": true, 112 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 113 | "attributes": {} 114 | }, 115 | { 116 | "id": "368d21e0-fbc1-42ce-96bf-a37f500ad543", 117 | "name": "manage-identity-providers", 118 | "description": "${role_manage-identity-providers}", 119 | "composite": false, 120 | "clientRole": true, 121 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 122 | "attributes": {} 123 | }, 124 | { 125 | "id": "f32977ec-bd66-42e1-bf82-e9768f18c57b", 126 | "name": "view-realm", 127 | "description": "${role_view-realm}", 128 | "composite": false, 129 | "clientRole": true, 130 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 131 | "attributes": {} 132 | }, 133 | { 134 | "id": "d8abefcf-681a-4552-a3c2-1b9211e72384", 135 | "name": "impersonation", 136 | "description": "${role_impersonation}", 137 | "composite": false, 138 | "clientRole": true, 139 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 140 | "attributes": {} 141 | }, 142 | { 143 | "id": "1f5f7bb5-7cff-4037-a092-467bbf8e56e4", 144 | "name": "manage-clients", 145 | "description": "${role_manage-clients}", 146 | "composite": false, 147 | "clientRole": true, 148 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 149 | "attributes": {} 150 | }, 151 | { 152 | "id": "495b5906-e87d-4085-beeb-77aa580aa289", 153 | "name": "query-groups", 154 | "description": "${role_query-groups}", 155 | "composite": false, 156 | "clientRole": true, 157 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 158 | "attributes": {} 159 | }, 160 | { 161 | "id": "9f013f62-6401-480d-8eac-5022ae9da574", 162 | "name": "manage-authorization", 163 | "description": "${role_manage-authorization}", 164 | "composite": false, 165 | "clientRole": true, 166 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 167 | "attributes": {} 168 | }, 169 | { 170 | "id": "57b8253d-5879-468b-8a05-db9771fcb0d6", 171 | "name": "view-clients", 172 | "description": "${role_view-clients}", 173 | "composite": true, 174 | "composites": { 175 | "client": { 176 | "realm-management": [ 177 | "query-clients" 178 | ] 179 | } 180 | }, 181 | "clientRole": true, 182 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 183 | "attributes": {} 184 | }, 185 | { 186 | "id": "3c743148-928c-492d-9d37-306eb3bc4e3c", 187 | "name": "view-authorization", 188 | "description": "${role_view-authorization}", 189 | "composite": false, 190 | "clientRole": true, 191 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 192 | "attributes": {} 193 | }, 194 | { 195 | "id": "b165e9c6-829e-4230-91bd-99b64380a7e8", 196 | "name": "realm-admin", 197 | "description": "${role_realm-admin}", 198 | "composite": true, 199 | "composites": { 200 | "client": { 201 | "realm-management": [ 202 | "query-users", 203 | "view-realm", 204 | "manage-identity-providers", 205 | "manage-clients", 206 | "impersonation", 207 | "query-groups", 208 | "manage-authorization", 209 | "view-clients", 210 | "view-authorization", 211 | "view-identity-providers", 212 | "view-events", 213 | "manage-realm", 214 | "create-client", 215 | "manage-events", 216 | "view-users", 217 | "query-clients", 218 | "manage-users", 219 | "query-realms" 220 | ] 221 | } 222 | }, 223 | "clientRole": true, 224 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 225 | "attributes": {} 226 | }, 227 | { 228 | "id": "d867f965-e9c7-493d-836a-4fdc4b4cd3eb", 229 | "name": "view-identity-providers", 230 | "description": "${role_view-identity-providers}", 231 | "composite": false, 232 | "clientRole": true, 233 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 234 | "attributes": {} 235 | }, 236 | { 237 | "id": "967c0e73-1ec8-4900-a2f6-5e5cca91cc41", 238 | "name": "manage-realm", 239 | "description": "${role_manage-realm}", 240 | "composite": false, 241 | "clientRole": true, 242 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 243 | "attributes": {} 244 | }, 245 | { 246 | "id": "0a5add39-fd33-4001-9400-f52edf58df48", 247 | "name": "view-events", 248 | "description": "${role_view-events}", 249 | "composite": false, 250 | "clientRole": true, 251 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 252 | "attributes": {} 253 | }, 254 | { 255 | "id": "794ec82a-a6ed-4298-89a1-75fd094b89f8", 256 | "name": "create-client", 257 | "description": "${role_create-client}", 258 | "composite": false, 259 | "clientRole": true, 260 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 261 | "attributes": {} 262 | }, 263 | { 264 | "id": "20123a90-71ff-48d2-866a-ffc5f3567eda", 265 | "name": "manage-events", 266 | "description": "${role_manage-events}", 267 | "composite": false, 268 | "clientRole": true, 269 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 270 | "attributes": {} 271 | }, 272 | { 273 | "id": "b8e99c58-2498-4fcd-9247-f7275f837db3", 274 | "name": "query-clients", 275 | "description": "${role_query-clients}", 276 | "composite": false, 277 | "clientRole": true, 278 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 279 | "attributes": {} 280 | }, 281 | { 282 | "id": "8e47c8be-783a-4090-9c05-371caf5907a2", 283 | "name": "view-users", 284 | "description": "${role_view-users}", 285 | "composite": true, 286 | "composites": { 287 | "client": { 288 | "realm-management": [ 289 | "query-users", 290 | "query-groups" 291 | ] 292 | } 293 | }, 294 | "clientRole": true, 295 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 296 | "attributes": {} 297 | }, 298 | { 299 | "id": "a9cbf23e-d58f-4015-88d3-48b4d955d7fa", 300 | "name": "manage-users", 301 | "description": "${role_manage-users}", 302 | "composite": false, 303 | "clientRole": true, 304 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 305 | "attributes": {} 306 | }, 307 | { 308 | "id": "96b729a7-489a-43fe-a3c8-92f9364b9815", 309 | "name": "query-realms", 310 | "description": "${role_query-realms}", 311 | "composite": false, 312 | "clientRole": true, 313 | "containerId": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 314 | "attributes": {} 315 | } 316 | ], 317 | "security-admin-console": [], 318 | "admin-cli": [], 319 | "account-console": [], 320 | "broker": [ 321 | { 322 | "id": "5a6059d3-d375-411f-a27b-4e8338c0abb5", 323 | "name": "read-token", 324 | "description": "${role_read-token}", 325 | "composite": false, 326 | "clientRole": true, 327 | "containerId": "57d3ba1e-fc5c-412e-92cc-2a5fcb06e097", 328 | "attributes": {} 329 | } 330 | ], 331 | "srv-client": [ 332 | { 333 | "id": "1979fe66-0b49-4962-8755-937adfa051df", 334 | "name": "CLIENT_ADMIN", 335 | "description": "", 336 | "composite": false, 337 | "clientRole": true, 338 | "containerId": "ff218c79-85f1-40b0-811f-f75b0984f147", 339 | "attributes": {} 340 | }, 341 | { 342 | "id": "037278cb-9459-474e-be20-638036ae5a82", 343 | "name": "uma_protection", 344 | "composite": false, 345 | "clientRole": true, 346 | "containerId": "ff218c79-85f1-40b0-811f-f75b0984f147", 347 | "attributes": {} 348 | } 349 | ], 350 | "account": [ 351 | { 352 | "id": "ee7d74af-6765-4937-bd1e-6fd88cc491de", 353 | "name": "manage-account-links", 354 | "description": "${role_manage-account-links}", 355 | "composite": false, 356 | "clientRole": true, 357 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 358 | "attributes": {} 359 | }, 360 | { 361 | "id": "6c8f0eb8-992d-4add-8545-4e5c9d025f1b", 362 | "name": "view-consent", 363 | "description": "${role_view-consent}", 364 | "composite": false, 365 | "clientRole": true, 366 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 367 | "attributes": {} 368 | }, 369 | { 370 | "id": "7a36def2-8775-4b04-a06b-843c4d3ebf67", 371 | "name": "delete-account", 372 | "description": "${role_delete-account}", 373 | "composite": false, 374 | "clientRole": true, 375 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 376 | "attributes": {} 377 | }, 378 | { 379 | "id": "0b447522-baef-4c94-85a1-23c1426fdccb", 380 | "name": "manage-consent", 381 | "description": "${role_manage-consent}", 382 | "composite": true, 383 | "composites": { 384 | "client": { 385 | "account": [ 386 | "view-consent" 387 | ] 388 | } 389 | }, 390 | "clientRole": true, 391 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 392 | "attributes": {} 393 | }, 394 | { 395 | "id": "d72b9525-3e59-4b18-9b79-c5946f5eea69", 396 | "name": "view-groups", 397 | "description": "${role_view-groups}", 398 | "composite": false, 399 | "clientRole": true, 400 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 401 | "attributes": {} 402 | }, 403 | { 404 | "id": "baae6415-efcf-4037-bfde-7b8701957052", 405 | "name": "view-profile", 406 | "description": "${role_view-profile}", 407 | "composite": false, 408 | "clientRole": true, 409 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 410 | "attributes": {} 411 | }, 412 | { 413 | "id": "3abf2f2a-246f-43db-85a0-dafc5c81c514", 414 | "name": "view-applications", 415 | "description": "${role_view-applications}", 416 | "composite": false, 417 | "clientRole": true, 418 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 419 | "attributes": {} 420 | }, 421 | { 422 | "id": "18edbcea-033d-45dd-b0bc-550efd7949b3", 423 | "name": "manage-account", 424 | "description": "${role_manage-account}", 425 | "composite": true, 426 | "composites": { 427 | "client": { 428 | "account": [ 429 | "manage-account-links" 430 | ] 431 | } 432 | }, 433 | "clientRole": true, 434 | "containerId": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 435 | "attributes": {} 436 | } 437 | ] 438 | } 439 | }, 440 | "groups": [], 441 | "defaultRole": { 442 | "id": "c838f9d5-a8c3-4b6f-a24c-96e259db9121", 443 | "name": "default-roles-javi", 444 | "description": "${role_default-roles}", 445 | "composite": true, 446 | "clientRole": false, 447 | "containerId": "5b1be921-15e4-4abb-9770-ff34129a5991" 448 | }, 449 | "requiredCredentials": [ 450 | "password" 451 | ], 452 | "otpPolicyType": "totp", 453 | "otpPolicyAlgorithm": "HmacSHA1", 454 | "otpPolicyInitialCounter": 0, 455 | "otpPolicyDigits": 6, 456 | "otpPolicyLookAheadWindow": 1, 457 | "otpPolicyPeriod": 30, 458 | "otpPolicyCodeReusable": false, 459 | "otpSupportedApplications": [ 460 | "totpAppGoogleName", 461 | "totpAppMicrosoftAuthenticatorName", 462 | "totpAppFreeOTPName" 463 | ], 464 | "webAuthnPolicyRpEntityName": "keycloak", 465 | "webAuthnPolicySignatureAlgorithms": [ 466 | "ES256" 467 | ], 468 | "webAuthnPolicyRpId": "", 469 | "webAuthnPolicyAttestationConveyancePreference": "not specified", 470 | "webAuthnPolicyAuthenticatorAttachment": "not specified", 471 | "webAuthnPolicyRequireResidentKey": "not specified", 472 | "webAuthnPolicyUserVerificationRequirement": "not specified", 473 | "webAuthnPolicyCreateTimeout": 0, 474 | "webAuthnPolicyAvoidSameAuthenticatorRegister": false, 475 | "webAuthnPolicyAcceptableAaguids": [], 476 | "webAuthnPolicyPasswordlessRpEntityName": "keycloak", 477 | "webAuthnPolicyPasswordlessSignatureAlgorithms": [ 478 | "ES256" 479 | ], 480 | "webAuthnPolicyPasswordlessRpId": "", 481 | "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", 482 | "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", 483 | "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", 484 | "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", 485 | "webAuthnPolicyPasswordlessCreateTimeout": 0, 486 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, 487 | "webAuthnPolicyPasswordlessAcceptableAaguids": [], 488 | "users": [ 489 | { 490 | "id": "54c7ef1a-d698-4d4d-9cb0-f0db0551a9cb", 491 | "createdTimestamp": 1712166900903, 492 | "username": "service-account-srv-client", 493 | "enabled": true, 494 | "totp": false, 495 | "emailVerified": false, 496 | "serviceAccountClientId": "srv-client", 497 | "disableableCredentialTypes": [], 498 | "requiredActions": [], 499 | "realmRoles": [ 500 | "default-roles-javi" 501 | ], 502 | "clientRoles": { 503 | "srv-client": [ 504 | "uma_protection" 505 | ] 506 | }, 507 | "notBefore": 0, 508 | "groups": [] 509 | } 510 | ], 511 | "scopeMappings": [ 512 | { 513 | "clientScope": "offline_access", 514 | "roles": [ 515 | "offline_access" 516 | ] 517 | } 518 | ], 519 | "clientScopeMappings": { 520 | "account": [ 521 | { 522 | "client": "account-console", 523 | "roles": [ 524 | "manage-account", 525 | "view-groups" 526 | ] 527 | } 528 | ] 529 | }, 530 | "clients": [ 531 | { 532 | "id": "1a774c5f-2bf3-4149-9d34-78c5d0279425", 533 | "clientId": "account", 534 | "name": "${client_account}", 535 | "rootUrl": "${authBaseUrl}", 536 | "baseUrl": "/realms/javi/account/", 537 | "surrogateAuthRequired": false, 538 | "enabled": true, 539 | "alwaysDisplayInConsole": false, 540 | "clientAuthenticatorType": "client-secret", 541 | "redirectUris": [ 542 | "/realms/javi/account/*" 543 | ], 544 | "webOrigins": [], 545 | "notBefore": 0, 546 | "bearerOnly": false, 547 | "consentRequired": false, 548 | "standardFlowEnabled": true, 549 | "implicitFlowEnabled": false, 550 | "directAccessGrantsEnabled": false, 551 | "serviceAccountsEnabled": false, 552 | "publicClient": true, 553 | "frontchannelLogout": false, 554 | "protocol": "openid-connect", 555 | "attributes": { 556 | "post.logout.redirect.uris": "+" 557 | }, 558 | "authenticationFlowBindingOverrides": {}, 559 | "fullScopeAllowed": false, 560 | "nodeReRegistrationTimeout": 0, 561 | "defaultClientScopes": [ 562 | "web-origins", 563 | "acr", 564 | "profile", 565 | "roles", 566 | "email" 567 | ], 568 | "optionalClientScopes": [ 569 | "address", 570 | "phone", 571 | "offline_access", 572 | "microprofile-jwt" 573 | ] 574 | }, 575 | { 576 | "id": "1e49b0dd-3cd0-464d-b2f7-2c70d834f3ec", 577 | "clientId": "account-console", 578 | "name": "${client_account-console}", 579 | "rootUrl": "${authBaseUrl}", 580 | "baseUrl": "/realms/javi/account/", 581 | "surrogateAuthRequired": false, 582 | "enabled": true, 583 | "alwaysDisplayInConsole": false, 584 | "clientAuthenticatorType": "client-secret", 585 | "redirectUris": [ 586 | "/realms/javi/account/*" 587 | ], 588 | "webOrigins": [], 589 | "notBefore": 0, 590 | "bearerOnly": false, 591 | "consentRequired": false, 592 | "standardFlowEnabled": true, 593 | "implicitFlowEnabled": false, 594 | "directAccessGrantsEnabled": false, 595 | "serviceAccountsEnabled": false, 596 | "publicClient": true, 597 | "frontchannelLogout": false, 598 | "protocol": "openid-connect", 599 | "attributes": { 600 | "post.logout.redirect.uris": "+", 601 | "pkce.code.challenge.method": "S256" 602 | }, 603 | "authenticationFlowBindingOverrides": {}, 604 | "fullScopeAllowed": false, 605 | "nodeReRegistrationTimeout": 0, 606 | "protocolMappers": [ 607 | { 608 | "id": "33be9718-439f-45cb-8f84-f0ed6eb77fe2", 609 | "name": "audience resolve", 610 | "protocol": "openid-connect", 611 | "protocolMapper": "oidc-audience-resolve-mapper", 612 | "consentRequired": false, 613 | "config": {} 614 | } 615 | ], 616 | "defaultClientScopes": [ 617 | "web-origins", 618 | "acr", 619 | "profile", 620 | "roles", 621 | "email" 622 | ], 623 | "optionalClientScopes": [ 624 | "address", 625 | "phone", 626 | "offline_access", 627 | "microprofile-jwt" 628 | ] 629 | }, 630 | { 631 | "id": "00850315-fa66-4503-b018-977c82997f82", 632 | "clientId": "admin-cli", 633 | "name": "${client_admin-cli}", 634 | "surrogateAuthRequired": false, 635 | "enabled": true, 636 | "alwaysDisplayInConsole": false, 637 | "clientAuthenticatorType": "client-secret", 638 | "redirectUris": [], 639 | "webOrigins": [], 640 | "notBefore": 0, 641 | "bearerOnly": false, 642 | "consentRequired": false, 643 | "standardFlowEnabled": false, 644 | "implicitFlowEnabled": false, 645 | "directAccessGrantsEnabled": true, 646 | "serviceAccountsEnabled": false, 647 | "publicClient": true, 648 | "frontchannelLogout": false, 649 | "protocol": "openid-connect", 650 | "attributes": {}, 651 | "authenticationFlowBindingOverrides": {}, 652 | "fullScopeAllowed": false, 653 | "nodeReRegistrationTimeout": 0, 654 | "defaultClientScopes": [ 655 | "web-origins", 656 | "acr", 657 | "profile", 658 | "roles", 659 | "email" 660 | ], 661 | "optionalClientScopes": [ 662 | "address", 663 | "phone", 664 | "offline_access", 665 | "microprofile-jwt" 666 | ] 667 | }, 668 | { 669 | "id": "57d3ba1e-fc5c-412e-92cc-2a5fcb06e097", 670 | "clientId": "broker", 671 | "name": "${client_broker}", 672 | "surrogateAuthRequired": false, 673 | "enabled": true, 674 | "alwaysDisplayInConsole": false, 675 | "clientAuthenticatorType": "client-secret", 676 | "redirectUris": [], 677 | "webOrigins": [], 678 | "notBefore": 0, 679 | "bearerOnly": true, 680 | "consentRequired": false, 681 | "standardFlowEnabled": true, 682 | "implicitFlowEnabled": false, 683 | "directAccessGrantsEnabled": false, 684 | "serviceAccountsEnabled": false, 685 | "publicClient": false, 686 | "frontchannelLogout": false, 687 | "protocol": "openid-connect", 688 | "attributes": {}, 689 | "authenticationFlowBindingOverrides": {}, 690 | "fullScopeAllowed": false, 691 | "nodeReRegistrationTimeout": 0, 692 | "defaultClientScopes": [ 693 | "web-origins", 694 | "acr", 695 | "profile", 696 | "roles", 697 | "email" 698 | ], 699 | "optionalClientScopes": [ 700 | "address", 701 | "phone", 702 | "offline_access", 703 | "microprofile-jwt" 704 | ] 705 | }, 706 | { 707 | "id": "ff218c79-85f1-40b0-811f-f75b0984f147", 708 | "clientId": "srv-client", 709 | "name": "", 710 | "description": "", 711 | "rootUrl": "http://localhost:8080/app", 712 | "adminUrl": "http://localhost:8080/app", 713 | "baseUrl": "http://localhost:8080/app", 714 | "surrogateAuthRequired": false, 715 | "enabled": true, 716 | "alwaysDisplayInConsole": false, 717 | "clientAuthenticatorType": "client-secret", 718 | "secret": "**********", 719 | "redirectUris": [ 720 | "http://localhost:8080/*" 721 | ], 722 | "webOrigins": [ 723 | "*" 724 | ], 725 | "notBefore": 0, 726 | "bearerOnly": false, 727 | "consentRequired": false, 728 | "standardFlowEnabled": true, 729 | "implicitFlowEnabled": false, 730 | "directAccessGrantsEnabled": true, 731 | "serviceAccountsEnabled": true, 732 | "authorizationServicesEnabled": true, 733 | "publicClient": false, 734 | "frontchannelLogout": true, 735 | "protocol": "openid-connect", 736 | "attributes": { 737 | "oidc.ciba.grant.enabled": "false", 738 | "client.secret.creation.time": "1712166900", 739 | "backchannel.logout.session.required": "true", 740 | "post.logout.redirect.uris": "http://localhost:8080/*", 741 | "oauth2.device.authorization.grant.enabled": "false", 742 | "backchannel.logout.revoke.offline.tokens": "false" 743 | }, 744 | "authenticationFlowBindingOverrides": {}, 745 | "fullScopeAllowed": true, 746 | "nodeReRegistrationTimeout": -1, 747 | "protocolMappers": [ 748 | { 749 | "id": "9aabd7b1-1fd0-4c9d-ab3b-67e9f0ed0485", 750 | "name": "Client ID", 751 | "protocol": "openid-connect", 752 | "protocolMapper": "oidc-usersessionmodel-note-mapper", 753 | "consentRequired": false, 754 | "config": { 755 | "user.session.note": "client_id", 756 | "id.token.claim": "true", 757 | "access.token.claim": "true", 758 | "claim.name": "client_id", 759 | "jsonType.label": "String" 760 | } 761 | }, 762 | { 763 | "id": "91ad6bbf-b861-4d1a-a847-9576ceebba9e", 764 | "name": "Client IP Address", 765 | "protocol": "openid-connect", 766 | "protocolMapper": "oidc-usersessionmodel-note-mapper", 767 | "consentRequired": false, 768 | "config": { 769 | "user.session.note": "clientAddress", 770 | "id.token.claim": "true", 771 | "access.token.claim": "true", 772 | "claim.name": "clientAddress", 773 | "jsonType.label": "String" 774 | } 775 | }, 776 | { 777 | "id": "a65aca89-fd07-4b0c-913b-a63067fefa2f", 778 | "name": "Client Host", 779 | "protocol": "openid-connect", 780 | "protocolMapper": "oidc-usersessionmodel-note-mapper", 781 | "consentRequired": false, 782 | "config": { 783 | "user.session.note": "clientHost", 784 | "id.token.claim": "true", 785 | "access.token.claim": "true", 786 | "claim.name": "clientHost", 787 | "jsonType.label": "String" 788 | } 789 | } 790 | ], 791 | "defaultClientScopes": [ 792 | "web-origins", 793 | "acr", 794 | "profile", 795 | "roles", 796 | "email" 797 | ], 798 | "optionalClientScopes": [ 799 | "address", 800 | "phone", 801 | "offline_access", 802 | "microprofile-jwt" 803 | ], 804 | "authorizationSettings": { 805 | "allowRemoteResourceManagement": true, 806 | "policyEnforcementMode": "ENFORCING", 807 | "resources": [ 808 | { 809 | "name": "Default Resource", 810 | "type": "urn:srv-client:resources:default", 811 | "ownerManagedAccess": false, 812 | "attributes": {}, 813 | "_id": "b3045091-de12-4667-a998-0a7459593233", 814 | "uris": [ 815 | "/*" 816 | ] 817 | } 818 | ], 819 | "policies": [ 820 | { 821 | "id": "412a4fc3-a2e1-4664-ae18-f2ee128cb9ab", 822 | "name": "Default Policy", 823 | "description": "A policy that grants access only for users within this realm", 824 | "type": "js", 825 | "logic": "POSITIVE", 826 | "decisionStrategy": "AFFIRMATIVE", 827 | "config": { 828 | "code": "// by default, grants any permission associated with this policy\n$evaluation.grant();\n" 829 | } 830 | }, 831 | { 832 | "id": "7676c773-fa31-436b-859d-6238a7d53245", 833 | "name": "Default Permission", 834 | "description": "A permission that applies to the default resource type", 835 | "type": "resource", 836 | "logic": "POSITIVE", 837 | "decisionStrategy": "UNANIMOUS", 838 | "config": { 839 | "defaultResourceType": "urn:srv-client:resources:default", 840 | "applyPolicies": "[\"Default Policy\"]" 841 | } 842 | } 843 | ], 844 | "scopes": [], 845 | "decisionStrategy": "UNANIMOUS" 846 | } 847 | }, 848 | { 849 | "id": "a113625e-fb17-4f4c-b3b5-fbb035cf840a", 850 | "clientId": "realm-management", 851 | "name": "${client_realm-management}", 852 | "surrogateAuthRequired": false, 853 | "enabled": true, 854 | "alwaysDisplayInConsole": false, 855 | "clientAuthenticatorType": "client-secret", 856 | "redirectUris": [], 857 | "webOrigins": [], 858 | "notBefore": 0, 859 | "bearerOnly": true, 860 | "consentRequired": false, 861 | "standardFlowEnabled": true, 862 | "implicitFlowEnabled": false, 863 | "directAccessGrantsEnabled": false, 864 | "serviceAccountsEnabled": false, 865 | "publicClient": false, 866 | "frontchannelLogout": false, 867 | "protocol": "openid-connect", 868 | "attributes": {}, 869 | "authenticationFlowBindingOverrides": {}, 870 | "fullScopeAllowed": false, 871 | "nodeReRegistrationTimeout": 0, 872 | "defaultClientScopes": [ 873 | "web-origins", 874 | "acr", 875 | "profile", 876 | "roles", 877 | "email" 878 | ], 879 | "optionalClientScopes": [ 880 | "address", 881 | "phone", 882 | "offline_access", 883 | "microprofile-jwt" 884 | ] 885 | }, 886 | { 887 | "id": "0f6a1373-9dba-44cf-b4ab-f300b39f9671", 888 | "clientId": "security-admin-console", 889 | "name": "${client_security-admin-console}", 890 | "rootUrl": "${authAdminUrl}", 891 | "baseUrl": "/admin/javi/console/", 892 | "surrogateAuthRequired": false, 893 | "enabled": true, 894 | "alwaysDisplayInConsole": false, 895 | "clientAuthenticatorType": "client-secret", 896 | "redirectUris": [ 897 | "/admin/javi/console/*" 898 | ], 899 | "webOrigins": [ 900 | "+" 901 | ], 902 | "notBefore": 0, 903 | "bearerOnly": false, 904 | "consentRequired": false, 905 | "standardFlowEnabled": true, 906 | "implicitFlowEnabled": false, 907 | "directAccessGrantsEnabled": false, 908 | "serviceAccountsEnabled": false, 909 | "publicClient": true, 910 | "frontchannelLogout": false, 911 | "protocol": "openid-connect", 912 | "attributes": { 913 | "post.logout.redirect.uris": "+", 914 | "pkce.code.challenge.method": "S256" 915 | }, 916 | "authenticationFlowBindingOverrides": {}, 917 | "fullScopeAllowed": false, 918 | "nodeReRegistrationTimeout": 0, 919 | "protocolMappers": [ 920 | { 921 | "id": "c64ccd34-78da-459c-bc55-0710b043223d", 922 | "name": "locale", 923 | "protocol": "openid-connect", 924 | "protocolMapper": "oidc-usermodel-attribute-mapper", 925 | "consentRequired": false, 926 | "config": { 927 | "userinfo.token.claim": "true", 928 | "user.attribute": "locale", 929 | "id.token.claim": "true", 930 | "access.token.claim": "true", 931 | "claim.name": "locale", 932 | "jsonType.label": "String" 933 | } 934 | } 935 | ], 936 | "defaultClientScopes": [ 937 | "web-origins", 938 | "acr", 939 | "profile", 940 | "roles", 941 | "email" 942 | ], 943 | "optionalClientScopes": [ 944 | "address", 945 | "phone", 946 | "offline_access", 947 | "microprofile-jwt" 948 | ] 949 | } 950 | ], 951 | "clientScopes": [ 952 | { 953 | "id": "48c28ce8-961b-4210-9a48-d40736718cf5", 954 | "name": "microprofile-jwt", 955 | "description": "Microprofile - JWT built-in scope", 956 | "protocol": "openid-connect", 957 | "attributes": { 958 | "include.in.token.scope": "true", 959 | "display.on.consent.screen": "false" 960 | }, 961 | "protocolMappers": [ 962 | { 963 | "id": "b6d580a8-5b1d-411d-b411-72f65351c502", 964 | "name": "groups", 965 | "protocol": "openid-connect", 966 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 967 | "consentRequired": false, 968 | "config": { 969 | "multivalued": "true", 970 | "user.attribute": "foo", 971 | "id.token.claim": "true", 972 | "access.token.claim": "true", 973 | "claim.name": "groups", 974 | "jsonType.label": "String" 975 | } 976 | }, 977 | { 978 | "id": "de2cf414-be68-4306-97bd-eadbb0e59c8a", 979 | "name": "upn", 980 | "protocol": "openid-connect", 981 | "protocolMapper": "oidc-usermodel-property-mapper", 982 | "consentRequired": false, 983 | "config": { 984 | "userinfo.token.claim": "true", 985 | "user.attribute": "username", 986 | "id.token.claim": "true", 987 | "access.token.claim": "true", 988 | "claim.name": "upn", 989 | "jsonType.label": "String" 990 | } 991 | } 992 | ] 993 | }, 994 | { 995 | "id": "00d98e33-cd70-4e44-a67b-b91a4401a909", 996 | "name": "acr", 997 | "description": "OpenID Connect scope for add acr (authentication context class reference) to the token", 998 | "protocol": "openid-connect", 999 | "attributes": { 1000 | "include.in.token.scope": "false", 1001 | "display.on.consent.screen": "false" 1002 | }, 1003 | "protocolMappers": [ 1004 | { 1005 | "id": "3f788998-7d11-4ba0-a28d-277378d7616a", 1006 | "name": "acr loa level", 1007 | "protocol": "openid-connect", 1008 | "protocolMapper": "oidc-acr-mapper", 1009 | "consentRequired": false, 1010 | "config": { 1011 | "id.token.claim": "true", 1012 | "access.token.claim": "true" 1013 | } 1014 | } 1015 | ] 1016 | }, 1017 | { 1018 | "id": "538e3c5f-6fb4-4acf-8cc8-08848c5f249d", 1019 | "name": "web-origins", 1020 | "description": "OpenID Connect scope for add allowed web origins to the access token", 1021 | "protocol": "openid-connect", 1022 | "attributes": { 1023 | "include.in.token.scope": "false", 1024 | "display.on.consent.screen": "false", 1025 | "consent.screen.text": "" 1026 | }, 1027 | "protocolMappers": [ 1028 | { 1029 | "id": "6983c933-993e-436b-bf0e-11aadd789dfd", 1030 | "name": "allowed web origins", 1031 | "protocol": "openid-connect", 1032 | "protocolMapper": "oidc-allowed-origins-mapper", 1033 | "consentRequired": false, 1034 | "config": {} 1035 | } 1036 | ] 1037 | }, 1038 | { 1039 | "id": "ce192ab5-b21f-4214-9d16-8915b88a1e75", 1040 | "name": "profile", 1041 | "description": "OpenID Connect built-in scope: profile", 1042 | "protocol": "openid-connect", 1043 | "attributes": { 1044 | "include.in.token.scope": "true", 1045 | "display.on.consent.screen": "true", 1046 | "consent.screen.text": "${profileScopeConsentText}" 1047 | }, 1048 | "protocolMappers": [ 1049 | { 1050 | "id": "a6b199e7-66a9-48ca-b689-6ae1a3ecdce8", 1051 | "name": "locale", 1052 | "protocol": "openid-connect", 1053 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1054 | "consentRequired": false, 1055 | "config": { 1056 | "userinfo.token.claim": "true", 1057 | "user.attribute": "locale", 1058 | "id.token.claim": "true", 1059 | "access.token.claim": "true", 1060 | "claim.name": "locale", 1061 | "jsonType.label": "String" 1062 | } 1063 | }, 1064 | { 1065 | "id": "1b4ed381-33ff-41cb-8a5c-c079ad72cf18", 1066 | "name": "profile", 1067 | "protocol": "openid-connect", 1068 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1069 | "consentRequired": false, 1070 | "config": { 1071 | "userinfo.token.claim": "true", 1072 | "user.attribute": "profile", 1073 | "id.token.claim": "true", 1074 | "access.token.claim": "true", 1075 | "claim.name": "profile", 1076 | "jsonType.label": "String" 1077 | } 1078 | }, 1079 | { 1080 | "id": "226022a7-12ee-4dfb-a01c-ba8dd5681534", 1081 | "name": "middle name", 1082 | "protocol": "openid-connect", 1083 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1084 | "consentRequired": false, 1085 | "config": { 1086 | "userinfo.token.claim": "true", 1087 | "user.attribute": "middleName", 1088 | "id.token.claim": "true", 1089 | "access.token.claim": "true", 1090 | "claim.name": "middle_name", 1091 | "jsonType.label": "String" 1092 | } 1093 | }, 1094 | { 1095 | "id": "d431227d-6ab3-482c-994e-176629203f6a", 1096 | "name": "updated at", 1097 | "protocol": "openid-connect", 1098 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1099 | "consentRequired": false, 1100 | "config": { 1101 | "userinfo.token.claim": "true", 1102 | "user.attribute": "updatedAt", 1103 | "id.token.claim": "true", 1104 | "access.token.claim": "true", 1105 | "claim.name": "updated_at", 1106 | "jsonType.label": "long" 1107 | } 1108 | }, 1109 | { 1110 | "id": "33b5e400-0acb-4e6a-ad90-aaeee91fbaae", 1111 | "name": "nickname", 1112 | "protocol": "openid-connect", 1113 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1114 | "consentRequired": false, 1115 | "config": { 1116 | "userinfo.token.claim": "true", 1117 | "user.attribute": "nickname", 1118 | "id.token.claim": "true", 1119 | "access.token.claim": "true", 1120 | "claim.name": "nickname", 1121 | "jsonType.label": "String" 1122 | } 1123 | }, 1124 | { 1125 | "id": "1dfddfcd-00bb-4605-99fa-9c13d5482978", 1126 | "name": "picture", 1127 | "protocol": "openid-connect", 1128 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1129 | "consentRequired": false, 1130 | "config": { 1131 | "userinfo.token.claim": "true", 1132 | "user.attribute": "picture", 1133 | "id.token.claim": "true", 1134 | "access.token.claim": "true", 1135 | "claim.name": "picture", 1136 | "jsonType.label": "String" 1137 | } 1138 | }, 1139 | { 1140 | "id": "5aac5a7e-22bb-47c9-a819-c584f8c37a0e", 1141 | "name": "birthdate", 1142 | "protocol": "openid-connect", 1143 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1144 | "consentRequired": false, 1145 | "config": { 1146 | "userinfo.token.claim": "true", 1147 | "user.attribute": "birthdate", 1148 | "id.token.claim": "true", 1149 | "access.token.claim": "true", 1150 | "claim.name": "birthdate", 1151 | "jsonType.label": "String" 1152 | } 1153 | }, 1154 | { 1155 | "id": "5eb920da-da7f-4b62-817a-c4a268a0cb9e", 1156 | "name": "family name", 1157 | "protocol": "openid-connect", 1158 | "protocolMapper": "oidc-usermodel-property-mapper", 1159 | "consentRequired": false, 1160 | "config": { 1161 | "userinfo.token.claim": "true", 1162 | "user.attribute": "lastName", 1163 | "id.token.claim": "true", 1164 | "access.token.claim": "true", 1165 | "claim.name": "family_name", 1166 | "jsonType.label": "String" 1167 | } 1168 | }, 1169 | { 1170 | "id": "7a91ee27-dcda-42fb-82f6-ae9543dcc6f2", 1171 | "name": "given name", 1172 | "protocol": "openid-connect", 1173 | "protocolMapper": "oidc-usermodel-property-mapper", 1174 | "consentRequired": false, 1175 | "config": { 1176 | "userinfo.token.claim": "true", 1177 | "user.attribute": "firstName", 1178 | "id.token.claim": "true", 1179 | "access.token.claim": "true", 1180 | "claim.name": "given_name", 1181 | "jsonType.label": "String" 1182 | } 1183 | }, 1184 | { 1185 | "id": "8a0405fc-7eb7-42e6-941d-d4f87582fc44", 1186 | "name": "username", 1187 | "protocol": "openid-connect", 1188 | "protocolMapper": "oidc-usermodel-property-mapper", 1189 | "consentRequired": false, 1190 | "config": { 1191 | "userinfo.token.claim": "true", 1192 | "user.attribute": "username", 1193 | "id.token.claim": "true", 1194 | "access.token.claim": "true", 1195 | "claim.name": "preferred_username", 1196 | "jsonType.label": "String" 1197 | } 1198 | }, 1199 | { 1200 | "id": "368b184c-953e-444e-9de2-22a4a66cddfb", 1201 | "name": "zoneinfo", 1202 | "protocol": "openid-connect", 1203 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1204 | "consentRequired": false, 1205 | "config": { 1206 | "userinfo.token.claim": "true", 1207 | "user.attribute": "zoneinfo", 1208 | "id.token.claim": "true", 1209 | "access.token.claim": "true", 1210 | "claim.name": "zoneinfo", 1211 | "jsonType.label": "String" 1212 | } 1213 | }, 1214 | { 1215 | "id": "27cd63a0-2da7-49a1-87ff-ac5ad9773432", 1216 | "name": "website", 1217 | "protocol": "openid-connect", 1218 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1219 | "consentRequired": false, 1220 | "config": { 1221 | "userinfo.token.claim": "true", 1222 | "user.attribute": "website", 1223 | "id.token.claim": "true", 1224 | "access.token.claim": "true", 1225 | "claim.name": "website", 1226 | "jsonType.label": "String" 1227 | } 1228 | }, 1229 | { 1230 | "id": "df94f437-70c4-481c-a825-0ad78ef38100", 1231 | "name": "full name", 1232 | "protocol": "openid-connect", 1233 | "protocolMapper": "oidc-full-name-mapper", 1234 | "consentRequired": false, 1235 | "config": { 1236 | "id.token.claim": "true", 1237 | "access.token.claim": "true", 1238 | "userinfo.token.claim": "true" 1239 | } 1240 | }, 1241 | { 1242 | "id": "41744cb0-c9bc-418f-a3f3-8b18bbae456f", 1243 | "name": "gender", 1244 | "protocol": "openid-connect", 1245 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1246 | "consentRequired": false, 1247 | "config": { 1248 | "userinfo.token.claim": "true", 1249 | "user.attribute": "gender", 1250 | "id.token.claim": "true", 1251 | "access.token.claim": "true", 1252 | "claim.name": "gender", 1253 | "jsonType.label": "String" 1254 | } 1255 | } 1256 | ] 1257 | }, 1258 | { 1259 | "id": "6619024c-88d9-40cc-b823-f7b32c06b679", 1260 | "name": "address", 1261 | "description": "OpenID Connect built-in scope: address", 1262 | "protocol": "openid-connect", 1263 | "attributes": { 1264 | "include.in.token.scope": "true", 1265 | "display.on.consent.screen": "true", 1266 | "consent.screen.text": "${addressScopeConsentText}" 1267 | }, 1268 | "protocolMappers": [ 1269 | { 1270 | "id": "432d5a4d-aef5-4671-b065-17ecdd7f631c", 1271 | "name": "address", 1272 | "protocol": "openid-connect", 1273 | "protocolMapper": "oidc-address-mapper", 1274 | "consentRequired": false, 1275 | "config": { 1276 | "user.attribute.formatted": "formatted", 1277 | "user.attribute.country": "country", 1278 | "user.attribute.postal_code": "postal_code", 1279 | "userinfo.token.claim": "true", 1280 | "user.attribute.street": "street", 1281 | "id.token.claim": "true", 1282 | "user.attribute.region": "region", 1283 | "access.token.claim": "true", 1284 | "user.attribute.locality": "locality" 1285 | } 1286 | } 1287 | ] 1288 | }, 1289 | { 1290 | "id": "e4d2ab19-b43b-424b-917c-bc0f4042ea42", 1291 | "name": "role_list", 1292 | "description": "SAML role list", 1293 | "protocol": "saml", 1294 | "attributes": { 1295 | "consent.screen.text": "${samlRoleListScopeConsentText}", 1296 | "display.on.consent.screen": "true" 1297 | }, 1298 | "protocolMappers": [ 1299 | { 1300 | "id": "10852512-97f1-4f21-914f-395f5ff3bcce", 1301 | "name": "role list", 1302 | "protocol": "saml", 1303 | "protocolMapper": "saml-role-list-mapper", 1304 | "consentRequired": false, 1305 | "config": { 1306 | "single": "false", 1307 | "attribute.nameformat": "Basic", 1308 | "attribute.name": "Role" 1309 | } 1310 | } 1311 | ] 1312 | }, 1313 | { 1314 | "id": "bf4b2970-ad3a-4ec3-9cd7-8826547d7f6d", 1315 | "name": "email", 1316 | "description": "OpenID Connect built-in scope: email", 1317 | "protocol": "openid-connect", 1318 | "attributes": { 1319 | "include.in.token.scope": "true", 1320 | "display.on.consent.screen": "true", 1321 | "consent.screen.text": "${emailScopeConsentText}" 1322 | }, 1323 | "protocolMappers": [ 1324 | { 1325 | "id": "88087ccb-faa0-484e-8857-f88a013b7967", 1326 | "name": "email", 1327 | "protocol": "openid-connect", 1328 | "protocolMapper": "oidc-usermodel-property-mapper", 1329 | "consentRequired": false, 1330 | "config": { 1331 | "userinfo.token.claim": "true", 1332 | "user.attribute": "email", 1333 | "id.token.claim": "true", 1334 | "access.token.claim": "true", 1335 | "claim.name": "email", 1336 | "jsonType.label": "String" 1337 | } 1338 | }, 1339 | { 1340 | "id": "d5e7f8dd-e4c8-4781-b5dd-66c0163b4500", 1341 | "name": "email verified", 1342 | "protocol": "openid-connect", 1343 | "protocolMapper": "oidc-usermodel-property-mapper", 1344 | "consentRequired": false, 1345 | "config": { 1346 | "userinfo.token.claim": "true", 1347 | "user.attribute": "emailVerified", 1348 | "id.token.claim": "true", 1349 | "access.token.claim": "true", 1350 | "claim.name": "email_verified", 1351 | "jsonType.label": "boolean" 1352 | } 1353 | } 1354 | ] 1355 | }, 1356 | { 1357 | "id": "cc897694-1d33-464b-b4fc-6ca42d790b71", 1358 | "name": "phone", 1359 | "description": "OpenID Connect built-in scope: phone", 1360 | "protocol": "openid-connect", 1361 | "attributes": { 1362 | "include.in.token.scope": "true", 1363 | "display.on.consent.screen": "true", 1364 | "consent.screen.text": "${phoneScopeConsentText}" 1365 | }, 1366 | "protocolMappers": [ 1367 | { 1368 | "id": "4c705c45-7ab8-42d7-8ee2-c737c729a83b", 1369 | "name": "phone number", 1370 | "protocol": "openid-connect", 1371 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1372 | "consentRequired": false, 1373 | "config": { 1374 | "userinfo.token.claim": "true", 1375 | "user.attribute": "phoneNumber", 1376 | "id.token.claim": "true", 1377 | "access.token.claim": "true", 1378 | "claim.name": "phone_number", 1379 | "jsonType.label": "String" 1380 | } 1381 | }, 1382 | { 1383 | "id": "4a16a7ac-c15a-414b-b415-99c6612ea513", 1384 | "name": "phone number verified", 1385 | "protocol": "openid-connect", 1386 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1387 | "consentRequired": false, 1388 | "config": { 1389 | "userinfo.token.claim": "true", 1390 | "user.attribute": "phoneNumberVerified", 1391 | "id.token.claim": "true", 1392 | "access.token.claim": "true", 1393 | "claim.name": "phone_number_verified", 1394 | "jsonType.label": "boolean" 1395 | } 1396 | } 1397 | ] 1398 | }, 1399 | { 1400 | "id": "2506cd1f-5376-4a53-9956-43215518f70e", 1401 | "name": "offline_access", 1402 | "description": "OpenID Connect built-in scope: offline_access", 1403 | "protocol": "openid-connect", 1404 | "attributes": { 1405 | "consent.screen.text": "${offlineAccessScopeConsentText}", 1406 | "display.on.consent.screen": "true" 1407 | } 1408 | }, 1409 | { 1410 | "id": "a2a1eafa-357a-4f7c-9fee-eb886b989592", 1411 | "name": "roles", 1412 | "description": "OpenID Connect scope for add user roles to the access token", 1413 | "protocol": "openid-connect", 1414 | "attributes": { 1415 | "include.in.token.scope": "false", 1416 | "display.on.consent.screen": "true", 1417 | "consent.screen.text": "${rolesScopeConsentText}" 1418 | }, 1419 | "protocolMappers": [ 1420 | { 1421 | "id": "83fb5bf8-ecb3-4e19-8de2-2a252fd24c42", 1422 | "name": "audience resolve", 1423 | "protocol": "openid-connect", 1424 | "protocolMapper": "oidc-audience-resolve-mapper", 1425 | "consentRequired": false, 1426 | "config": {} 1427 | }, 1428 | { 1429 | "id": "9ca3e197-4228-478f-a387-40da3d074f50", 1430 | "name": "realm roles", 1431 | "protocol": "openid-connect", 1432 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 1433 | "consentRequired": false, 1434 | "config": { 1435 | "user.attribute": "foo", 1436 | "access.token.claim": "true", 1437 | "claim.name": "realm_access.roles", 1438 | "jsonType.label": "String", 1439 | "multivalued": "true" 1440 | } 1441 | }, 1442 | { 1443 | "id": "1d5dd0d3-fb1f-4a31-ab6f-a42838900704", 1444 | "name": "client roles", 1445 | "protocol": "openid-connect", 1446 | "protocolMapper": "oidc-usermodel-client-role-mapper", 1447 | "consentRequired": false, 1448 | "config": { 1449 | "user.attribute": "foo", 1450 | "access.token.claim": "true", 1451 | "claim.name": "resource_access.${client_id}.roles", 1452 | "jsonType.label": "String", 1453 | "multivalued": "true" 1454 | } 1455 | } 1456 | ] 1457 | } 1458 | ], 1459 | "defaultDefaultClientScopes": [ 1460 | "role_list", 1461 | "profile", 1462 | "email", 1463 | "roles", 1464 | "web-origins", 1465 | "acr" 1466 | ], 1467 | "defaultOptionalClientScopes": [ 1468 | "offline_access", 1469 | "address", 1470 | "phone", 1471 | "microprofile-jwt" 1472 | ], 1473 | "browserSecurityHeaders": { 1474 | "contentSecurityPolicyReportOnly": "", 1475 | "xContentTypeOptions": "nosniff", 1476 | "xRobotsTag": "none", 1477 | "xFrameOptions": "SAMEORIGIN", 1478 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", 1479 | "xXSSProtection": "1; mode=block", 1480 | "strictTransportSecurity": "max-age=31536000; includeSubDomains" 1481 | }, 1482 | "smtpServer": {}, 1483 | "eventsEnabled": false, 1484 | "eventsListeners": [ 1485 | "jboss-logging" 1486 | ], 1487 | "enabledEventTypes": [], 1488 | "adminEventsEnabled": false, 1489 | "adminEventsDetailsEnabled": false, 1490 | "identityProviders": [], 1491 | "identityProviderMappers": [], 1492 | "components": { 1493 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ 1494 | { 1495 | "id": "fd24473a-a8bf-491d-84be-44653e8b662b", 1496 | "name": "Allowed Client Scopes", 1497 | "providerId": "allowed-client-templates", 1498 | "subType": "anonymous", 1499 | "subComponents": {}, 1500 | "config": { 1501 | "allow-default-scopes": [ 1502 | "true" 1503 | ] 1504 | } 1505 | }, 1506 | { 1507 | "id": "a9ebe93a-c285-4fd2-92a4-d71d5ad7802b", 1508 | "name": "Max Clients Limit", 1509 | "providerId": "max-clients", 1510 | "subType": "anonymous", 1511 | "subComponents": {}, 1512 | "config": { 1513 | "max-clients": [ 1514 | "200" 1515 | ] 1516 | } 1517 | }, 1518 | { 1519 | "id": "7557c775-512d-44ee-942c-b7d2d80f4c46", 1520 | "name": "Consent Required", 1521 | "providerId": "consent-required", 1522 | "subType": "anonymous", 1523 | "subComponents": {}, 1524 | "config": {} 1525 | }, 1526 | { 1527 | "id": "2c74c2d3-2981-4906-8da0-ecffe8d7bae8", 1528 | "name": "Allowed Protocol Mapper Types", 1529 | "providerId": "allowed-protocol-mappers", 1530 | "subType": "anonymous", 1531 | "subComponents": {}, 1532 | "config": { 1533 | "allowed-protocol-mapper-types": [ 1534 | "oidc-address-mapper", 1535 | "oidc-usermodel-attribute-mapper", 1536 | "oidc-full-name-mapper", 1537 | "saml-user-property-mapper", 1538 | "oidc-usermodel-property-mapper", 1539 | "saml-role-list-mapper", 1540 | "saml-user-attribute-mapper", 1541 | "oidc-sha256-pairwise-sub-mapper" 1542 | ] 1543 | } 1544 | }, 1545 | { 1546 | "id": "46a36b5e-3978-4bb6-8dfc-d260e7437ad8", 1547 | "name": "Full Scope Disabled", 1548 | "providerId": "scope", 1549 | "subType": "anonymous", 1550 | "subComponents": {}, 1551 | "config": {} 1552 | }, 1553 | { 1554 | "id": "5327a1b3-cfcd-467c-ba64-fd2f656a431d", 1555 | "name": "Allowed Protocol Mapper Types", 1556 | "providerId": "allowed-protocol-mappers", 1557 | "subType": "authenticated", 1558 | "subComponents": {}, 1559 | "config": { 1560 | "allowed-protocol-mapper-types": [ 1561 | "oidc-usermodel-attribute-mapper", 1562 | "saml-user-property-mapper", 1563 | "oidc-sha256-pairwise-sub-mapper", 1564 | "oidc-full-name-mapper", 1565 | "oidc-address-mapper", 1566 | "oidc-usermodel-property-mapper", 1567 | "saml-user-attribute-mapper", 1568 | "saml-role-list-mapper" 1569 | ] 1570 | } 1571 | }, 1572 | { 1573 | "id": "33b5aa2d-48c4-4bfa-8230-1f2612d64260", 1574 | "name": "Trusted Hosts", 1575 | "providerId": "trusted-hosts", 1576 | "subType": "anonymous", 1577 | "subComponents": {}, 1578 | "config": { 1579 | "host-sending-registration-request-must-match": [ 1580 | "true" 1581 | ], 1582 | "client-uris-must-match": [ 1583 | "true" 1584 | ] 1585 | } 1586 | }, 1587 | { 1588 | "id": "89adcfef-c031-4fdb-9dc4-420a1a3c095e", 1589 | "name": "Allowed Client Scopes", 1590 | "providerId": "allowed-client-templates", 1591 | "subType": "authenticated", 1592 | "subComponents": {}, 1593 | "config": { 1594 | "allow-default-scopes": [ 1595 | "true" 1596 | ] 1597 | } 1598 | } 1599 | ], 1600 | "org.keycloak.keys.KeyProvider": [ 1601 | { 1602 | "id": "79f5abc5-da7b-46c5-a933-cf8873363cc0", 1603 | "name": "rsa-enc-generated", 1604 | "providerId": "rsa-enc-generated", 1605 | "subComponents": {}, 1606 | "config": { 1607 | "priority": [ 1608 | "100" 1609 | ], 1610 | "algorithm": [ 1611 | "RSA-OAEP" 1612 | ] 1613 | } 1614 | }, 1615 | { 1616 | "id": "2798d0f4-980f-49ea-a194-415082a77c59", 1617 | "name": "aes-generated", 1618 | "providerId": "aes-generated", 1619 | "subComponents": {}, 1620 | "config": { 1621 | "priority": [ 1622 | "100" 1623 | ] 1624 | } 1625 | }, 1626 | { 1627 | "id": "0f23de01-fbfa-40c4-850a-ebbf60ba7aa9", 1628 | "name": "rsa-generated", 1629 | "providerId": "rsa-generated", 1630 | "subComponents": {}, 1631 | "config": { 1632 | "priority": [ 1633 | "100" 1634 | ] 1635 | } 1636 | }, 1637 | { 1638 | "id": "6e5061b3-51cb-4317-9697-f221e27ca24a", 1639 | "name": "hmac-generated", 1640 | "providerId": "hmac-generated", 1641 | "subComponents": {}, 1642 | "config": { 1643 | "priority": [ 1644 | "100" 1645 | ], 1646 | "algorithm": [ 1647 | "HS256" 1648 | ] 1649 | } 1650 | } 1651 | ] 1652 | }, 1653 | "internationalizationEnabled": false, 1654 | "supportedLocales": [], 1655 | "authenticationFlows": [ 1656 | { 1657 | "id": "1f68e228-75ab-4d43-b708-f79dcafd85f4", 1658 | "alias": "Account verification options", 1659 | "description": "Method with which to verity the existing account", 1660 | "providerId": "basic-flow", 1661 | "topLevel": false, 1662 | "builtIn": true, 1663 | "authenticationExecutions": [ 1664 | { 1665 | "authenticator": "idp-email-verification", 1666 | "authenticatorFlow": false, 1667 | "requirement": "ALTERNATIVE", 1668 | "priority": 10, 1669 | "autheticatorFlow": false, 1670 | "userSetupAllowed": false 1671 | }, 1672 | { 1673 | "authenticatorFlow": true, 1674 | "requirement": "ALTERNATIVE", 1675 | "priority": 20, 1676 | "autheticatorFlow": true, 1677 | "flowAlias": "Verify Existing Account by Re-authentication", 1678 | "userSetupAllowed": false 1679 | } 1680 | ] 1681 | }, 1682 | { 1683 | "id": "345f34ce-08fd-4d3a-a547-d077be5a79f3", 1684 | "alias": "Authentication Options", 1685 | "description": "Authentication options.", 1686 | "providerId": "basic-flow", 1687 | "topLevel": false, 1688 | "builtIn": true, 1689 | "authenticationExecutions": [ 1690 | { 1691 | "authenticator": "basic-auth", 1692 | "authenticatorFlow": false, 1693 | "requirement": "REQUIRED", 1694 | "priority": 10, 1695 | "autheticatorFlow": false, 1696 | "userSetupAllowed": false 1697 | }, 1698 | { 1699 | "authenticator": "basic-auth-otp", 1700 | "authenticatorFlow": false, 1701 | "requirement": "DISABLED", 1702 | "priority": 20, 1703 | "autheticatorFlow": false, 1704 | "userSetupAllowed": false 1705 | }, 1706 | { 1707 | "authenticator": "auth-spnego", 1708 | "authenticatorFlow": false, 1709 | "requirement": "DISABLED", 1710 | "priority": 30, 1711 | "autheticatorFlow": false, 1712 | "userSetupAllowed": false 1713 | } 1714 | ] 1715 | }, 1716 | { 1717 | "id": "b0b19532-7a70-41ed-a661-415c6a5d2d57", 1718 | "alias": "Browser - Conditional OTP", 1719 | "description": "Flow to determine if the OTP is required for the authentication", 1720 | "providerId": "basic-flow", 1721 | "topLevel": false, 1722 | "builtIn": true, 1723 | "authenticationExecutions": [ 1724 | { 1725 | "authenticator": "conditional-user-configured", 1726 | "authenticatorFlow": false, 1727 | "requirement": "REQUIRED", 1728 | "priority": 10, 1729 | "autheticatorFlow": false, 1730 | "userSetupAllowed": false 1731 | }, 1732 | { 1733 | "authenticator": "auth-otp-form", 1734 | "authenticatorFlow": false, 1735 | "requirement": "REQUIRED", 1736 | "priority": 20, 1737 | "autheticatorFlow": false, 1738 | "userSetupAllowed": false 1739 | } 1740 | ] 1741 | }, 1742 | { 1743 | "id": "3372899b-1523-4f7d-9ab3-e75fb794bca1", 1744 | "alias": "Direct Grant - Conditional OTP", 1745 | "description": "Flow to determine if the OTP is required for the authentication", 1746 | "providerId": "basic-flow", 1747 | "topLevel": false, 1748 | "builtIn": true, 1749 | "authenticationExecutions": [ 1750 | { 1751 | "authenticator": "conditional-user-configured", 1752 | "authenticatorFlow": false, 1753 | "requirement": "REQUIRED", 1754 | "priority": 10, 1755 | "autheticatorFlow": false, 1756 | "userSetupAllowed": false 1757 | }, 1758 | { 1759 | "authenticator": "direct-grant-validate-otp", 1760 | "authenticatorFlow": false, 1761 | "requirement": "REQUIRED", 1762 | "priority": 20, 1763 | "autheticatorFlow": false, 1764 | "userSetupAllowed": false 1765 | } 1766 | ] 1767 | }, 1768 | { 1769 | "id": "7c094924-4ca8-4c0b-ad51-00ff602c50b5", 1770 | "alias": "First broker login - Conditional OTP", 1771 | "description": "Flow to determine if the OTP is required for the authentication", 1772 | "providerId": "basic-flow", 1773 | "topLevel": false, 1774 | "builtIn": true, 1775 | "authenticationExecutions": [ 1776 | { 1777 | "authenticator": "conditional-user-configured", 1778 | "authenticatorFlow": false, 1779 | "requirement": "REQUIRED", 1780 | "priority": 10, 1781 | "autheticatorFlow": false, 1782 | "userSetupAllowed": false 1783 | }, 1784 | { 1785 | "authenticator": "auth-otp-form", 1786 | "authenticatorFlow": false, 1787 | "requirement": "REQUIRED", 1788 | "priority": 20, 1789 | "autheticatorFlow": false, 1790 | "userSetupAllowed": false 1791 | } 1792 | ] 1793 | }, 1794 | { 1795 | "id": "50cf3e25-4049-4c02-a24a-cc958f93812b", 1796 | "alias": "Handle Existing Account", 1797 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", 1798 | "providerId": "basic-flow", 1799 | "topLevel": false, 1800 | "builtIn": true, 1801 | "authenticationExecutions": [ 1802 | { 1803 | "authenticator": "idp-confirm-link", 1804 | "authenticatorFlow": false, 1805 | "requirement": "REQUIRED", 1806 | "priority": 10, 1807 | "autheticatorFlow": false, 1808 | "userSetupAllowed": false 1809 | }, 1810 | { 1811 | "authenticatorFlow": true, 1812 | "requirement": "REQUIRED", 1813 | "priority": 20, 1814 | "autheticatorFlow": true, 1815 | "flowAlias": "Account verification options", 1816 | "userSetupAllowed": false 1817 | } 1818 | ] 1819 | }, 1820 | { 1821 | "id": "061a239d-db17-41b0-9c71-19cbc5255047", 1822 | "alias": "Reset - Conditional OTP", 1823 | "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", 1824 | "providerId": "basic-flow", 1825 | "topLevel": false, 1826 | "builtIn": true, 1827 | "authenticationExecutions": [ 1828 | { 1829 | "authenticator": "conditional-user-configured", 1830 | "authenticatorFlow": false, 1831 | "requirement": "REQUIRED", 1832 | "priority": 10, 1833 | "autheticatorFlow": false, 1834 | "userSetupAllowed": false 1835 | }, 1836 | { 1837 | "authenticator": "reset-otp", 1838 | "authenticatorFlow": false, 1839 | "requirement": "REQUIRED", 1840 | "priority": 20, 1841 | "autheticatorFlow": false, 1842 | "userSetupAllowed": false 1843 | } 1844 | ] 1845 | }, 1846 | { 1847 | "id": "46f07489-bdca-46cc-b624-218b706db60e", 1848 | "alias": "User creation or linking", 1849 | "description": "Flow for the existing/non-existing user alternatives", 1850 | "providerId": "basic-flow", 1851 | "topLevel": false, 1852 | "builtIn": true, 1853 | "authenticationExecutions": [ 1854 | { 1855 | "authenticatorConfig": "create unique user config", 1856 | "authenticator": "idp-create-user-if-unique", 1857 | "authenticatorFlow": false, 1858 | "requirement": "ALTERNATIVE", 1859 | "priority": 10, 1860 | "autheticatorFlow": false, 1861 | "userSetupAllowed": false 1862 | }, 1863 | { 1864 | "authenticatorFlow": true, 1865 | "requirement": "ALTERNATIVE", 1866 | "priority": 20, 1867 | "autheticatorFlow": true, 1868 | "flowAlias": "Handle Existing Account", 1869 | "userSetupAllowed": false 1870 | } 1871 | ] 1872 | }, 1873 | { 1874 | "id": "2eda9796-f4eb-4963-9f65-b02f3a1743ec", 1875 | "alias": "Verify Existing Account by Re-authentication", 1876 | "description": "Reauthentication of existing account", 1877 | "providerId": "basic-flow", 1878 | "topLevel": false, 1879 | "builtIn": true, 1880 | "authenticationExecutions": [ 1881 | { 1882 | "authenticator": "idp-username-password-form", 1883 | "authenticatorFlow": false, 1884 | "requirement": "REQUIRED", 1885 | "priority": 10, 1886 | "autheticatorFlow": false, 1887 | "userSetupAllowed": false 1888 | }, 1889 | { 1890 | "authenticatorFlow": true, 1891 | "requirement": "CONDITIONAL", 1892 | "priority": 20, 1893 | "autheticatorFlow": true, 1894 | "flowAlias": "First broker login - Conditional OTP", 1895 | "userSetupAllowed": false 1896 | } 1897 | ] 1898 | }, 1899 | { 1900 | "id": "e3e47cdb-e6e7-4a0d-b3f4-54b1a192679c", 1901 | "alias": "browser", 1902 | "description": "browser based authentication", 1903 | "providerId": "basic-flow", 1904 | "topLevel": true, 1905 | "builtIn": true, 1906 | "authenticationExecutions": [ 1907 | { 1908 | "authenticator": "auth-cookie", 1909 | "authenticatorFlow": false, 1910 | "requirement": "ALTERNATIVE", 1911 | "priority": 10, 1912 | "autheticatorFlow": false, 1913 | "userSetupAllowed": false 1914 | }, 1915 | { 1916 | "authenticator": "auth-spnego", 1917 | "authenticatorFlow": false, 1918 | "requirement": "DISABLED", 1919 | "priority": 20, 1920 | "autheticatorFlow": false, 1921 | "userSetupAllowed": false 1922 | }, 1923 | { 1924 | "authenticator": "identity-provider-redirector", 1925 | "authenticatorFlow": false, 1926 | "requirement": "ALTERNATIVE", 1927 | "priority": 25, 1928 | "autheticatorFlow": false, 1929 | "userSetupAllowed": false 1930 | }, 1931 | { 1932 | "authenticatorFlow": true, 1933 | "requirement": "ALTERNATIVE", 1934 | "priority": 30, 1935 | "autheticatorFlow": true, 1936 | "flowAlias": "forms", 1937 | "userSetupAllowed": false 1938 | } 1939 | ] 1940 | }, 1941 | { 1942 | "id": "a06e4990-f3e2-4e6c-a0d6-81ddf5d9a40e", 1943 | "alias": "clients", 1944 | "description": "Base authentication for clients", 1945 | "providerId": "client-flow", 1946 | "topLevel": true, 1947 | "builtIn": true, 1948 | "authenticationExecutions": [ 1949 | { 1950 | "authenticator": "client-secret", 1951 | "authenticatorFlow": false, 1952 | "requirement": "ALTERNATIVE", 1953 | "priority": 10, 1954 | "autheticatorFlow": false, 1955 | "userSetupAllowed": false 1956 | }, 1957 | { 1958 | "authenticator": "client-jwt", 1959 | "authenticatorFlow": false, 1960 | "requirement": "ALTERNATIVE", 1961 | "priority": 20, 1962 | "autheticatorFlow": false, 1963 | "userSetupAllowed": false 1964 | }, 1965 | { 1966 | "authenticator": "client-secret-jwt", 1967 | "authenticatorFlow": false, 1968 | "requirement": "ALTERNATIVE", 1969 | "priority": 30, 1970 | "autheticatorFlow": false, 1971 | "userSetupAllowed": false 1972 | }, 1973 | { 1974 | "authenticator": "client-x509", 1975 | "authenticatorFlow": false, 1976 | "requirement": "ALTERNATIVE", 1977 | "priority": 40, 1978 | "autheticatorFlow": false, 1979 | "userSetupAllowed": false 1980 | } 1981 | ] 1982 | }, 1983 | { 1984 | "id": "083641b6-72a2-495c-8eb8-efd88b41a4de", 1985 | "alias": "direct grant", 1986 | "description": "OpenID Connect Resource Owner Grant", 1987 | "providerId": "basic-flow", 1988 | "topLevel": true, 1989 | "builtIn": true, 1990 | "authenticationExecutions": [ 1991 | { 1992 | "authenticator": "direct-grant-validate-username", 1993 | "authenticatorFlow": false, 1994 | "requirement": "REQUIRED", 1995 | "priority": 10, 1996 | "autheticatorFlow": false, 1997 | "userSetupAllowed": false 1998 | }, 1999 | { 2000 | "authenticator": "direct-grant-validate-password", 2001 | "authenticatorFlow": false, 2002 | "requirement": "REQUIRED", 2003 | "priority": 20, 2004 | "autheticatorFlow": false, 2005 | "userSetupAllowed": false 2006 | }, 2007 | { 2008 | "authenticatorFlow": true, 2009 | "requirement": "CONDITIONAL", 2010 | "priority": 30, 2011 | "autheticatorFlow": true, 2012 | "flowAlias": "Direct Grant - Conditional OTP", 2013 | "userSetupAllowed": false 2014 | } 2015 | ] 2016 | }, 2017 | { 2018 | "id": "e16fbff2-02b3-4975-9e19-4739b990f052", 2019 | "alias": "docker auth", 2020 | "description": "Used by Docker clients to authenticate against the IDP", 2021 | "providerId": "basic-flow", 2022 | "topLevel": true, 2023 | "builtIn": true, 2024 | "authenticationExecutions": [ 2025 | { 2026 | "authenticator": "docker-http-basic-authenticator", 2027 | "authenticatorFlow": false, 2028 | "requirement": "REQUIRED", 2029 | "priority": 10, 2030 | "autheticatorFlow": false, 2031 | "userSetupAllowed": false 2032 | } 2033 | ] 2034 | }, 2035 | { 2036 | "id": "cf89ccfd-19df-4f17-b487-7747bc49d95d", 2037 | "alias": "first broker login", 2038 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 2039 | "providerId": "basic-flow", 2040 | "topLevel": true, 2041 | "builtIn": true, 2042 | "authenticationExecutions": [ 2043 | { 2044 | "authenticatorConfig": "review profile config", 2045 | "authenticator": "idp-review-profile", 2046 | "authenticatorFlow": false, 2047 | "requirement": "REQUIRED", 2048 | "priority": 10, 2049 | "autheticatorFlow": false, 2050 | "userSetupAllowed": false 2051 | }, 2052 | { 2053 | "authenticatorFlow": true, 2054 | "requirement": "REQUIRED", 2055 | "priority": 20, 2056 | "autheticatorFlow": true, 2057 | "flowAlias": "User creation or linking", 2058 | "userSetupAllowed": false 2059 | } 2060 | ] 2061 | }, 2062 | { 2063 | "id": "780296a3-679e-4ffe-982b-f173e9bf528e", 2064 | "alias": "forms", 2065 | "description": "Username, password, otp and other auth forms.", 2066 | "providerId": "basic-flow", 2067 | "topLevel": false, 2068 | "builtIn": true, 2069 | "authenticationExecutions": [ 2070 | { 2071 | "authenticator": "auth-username-password-form", 2072 | "authenticatorFlow": false, 2073 | "requirement": "REQUIRED", 2074 | "priority": 10, 2075 | "autheticatorFlow": false, 2076 | "userSetupAllowed": false 2077 | }, 2078 | { 2079 | "authenticatorFlow": true, 2080 | "requirement": "CONDITIONAL", 2081 | "priority": 20, 2082 | "autheticatorFlow": true, 2083 | "flowAlias": "Browser - Conditional OTP", 2084 | "userSetupAllowed": false 2085 | } 2086 | ] 2087 | }, 2088 | { 2089 | "id": "8e771671-6a15-41bf-90c9-8749bfcd7a0a", 2090 | "alias": "http challenge", 2091 | "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", 2092 | "providerId": "basic-flow", 2093 | "topLevel": true, 2094 | "builtIn": true, 2095 | "authenticationExecutions": [ 2096 | { 2097 | "authenticator": "no-cookie-redirect", 2098 | "authenticatorFlow": false, 2099 | "requirement": "REQUIRED", 2100 | "priority": 10, 2101 | "autheticatorFlow": false, 2102 | "userSetupAllowed": false 2103 | }, 2104 | { 2105 | "authenticatorFlow": true, 2106 | "requirement": "REQUIRED", 2107 | "priority": 20, 2108 | "autheticatorFlow": true, 2109 | "flowAlias": "Authentication Options", 2110 | "userSetupAllowed": false 2111 | } 2112 | ] 2113 | }, 2114 | { 2115 | "id": "c9ee220d-8472-48b8-aed7-178d93f5ad11", 2116 | "alias": "registration", 2117 | "description": "registration flow", 2118 | "providerId": "basic-flow", 2119 | "topLevel": true, 2120 | "builtIn": true, 2121 | "authenticationExecutions": [ 2122 | { 2123 | "authenticator": "registration-page-form", 2124 | "authenticatorFlow": true, 2125 | "requirement": "REQUIRED", 2126 | "priority": 10, 2127 | "autheticatorFlow": true, 2128 | "flowAlias": "registration form", 2129 | "userSetupAllowed": false 2130 | } 2131 | ] 2132 | }, 2133 | { 2134 | "id": "73567936-4962-4c13-bb89-4aa5cb506ea9", 2135 | "alias": "registration form", 2136 | "description": "registration form", 2137 | "providerId": "form-flow", 2138 | "topLevel": false, 2139 | "builtIn": true, 2140 | "authenticationExecutions": [ 2141 | { 2142 | "authenticator": "registration-user-creation", 2143 | "authenticatorFlow": false, 2144 | "requirement": "REQUIRED", 2145 | "priority": 20, 2146 | "autheticatorFlow": false, 2147 | "userSetupAllowed": false 2148 | }, 2149 | { 2150 | "authenticator": "registration-profile-action", 2151 | "authenticatorFlow": false, 2152 | "requirement": "REQUIRED", 2153 | "priority": 40, 2154 | "autheticatorFlow": false, 2155 | "userSetupAllowed": false 2156 | }, 2157 | { 2158 | "authenticator": "registration-password-action", 2159 | "authenticatorFlow": false, 2160 | "requirement": "REQUIRED", 2161 | "priority": 50, 2162 | "autheticatorFlow": false, 2163 | "userSetupAllowed": false 2164 | }, 2165 | { 2166 | "authenticator": "registration-recaptcha-action", 2167 | "authenticatorFlow": false, 2168 | "requirement": "DISABLED", 2169 | "priority": 60, 2170 | "autheticatorFlow": false, 2171 | "userSetupAllowed": false 2172 | } 2173 | ] 2174 | }, 2175 | { 2176 | "id": "c861408c-8071-4e70-b9c0-19c9c12e5f9e", 2177 | "alias": "reset credentials", 2178 | "description": "Reset credentials for a user if they forgot their password or something", 2179 | "providerId": "basic-flow", 2180 | "topLevel": true, 2181 | "builtIn": true, 2182 | "authenticationExecutions": [ 2183 | { 2184 | "authenticator": "reset-credentials-choose-user", 2185 | "authenticatorFlow": false, 2186 | "requirement": "REQUIRED", 2187 | "priority": 10, 2188 | "autheticatorFlow": false, 2189 | "userSetupAllowed": false 2190 | }, 2191 | { 2192 | "authenticator": "reset-credential-email", 2193 | "authenticatorFlow": false, 2194 | "requirement": "REQUIRED", 2195 | "priority": 20, 2196 | "autheticatorFlow": false, 2197 | "userSetupAllowed": false 2198 | }, 2199 | { 2200 | "authenticator": "reset-password", 2201 | "authenticatorFlow": false, 2202 | "requirement": "REQUIRED", 2203 | "priority": 30, 2204 | "autheticatorFlow": false, 2205 | "userSetupAllowed": false 2206 | }, 2207 | { 2208 | "authenticatorFlow": true, 2209 | "requirement": "CONDITIONAL", 2210 | "priority": 40, 2211 | "autheticatorFlow": true, 2212 | "flowAlias": "Reset - Conditional OTP", 2213 | "userSetupAllowed": false 2214 | } 2215 | ] 2216 | }, 2217 | { 2218 | "id": "67e24d75-bb36-4643-90e7-8feae070ae94", 2219 | "alias": "saml ecp", 2220 | "description": "SAML ECP Profile Authentication Flow", 2221 | "providerId": "basic-flow", 2222 | "topLevel": true, 2223 | "builtIn": true, 2224 | "authenticationExecutions": [ 2225 | { 2226 | "authenticator": "http-basic-authenticator", 2227 | "authenticatorFlow": false, 2228 | "requirement": "REQUIRED", 2229 | "priority": 10, 2230 | "autheticatorFlow": false, 2231 | "userSetupAllowed": false 2232 | } 2233 | ] 2234 | } 2235 | ], 2236 | "authenticatorConfig": [ 2237 | { 2238 | "id": "9adcfad7-6c41-40d4-8950-ee30f76b92ce", 2239 | "alias": "create unique user config", 2240 | "config": { 2241 | "require.password.update.after.registration": "false" 2242 | } 2243 | }, 2244 | { 2245 | "id": "983ba940-2695-4b52-81fc-13826eeddad1", 2246 | "alias": "review profile config", 2247 | "config": { 2248 | "update.profile.on.first.login": "missing" 2249 | } 2250 | } 2251 | ], 2252 | "requiredActions": [ 2253 | { 2254 | "alias": "CONFIGURE_TOTP", 2255 | "name": "Configure OTP", 2256 | "providerId": "CONFIGURE_TOTP", 2257 | "enabled": true, 2258 | "defaultAction": false, 2259 | "priority": 10, 2260 | "config": {} 2261 | }, 2262 | { 2263 | "alias": "TERMS_AND_CONDITIONS", 2264 | "name": "Terms and Conditions", 2265 | "providerId": "TERMS_AND_CONDITIONS", 2266 | "enabled": false, 2267 | "defaultAction": false, 2268 | "priority": 20, 2269 | "config": {} 2270 | }, 2271 | { 2272 | "alias": "UPDATE_PASSWORD", 2273 | "name": "Update Password", 2274 | "providerId": "UPDATE_PASSWORD", 2275 | "enabled": true, 2276 | "defaultAction": false, 2277 | "priority": 30, 2278 | "config": {} 2279 | }, 2280 | { 2281 | "alias": "UPDATE_PROFILE", 2282 | "name": "Update Profile", 2283 | "providerId": "UPDATE_PROFILE", 2284 | "enabled": true, 2285 | "defaultAction": false, 2286 | "priority": 40, 2287 | "config": {} 2288 | }, 2289 | { 2290 | "alias": "VERIFY_EMAIL", 2291 | "name": "Verify Email", 2292 | "providerId": "VERIFY_EMAIL", 2293 | "enabled": true, 2294 | "defaultAction": false, 2295 | "priority": 50, 2296 | "config": {} 2297 | }, 2298 | { 2299 | "alias": "delete_account", 2300 | "name": "Delete Account", 2301 | "providerId": "delete_account", 2302 | "enabled": false, 2303 | "defaultAction": false, 2304 | "priority": 60, 2305 | "config": {} 2306 | }, 2307 | { 2308 | "alias": "webauthn-register", 2309 | "name": "Webauthn Register", 2310 | "providerId": "webauthn-register", 2311 | "enabled": true, 2312 | "defaultAction": false, 2313 | "priority": 70, 2314 | "config": {} 2315 | }, 2316 | { 2317 | "alias": "webauthn-register-passwordless", 2318 | "name": "Webauthn Register Passwordless", 2319 | "providerId": "webauthn-register-passwordless", 2320 | "enabled": true, 2321 | "defaultAction": false, 2322 | "priority": 80, 2323 | "config": {} 2324 | }, 2325 | { 2326 | "alias": "update_user_locale", 2327 | "name": "Update User Locale", 2328 | "providerId": "update_user_locale", 2329 | "enabled": true, 2330 | "defaultAction": false, 2331 | "priority": 1000, 2332 | "config": {} 2333 | } 2334 | ], 2335 | "browserFlow": "browser", 2336 | "registrationFlow": "registration", 2337 | "directGrantFlow": "direct grant", 2338 | "resetCredentialsFlow": "reset credentials", 2339 | "clientAuthenticationFlow": "clients", 2340 | "dockerAuthenticationFlow": "docker auth", 2341 | "attributes": { 2342 | "cibaBackchannelTokenDeliveryMode": "poll", 2343 | "cibaExpiresIn": "120", 2344 | "cibaAuthRequestedUserHint": "login_hint", 2345 | "oauth2DeviceCodeLifespan": "600", 2346 | "oauth2DevicePollingInterval": "5", 2347 | "parRequestUriLifespan": "60", 2348 | "cibaInterval": "5", 2349 | "realmReusableOtpCode": "false" 2350 | }, 2351 | "keycloakVersion": "21.1.1", 2352 | "userManagedAccessAllowed": false, 2353 | "clientProfiles": { 2354 | "profiles": [] 2355 | }, 2356 | "clientPolicies": { 2357 | "policies": [] 2358 | } 2359 | } 2360 | -------------------------------------------------------------------------------- /docker/postgres/docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | db: 3 | image: postgres 4 | container_name: db_postgres 5 | restart: always 6 | environment: 7 | POSTGRES_USER: admin 8 | POSTGRES_PASSWORD: admin 9 | POSTGRES_DB: db_dummy 10 | ports: 11 | - "5432:5432" 12 | # volumes: 13 | # - ./init.sql:/docker-entrypoint-initdb.d/init.sql 14 | 15 | -------------------------------------------------------------------------------- /docker/postgres/init.sql: -------------------------------------------------------------------------------- 1 | create table dummies ( 2 | id integer primary key, 3 | info varchar(255) 4 | ); 5 | -------------------------------------------------------------------------------- /k8s/java-deployment-svc.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: java-spring3-microservice 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: java-spring3-microservice 10 | template: 11 | metadata: 12 | labels: 13 | app: java-spring3-microservice 14 | spec: 15 | containers: 16 | - name: java-spring3-microservice 17 | image: java-spring3-microservice:0.1.0 18 | imagePullPolicy: IfNotPresent 19 | ports: 20 | - containerPort: 8080 21 | env: 22 | - name: DB_HOST 23 | value: postgresql 24 | - name: DB_NAME 25 | value: db_dummy 26 | - name: DB_USER 27 | value: admin 28 | - name: DB_PASSWORD 29 | value: admin 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: java-spring3-microservice-svc 35 | spec: 36 | selector: 37 | app: java-spring3-microservice 38 | ports: 39 | - protocol: TCP 40 | port: 8080 41 | targetPort: 8080 42 | type: NodePort 43 | 44 | -------------------------------------------------------------------------------- /k8s/postgres-deployment-svc.yml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: StatefulSet 3 | metadata: 4 | name: postgresql 5 | spec: 6 | serviceName: postgresql 7 | replicas: 1 8 | selector: 9 | matchLabels: 10 | app: postgresql 11 | template: 12 | metadata: 13 | labels: 14 | app: postgresql 15 | spec: 16 | containers: 17 | - name: postgresql 18 | image: postgres:latest 19 | env: 20 | - name: POSTGRES_DB 21 | value: db_dummy 22 | - name: POSTGRES_USER 23 | value: admin 24 | - name: POSTGRES_PASSWORD 25 | value: admin 26 | ports: 27 | - containerPort: 5432 28 | name: postgresql 29 | 30 | --- 31 | apiVersion: v1 32 | kind: Service 33 | metadata: 34 | name: postgresql 35 | spec: 36 | selector: 37 | app: postgresql 38 | ports: 39 | - protocol: TCP 40 | port: 5432 41 | targetPort: 5432 42 | 43 | --- 44 | apiVersion: v1 45 | kind: PersistentVolumeClaim 46 | metadata: 47 | name: postgresql-pvc 48 | spec: 49 | accessModes: 50 | - ReadWriteOnce 51 | resources: 52 | requests: 53 | storage: 1Gi 54 | 55 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 3.2.8 9 | 10 | 11 | 12 | com.javi 13 | microservice 14 | 0.0.1 15 | jar 16 | java-spring3-microservice 17 | Java archetype oriented to Microservices 18 | 19 | 20 | 21 21 | 0.9.28 22 | 0.0.1 23 | 24 | 25 | 26 | 27 | 28 | com.javi 29 | microservice-lib 30 | ${microservice-lib.version} 31 | 32 | 33 | 34 | 35 | org.projectlombok 36 | lombok 37 | 38 | 39 | 40 | 41 | org.springframework.boot 42 | spring-boot-starter-web 43 | 44 | 45 | org.springframework.boot 46 | spring-boot-starter-data-jpa 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-security 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-actuator 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-devtools 59 | runtime 60 | true 61 | 62 | 63 | 64 | 65 | org.postgresql 66 | postgresql 67 | runtime 68 | 69 | 70 | 71 | 72 | org.springframework.boot 73 | spring-boot-starter-test 74 | test 75 | 76 | 77 | org.junit.jupiter 78 | junit-jupiter-engine 79 | test 80 | 81 | 82 | org.mockito 83 | mockito-junit-jupiter 84 | test 85 | 86 | 87 | com.h2database 88 | h2 89 | test 90 | 91 | 92 | 93 | 94 | 95 | 96 | dev 97 | 98 | dev 99 | 100 | 101 | 102 | 103 | 104 | ${project.artifactId} 105 | 106 | 107 | org.springframework.boot 108 | spring-boot-maven-plugin 109 | 110 | 111 | org.graalvm.buildtools 112 | graalvm-native-maven-plugin 113 | ${graalvm.version} 114 | 115 | 116 | 117 | 118 | 119 | -------------------------------------------------------------------------------- /ships/get.ship: -------------------------------------------------------------------------------- 1 | 2 | ~[BASE]~ 3 | url http://localhost:8080/app/dummy 4 | method GET 5 | 6 | ~[HEADERS]~ 7 | accept application/json 8 | authorization Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJVktibk93SjNPUlYyVDloWGlqVl9oVXJfX3Y4ZEVIdkxyalgzXzhiZ29ZIn0.eyJleHAiOjE3MjUwNTIyMTUsImlhdCI6MTcyNTA1MTkxNSwianRpIjoiZTg3YjYwMDAtZTk2OC00NmVkLThmMGYtN2I3OGFjMzc2NTc5IiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgxL3JlYWxtcy9qYXZpIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjZkZmQyY2M1LWVmMWItNDhmZS05Y2I0LWZhMDNmZjRiNTg4NyIsInR5cCI6IkJlYXJlciIsImF6cCI6InNydi1jbGllbnQiLCJzZXNzaW9uX3N0YXRlIjoiNDdkYmU5YzMtZDY1OS00MzJjLWFiZTktNmExODZmZGRjNDc3IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtb3Jmb3N5cyIsInVtYV9hdXRob3JpemF0aW9uIiwiQURNSU4iXX0sInJlc291cmNlX2FjY2VzcyI6eyJzcnYtY2xpZW50Ijp7InJvbGVzIjpbIkNMSUVOVF9BRE1JTiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiNDdkYmU5YzMtZDY1OS00MzJjLWFiZTktNmExODZmZGRjNDc3IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYXZpIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIn0.P2rOEwr-l0gi4jN7BDZqsP_jW5xCPNn2lx-afWdEJoljG81RK-YwVjiAaCiNDroXMZLwQG8Ko-9UrXavWQUr9RDhMXlCQtH97rqpkBEoxq8mxPHt5y3m8DIxxt4iVbRZmM0i8Iz3C0RMxrA_fP6_0K4_sZO-gMaNkfOKKW07hhBSPCG_cI5hBEinmmwjvCd8H8fDDUbfbedih7bj7l1h8cWrzy3OeJe5rTp4Qqri9ckmkD4sGHXJeBIKTTDuc5zyqq_plY9R_JW7HLarWzgWff-Ul5Er2cIUTvTK2Da0-3Z88ip5qndYCCvDbQ4Nklkr1ziJY8hDGR4UF4qEwzuQ7Q 9 | -------------------------------------------------------------------------------- /ships/get_paged.ship: -------------------------------------------------------------------------------- 1 | 2 | ~[BASE]~ 3 | url http://localhost:8080/app/dummy?size=4&sortBy=id&sortOrder=desc 4 | method GET 5 | 6 | ~[HEADERS]~ 7 | accept application/json 8 | authorization Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJVktibk93SjNPUlYyVDloWGlqVl9oVXJfX3Y4ZEVIdkxyalgzXzhiZ29ZIn0.eyJleHAiOjE3MTIyNzQzOTIsImlhdCI6MTcxMjI3NDA5MiwianRpIjoiNGUzOWQwODItMjJjZS00YjE2LTk1MWUtMjZkYjUxNmY4MzVjIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgxL3JlYWxtcy9vcmZvc3lzIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjZkZmQyY2M1LWVmMWItNDhmZS05Y2I0LWZhMDNmZjRiNTg4NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImphdmEtc3ByaW5nMy1rZXljbG9hayIsInNlc3Npb25fc3RhdGUiOiIxNDgyYzQ4Ni00OGMwLTQyZTgtOWUxOC0zYWQ4ZDU2Yjk2ZDIiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbIioiXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbIm9mZmxpbmVfYWNjZXNzIiwiZGVmYXVsdC1yb2xlcy1vcmZvc3lzIiwidW1hX2F1dGhvcml6YXRpb24iLCJBRE1JTiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImphdmEtc3ByaW5nMy1rZXljbG9hayI6eyJyb2xlcyI6WyJDTElFTlRfQURNSU4iXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInNjb3BlIjoicHJvZmlsZSBlbWFpbCIsInNpZCI6IjE0ODJjNDg2LTQ4YzAtNDJlOC05ZTE4LTNhZDhkNTZiOTZkMiIsImVtYWlsX3ZlcmlmaWVkIjpmYWxzZSwicHJlZmVycmVkX3VzZXJuYW1lIjoiamF2aSIsImdpdmVuX25hbWUiOiIiLCJmYW1pbHlfbmFtZSI6IiJ9.q5sg2pBZ9_CVeZ57frz2f4Dx1W5poKPEDGQ9qduHHDv8poTehdvd84CW1i0mXph36i7JriQYmASIUzcDg7jdv__-dPXASLOfw3vL_EDQqpxaWTFv1eBjBZpGWvI3J8RkN3ns4kQprO0cbNvKr-bIHRgL-EX78E5r6Prn3EcrihI27SRm2e2tur9qILg5Cm9QHqWbOMXOP4iy1ZQ_AZ855Dyg0TRKwMDTf6mOo4f8xjhvS3hGr1PoihPMXrzo_f4olx3AsQE9MSkIOZPwkk5zNPYyq5K4J6ZzYqyvxRFr6lQ-mJeabcZV16AALZIcs9BfqadiGDPG_Io3qeq4p2b1pQ 9 | -------------------------------------------------------------------------------- /ships/keycloak_token.ship: -------------------------------------------------------------------------------- 1 | 2 | ~[BASE]~ 3 | url http://localhost:8081/realms/javi/protocol/openid-connect/token 4 | method POST 5 | 6 | ~[HEADERS]~ 7 | accept application/json 8 | content-type application/x-www-form-urlencoded 9 | 10 | ~[BODY]~ 11 | grant_type password 12 | client_id srv-client 13 | client_secret RqaTlO0d2OnBbeRuImNnbLWm5yZL66Mo 14 | username javi 15 | password javi 16 | -------------------------------------------------------------------------------- /ships/post.ship: -------------------------------------------------------------------------------- 1 | 2 | ~[BASE]~ 3 | url http://localhost:8080/app/dummy 4 | method POST 5 | 6 | ~[HEADERS]~ 7 | accept application/json 8 | content-type application/json 9 | authorization Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJJVktibk93SjNPUlYyVDloWGlqVl9oVXJfX3Y4ZEVIdkxyalgzXzhiZ29ZIn0.eyJleHAiOjE3MTU3MTI4MDYsImlhdCI6MTcxNTcxMjUwNiwianRpIjoiODhjYjdjZDMtNjM5MC00ODc5LTg1YWItYzU2OTc3NjBmNTJiIiwiaXNzIjoiaHR0cDovL2xvY2FsaG9zdDo4MDgxL3JlYWxtcy9vcmZvc3lzIiwiYXVkIjoiYWNjb3VudCIsInN1YiI6IjZkZmQyY2M1LWVmMWItNDhmZS05Y2I0LWZhMDNmZjRiNTg4NyIsInR5cCI6IkJlYXJlciIsImF6cCI6ImphdmEtc3ByaW5nMy1taWNyb3NlcnZpY2UiLCJzZXNzaW9uX3N0YXRlIjoiYzM1NGE1ZGMtNDI5Mi00MDg3LTk2YzAtOTc4OTQ2Mzk3M2E5IiwiYWNyIjoiMSIsImFsbG93ZWQtb3JpZ2lucyI6WyIqIl0sInJlYWxtX2FjY2VzcyI6eyJyb2xlcyI6WyJvZmZsaW5lX2FjY2VzcyIsImRlZmF1bHQtcm9sZXMtb3Jmb3N5cyIsInVtYV9hdXRob3JpemF0aW9uIiwiQURNSU4iXX0sInJlc291cmNlX2FjY2VzcyI6eyJqYXZhLXNwcmluZzMtbWljcm9zZXJ2aWNlIjp7InJvbGVzIjpbIkNMSUVOVF9BRE1JTiJdfSwiYWNjb3VudCI6eyJyb2xlcyI6WyJtYW5hZ2UtYWNjb3VudCIsIm1hbmFnZS1hY2NvdW50LWxpbmtzIiwidmlldy1wcm9maWxlIl19fSwic2NvcGUiOiJwcm9maWxlIGVtYWlsIiwic2lkIjoiYzM1NGE1ZGMtNDI5Mi00MDg3LTk2YzAtOTc4OTQ2Mzk3M2E5IiwiZW1haWxfdmVyaWZpZWQiOmZhbHNlLCJwcmVmZXJyZWRfdXNlcm5hbWUiOiJqYXZpIiwiZ2l2ZW5fbmFtZSI6IiIsImZhbWlseV9uYW1lIjoiIn0.rzco5VSkouB1yvBp_kxA1C_KC7aMc_u9aV6ic1Hyocoimt93K28yDpzuI_LQxtWzgkm_eIel2N1iPf1cPPC_1fQnhqxS6pOTE2GT13a8T1Qmo6PkBYrOVTb02C2S1CnoR9nHj6UtKmbPShyiCZxNspQMzbcu_pQEJcxI7JmtccWpwRaDbiInzOZOvZrt9aEHeIBB-JI0Bs9hHyeSKU5KfjWfb0W41uFs6LfuimnVoJbkILAW0sa5EEB-i-lDSRxvanbdaIv0AJTleD7CeGfsvrmqZxZiZxHxc6F5MExPotysiHQI7Fp_cK8t-hZGr6E-auW4gh9OvzIic4ZOptKzcw 10 | 11 | ~[BODY]~ 12 | { 13 | "info": "golang" 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/javi/Application.java: -------------------------------------------------------------------------------- 1 | package com.javi; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | @SpringBootApplication 7 | public class Application { 8 | 9 | public static void main(String[] args) { 10 | SpringApplication.run(Application.class, args); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/javi/adapter/in/DummyController.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.in; 2 | 3 | import com.javi.annotation.SwaggerSecurity; 4 | import com.javi.annotation.WebAdapter; 5 | import com.javi.application.in.DummyUseCase; 6 | import com.javi.application.in.request.DummyRequest; 7 | import com.javi.application.in.response.DummyResponse; 8 | import com.javi.domain.model.Dummy; 9 | import com.javi.pagination.Page; 10 | import com.javi.response.RestResponsePagination; 11 | 12 | import jakarta.validation.Valid; 13 | import lombok.AllArgsConstructor; 14 | 15 | import org.springframework.web.bind.annotation.GetMapping; 16 | import org.springframework.web.bind.annotation.PathVariable; 17 | import org.springframework.web.bind.annotation.PostMapping; 18 | import org.springframework.web.bind.annotation.RequestBody; 19 | import org.springframework.web.bind.annotation.RequestMapping; 20 | import org.springframework.web.bind.annotation.RequestParam; 21 | import org.springframework.web.bind.annotation.RestController; 22 | import org.springframework.http.ResponseEntity; 23 | import org.springframework.security.access.prepost.PreAuthorize; 24 | 25 | @WebAdapter 26 | @RestController 27 | @AllArgsConstructor 28 | @RequestMapping(value = "/dummy") 29 | @SwaggerSecurity 30 | public class DummyController { 31 | 32 | private final DummyUseCase dummyUseCase; 33 | 34 | @GetMapping("/{id}") 35 | @PreAuthorize("hasRole('CLIENT_ADMIN')") 36 | public ResponseEntity find(@PathVariable("id") int id) { 37 | return ResponseEntity.ok(new DummyResponse(dummyUseCase.findById(id))); 38 | } 39 | 40 | @GetMapping 41 | @PreAuthorize("hasRole('CLIENT_ADMIN')") 42 | public ResponseEntity> findAll( 43 | @RequestParam(defaultValue = "0") int page, 44 | @RequestParam(defaultValue = "10") int size, 45 | @RequestParam(defaultValue = "id") String sortBy, 46 | @RequestParam(defaultValue = "asc") String sortOrder) { 47 | var pageObj = new Page(page, size, sortBy, sortOrder); 48 | var pageElements = dummyUseCase.findAll(pageObj); 49 | var response = new RestResponsePagination<>(pageElements.pagination(), pageElements.results()); 50 | return ResponseEntity.ok(response); 51 | } 52 | 53 | @PostMapping 54 | @PreAuthorize("hasRole('CLIENT_ADMIN')") 55 | public ResponseEntity save(@Valid @RequestBody DummyRequest request) { 56 | var dummy = dummyUseCase.save(new Dummy(request.info())); 57 | return ResponseEntity.ok(new DummyResponse(dummy)); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/javi/adapter/out/DummyPersistenceAdapter.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.out; 2 | 3 | import java.util.Optional; 4 | 5 | import com.javi.application.out.DummyPersistence; 6 | import com.javi.domain.model.Dummy; 7 | import com.javi.pagination.Page; 8 | import com.javi.pagination.Paginator; 9 | import com.javi.adapter.out.repositories.DummyEntityRepository; 10 | import com.javi.annotation.PersistenceAdapter; 11 | import com.javi.adapter.out.mappers.DummyMapper; 12 | 13 | import lombok.AllArgsConstructor; 14 | 15 | @PersistenceAdapter 16 | @AllArgsConstructor 17 | public class DummyPersistenceAdapter implements DummyPersistence { 18 | 19 | private final DummyEntityRepository dummyEntityRepository; 20 | 21 | @Override 22 | public Optional findById(int id) { 23 | return dummyEntityRepository 24 | .findById(id) 25 | .map(DummyMapper::entityToDomain); 26 | } 27 | 28 | @Override 29 | public Dummy save(Dummy dummy) { 30 | var dummyEntity = DummyMapper.domainToEntity(dummy); 31 | dummy = DummyMapper.entityToDomain(dummyEntityRepository.save(dummyEntity)); 32 | return dummy; 33 | } 34 | 35 | @Override 36 | public Paginator.Pair findAll(Page page) { 37 | return Paginator.create(dummyEntityRepository.findAll(page.getPageable()), DummyMapper::entityToDomain); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/javi/adapter/out/entities/DummyEntity.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.out.entities; 2 | 3 | 4 | import com.javi.auditory.model.Auditable; 5 | 6 | import jakarta.persistence.Column; 7 | import jakarta.persistence.Entity; 8 | import jakarta.persistence.GeneratedValue; 9 | import jakarta.persistence.GenerationType; 10 | import jakarta.persistence.Id; 11 | import lombok.AllArgsConstructor; 12 | import lombok.Getter; 13 | import lombok.NoArgsConstructor; 14 | 15 | @AllArgsConstructor 16 | @NoArgsConstructor 17 | @Getter 18 | @Entity(name = "dummies") 19 | public class DummyEntity extends Auditable { 20 | 21 | @Id 22 | @GeneratedValue(strategy = GenerationType.IDENTITY) 23 | private int id; 24 | 25 | @Column(nullable = false) 26 | private String info; 27 | 28 | public DummyEntity(String info) { 29 | this.info = info; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/javi/adapter/out/mappers/DummyMapper.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.out.mappers; 2 | 3 | import com.javi.adapter.out.entities.DummyEntity; 4 | import com.javi.domain.model.Dummy; 5 | 6 | public class DummyMapper { 7 | 8 | public static Dummy entityToDomain(DummyEntity dummyEntity) { 9 | if (dummyEntity != null) { 10 | return new Dummy(dummyEntity.getInfo()); 11 | } 12 | throw new IllegalStateException("DummyEntity is null"); 13 | } 14 | 15 | public static DummyEntity domainToEntity(Dummy dummy) { 16 | return dummy != null ? new DummyEntity(dummy.info()) : null; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/javi/adapter/out/repositories/DummyEntityRepository.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.out.repositories; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import com.javi.adapter.out.entities.DummyEntity; 6 | 7 | public interface DummyEntityRepository extends JpaRepository { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/javi/application/in/DummyUseCase.java: -------------------------------------------------------------------------------- 1 | package com.javi.application.in; 2 | 3 | import com.javi.domain.model.Dummy; 4 | 5 | public interface DummyUseCase extends FindByIdUseCase, FindAllUseCase, SaveUseCase { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/javi/application/in/request/DummyRequest.java: -------------------------------------------------------------------------------- 1 | package com.javi.application.in.request; 2 | 3 | import jakarta.validation.constraints.NotBlank; 4 | 5 | public record DummyRequest(@NotBlank(message = "Info is mandatory") String info) {} 6 | -------------------------------------------------------------------------------- /src/main/java/com/javi/application/in/response/DummyResponse.java: -------------------------------------------------------------------------------- 1 | package com.javi.application.in.response; 2 | 3 | import com.javi.domain.model.Dummy; 4 | 5 | public record DummyResponse(Dummy dummy) { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/javi/application/out/DummyPersistence.java: -------------------------------------------------------------------------------- 1 | package com.javi.application.out; 2 | 3 | import com.javi.domain.model.Dummy; 4 | 5 | public interface DummyPersistence extends FindByIdPersistence, FindAllPersistence, SavePersistence { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/javi/common/configuration/BeanConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.javi.common.configuration; 2 | 3 | import org.springframework.context.annotation.ComponentScan; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | @ComponentScan("com.javi") 8 | public class BeanConfiguration { 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/javi/common/exception/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.javi.common.exception; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import com.javi.exception.BackEndException; 6 | 7 | public class NotFoundException extends BackEndException { 8 | 9 | public NotFoundException() { 10 | super(HttpStatus.NOT_FOUND, "NOT_FOUND", "Entity not found!"); 11 | } 12 | 13 | public NotFoundException(HttpStatus httpStatus, String code, String description) { 14 | super(httpStatus, code, description); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/javi/domain/model/Dummy.java: -------------------------------------------------------------------------------- 1 | package com.javi.domain.model; 2 | 3 | import java.util.Objects; 4 | 5 | public record Dummy(String info) { 6 | public Dummy { 7 | info = Objects.requireNonNull(info); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/javi/domain/service/DummyService.java: -------------------------------------------------------------------------------- 1 | package com.javi.domain.service; 2 | 3 | import com.javi.common.exception.NotFoundException; 4 | import com.javi.domain.model.Dummy; 5 | import com.javi.pagination.Page; 6 | import com.javi.pagination.Paginator; 7 | 8 | import lombok.AllArgsConstructor; 9 | import lombok.extern.slf4j.Slf4j; 10 | 11 | import com.javi.annotation.UseCase; 12 | import com.javi.application.in.DummyUseCase; 13 | import com.javi.application.out.DummyPersistence; 14 | 15 | @UseCase 16 | @AllArgsConstructor 17 | @Slf4j 18 | public class DummyService implements DummyUseCase { 19 | 20 | private final DummyPersistence dummyPersistence; 21 | 22 | @Override 23 | public Dummy findById(int id) { 24 | log.info("Searching dummy by id: {}", id); 25 | return this.dummyPersistence.findById(id).orElseThrow(NotFoundException::new); 26 | } 27 | 28 | @Override 29 | public Paginator.Pair findAll(Page page) { 30 | log.info("Searching dummies"); 31 | return dummyPersistence.findAll(page); 32 | } 33 | 34 | @Override 35 | public Dummy save(Dummy dummy) { 36 | log.info("Saving dummy: {}", dummy.toString()); 37 | return this.dummyPersistence.save(dummy); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application.name: java-spring3-microservice 3 | threads.virtual.enabled: true 4 | datasource: 5 | url: jdbc:postgresql://${DB_HOST}/${DB_NAME} 6 | username: ${DB_USER} 7 | password: ${DB_PASSWORD} 8 | driver-class-name: org.postgresql.Driver 9 | jpa: 10 | show-sql: ${JPA_SHOW_SQL:true} 11 | database-platform: org.hibernate.dialect.PostgreSQLDialect 12 | hibernate.ddl-auto: none 13 | security: 14 | oauth2: 15 | resourceserver: 16 | jwt: 17 | issuer-uri: ${KEYCLOAK_HOST:http://localhost:8081}/realms/javi 18 | jwk-set-uri: ${spring.security.oauth2.resourceserver.jwt.issuer-uri}/protocol/openid-connect/certs 19 | devtools.restart.enabled: false 20 | 21 | springdoc: 22 | api-docs: 23 | enabled: ${SWAGGER_ENABLED:true} 24 | swagger-ui: 25 | enabled: ${SWAGGER_ENABLED:true} 26 | oauth: 27 | client-id: ${spring.application.name} 28 | client-secret: ${KEYCLOAK_CLIENT_SECRET} 29 | # path: /swagger-ui/index.html 30 | default-consumes-media-type: application/json 31 | default-produces-media-type: application/json 32 | 33 | server.servlet.context-path: /app 34 | 35 | tracing.url: ${TRACING_HOST:http://localhost:4318}/v1/traces 36 | 37 | management: 38 | endpoints.web.exposure.include: health,env,metrics 39 | tracing.sampling.probability: 1.0 40 | 41 | logging: 42 | pattern: 43 | level: "%5p [${spring.application.name:}, traceID=%X{traceId:-}, spanID=%X{spanId:-}]" 44 | 45 | logstash.destination: ${LOGSTASH_HOST:http://localhost:5000} 46 | 47 | --- 48 | 49 | spring: 50 | config.activate.on-profile: dev 51 | datasource: 52 | url: jdbc:postgresql://localhost:5432/db_dummy 53 | username: admin 54 | password: admin 55 | jpa: 56 | hibernate: 57 | ddl-auto: create 58 | # ddl-auto: update 59 | properties: 60 | javax: 61 | persistence: 62 | schema-generation: 63 | create-source: metadata 64 | scripts: 65 | action: create 66 | create-target: schema.sql 67 | create-script-source: metadata 68 | devtools.restart.enabled: true 69 | 70 | springdoc: 71 | swagger-ui: 72 | oauth: 73 | client-secret: ${KEYCLOAK_CLIENT_SECRET:RqaTlO0d2OnBbeRuImNnbLWm5yZL66Mo} 74 | -------------------------------------------------------------------------------- /src/test/java/com/javi/adapter/in/DummyControllerTest.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.in; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.Mockito; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 7 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 8 | import org.springframework.boot.test.mock.mockito.MockBean; 9 | import org.springframework.test.context.aot.DisabledInAotMode; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 12 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 13 | 14 | import java.util.Collections; 15 | 16 | import com.javi.application.in.DummyUseCase; 17 | import com.javi.domain.model.Dummy; 18 | import com.javi.pagination.Page; 19 | import com.javi.pagination.Paginator; 20 | import com.javi.response.PaginationResponse; 21 | 22 | @WebMvcTest(controllers = DummyController.class) 23 | @AutoConfigureMockMvc(addFilters = false) 24 | @DisabledInAotMode 25 | public class DummyControllerTest { 26 | 27 | @Autowired 28 | private MockMvc mockMvc; 29 | 30 | @MockBean 31 | private DummyUseCase dummyUseCase; 32 | 33 | @Test 34 | void find() throws Exception { 35 | Mockito.when(dummyUseCase.findById(1)).thenReturn(new Dummy("dummy")); 36 | 37 | mockMvc.perform(get("/dummy/{id}", 1) 38 | .header("Content-Type", "application/json")) 39 | .andExpect(status().isOk()); 40 | } 41 | 42 | @Test 43 | void findAll() throws Exception { 44 | var page = new Page(0, 10, "id", "asc"); 45 | var pair = new Paginator.Pair(new PaginationResponse(0, 10, 0), Collections.emptyList()); 46 | 47 | Mockito.when(dummyUseCase.findAll(page)).thenReturn(pair); 48 | 49 | mockMvc.perform(get("/dummy") 50 | .header("Content-Type", "application/json")) 51 | .andExpect(status().isOk()); 52 | } 53 | 54 | @Test 55 | void save() throws Exception { 56 | var dummy = new Dummy("dummy"); 57 | Mockito.when(dummyUseCase.save(dummy)).thenReturn(dummy); 58 | 59 | var json = """ 60 | { 61 | "info": "papa" 62 | } 63 | """; 64 | 65 | mockMvc.perform(post("/dummy") 66 | .header("Content-Type", "application/json") 67 | .content(json)) 68 | .andExpect(status().isOk()); 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/javi/adapter/out/DummyPersistenceTest.java: -------------------------------------------------------------------------------- 1 | package com.javi.adapter.out; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.autoconfigure.orm.jpa.DataJpaTest; 6 | import org.springframework.boot.test.context.TestConfiguration; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Import; 9 | import org.springframework.data.domain.AuditorAware; 10 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 11 | import org.springframework.test.context.jdbc.Sql; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | 15 | import java.util.Optional; 16 | 17 | import com.javi.adapter.out.mappers.DummyMapper; 18 | import com.javi.domain.model.Dummy; 19 | import com.javi.pagination.Page; 20 | 21 | @DataJpaTest(properties = { 22 | "spring.datasource.url=jdbc:h2:mem:testdb", 23 | "spring.jpa.hibernate.ddl-auto=create-drop" 24 | }) 25 | @Import({ DummyPersistenceAdapter.class, DummyMapper.class }) 26 | public class DummyPersistenceTest { 27 | 28 | @Autowired 29 | private DummyPersistenceAdapter dummyPersistenceAdapter; 30 | 31 | @TestConfiguration 32 | @EnableJpaAuditing(auditorAwareRef = "testAuditorAware") 33 | public static class TestConfig { 34 | @Bean 35 | public AuditorAware testAuditorAware() { 36 | return () -> Optional.ofNullable("TestUser"); 37 | } 38 | } 39 | 40 | @Test 41 | @Sql("db.sql") 42 | public void findById() { 43 | var dummy = dummyPersistenceAdapter.findById(1); 44 | assertThat(dummy.get().info()).isEqualTo("dummy 1"); 45 | } 46 | 47 | @Test 48 | @Sql("db.sql") 49 | public void findAll() { 50 | var page = new Page(0, 10, "id", "asc"); 51 | 52 | var result = dummyPersistenceAdapter.findAll(page); 53 | 54 | assertThat(result.results().size()).isEqualTo(2); 55 | } 56 | 57 | @Test 58 | public void save() { 59 | var dummy = new Dummy("saved"); 60 | dummy = dummyPersistenceAdapter.save(dummy); 61 | 62 | assertThat(dummy.info()).isEqualTo("saved"); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/test/java/com/javi/domain/service/DummyServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.javi.domain.service; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.mockito.Mockito; 5 | 6 | import com.javi.application.out.DummyPersistence; 7 | import com.javi.domain.model.Dummy; 8 | import com.javi.pagination.Page; 9 | import com.javi.pagination.Paginator; 10 | import com.javi.response.PaginationResponse; 11 | 12 | import static org.assertj.core.api.Assertions.assertThat; 13 | import static org.mockito.BDDMockito.*; 14 | 15 | import java.util.Collections; 16 | import java.util.Optional; 17 | 18 | public class DummyServiceTest { 19 | 20 | private final DummyPersistence dummyPersistence = Mockito.mock(DummyPersistence.class); 21 | private final DummyService dummyService = new DummyService(dummyPersistence); 22 | 23 | @Test 24 | public void find() { 25 | Dummy dummy = Mockito.mock(Dummy.class); 26 | given(dummy.info()).willReturn("test"); 27 | 28 | given(dummyPersistence.findById(eq(1))).willReturn(Optional.of(dummy)); 29 | 30 | dummy = dummyService.findById(1); 31 | 32 | assertThat(dummy).isNotNull(); 33 | } 34 | 35 | @Test 36 | public void findAll() { 37 | var page = new Page(0, 10, "id", "asc"); 38 | var pair = new Paginator.Pair(new PaginationResponse(0, 10, 0), Collections.emptyList()); 39 | 40 | given(dummyPersistence.findAll(page)).willReturn(pair); 41 | 42 | var res = dummyService.findAll(page); 43 | 44 | assertThat(res).isNotNull(); 45 | } 46 | @Test 47 | public void save() { 48 | Dummy dummy = Mockito.mock(Dummy.class); 49 | given(dummy.info()).willReturn("save"); 50 | 51 | given(dummyPersistence.save(eq(dummy))).willReturn(dummy); 52 | 53 | dummy = dummyService.save(dummy); 54 | 55 | assertThat(dummy.info()).isEqualTo("save"); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/test/resources/com/javi/adapter/out/db.sql: -------------------------------------------------------------------------------- 1 | insert into dummies (id, info, create_date, created_by) values (1, 'dummy 1', current_timestamp, 'javi'); 2 | insert into dummies (id, info, create_date, created_by) values (2, 'dummy 2', current_timestamp, 'javi'); 3 | --------------------------------------------------------------------------------