├── .gitignore ├── .mvn └── wrapper │ ├── MavenWrapperDownloader.java │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── LICENSE ├── README.md ├── docker ├── .env ├── docker-compose.yml └── keycloak │ └── realms │ ├── default-users.json │ └── realm-export.json ├── mvnw ├── mvnw.cmd ├── pom.xml ├── readme-images ├── logo_250x60.png ├── swagger-ui-open-id-discovery.png ├── swagger-ui-with-auth-code-flow.png ├── swagger-ui-with-bearer-scheme.png ├── swagger-ui-with-keycloak-auth-for-endpoints.png ├── swagger-ui-with-openid-discovery-scheme.png ├── swagger-ui-with-password-flow.png └── token-with-realm-roles.png └── src ├── main ├── java │ └── in │ │ └── keepgrowing │ │ └── springbootswaggeruikeycloak │ │ ├── SpringBootSwaggerUiKeycloakApplication.java │ │ ├── products │ │ ├── domain │ │ │ ├── model │ │ │ │ └── Product.java │ │ │ └── repositories │ │ │ │ └── ProductRepository.java │ │ ├── infrastructure │ │ │ ├── config │ │ │ │ ├── MvcConfig.java │ │ │ │ └── ProductBeanConfig.java │ │ │ └── repositories │ │ │ │ └── InMemoryProductRepository.java │ │ └── presentation │ │ │ └── controllers │ │ │ ├── ProductController.java │ │ │ └── ProductControllerPaths.java │ │ └── shared │ │ └── infrastructure │ │ └── config │ │ ├── security │ │ ├── CustomMethodSecurityConfig.java │ │ ├── KeycloakConfig.java │ │ ├── KeycloakProperties.java │ │ └── SecurityConfig.java │ │ └── swagger │ │ ├── ApiInfoProvider.java │ │ ├── SwaggerBeanConfig.java │ │ ├── SwaggerProperties.java │ │ └── authorization │ │ ├── SwaggerAuthorizationCodeConfig.java │ │ ├── SwaggerBearerConfig.java │ │ ├── SwaggerImplicitConfig.java │ │ ├── SwaggerOpenIdConfig.java │ │ └── SwaggerPasswordFlowConfig.java └── resources │ ├── application-test.properties │ └── application.properties └── test └── java └── in └── keepgrowing └── springbootswaggeruikeycloak ├── SpringBootSwaggerUiKeycloakApplicationTests.java ├── products ├── domain │ └── model │ │ └── TestProductProvider.java ├── infrastructure │ └── repositories │ │ └── InMemoryProductRepositoryTest.java └── presentation │ └── controllers │ └── ProductControllerTest.java └── shared └── infrastructure ├── annotations └── RestControllerIntegrationTest.java └── config ├── ControllerIntegrationTestConfig.java └── TestSecurityConfig.java /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | -------------------------------------------------------------------------------- /.mvn/wrapper/MavenWrapperDownloader.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2007-present the original author or authors. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * https://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | import java.net.*; 17 | import java.io.*; 18 | import java.nio.channels.*; 19 | import java.util.Properties; 20 | 21 | public class MavenWrapperDownloader { 22 | 23 | private static final String WRAPPER_VERSION = "0.5.6"; 24 | /** 25 | * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided. 26 | */ 27 | private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/" 28 | + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar"; 29 | 30 | /** 31 | * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to 32 | * use instead of the default one. 33 | */ 34 | private static final String MAVEN_WRAPPER_PROPERTIES_PATH = 35 | ".mvn/wrapper/maven-wrapper.properties"; 36 | 37 | /** 38 | * Path where the maven-wrapper.jar will be saved to. 39 | */ 40 | private static final String MAVEN_WRAPPER_JAR_PATH = 41 | ".mvn/wrapper/maven-wrapper.jar"; 42 | 43 | /** 44 | * Name of the property which should be used to override the default download url for the wrapper. 45 | */ 46 | private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl"; 47 | 48 | public static void main(String args[]) { 49 | System.out.println("- Downloader started"); 50 | File baseDirectory = new File(args[0]); 51 | System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath()); 52 | 53 | // If the maven-wrapper.properties exists, read it and check if it contains a custom 54 | // wrapperUrl parameter. 55 | File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH); 56 | String url = DEFAULT_DOWNLOAD_URL; 57 | if(mavenWrapperPropertyFile.exists()) { 58 | FileInputStream mavenWrapperPropertyFileInputStream = null; 59 | try { 60 | mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile); 61 | Properties mavenWrapperProperties = new Properties(); 62 | mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream); 63 | url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url); 64 | } catch (IOException e) { 65 | System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'"); 66 | } finally { 67 | try { 68 | if(mavenWrapperPropertyFileInputStream != null) { 69 | mavenWrapperPropertyFileInputStream.close(); 70 | } 71 | } catch (IOException e) { 72 | // Ignore ... 73 | } 74 | } 75 | } 76 | System.out.println("- Downloading from: " + url); 77 | 78 | File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH); 79 | if(!outputFile.getParentFile().exists()) { 80 | if(!outputFile.getParentFile().mkdirs()) { 81 | System.out.println( 82 | "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'"); 83 | } 84 | } 85 | System.out.println("- Downloading to: " + outputFile.getAbsolutePath()); 86 | try { 87 | downloadFileFromURL(url, outputFile); 88 | System.out.println("Done"); 89 | System.exit(0); 90 | } catch (Throwable e) { 91 | System.out.println("- Error downloading"); 92 | e.printStackTrace(); 93 | System.exit(1); 94 | } 95 | } 96 | 97 | private static void downloadFileFromURL(String urlString, File destination) throws Exception { 98 | if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) { 99 | String username = System.getenv("MVNW_USERNAME"); 100 | char[] password = System.getenv("MVNW_PASSWORD").toCharArray(); 101 | Authenticator.setDefault(new Authenticator() { 102 | @Override 103 | protected PasswordAuthentication getPasswordAuthentication() { 104 | return new PasswordAuthentication(username, password); 105 | } 106 | }); 107 | } 108 | URL website = new URL(urlString); 109 | ReadableByteChannel rbc; 110 | rbc = Channels.newChannel(website.openStream()); 111 | FileOutputStream fos = new FileOutputStream(destination); 112 | fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE); 113 | fos.close(); 114 | rbc.close(); 115 | } 116 | 117 | } 118 | -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # spring-boot-swagger-ui-keycloak 2 | 3 | [![keep_growing logo](readme-images/logo_250x60.png)](https://keepgrowing.in/) 4 | 5 | ## About this project 6 | 7 | This Spring Boot project shows an example configuration of Springdoc and Keycloak Spring Boot adapter that ensures that 8 | only authenticated users can call secured endpoints available through Swagger UI: 9 | 10 | ![swagger ui with authorization code flow screenshot](readme-images/swagger-ui-with-auth-code-flow.png) 11 | 12 | * client_id: `spring-boot-example-app` 13 | * client_secret should be left empty 14 | 15 | ## Getting started 16 | 17 | First, [clone](https://docs.github.com/en/github/creating-cloning-and-archiving-repositories/cloning-a-repository-from-github/cloning-a-repository) 18 | this repository. 19 | 20 | Then, build it locally with: 21 | 22 | ```shell 23 | mvn clean install 24 | ``` 25 | 26 | You can run the app in a command line with the following command: 27 | 28 | ```shell 29 | mvn spring-boot:run 30 | ``` 31 | 32 | You can run the `keycloak` container with the following commands: 33 | 34 | ```shell 35 | cd docker 36 | docker-compose up -d 37 | ``` 38 | 39 | ### Running tests 40 | 41 | You can run tests with: 42 | 43 | ```shell 44 | mvn test 45 | ``` 46 | 47 | The MVC tests use Spring Boot Security not Keycloak. The `HttpSecurity` configuration stays the same. 48 | 49 | ### Credentials 50 | 51 | #### For the Keycloak admin 52 | 53 | * username: `admin` 54 | * password: `admin` 55 | 56 | #### For example users of this app 57 | 58 | * available usernames: `christina`, `hanna`, `carlo`, `noel` 59 | * password: `test` 60 | * `christina` has the `chief-operating-officer` realm role that is required to call the `POST: /api/products` endpoint 61 | 62 | The `keycloak` service starts with the default realm imported from the 63 | [docker/keycloak/realms/realm-export.json](docker/keycloak/realms/realm-export.json) file that specifies all the default 64 | users. 65 | 66 | ### Visit API documentation 67 | 68 | Make sure that the app is running. 69 | 70 | * Swagger UI: [http://localhost:8080/swagger-ui.html](http://localhost:8080/swagger-ui.html) 71 | (click the `Authorize` button and log in as an example user of the `spring-boot-example-app` client to test secured 72 | endpoints, the client is `public` so you don't need to fill the `client_secret` field) 73 | * OpenAPI specification: [http://localhost:8080/v3/api-docs](http://localhost:8080/v3/api-docs) 74 | 75 | ### Visit Keycloak 76 | 77 | Make sure that the `keycloak` container is up. 78 | 79 | * Admin panel: [http://localhost:8024/auth](http://localhost:8024/auth) (log in as the Keycloak admin [`admin:admin`]) 80 | * As an admin you can see a list of users associated with the `Spring-Boot-Example` realm by clicking 81 | the `View all users` button on the 82 | [http://localhost:8024/auth/admin/master/console/#/realms/Spring-Boot-Example/users](http://localhost:8024/auth/admin/master/console/#/realms/Spring-Boot-Example/users) 83 | page. 84 | * What's more, you can log in as any user associated with the `Spring-Boot-Example` realm by clicking the `Sign in` 85 | button on the 86 | [http://localhost:8024/auth/realms/Spring-Boot-Example/account](http://localhost:8024/auth/realms/Spring-Boot-Example/account) 87 | page. 88 | * The realm roles are available under 89 | the [http://localhost:8024/auth/admin/master/console/#/realms/Spring-Boot-Example/roles](http://localhost:8024/auth/admin/master/console/#/realms/Spring-Boot-Example/roles) 90 | url. 91 | 92 | ## Features 93 | 94 | * Application secured with Keycloak 95 | * Swagger UI available for everyone but only authenticated users can call secured endpoints 96 | * Only users with `chief-operating-officer` realm role can call the `POST: /api/products` endpoint 97 | (this role is assigned to `christina` by default) 98 | * OpenAPI 3 specification 99 | 100 | ### Experimental Authorization configurations for Swagger UI 101 | 102 | We can enable/disable specific application properties to run the app with different configurations. 103 | 104 | Prior to `springdoc-openapi v1.6.6`, the Swagger configs for the `OpenID Connect Discovery` scheme, 105 | the `Authorization Code` and `Password` flows didn't work 106 | with [Spring Boot csrf protection enabled for Springdoc](https://springdoc.org/#how-can-i-enable-csrf-support). The 107 | issue was fixed 108 | in [CSRF header should not be sent to cross domain sites](https://github.com/sebastien-helbert/springdoc-openapi/commit/a9cea74b832e639da0433c7e3eaf0025ebcce9c9) 109 | PR and this project uses this fix. 110 | 111 | #### Swagger UI with OpenID Connect Discovery scheme 112 | 113 | To enable the Swagger Authentication config for 114 | the [OpenID Connect Discovery scheme](https://swagger.io/docs/specification/authentication/openid-connect-discovery/), 115 | edit the `application.properties` file so that it contains: 116 | 117 | ``` 118 | security.config.openid-discovery=true 119 | security.config.authcode-flow=false 120 | ``` 121 | 122 | Alternatively, run the app with the following command: 123 | 124 | ```shell 125 | mvn spring-boot:run -Dspring-boot.run.arguments="--security.config.openid-discovery=true --security.config.authcode-flow=false" 126 | ``` 127 | 128 | The result: 129 | 130 | ![swagger ui with openid discovery screenshot](readme-images/swagger-ui-with-openid-discovery-scheme.png) 131 | 132 | #### Swagger UI with Bearer Authentication 133 | 134 | To enable the Swagger Authentication config for 135 | the [Bearer Authentication](https://swagger.io/docs/specification/authentication/bearer-authentication/), edit the 136 | `application.properties` file so that it contains: 137 | 138 | ``` 139 | security.config.bearer=true 140 | security.config.authcode-flow=false 141 | ``` 142 | 143 | Alternatively, run the app with the following command: 144 | 145 | ```shell 146 | mvn spring-boot:run -Dspring-boot.run.arguments="--security.config.bearer=true --security.config.authcode-flow=false" 147 | ``` 148 | 149 | The result: 150 | 151 | ![swagger ui with bearer authentication screenshot](readme-images/swagger-ui-with-bearer-scheme.png) 152 | 153 | Make sure that the token contains realm roles (`realm_access` in the screenshot below): 154 | 155 | ![token with realm roles screenshot](readme-images/token-with-realm-roles.png) 156 | 157 | Otherwise, the POST endpoint will return 403 Forbidden (it requires the `chief-operating-officer` role). 158 | 159 | #### Swagger UI with Resource Owner Password flow 160 | 161 | To enable the Swagger Authentication config for 162 | the [Password Flow](https://swagger.io/docs/specification/authentication/oauth2/), edit the `application.properties` 163 | file so that it contains: 164 | 165 | ``` 166 | security.config.password-flow=true 167 | security.config.authcode-flow=false 168 | ``` 169 | 170 | Alternatively, run the app with the following command: 171 | 172 | ```shell 173 | mvn spring-boot:run -Dspring-boot.run.arguments="--security.config.password-flow=true --security.config.authcode-flow=false" 174 | ``` 175 | 176 | The result: 177 | 178 | ![swagger ui with password flow screenshot](readme-images/swagger-ui-with-password-flow.png) 179 | 180 | #### Swagger UI with Implicit flow 181 | 182 | This flow is deprecated[^1] 183 | 184 | To enable the Swagger Authentication config for 185 | the [Implicit Flow](https://swagger.io/docs/specification/authentication/oauth2/), edit the `application.properties` 186 | file so that it contains: 187 | 188 | ``` 189 | security.config.implicit-flow=true 190 | security.config.authcode-flow=false 191 | ``` 192 | 193 | Alternatively, run the app with the following command: 194 | 195 | ```shell 196 | mvn spring-boot:run -Dspring-boot.run.arguments="--security.config.implicit-flow=true --security.config.authcode-flow=false" 197 | ``` 198 | 199 | The result: 200 | 201 | ![swagger ui with keycloak auth for endpoints screenshot](readme-images/swagger-ui-with-keycloak-auth-for-endpoints.png) 202 | 203 | [^1]: *Why not Implicit flow* in [How to authorize requests via Postman](https://keepgrowing.in/tools/kecloak-in-docker-7-how-to-authorize-requests-via-postman) 204 | 205 | ## Built With 206 | 207 | * [Spring Boot v2.6+](https://spring.io/projects/spring-boot) 208 | * [Maven](https://maven.apache.org/) 209 | * [Keycloak](https://www.keycloak.org/) 210 | * [Keycloak Spring Boot adapter](https://www.keycloak.org/docs/latest/securing_apps/#_spring_boot_adapter) 211 | * [springdoc-openapi](https://springdoc.org/) 212 | * [Docker Compose](https://docs.docker.com/compose/) 213 | * [Dummy4j](https://daniel-frak.github.io/dummy4j/) 214 | -------------------------------------------------------------------------------- /docker/.env: -------------------------------------------------------------------------------- 1 | COMPOSE_PROJECT_NAME=springbootswaggerkeycloak 2 | 3 | KEYCLOAK_VERSION=16.1.1 4 | KEYCLOAK_USER=admin 5 | KEYCLOAK_PASSWORD=admin -------------------------------------------------------------------------------- /docker/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.3' 2 | services: 3 | keycloak: 4 | image: jboss/keycloak:${KEYCLOAK_VERSION} 5 | ports: 6 | - "8024:8080" 7 | environment: 8 | - KEYCLOAK_USER=${KEYCLOAK_USER} 9 | - KEYCLOAK_PASSWORD=${KEYCLOAK_PASSWORD} 10 | - KEYCLOAK_IMPORT=/tmp/realm-export.json 11 | volumes: 12 | - ./keycloak/realms/realm-export.json:/tmp/realm-export.json -------------------------------------------------------------------------------- /docker/keycloak/realms/default-users.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "username": "christina", 5 | "enabled": true, 6 | "email": "christina@test.com", 7 | "firstName": "Christina", 8 | "lastName": "Travis", 9 | "credentials": [ 10 | { 11 | "type": "password", 12 | "value": "test" 13 | } 14 | ], 15 | "realmRoles": [ 16 | "user", 17 | "chief-operating-officer" 18 | ], 19 | "clientRoles": { 20 | "account": [ 21 | "view-profile", 22 | "manage-account" 23 | ] 24 | } 25 | }, 26 | { 27 | "username": "hanna", 28 | "enabled": true, 29 | "email": "hanna@test.com", 30 | "firstName": "Hanna", 31 | "lastName": "Davis", 32 | "credentials": [ 33 | { 34 | "type": "password", 35 | "value": "test" 36 | } 37 | ], 38 | "realmRoles": [ 39 | "user" 40 | ], 41 | "clientRoles": { 42 | "account": [ 43 | "view-profile", 44 | "manage-account" 45 | ] 46 | } 47 | }, 48 | { 49 | "username": "carlo", 50 | "enabled": true, 51 | "email": "carlo@test.com", 52 | "firstName": "Carlo", 53 | "lastName": "Velazquez", 54 | "credentials": [ 55 | { 56 | "type": "password", 57 | "value": "test" 58 | } 59 | ], 60 | "realmRoles": [ 61 | "user" 62 | ], 63 | "clientRoles": { 64 | "account": [ 65 | "view-profile", 66 | "manage-account" 67 | ] 68 | } 69 | }, 70 | { 71 | "username": "noel", 72 | "enabled": true, 73 | "email": "noel@test.com", 74 | "firstName": "Noel", 75 | "lastName": "Horton", 76 | "credentials": [ 77 | { 78 | "type": "password", 79 | "value": "test" 80 | } 81 | ], 82 | "realmRoles": [ 83 | "user" 84 | ], 85 | "clientRoles": { 86 | "account": [ 87 | "view-profile", 88 | "manage-account" 89 | ] 90 | } 91 | } 92 | ] 93 | } 94 | -------------------------------------------------------------------------------- /docker/keycloak/realms/realm-export.json: -------------------------------------------------------------------------------- 1 | { 2 | "users": [ 3 | { 4 | "username": "christina", 5 | "enabled": true, 6 | "email": "christina@test.com", 7 | "firstName": "Christina", 8 | "lastName": "Travis", 9 | "credentials": [ 10 | { 11 | "type": "password", 12 | "value": "test" 13 | } 14 | ], 15 | "realmRoles": [ 16 | "user", 17 | "chief-operating-officer" 18 | ], 19 | "clientRoles": { 20 | "account": [ 21 | "view-profile", 22 | "manage-account" 23 | ] 24 | } 25 | }, 26 | { 27 | "username": "hanna", 28 | "enabled": true, 29 | "email": "hanna@test.com", 30 | "firstName": "Hanna", 31 | "lastName": "Davis", 32 | "credentials": [ 33 | { 34 | "type": "password", 35 | "value": "test" 36 | } 37 | ], 38 | "realmRoles": [ 39 | "user" 40 | ], 41 | "clientRoles": { 42 | "account": [ 43 | "view-profile", 44 | "manage-account" 45 | ] 46 | } 47 | }, 48 | { 49 | "username": "carlo", 50 | "enabled": true, 51 | "email": "carlo@test.com", 52 | "firstName": "Carlo", 53 | "lastName": "Velazquez", 54 | "credentials": [ 55 | { 56 | "type": "password", 57 | "value": "test" 58 | } 59 | ], 60 | "realmRoles": [ 61 | "user" 62 | ], 63 | "clientRoles": { 64 | "account": [ 65 | "view-profile", 66 | "manage-account" 67 | ] 68 | } 69 | }, 70 | { 71 | "username": "noel", 72 | "enabled": true, 73 | "email": "noel@test.com", 74 | "firstName": "Noel", 75 | "lastName": "Horton", 76 | "credentials": [ 77 | { 78 | "type": "password", 79 | "value": "test" 80 | } 81 | ], 82 | "realmRoles": [ 83 | "user" 84 | ], 85 | "clientRoles": { 86 | "account": [ 87 | "view-profile", 88 | "manage-account" 89 | ] 90 | } 91 | } 92 | ], 93 | "id": "Spring-Boot-Example", 94 | "realm": "Spring-Boot-Example", 95 | "notBefore": 0, 96 | "defaultSignatureAlgorithm": "RS256", 97 | "revokeRefreshToken": false, 98 | "refreshTokenMaxReuse": 0, 99 | "accessTokenLifespan": 300, 100 | "accessTokenLifespanForImplicitFlow": 900, 101 | "ssoSessionIdleTimeout": 1800, 102 | "ssoSessionMaxLifespan": 36000, 103 | "ssoSessionIdleTimeoutRememberMe": 0, 104 | "ssoSessionMaxLifespanRememberMe": 0, 105 | "offlineSessionIdleTimeout": 2592000, 106 | "offlineSessionMaxLifespanEnabled": false, 107 | "offlineSessionMaxLifespan": 5184000, 108 | "clientSessionIdleTimeout": 0, 109 | "clientSessionMaxLifespan": 0, 110 | "clientOfflineSessionIdleTimeout": 0, 111 | "clientOfflineSessionMaxLifespan": 0, 112 | "accessCodeLifespan": 60, 113 | "accessCodeLifespanUserAction": 300, 114 | "accessCodeLifespanLogin": 1800, 115 | "actionTokenGeneratedByAdminLifespan": 43200, 116 | "actionTokenGeneratedByUserLifespan": 300, 117 | "oauth2DeviceCodeLifespan": 600, 118 | "oauth2DevicePollingInterval": 5, 119 | "enabled": true, 120 | "sslRequired": "external", 121 | "registrationAllowed": false, 122 | "registrationEmailAsUsername": false, 123 | "rememberMe": false, 124 | "verifyEmail": false, 125 | "loginWithEmailAllowed": true, 126 | "duplicateEmailsAllowed": false, 127 | "resetPasswordAllowed": false, 128 | "editUsernameAllowed": false, 129 | "bruteForceProtected": false, 130 | "permanentLockout": false, 131 | "maxFailureWaitSeconds": 900, 132 | "minimumQuickLoginWaitSeconds": 60, 133 | "waitIncrementSeconds": 60, 134 | "quickLoginCheckMilliSeconds": 1000, 135 | "maxDeltaTimeSeconds": 43200, 136 | "failureFactor": 30, 137 | "roles": { 138 | "realm": [ 139 | { 140 | "id": "591329fe-ddff-4cf6-8499-f13290cf6385", 141 | "name": "chief-operating-officer", 142 | "composite": false, 143 | "clientRole": false, 144 | "containerId": "Spring-Boot-Example", 145 | "attributes": {} 146 | }, 147 | { 148 | "id": "a87067d1-06b2-4f67-8bce-82feff09d6d5", 149 | "name": "offline_access", 150 | "description": "${role_offline-access}", 151 | "composite": false, 152 | "clientRole": false, 153 | "containerId": "Spring-Boot-Example", 154 | "attributes": {} 155 | }, 156 | { 157 | "id": "b57bb786-95bd-4dfe-a0e3-2780b10aada0", 158 | "name": "uma_authorization", 159 | "description": "${role_uma_authorization}", 160 | "composite": false, 161 | "clientRole": false, 162 | "containerId": "Spring-Boot-Example", 163 | "attributes": {} 164 | }, 165 | { 166 | "id": "d9c9e136-4b2d-444c-be74-cc53a2ebb752", 167 | "name": "default-roles-spring-boot-example", 168 | "description": "${role_default-roles}", 169 | "composite": true, 170 | "composites": { 171 | "realm": [ 172 | "offline_access", 173 | "uma_authorization" 174 | ], 175 | "client": { 176 | "account": [ 177 | "view-profile", 178 | "manage-account" 179 | ] 180 | } 181 | }, 182 | "clientRole": false, 183 | "containerId": "Spring-Boot-Example", 184 | "attributes": {} 185 | }, 186 | { 187 | "id": "d3a5a4d7-ff9e-445e-945d-3503f2c3b1fc", 188 | "name": "user", 189 | "composite": false, 190 | "clientRole": false, 191 | "containerId": "Spring-Boot-Example", 192 | "attributes": {} 193 | } 194 | ], 195 | "client": { 196 | "realm-management": [ 197 | { 198 | "id": "4808f93a-b483-442d-88d2-882792aa206c", 199 | "name": "manage-authorization", 200 | "description": "${role_manage-authorization}", 201 | "composite": false, 202 | "clientRole": true, 203 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 204 | "attributes": {} 205 | }, 206 | { 207 | "id": "8a79e971-7e96-4cfa-b321-9a5bcd332dd5", 208 | "name": "manage-users", 209 | "description": "${role_manage-users}", 210 | "composite": false, 211 | "clientRole": true, 212 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 213 | "attributes": {} 214 | }, 215 | { 216 | "id": "c5964949-6139-4c0f-92f4-c1be60152896", 217 | "name": "realm-admin", 218 | "description": "${role_realm-admin}", 219 | "composite": true, 220 | "composites": { 221 | "client": { 222 | "realm-management": [ 223 | "manage-authorization", 224 | "manage-users", 225 | "view-authorization", 226 | "view-identity-providers", 227 | "query-users", 228 | "create-client", 229 | "view-events", 230 | "query-realms", 231 | "manage-clients", 232 | "manage-identity-providers", 233 | "manage-events", 234 | "view-clients", 235 | "manage-realm", 236 | "view-users", 237 | "query-clients", 238 | "view-realm", 239 | "impersonation", 240 | "query-groups" 241 | ] 242 | } 243 | }, 244 | "clientRole": true, 245 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 246 | "attributes": {} 247 | }, 248 | { 249 | "id": "526b8782-6926-4533-91a0-fe66c490d40e", 250 | "name": "query-users", 251 | "description": "${role_query-users}", 252 | "composite": false, 253 | "clientRole": true, 254 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 255 | "attributes": {} 256 | }, 257 | { 258 | "id": "1bcc9fa7-2d61-48a5-b9bc-446ddfaa4a70", 259 | "name": "view-authorization", 260 | "description": "${role_view-authorization}", 261 | "composite": false, 262 | "clientRole": true, 263 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 264 | "attributes": {} 265 | }, 266 | { 267 | "id": "3f035a24-fce6-4707-bdca-1ade709627b2", 268 | "name": "view-identity-providers", 269 | "description": "${role_view-identity-providers}", 270 | "composite": false, 271 | "clientRole": true, 272 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 273 | "attributes": {} 274 | }, 275 | { 276 | "id": "a817c4f6-75f0-42ed-bb99-2e22e1edf1eb", 277 | "name": "create-client", 278 | "description": "${role_create-client}", 279 | "composite": false, 280 | "clientRole": true, 281 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 282 | "attributes": {} 283 | }, 284 | { 285 | "id": "fdef5015-eb8a-4974-a8ae-e294975c3c46", 286 | "name": "query-realms", 287 | "description": "${role_query-realms}", 288 | "composite": false, 289 | "clientRole": true, 290 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 291 | "attributes": {} 292 | }, 293 | { 294 | "id": "daf088c0-4817-4fea-b7c8-cb1637b2c962", 295 | "name": "view-events", 296 | "description": "${role_view-events}", 297 | "composite": false, 298 | "clientRole": true, 299 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 300 | "attributes": {} 301 | }, 302 | { 303 | "id": "73f9866a-8eec-4046-b302-74f362f4188f", 304 | "name": "manage-clients", 305 | "description": "${role_manage-clients}", 306 | "composite": false, 307 | "clientRole": true, 308 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 309 | "attributes": {} 310 | }, 311 | { 312 | "id": "d2011eab-a407-4c44-a6ea-1cf81ea03e4b", 313 | "name": "manage-events", 314 | "description": "${role_manage-events}", 315 | "composite": false, 316 | "clientRole": true, 317 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 318 | "attributes": {} 319 | }, 320 | { 321 | "id": "5a44ddc8-4d28-4ecc-bd6d-3bf021752ce4", 322 | "name": "manage-identity-providers", 323 | "description": "${role_manage-identity-providers}", 324 | "composite": false, 325 | "clientRole": true, 326 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 327 | "attributes": {} 328 | }, 329 | { 330 | "id": "61a6cba3-f38c-4f79-ac12-641e4561e9cc", 331 | "name": "view-clients", 332 | "description": "${role_view-clients}", 333 | "composite": true, 334 | "composites": { 335 | "client": { 336 | "realm-management": [ 337 | "query-clients" 338 | ] 339 | } 340 | }, 341 | "clientRole": true, 342 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 343 | "attributes": {} 344 | }, 345 | { 346 | "id": "8b3711b6-b11a-452c-919f-147cbc3e3470", 347 | "name": "manage-realm", 348 | "description": "${role_manage-realm}", 349 | "composite": false, 350 | "clientRole": true, 351 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 352 | "attributes": {} 353 | }, 354 | { 355 | "id": "38c17a36-f1e3-47d3-972a-fde86d68dd60", 356 | "name": "view-users", 357 | "description": "${role_view-users}", 358 | "composite": true, 359 | "composites": { 360 | "client": { 361 | "realm-management": [ 362 | "query-users", 363 | "query-groups" 364 | ] 365 | } 366 | }, 367 | "clientRole": true, 368 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 369 | "attributes": {} 370 | }, 371 | { 372 | "id": "0fbda38b-6bc0-4aea-8e95-613936c96089", 373 | "name": "query-clients", 374 | "description": "${role_query-clients}", 375 | "composite": false, 376 | "clientRole": true, 377 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 378 | "attributes": {} 379 | }, 380 | { 381 | "id": "6e1fe485-5226-4d59-a680-880536c7b925", 382 | "name": "view-realm", 383 | "description": "${role_view-realm}", 384 | "composite": false, 385 | "clientRole": true, 386 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 387 | "attributes": {} 388 | }, 389 | { 390 | "id": "fbf09901-3b08-4bc2-92ed-6739d2aa0ff4", 391 | "name": "impersonation", 392 | "description": "${role_impersonation}", 393 | "composite": false, 394 | "clientRole": true, 395 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 396 | "attributes": {} 397 | }, 398 | { 399 | "id": "686c458e-bf8e-49db-92d6-30e6182174b6", 400 | "name": "query-groups", 401 | "description": "${role_query-groups}", 402 | "composite": false, 403 | "clientRole": true, 404 | "containerId": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 405 | "attributes": {} 406 | } 407 | ], 408 | "security-admin-console": [], 409 | "admin-cli": [], 410 | "spring-boot-example-app": [], 411 | "account-console": [], 412 | "broker": [ 413 | { 414 | "id": "b5b26a9b-9c8c-4044-adad-ab16634f975f", 415 | "name": "read-token", 416 | "description": "${role_read-token}", 417 | "composite": false, 418 | "clientRole": true, 419 | "containerId": "1240d5b4-07d4-4cef-ac5e-69252b184073", 420 | "attributes": {} 421 | } 422 | ], 423 | "account": [ 424 | { 425 | "id": "009afc0b-97ca-4d17-8a80-aa2aaf1328c2", 426 | "name": "manage-consent", 427 | "description": "${role_manage-consent}", 428 | "composite": true, 429 | "composites": { 430 | "client": { 431 | "account": [ 432 | "view-consent" 433 | ] 434 | } 435 | }, 436 | "clientRole": true, 437 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 438 | "attributes": {} 439 | }, 440 | { 441 | "id": "d6c3cd84-d0a3-4d15-baa2-0d6698c2c711", 442 | "name": "view-consent", 443 | "description": "${role_view-consent}", 444 | "composite": false, 445 | "clientRole": true, 446 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 447 | "attributes": {} 448 | }, 449 | { 450 | "id": "8471fd9d-11ed-46b8-92e4-375494606a1e", 451 | "name": "delete-account", 452 | "description": "${role_delete-account}", 453 | "composite": false, 454 | "clientRole": true, 455 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 456 | "attributes": {} 457 | }, 458 | { 459 | "id": "478c4963-0316-4d2f-827a-0d51b03368c3", 460 | "name": "view-profile", 461 | "description": "${role_view-profile}", 462 | "composite": false, 463 | "clientRole": true, 464 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 465 | "attributes": {} 466 | }, 467 | { 468 | "id": "61e9ba4f-e323-4c98-b28c-978ad8703dbc", 469 | "name": "view-applications", 470 | "description": "${role_view-applications}", 471 | "composite": false, 472 | "clientRole": true, 473 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 474 | "attributes": {} 475 | }, 476 | { 477 | "id": "942e9c91-a1ce-4ddb-ad31-f3b7c7566ebd", 478 | "name": "manage-account", 479 | "description": "${role_manage-account}", 480 | "composite": true, 481 | "composites": { 482 | "client": { 483 | "account": [ 484 | "manage-account-links" 485 | ] 486 | } 487 | }, 488 | "clientRole": true, 489 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 490 | "attributes": {} 491 | }, 492 | { 493 | "id": "34e82ef6-9572-4f0d-a268-f1ecbc8688fd", 494 | "name": "manage-account-links", 495 | "description": "${role_manage-account-links}", 496 | "composite": false, 497 | "clientRole": true, 498 | "containerId": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 499 | "attributes": {} 500 | } 501 | ] 502 | } 503 | }, 504 | "groups": [], 505 | "defaultRole": { 506 | "id": "d9c9e136-4b2d-444c-be74-cc53a2ebb752", 507 | "name": "default-roles-spring-boot-example", 508 | "description": "${role_default-roles}", 509 | "composite": true, 510 | "clientRole": false, 511 | "containerId": "Spring-Boot-Example" 512 | }, 513 | "requiredCredentials": [ 514 | "password" 515 | ], 516 | "otpPolicyType": "totp", 517 | "otpPolicyAlgorithm": "HmacSHA1", 518 | "otpPolicyInitialCounter": 0, 519 | "otpPolicyDigits": 6, 520 | "otpPolicyLookAheadWindow": 1, 521 | "otpPolicyPeriod": 30, 522 | "otpSupportedApplications": [ 523 | "FreeOTP", 524 | "Google Authenticator" 525 | ], 526 | "webAuthnPolicyRpEntityName": "keycloak", 527 | "webAuthnPolicySignatureAlgorithms": [ 528 | "ES256" 529 | ], 530 | "webAuthnPolicyRpId": "", 531 | "webAuthnPolicyAttestationConveyancePreference": "not specified", 532 | "webAuthnPolicyAuthenticatorAttachment": "not specified", 533 | "webAuthnPolicyRequireResidentKey": "not specified", 534 | "webAuthnPolicyUserVerificationRequirement": "not specified", 535 | "webAuthnPolicyCreateTimeout": 0, 536 | "webAuthnPolicyAvoidSameAuthenticatorRegister": false, 537 | "webAuthnPolicyAcceptableAaguids": [], 538 | "webAuthnPolicyPasswordlessRpEntityName": "keycloak", 539 | "webAuthnPolicyPasswordlessSignatureAlgorithms": [ 540 | "ES256" 541 | ], 542 | "webAuthnPolicyPasswordlessRpId": "", 543 | "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified", 544 | "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified", 545 | "webAuthnPolicyPasswordlessRequireResidentKey": "not specified", 546 | "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified", 547 | "webAuthnPolicyPasswordlessCreateTimeout": 0, 548 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false, 549 | "webAuthnPolicyPasswordlessAcceptableAaguids": [], 550 | "scopeMappings": [ 551 | { 552 | "clientScope": "offline_access", 553 | "roles": [ 554 | "offline_access" 555 | ] 556 | } 557 | ], 558 | "clientScopeMappings": { 559 | "account": [ 560 | { 561 | "client": "account-console", 562 | "roles": [ 563 | "manage-account" 564 | ] 565 | } 566 | ] 567 | }, 568 | "clients": [ 569 | { 570 | "id": "2f963220-4bfa-46ce-8797-cc69adcdf0eb", 571 | "clientId": "account", 572 | "name": "${client_account}", 573 | "rootUrl": "${authBaseUrl}", 574 | "baseUrl": "/realms/Spring-Boot-Example/account/", 575 | "surrogateAuthRequired": false, 576 | "enabled": true, 577 | "alwaysDisplayInConsole": false, 578 | "clientAuthenticatorType": "client-secret", 579 | "redirectUris": [ 580 | "/realms/Spring-Boot-Example/account/*" 581 | ], 582 | "webOrigins": [], 583 | "notBefore": 0, 584 | "bearerOnly": false, 585 | "consentRequired": false, 586 | "standardFlowEnabled": true, 587 | "implicitFlowEnabled": false, 588 | "directAccessGrantsEnabled": false, 589 | "serviceAccountsEnabled": false, 590 | "publicClient": true, 591 | "frontchannelLogout": false, 592 | "protocol": "openid-connect", 593 | "attributes": {}, 594 | "authenticationFlowBindingOverrides": {}, 595 | "fullScopeAllowed": false, 596 | "nodeReRegistrationTimeout": 0, 597 | "defaultClientScopes": [ 598 | "web-origins", 599 | "roles", 600 | "profile", 601 | "email" 602 | ], 603 | "optionalClientScopes": [ 604 | "address", 605 | "phone", 606 | "offline_access", 607 | "microprofile-jwt" 608 | ] 609 | }, 610 | { 611 | "id": "8b53d69b-1dbb-441a-8d76-1504fdf5b542", 612 | "clientId": "account-console", 613 | "name": "${client_account-console}", 614 | "rootUrl": "${authBaseUrl}", 615 | "baseUrl": "/realms/Spring-Boot-Example/account/", 616 | "surrogateAuthRequired": false, 617 | "enabled": true, 618 | "alwaysDisplayInConsole": false, 619 | "clientAuthenticatorType": "client-secret", 620 | "redirectUris": [ 621 | "/realms/Spring-Boot-Example/account/*" 622 | ], 623 | "webOrigins": [], 624 | "notBefore": 0, 625 | "bearerOnly": false, 626 | "consentRequired": false, 627 | "standardFlowEnabled": true, 628 | "implicitFlowEnabled": false, 629 | "directAccessGrantsEnabled": false, 630 | "serviceAccountsEnabled": false, 631 | "publicClient": true, 632 | "frontchannelLogout": false, 633 | "protocol": "openid-connect", 634 | "attributes": { 635 | "pkce.code.challenge.method": "S256" 636 | }, 637 | "authenticationFlowBindingOverrides": {}, 638 | "fullScopeAllowed": false, 639 | "nodeReRegistrationTimeout": 0, 640 | "protocolMappers": [ 641 | { 642 | "id": "ca156c36-bf21-4a77-bb32-709a9ae84aa5", 643 | "name": "audience resolve", 644 | "protocol": "openid-connect", 645 | "protocolMapper": "oidc-audience-resolve-mapper", 646 | "consentRequired": false, 647 | "config": {} 648 | } 649 | ], 650 | "defaultClientScopes": [ 651 | "web-origins", 652 | "roles", 653 | "profile", 654 | "email" 655 | ], 656 | "optionalClientScopes": [ 657 | "address", 658 | "phone", 659 | "offline_access", 660 | "microprofile-jwt" 661 | ] 662 | }, 663 | { 664 | "id": "80a71fc4-b35c-41bf-b5ae-d2d65589187e", 665 | "clientId": "admin-cli", 666 | "name": "${client_admin-cli}", 667 | "surrogateAuthRequired": false, 668 | "enabled": true, 669 | "alwaysDisplayInConsole": false, 670 | "clientAuthenticatorType": "client-secret", 671 | "redirectUris": [], 672 | "webOrigins": [], 673 | "notBefore": 0, 674 | "bearerOnly": false, 675 | "consentRequired": false, 676 | "standardFlowEnabled": false, 677 | "implicitFlowEnabled": false, 678 | "directAccessGrantsEnabled": true, 679 | "serviceAccountsEnabled": false, 680 | "publicClient": true, 681 | "frontchannelLogout": false, 682 | "protocol": "openid-connect", 683 | "attributes": {}, 684 | "authenticationFlowBindingOverrides": {}, 685 | "fullScopeAllowed": false, 686 | "nodeReRegistrationTimeout": 0, 687 | "defaultClientScopes": [ 688 | "web-origins", 689 | "roles", 690 | "profile", 691 | "email" 692 | ], 693 | "optionalClientScopes": [ 694 | "address", 695 | "phone", 696 | "offline_access", 697 | "microprofile-jwt" 698 | ] 699 | }, 700 | { 701 | "id": "1240d5b4-07d4-4cef-ac5e-69252b184073", 702 | "clientId": "broker", 703 | "name": "${client_broker}", 704 | "surrogateAuthRequired": false, 705 | "enabled": true, 706 | "alwaysDisplayInConsole": false, 707 | "clientAuthenticatorType": "client-secret", 708 | "redirectUris": [], 709 | "webOrigins": [], 710 | "notBefore": 0, 711 | "bearerOnly": true, 712 | "consentRequired": false, 713 | "standardFlowEnabled": true, 714 | "implicitFlowEnabled": false, 715 | "directAccessGrantsEnabled": false, 716 | "serviceAccountsEnabled": false, 717 | "publicClient": false, 718 | "frontchannelLogout": false, 719 | "protocol": "openid-connect", 720 | "attributes": {}, 721 | "authenticationFlowBindingOverrides": {}, 722 | "fullScopeAllowed": false, 723 | "nodeReRegistrationTimeout": 0, 724 | "defaultClientScopes": [ 725 | "web-origins", 726 | "roles", 727 | "profile", 728 | "email" 729 | ], 730 | "optionalClientScopes": [ 731 | "address", 732 | "phone", 733 | "offline_access", 734 | "microprofile-jwt" 735 | ] 736 | }, 737 | { 738 | "id": "a2b0ddc4-d73a-4c65-a0bb-d08032bb79fc", 739 | "clientId": "realm-management", 740 | "name": "${client_realm-management}", 741 | "surrogateAuthRequired": false, 742 | "enabled": true, 743 | "alwaysDisplayInConsole": false, 744 | "clientAuthenticatorType": "client-secret", 745 | "redirectUris": [], 746 | "webOrigins": [], 747 | "notBefore": 0, 748 | "bearerOnly": true, 749 | "consentRequired": false, 750 | "standardFlowEnabled": true, 751 | "implicitFlowEnabled": false, 752 | "directAccessGrantsEnabled": false, 753 | "serviceAccountsEnabled": false, 754 | "publicClient": false, 755 | "frontchannelLogout": false, 756 | "protocol": "openid-connect", 757 | "attributes": {}, 758 | "authenticationFlowBindingOverrides": {}, 759 | "fullScopeAllowed": false, 760 | "nodeReRegistrationTimeout": 0, 761 | "defaultClientScopes": [ 762 | "web-origins", 763 | "roles", 764 | "profile", 765 | "email" 766 | ], 767 | "optionalClientScopes": [ 768 | "address", 769 | "phone", 770 | "offline_access", 771 | "microprofile-jwt" 772 | ] 773 | }, 774 | { 775 | "id": "e4b55b48-58c0-4888-b547-4570cc159b27", 776 | "clientId": "security-admin-console", 777 | "name": "${client_security-admin-console}", 778 | "rootUrl": "${authAdminUrl}", 779 | "baseUrl": "/admin/Spring-Boot-Example/console/", 780 | "surrogateAuthRequired": false, 781 | "enabled": true, 782 | "alwaysDisplayInConsole": false, 783 | "clientAuthenticatorType": "client-secret", 784 | "redirectUris": [ 785 | "/admin/Spring-Boot-Example/console/*" 786 | ], 787 | "webOrigins": [ 788 | "+" 789 | ], 790 | "notBefore": 0, 791 | "bearerOnly": false, 792 | "consentRequired": false, 793 | "standardFlowEnabled": true, 794 | "implicitFlowEnabled": false, 795 | "directAccessGrantsEnabled": false, 796 | "serviceAccountsEnabled": false, 797 | "publicClient": true, 798 | "frontchannelLogout": false, 799 | "protocol": "openid-connect", 800 | "attributes": { 801 | "pkce.code.challenge.method": "S256" 802 | }, 803 | "authenticationFlowBindingOverrides": {}, 804 | "fullScopeAllowed": false, 805 | "nodeReRegistrationTimeout": 0, 806 | "protocolMappers": [ 807 | { 808 | "id": "0e6eba06-56e4-44e4-8bee-7c9c3f8dfbb6", 809 | "name": "locale", 810 | "protocol": "openid-connect", 811 | "protocolMapper": "oidc-usermodel-attribute-mapper", 812 | "consentRequired": false, 813 | "config": { 814 | "userinfo.token.claim": "true", 815 | "user.attribute": "locale", 816 | "id.token.claim": "true", 817 | "access.token.claim": "true", 818 | "claim.name": "locale", 819 | "jsonType.label": "String" 820 | } 821 | } 822 | ], 823 | "defaultClientScopes": [ 824 | "web-origins", 825 | "roles", 826 | "profile", 827 | "email" 828 | ], 829 | "optionalClientScopes": [ 830 | "address", 831 | "phone", 832 | "offline_access", 833 | "microprofile-jwt" 834 | ] 835 | }, 836 | { 837 | "id": "2b7b280f-04b9-4cd0-9c4d-3698f9915380", 838 | "clientId": "spring-boot-example-app", 839 | "adminUrl": "*", 840 | "baseUrl": "http://localhost:8080", 841 | "surrogateAuthRequired": false, 842 | "enabled": true, 843 | "alwaysDisplayInConsole": false, 844 | "clientAuthenticatorType": "client-secret", 845 | "redirectUris": [ 846 | "*" 847 | ], 848 | "webOrigins": [ 849 | "*" 850 | ], 851 | "notBefore": 0, 852 | "bearerOnly": false, 853 | "consentRequired": false, 854 | "standardFlowEnabled": true, 855 | "implicitFlowEnabled": true, 856 | "directAccessGrantsEnabled": true, 857 | "serviceAccountsEnabled": false, 858 | "publicClient": true, 859 | "frontchannelLogout": false, 860 | "protocol": "openid-connect", 861 | "attributes": { 862 | "id.token.as.detached.signature": "false", 863 | "saml.assertion.signature": "false", 864 | "saml.force.post.binding": "false", 865 | "saml.multivalued.roles": "false", 866 | "saml.encrypt": "false", 867 | "oauth2.device.authorization.grant.enabled": "false", 868 | "backchannel.logout.revoke.offline.tokens": "false", 869 | "saml.server.signature": "false", 870 | "saml.server.signature.keyinfo.ext": "false", 871 | "use.refresh.tokens": "true", 872 | "exclude.session.state.from.auth.response": "false", 873 | "oidc.ciba.grant.enabled": "false", 874 | "saml.artifact.binding": "false", 875 | "backchannel.logout.session.required": "true", 876 | "client_credentials.use_refresh_token": "false", 877 | "saml_force_name_id_format": "false", 878 | "require.pushed.authorization.requests": "false", 879 | "saml.client.signature": "false", 880 | "tls.client.certificate.bound.access.tokens": "false", 881 | "saml.authnstatement": "false", 882 | "display.on.consent.screen": "false", 883 | "saml.onetimeuse.condition": "false" 884 | }, 885 | "authenticationFlowBindingOverrides": {}, 886 | "fullScopeAllowed": true, 887 | "nodeReRegistrationTimeout": -1, 888 | "defaultClientScopes": [ 889 | "web-origins", 890 | "roles", 891 | "profile", 892 | "email" 893 | ], 894 | "optionalClientScopes": [ 895 | "address", 896 | "phone", 897 | "offline_access", 898 | "microprofile-jwt" 899 | ] 900 | } 901 | ], 902 | "clientScopes": [ 903 | { 904 | "id": "7c52dd09-fe20-411b-bbcc-715c9c2705ed", 905 | "name": "address", 906 | "description": "OpenID Connect built-in scope: address", 907 | "protocol": "openid-connect", 908 | "attributes": { 909 | "include.in.token.scope": "true", 910 | "display.on.consent.screen": "true", 911 | "consent.screen.text": "${addressScopeConsentText}" 912 | }, 913 | "protocolMappers": [ 914 | { 915 | "id": "973be932-3ba7-42a4-af3e-d7853ef36bc7", 916 | "name": "address", 917 | "protocol": "openid-connect", 918 | "protocolMapper": "oidc-address-mapper", 919 | "consentRequired": false, 920 | "config": { 921 | "user.attribute.formatted": "formatted", 922 | "user.attribute.country": "country", 923 | "user.attribute.postal_code": "postal_code", 924 | "userinfo.token.claim": "true", 925 | "user.attribute.street": "street", 926 | "id.token.claim": "true", 927 | "user.attribute.region": "region", 928 | "access.token.claim": "true", 929 | "user.attribute.locality": "locality" 930 | } 931 | } 932 | ] 933 | }, 934 | { 935 | "id": "47218f1f-b9f6-463d-b632-a9b9ee8c986e", 936 | "name": "role_list", 937 | "description": "SAML role list", 938 | "protocol": "saml", 939 | "attributes": { 940 | "consent.screen.text": "${samlRoleListScopeConsentText}", 941 | "display.on.consent.screen": "true" 942 | }, 943 | "protocolMappers": [ 944 | { 945 | "id": "2f0da8c2-b277-4b6e-848d-95b4c9f38063", 946 | "name": "role list", 947 | "protocol": "saml", 948 | "protocolMapper": "saml-role-list-mapper", 949 | "consentRequired": false, 950 | "config": { 951 | "single": "false", 952 | "attribute.nameformat": "Basic", 953 | "attribute.name": "Role" 954 | } 955 | } 956 | ] 957 | }, 958 | { 959 | "id": "74882ab8-f448-4c44-91c6-54bacae4162e", 960 | "name": "roles", 961 | "description": "OpenID Connect scope for add user roles to the access token", 962 | "protocol": "openid-connect", 963 | "attributes": { 964 | "include.in.token.scope": "false", 965 | "display.on.consent.screen": "true", 966 | "consent.screen.text": "${rolesScopeConsentText}" 967 | }, 968 | "protocolMappers": [ 969 | { 970 | "id": "4ba87af7-8487-4002-9ff2-0b031664be1b", 971 | "name": "client roles", 972 | "protocol": "openid-connect", 973 | "protocolMapper": "oidc-usermodel-client-role-mapper", 974 | "consentRequired": false, 975 | "config": { 976 | "user.attribute": "foo", 977 | "access.token.claim": "true", 978 | "claim.name": "resource_access.${client_id}.roles", 979 | "jsonType.label": "String", 980 | "multivalued": "true" 981 | } 982 | }, 983 | { 984 | "id": "3876198c-7575-4847-a714-c6092fa64547", 985 | "name": "audience resolve", 986 | "protocol": "openid-connect", 987 | "protocolMapper": "oidc-audience-resolve-mapper", 988 | "consentRequired": false, 989 | "config": {} 990 | }, 991 | { 992 | "id": "6a9c02d6-b41b-40e5-83e8-02a646afbe6a", 993 | "name": "realm roles", 994 | "protocol": "openid-connect", 995 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 996 | "consentRequired": false, 997 | "config": { 998 | "user.attribute": "foo", 999 | "access.token.claim": "true", 1000 | "claim.name": "realm_access.roles", 1001 | "jsonType.label": "String", 1002 | "multivalued": "true" 1003 | } 1004 | } 1005 | ] 1006 | }, 1007 | { 1008 | "id": "88e04206-e25c-4972-b2a9-904f86f3afff", 1009 | "name": "phone", 1010 | "description": "OpenID Connect built-in scope: phone", 1011 | "protocol": "openid-connect", 1012 | "attributes": { 1013 | "include.in.token.scope": "true", 1014 | "display.on.consent.screen": "true", 1015 | "consent.screen.text": "${phoneScopeConsentText}" 1016 | }, 1017 | "protocolMappers": [ 1018 | { 1019 | "id": "5909bf76-bd32-49e5-91c0-ed50ac82a25c", 1020 | "name": "phone number", 1021 | "protocol": "openid-connect", 1022 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1023 | "consentRequired": false, 1024 | "config": { 1025 | "userinfo.token.claim": "true", 1026 | "user.attribute": "phoneNumber", 1027 | "id.token.claim": "true", 1028 | "access.token.claim": "true", 1029 | "claim.name": "phone_number", 1030 | "jsonType.label": "String" 1031 | } 1032 | }, 1033 | { 1034 | "id": "cf282977-9228-478d-9678-4d15e384b2c4", 1035 | "name": "phone number verified", 1036 | "protocol": "openid-connect", 1037 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1038 | "consentRequired": false, 1039 | "config": { 1040 | "userinfo.token.claim": "true", 1041 | "user.attribute": "phoneNumberVerified", 1042 | "id.token.claim": "true", 1043 | "access.token.claim": "true", 1044 | "claim.name": "phone_number_verified", 1045 | "jsonType.label": "boolean" 1046 | } 1047 | } 1048 | ] 1049 | }, 1050 | { 1051 | "id": "3b77f399-383c-4df6-ae14-299302af3c79", 1052 | "name": "microprofile-jwt", 1053 | "description": "Microprofile - JWT built-in scope", 1054 | "protocol": "openid-connect", 1055 | "attributes": { 1056 | "include.in.token.scope": "true", 1057 | "display.on.consent.screen": "false" 1058 | }, 1059 | "protocolMappers": [ 1060 | { 1061 | "id": "41992885-e81a-44b3-852b-10938db0928c", 1062 | "name": "groups", 1063 | "protocol": "openid-connect", 1064 | "protocolMapper": "oidc-usermodel-realm-role-mapper", 1065 | "consentRequired": false, 1066 | "config": { 1067 | "multivalued": "true", 1068 | "userinfo.token.claim": "true", 1069 | "user.attribute": "foo", 1070 | "id.token.claim": "true", 1071 | "access.token.claim": "true", 1072 | "claim.name": "groups", 1073 | "jsonType.label": "String" 1074 | } 1075 | }, 1076 | { 1077 | "id": "b9734a5d-1825-4c08-8b3c-d4da5d44365d", 1078 | "name": "upn", 1079 | "protocol": "openid-connect", 1080 | "protocolMapper": "oidc-usermodel-property-mapper", 1081 | "consentRequired": false, 1082 | "config": { 1083 | "userinfo.token.claim": "true", 1084 | "user.attribute": "username", 1085 | "id.token.claim": "true", 1086 | "access.token.claim": "true", 1087 | "claim.name": "upn", 1088 | "jsonType.label": "String" 1089 | } 1090 | } 1091 | ] 1092 | }, 1093 | { 1094 | "id": "8f1e6054-832e-4a9f-8e79-fd7b35ad0525", 1095 | "name": "offline_access", 1096 | "description": "OpenID Connect built-in scope: offline_access", 1097 | "protocol": "openid-connect", 1098 | "attributes": { 1099 | "consent.screen.text": "${offlineAccessScopeConsentText}", 1100 | "display.on.consent.screen": "true" 1101 | } 1102 | }, 1103 | { 1104 | "id": "74785dec-68d5-44e6-bd94-c28ee23b4a2a", 1105 | "name": "profile", 1106 | "description": "OpenID Connect built-in scope: profile", 1107 | "protocol": "openid-connect", 1108 | "attributes": { 1109 | "include.in.token.scope": "true", 1110 | "display.on.consent.screen": "true", 1111 | "consent.screen.text": "${profileScopeConsentText}" 1112 | }, 1113 | "protocolMappers": [ 1114 | { 1115 | "id": "8a576c62-0093-4988-baef-6180db7d9486", 1116 | "name": "birthdate", 1117 | "protocol": "openid-connect", 1118 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1119 | "consentRequired": false, 1120 | "config": { 1121 | "userinfo.token.claim": "true", 1122 | "user.attribute": "birthdate", 1123 | "id.token.claim": "true", 1124 | "access.token.claim": "true", 1125 | "claim.name": "birthdate", 1126 | "jsonType.label": "String" 1127 | } 1128 | }, 1129 | { 1130 | "id": "e69b0465-7a4c-4850-9ebc-8c09c302407a", 1131 | "name": "family name", 1132 | "protocol": "openid-connect", 1133 | "protocolMapper": "oidc-usermodel-property-mapper", 1134 | "consentRequired": false, 1135 | "config": { 1136 | "userinfo.token.claim": "true", 1137 | "user.attribute": "lastName", 1138 | "id.token.claim": "true", 1139 | "access.token.claim": "true", 1140 | "claim.name": "family_name", 1141 | "jsonType.label": "String" 1142 | } 1143 | }, 1144 | { 1145 | "id": "44c79304-302f-47c7-be66-cdd69c16cb6a", 1146 | "name": "nickname", 1147 | "protocol": "openid-connect", 1148 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1149 | "consentRequired": false, 1150 | "config": { 1151 | "userinfo.token.claim": "true", 1152 | "user.attribute": "nickname", 1153 | "id.token.claim": "true", 1154 | "access.token.claim": "true", 1155 | "claim.name": "nickname", 1156 | "jsonType.label": "String" 1157 | } 1158 | }, 1159 | { 1160 | "id": "796e0d4c-1861-4073-8930-8f0db3f93029", 1161 | "name": "website", 1162 | "protocol": "openid-connect", 1163 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1164 | "consentRequired": false, 1165 | "config": { 1166 | "userinfo.token.claim": "true", 1167 | "user.attribute": "website", 1168 | "id.token.claim": "true", 1169 | "access.token.claim": "true", 1170 | "claim.name": "website", 1171 | "jsonType.label": "String" 1172 | } 1173 | }, 1174 | { 1175 | "id": "5b7d8d07-6dbb-4edb-bb27-f2467c5be400", 1176 | "name": "picture", 1177 | "protocol": "openid-connect", 1178 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1179 | "consentRequired": false, 1180 | "config": { 1181 | "userinfo.token.claim": "true", 1182 | "user.attribute": "picture", 1183 | "id.token.claim": "true", 1184 | "access.token.claim": "true", 1185 | "claim.name": "picture", 1186 | "jsonType.label": "String" 1187 | } 1188 | }, 1189 | { 1190 | "id": "3bb9505e-e181-4770-ab00-779ab526a631", 1191 | "name": "updated at", 1192 | "protocol": "openid-connect", 1193 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1194 | "consentRequired": false, 1195 | "config": { 1196 | "userinfo.token.claim": "true", 1197 | "user.attribute": "updatedAt", 1198 | "id.token.claim": "true", 1199 | "access.token.claim": "true", 1200 | "claim.name": "updated_at", 1201 | "jsonType.label": "String" 1202 | } 1203 | }, 1204 | { 1205 | "id": "e9819cbf-831f-4a8d-bd1e-c6260ef3a924", 1206 | "name": "gender", 1207 | "protocol": "openid-connect", 1208 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1209 | "consentRequired": false, 1210 | "config": { 1211 | "userinfo.token.claim": "true", 1212 | "user.attribute": "gender", 1213 | "id.token.claim": "true", 1214 | "access.token.claim": "true", 1215 | "claim.name": "gender", 1216 | "jsonType.label": "String" 1217 | } 1218 | }, 1219 | { 1220 | "id": "8cdcda2c-d787-4d84-b721-256dc6c329ec", 1221 | "name": "locale", 1222 | "protocol": "openid-connect", 1223 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1224 | "consentRequired": false, 1225 | "config": { 1226 | "userinfo.token.claim": "true", 1227 | "user.attribute": "locale", 1228 | "id.token.claim": "true", 1229 | "access.token.claim": "true", 1230 | "claim.name": "locale", 1231 | "jsonType.label": "String" 1232 | } 1233 | }, 1234 | { 1235 | "id": "20a932f2-156d-4542-a926-aee10738fd4f", 1236 | "name": "zoneinfo", 1237 | "protocol": "openid-connect", 1238 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1239 | "consentRequired": false, 1240 | "config": { 1241 | "userinfo.token.claim": "true", 1242 | "user.attribute": "zoneinfo", 1243 | "id.token.claim": "true", 1244 | "access.token.claim": "true", 1245 | "claim.name": "zoneinfo", 1246 | "jsonType.label": "String" 1247 | } 1248 | }, 1249 | { 1250 | "id": "6641c185-7daa-495b-a809-043c05e99f65", 1251 | "name": "middle name", 1252 | "protocol": "openid-connect", 1253 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1254 | "consentRequired": false, 1255 | "config": { 1256 | "userinfo.token.claim": "true", 1257 | "user.attribute": "middleName", 1258 | "id.token.claim": "true", 1259 | "access.token.claim": "true", 1260 | "claim.name": "middle_name", 1261 | "jsonType.label": "String" 1262 | } 1263 | }, 1264 | { 1265 | "id": "a97a0615-24c9-4020-b12d-6b4a4ec3924d", 1266 | "name": "username", 1267 | "protocol": "openid-connect", 1268 | "protocolMapper": "oidc-usermodel-property-mapper", 1269 | "consentRequired": false, 1270 | "config": { 1271 | "userinfo.token.claim": "true", 1272 | "user.attribute": "username", 1273 | "id.token.claim": "true", 1274 | "access.token.claim": "true", 1275 | "claim.name": "preferred_username", 1276 | "jsonType.label": "String" 1277 | } 1278 | }, 1279 | { 1280 | "id": "babe6629-85b7-48a1-8cd1-798deb354300", 1281 | "name": "full name", 1282 | "protocol": "openid-connect", 1283 | "protocolMapper": "oidc-full-name-mapper", 1284 | "consentRequired": false, 1285 | "config": { 1286 | "id.token.claim": "true", 1287 | "access.token.claim": "true", 1288 | "userinfo.token.claim": "true" 1289 | } 1290 | }, 1291 | { 1292 | "id": "8049bd76-c160-40ca-8d37-cb4a8443cc9a", 1293 | "name": "given name", 1294 | "protocol": "openid-connect", 1295 | "protocolMapper": "oidc-usermodel-property-mapper", 1296 | "consentRequired": false, 1297 | "config": { 1298 | "userinfo.token.claim": "true", 1299 | "user.attribute": "firstName", 1300 | "id.token.claim": "true", 1301 | "access.token.claim": "true", 1302 | "claim.name": "given_name", 1303 | "jsonType.label": "String" 1304 | } 1305 | }, 1306 | { 1307 | "id": "feb0c646-ee8c-4da2-aaa8-ee48f47020fa", 1308 | "name": "profile", 1309 | "protocol": "openid-connect", 1310 | "protocolMapper": "oidc-usermodel-attribute-mapper", 1311 | "consentRequired": false, 1312 | "config": { 1313 | "userinfo.token.claim": "true", 1314 | "user.attribute": "profile", 1315 | "id.token.claim": "true", 1316 | "access.token.claim": "true", 1317 | "claim.name": "profile", 1318 | "jsonType.label": "String" 1319 | } 1320 | } 1321 | ] 1322 | }, 1323 | { 1324 | "id": "c7c2f1d3-d88d-48f5-9e65-0c393190244a", 1325 | "name": "email", 1326 | "description": "OpenID Connect built-in scope: email", 1327 | "protocol": "openid-connect", 1328 | "attributes": { 1329 | "include.in.token.scope": "true", 1330 | "display.on.consent.screen": "true", 1331 | "consent.screen.text": "${emailScopeConsentText}" 1332 | }, 1333 | "protocolMappers": [ 1334 | { 1335 | "id": "a6e74316-8131-46da-9536-4dc3cd3cc1b8", 1336 | "name": "email verified", 1337 | "protocol": "openid-connect", 1338 | "protocolMapper": "oidc-usermodel-property-mapper", 1339 | "consentRequired": false, 1340 | "config": { 1341 | "userinfo.token.claim": "true", 1342 | "user.attribute": "emailVerified", 1343 | "id.token.claim": "true", 1344 | "access.token.claim": "true", 1345 | "claim.name": "email_verified", 1346 | "jsonType.label": "boolean" 1347 | } 1348 | }, 1349 | { 1350 | "id": "88ce7caa-d88d-4b0b-985d-9e729a39a600", 1351 | "name": "email", 1352 | "protocol": "openid-connect", 1353 | "protocolMapper": "oidc-usermodel-property-mapper", 1354 | "consentRequired": false, 1355 | "config": { 1356 | "userinfo.token.claim": "true", 1357 | "user.attribute": "email", 1358 | "id.token.claim": "true", 1359 | "access.token.claim": "true", 1360 | "claim.name": "email", 1361 | "jsonType.label": "String" 1362 | } 1363 | } 1364 | ] 1365 | }, 1366 | { 1367 | "id": "d91be3d3-86bf-4f88-9859-9bd5028cce0f", 1368 | "name": "web-origins", 1369 | "description": "OpenID Connect scope for add allowed web origins to the access token", 1370 | "protocol": "openid-connect", 1371 | "attributes": { 1372 | "include.in.token.scope": "false", 1373 | "display.on.consent.screen": "false", 1374 | "consent.screen.text": "" 1375 | }, 1376 | "protocolMappers": [ 1377 | { 1378 | "id": "29ee670e-358d-4fa0-8882-51323d3759bc", 1379 | "name": "allowed web origins", 1380 | "protocol": "openid-connect", 1381 | "protocolMapper": "oidc-allowed-origins-mapper", 1382 | "consentRequired": false, 1383 | "config": {} 1384 | } 1385 | ] 1386 | } 1387 | ], 1388 | "defaultDefaultClientScopes": [ 1389 | "role_list", 1390 | "profile", 1391 | "roles", 1392 | "email", 1393 | "web-origins" 1394 | ], 1395 | "defaultOptionalClientScopes": [ 1396 | "microprofile-jwt", 1397 | "address", 1398 | "phone", 1399 | "offline_access" 1400 | ], 1401 | "browserSecurityHeaders": { 1402 | "contentSecurityPolicyReportOnly": "", 1403 | "xContentTypeOptions": "nosniff", 1404 | "xRobotsTag": "none", 1405 | "xFrameOptions": "SAMEORIGIN", 1406 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';", 1407 | "xXSSProtection": "1; mode=block", 1408 | "strictTransportSecurity": "max-age=31536000; includeSubDomains" 1409 | }, 1410 | "smtpServer": {}, 1411 | "eventsEnabled": false, 1412 | "eventsListeners": [ 1413 | "jboss-logging" 1414 | ], 1415 | "enabledEventTypes": [], 1416 | "adminEventsEnabled": false, 1417 | "adminEventsDetailsEnabled": false, 1418 | "identityProviders": [], 1419 | "identityProviderMappers": [], 1420 | "components": { 1421 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [ 1422 | { 1423 | "id": "cee4f725-8eca-48be-8ebc-dbd5469fc3f6", 1424 | "name": "Allowed Protocol Mapper Types", 1425 | "providerId": "allowed-protocol-mappers", 1426 | "subType": "anonymous", 1427 | "subComponents": {}, 1428 | "config": { 1429 | "allowed-protocol-mapper-types": [ 1430 | "oidc-usermodel-property-mapper", 1431 | "oidc-address-mapper", 1432 | "oidc-full-name-mapper", 1433 | "saml-user-attribute-mapper", 1434 | "saml-user-property-mapper", 1435 | "oidc-usermodel-attribute-mapper", 1436 | "oidc-sha256-pairwise-sub-mapper", 1437 | "saml-role-list-mapper" 1438 | ] 1439 | } 1440 | }, 1441 | { 1442 | "id": "0ae3e54d-343a-437c-bee4-8f1f3f9a1b57", 1443 | "name": "Trusted Hosts", 1444 | "providerId": "trusted-hosts", 1445 | "subType": "anonymous", 1446 | "subComponents": {}, 1447 | "config": { 1448 | "host-sending-registration-request-must-match": [ 1449 | "true" 1450 | ], 1451 | "client-uris-must-match": [ 1452 | "true" 1453 | ] 1454 | } 1455 | }, 1456 | { 1457 | "id": "ada6edf9-6105-47f8-89c9-df851100995c", 1458 | "name": "Full Scope Disabled", 1459 | "providerId": "scope", 1460 | "subType": "anonymous", 1461 | "subComponents": {}, 1462 | "config": {} 1463 | }, 1464 | { 1465 | "id": "5c215fe8-138c-4c76-92e0-0184094eee81", 1466 | "name": "Max Clients Limit", 1467 | "providerId": "max-clients", 1468 | "subType": "anonymous", 1469 | "subComponents": {}, 1470 | "config": { 1471 | "max-clients": [ 1472 | "200" 1473 | ] 1474 | } 1475 | }, 1476 | { 1477 | "id": "22e34b5e-967c-4840-8781-6d45fa650f6a", 1478 | "name": "Allowed Client Scopes", 1479 | "providerId": "allowed-client-templates", 1480 | "subType": "anonymous", 1481 | "subComponents": {}, 1482 | "config": { 1483 | "allow-default-scopes": [ 1484 | "true" 1485 | ] 1486 | } 1487 | }, 1488 | { 1489 | "id": "8649a03c-d6f0-4161-9e38-e54f75434cb3", 1490 | "name": "Allowed Client Scopes", 1491 | "providerId": "allowed-client-templates", 1492 | "subType": "authenticated", 1493 | "subComponents": {}, 1494 | "config": { 1495 | "allow-default-scopes": [ 1496 | "true" 1497 | ] 1498 | } 1499 | }, 1500 | { 1501 | "id": "d981aaf1-e449-4fc3-ba1b-03d1d6fb0aae", 1502 | "name": "Allowed Protocol Mapper Types", 1503 | "providerId": "allowed-protocol-mappers", 1504 | "subType": "authenticated", 1505 | "subComponents": {}, 1506 | "config": { 1507 | "allowed-protocol-mapper-types": [ 1508 | "oidc-sha256-pairwise-sub-mapper", 1509 | "oidc-usermodel-attribute-mapper", 1510 | "oidc-address-mapper", 1511 | "saml-user-property-mapper", 1512 | "saml-user-attribute-mapper", 1513 | "oidc-full-name-mapper", 1514 | "saml-role-list-mapper", 1515 | "oidc-usermodel-property-mapper" 1516 | ] 1517 | } 1518 | }, 1519 | { 1520 | "id": "e69d686c-289b-4eef-b490-8d867f350e02", 1521 | "name": "Consent Required", 1522 | "providerId": "consent-required", 1523 | "subType": "anonymous", 1524 | "subComponents": {}, 1525 | "config": {} 1526 | } 1527 | ], 1528 | "org.keycloak.keys.KeyProvider": [ 1529 | { 1530 | "id": "eddfc655-3e9e-46b8-8007-83d976cbe452", 1531 | "name": "rsa-generated", 1532 | "providerId": "rsa-generated", 1533 | "subComponents": {}, 1534 | "config": { 1535 | "keyUse": [ 1536 | "sig" 1537 | ], 1538 | "priority": [ 1539 | "100" 1540 | ] 1541 | } 1542 | }, 1543 | { 1544 | "id": "afba3ff3-0c30-4e2b-9a23-007d0e95c0d8", 1545 | "name": "hmac-generated", 1546 | "providerId": "hmac-generated", 1547 | "subComponents": {}, 1548 | "config": { 1549 | "priority": [ 1550 | "100" 1551 | ], 1552 | "algorithm": [ 1553 | "HS256" 1554 | ] 1555 | } 1556 | }, 1557 | { 1558 | "id": "48be4e55-eadc-4c0c-8d57-7994a9a9ece1", 1559 | "name": "aes-generated", 1560 | "providerId": "aes-generated", 1561 | "subComponents": {}, 1562 | "config": { 1563 | "priority": [ 1564 | "100" 1565 | ] 1566 | } 1567 | }, 1568 | { 1569 | "id": "8d32e17a-7fcf-4520-a3f2-3edacf927ead", 1570 | "name": "rsa-enc-generated", 1571 | "providerId": "rsa-generated", 1572 | "subComponents": {}, 1573 | "config": { 1574 | "keyUse": [ 1575 | "enc" 1576 | ], 1577 | "priority": [ 1578 | "100" 1579 | ] 1580 | } 1581 | } 1582 | ] 1583 | }, 1584 | "internationalizationEnabled": false, 1585 | "supportedLocales": [], 1586 | "authenticationFlows": [ 1587 | { 1588 | "id": "844def20-137c-4a53-8307-21a54678abc9", 1589 | "alias": "Account verification options", 1590 | "description": "Method with which to verity the existing account", 1591 | "providerId": "basic-flow", 1592 | "topLevel": false, 1593 | "builtIn": true, 1594 | "authenticationExecutions": [ 1595 | { 1596 | "authenticator": "idp-email-verification", 1597 | "authenticatorFlow": false, 1598 | "requirement": "ALTERNATIVE", 1599 | "priority": 10, 1600 | "userSetupAllowed": false, 1601 | "autheticatorFlow": false 1602 | }, 1603 | { 1604 | "authenticatorFlow": true, 1605 | "requirement": "ALTERNATIVE", 1606 | "priority": 20, 1607 | "flowAlias": "Verify Existing Account by Re-authentication", 1608 | "userSetupAllowed": false, 1609 | "autheticatorFlow": true 1610 | } 1611 | ] 1612 | }, 1613 | { 1614 | "id": "910b15a2-2c44-495f-b5b8-92b0a9ecfedb", 1615 | "alias": "Authentication Options", 1616 | "description": "Authentication options.", 1617 | "providerId": "basic-flow", 1618 | "topLevel": false, 1619 | "builtIn": true, 1620 | "authenticationExecutions": [ 1621 | { 1622 | "authenticator": "basic-auth", 1623 | "authenticatorFlow": false, 1624 | "requirement": "REQUIRED", 1625 | "priority": 10, 1626 | "userSetupAllowed": false, 1627 | "autheticatorFlow": false 1628 | }, 1629 | { 1630 | "authenticator": "basic-auth-otp", 1631 | "authenticatorFlow": false, 1632 | "requirement": "DISABLED", 1633 | "priority": 20, 1634 | "userSetupAllowed": false, 1635 | "autheticatorFlow": false 1636 | }, 1637 | { 1638 | "authenticator": "auth-spnego", 1639 | "authenticatorFlow": false, 1640 | "requirement": "DISABLED", 1641 | "priority": 30, 1642 | "userSetupAllowed": false, 1643 | "autheticatorFlow": false 1644 | } 1645 | ] 1646 | }, 1647 | { 1648 | "id": "146c1dd1-bc60-4cc7-8021-aab6f46c2b69", 1649 | "alias": "Browser - Conditional OTP", 1650 | "description": "Flow to determine if the OTP is required for the authentication", 1651 | "providerId": "basic-flow", 1652 | "topLevel": false, 1653 | "builtIn": true, 1654 | "authenticationExecutions": [ 1655 | { 1656 | "authenticator": "conditional-user-configured", 1657 | "authenticatorFlow": false, 1658 | "requirement": "REQUIRED", 1659 | "priority": 10, 1660 | "userSetupAllowed": false, 1661 | "autheticatorFlow": false 1662 | }, 1663 | { 1664 | "authenticator": "auth-otp-form", 1665 | "authenticatorFlow": false, 1666 | "requirement": "REQUIRED", 1667 | "priority": 20, 1668 | "userSetupAllowed": false, 1669 | "autheticatorFlow": false 1670 | } 1671 | ] 1672 | }, 1673 | { 1674 | "id": "d23f4a1d-3032-4aa3-99d2-8a951e5eacb3", 1675 | "alias": "Direct Grant - Conditional OTP", 1676 | "description": "Flow to determine if the OTP is required for the authentication", 1677 | "providerId": "basic-flow", 1678 | "topLevel": false, 1679 | "builtIn": true, 1680 | "authenticationExecutions": [ 1681 | { 1682 | "authenticator": "conditional-user-configured", 1683 | "authenticatorFlow": false, 1684 | "requirement": "REQUIRED", 1685 | "priority": 10, 1686 | "userSetupAllowed": false, 1687 | "autheticatorFlow": false 1688 | }, 1689 | { 1690 | "authenticator": "direct-grant-validate-otp", 1691 | "authenticatorFlow": false, 1692 | "requirement": "REQUIRED", 1693 | "priority": 20, 1694 | "userSetupAllowed": false, 1695 | "autheticatorFlow": false 1696 | } 1697 | ] 1698 | }, 1699 | { 1700 | "id": "b8bcac65-fd2c-494d-9b65-dc141f48a318", 1701 | "alias": "First broker login - Conditional OTP", 1702 | "description": "Flow to determine if the OTP is required for the authentication", 1703 | "providerId": "basic-flow", 1704 | "topLevel": false, 1705 | "builtIn": true, 1706 | "authenticationExecutions": [ 1707 | { 1708 | "authenticator": "conditional-user-configured", 1709 | "authenticatorFlow": false, 1710 | "requirement": "REQUIRED", 1711 | "priority": 10, 1712 | "userSetupAllowed": false, 1713 | "autheticatorFlow": false 1714 | }, 1715 | { 1716 | "authenticator": "auth-otp-form", 1717 | "authenticatorFlow": false, 1718 | "requirement": "REQUIRED", 1719 | "priority": 20, 1720 | "userSetupAllowed": false, 1721 | "autheticatorFlow": false 1722 | } 1723 | ] 1724 | }, 1725 | { 1726 | "id": "ea0a9bac-bab5-439f-97a2-30955a442223", 1727 | "alias": "Handle Existing Account", 1728 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider", 1729 | "providerId": "basic-flow", 1730 | "topLevel": false, 1731 | "builtIn": true, 1732 | "authenticationExecutions": [ 1733 | { 1734 | "authenticator": "idp-confirm-link", 1735 | "authenticatorFlow": false, 1736 | "requirement": "REQUIRED", 1737 | "priority": 10, 1738 | "userSetupAllowed": false, 1739 | "autheticatorFlow": false 1740 | }, 1741 | { 1742 | "authenticatorFlow": true, 1743 | "requirement": "REQUIRED", 1744 | "priority": 20, 1745 | "flowAlias": "Account verification options", 1746 | "userSetupAllowed": false, 1747 | "autheticatorFlow": true 1748 | } 1749 | ] 1750 | }, 1751 | { 1752 | "id": "47d1a816-1c49-4177-b3e1-d176b82e68df", 1753 | "alias": "Reset - Conditional OTP", 1754 | "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.", 1755 | "providerId": "basic-flow", 1756 | "topLevel": false, 1757 | "builtIn": true, 1758 | "authenticationExecutions": [ 1759 | { 1760 | "authenticator": "conditional-user-configured", 1761 | "authenticatorFlow": false, 1762 | "requirement": "REQUIRED", 1763 | "priority": 10, 1764 | "userSetupAllowed": false, 1765 | "autheticatorFlow": false 1766 | }, 1767 | { 1768 | "authenticator": "reset-otp", 1769 | "authenticatorFlow": false, 1770 | "requirement": "REQUIRED", 1771 | "priority": 20, 1772 | "userSetupAllowed": false, 1773 | "autheticatorFlow": false 1774 | } 1775 | ] 1776 | }, 1777 | { 1778 | "id": "e4a5c854-fc02-4323-916b-c36eb769b01e", 1779 | "alias": "User creation or linking", 1780 | "description": "Flow for the existing/non-existing user alternatives", 1781 | "providerId": "basic-flow", 1782 | "topLevel": false, 1783 | "builtIn": true, 1784 | "authenticationExecutions": [ 1785 | { 1786 | "authenticatorConfig": "create unique user config", 1787 | "authenticator": "idp-create-user-if-unique", 1788 | "authenticatorFlow": false, 1789 | "requirement": "ALTERNATIVE", 1790 | "priority": 10, 1791 | "userSetupAllowed": false, 1792 | "autheticatorFlow": false 1793 | }, 1794 | { 1795 | "authenticatorFlow": true, 1796 | "requirement": "ALTERNATIVE", 1797 | "priority": 20, 1798 | "flowAlias": "Handle Existing Account", 1799 | "userSetupAllowed": false, 1800 | "autheticatorFlow": true 1801 | } 1802 | ] 1803 | }, 1804 | { 1805 | "id": "9cf13ef1-29a1-4a0c-b5a5-b4bd845055fe", 1806 | "alias": "Verify Existing Account by Re-authentication", 1807 | "description": "Reauthentication of existing account", 1808 | "providerId": "basic-flow", 1809 | "topLevel": false, 1810 | "builtIn": true, 1811 | "authenticationExecutions": [ 1812 | { 1813 | "authenticator": "idp-username-password-form", 1814 | "authenticatorFlow": false, 1815 | "requirement": "REQUIRED", 1816 | "priority": 10, 1817 | "userSetupAllowed": false, 1818 | "autheticatorFlow": false 1819 | }, 1820 | { 1821 | "authenticatorFlow": true, 1822 | "requirement": "CONDITIONAL", 1823 | "priority": 20, 1824 | "flowAlias": "First broker login - Conditional OTP", 1825 | "userSetupAllowed": false, 1826 | "autheticatorFlow": true 1827 | } 1828 | ] 1829 | }, 1830 | { 1831 | "id": "c3c54c0b-96f2-47c2-99ed-280310375bdc", 1832 | "alias": "browser", 1833 | "description": "browser based authentication", 1834 | "providerId": "basic-flow", 1835 | "topLevel": true, 1836 | "builtIn": true, 1837 | "authenticationExecutions": [ 1838 | { 1839 | "authenticator": "auth-cookie", 1840 | "authenticatorFlow": false, 1841 | "requirement": "ALTERNATIVE", 1842 | "priority": 10, 1843 | "userSetupAllowed": false, 1844 | "autheticatorFlow": false 1845 | }, 1846 | { 1847 | "authenticator": "auth-spnego", 1848 | "authenticatorFlow": false, 1849 | "requirement": "DISABLED", 1850 | "priority": 20, 1851 | "userSetupAllowed": false, 1852 | "autheticatorFlow": false 1853 | }, 1854 | { 1855 | "authenticator": "identity-provider-redirector", 1856 | "authenticatorFlow": false, 1857 | "requirement": "ALTERNATIVE", 1858 | "priority": 25, 1859 | "userSetupAllowed": false, 1860 | "autheticatorFlow": false 1861 | }, 1862 | { 1863 | "authenticatorFlow": true, 1864 | "requirement": "ALTERNATIVE", 1865 | "priority": 30, 1866 | "flowAlias": "forms", 1867 | "userSetupAllowed": false, 1868 | "autheticatorFlow": true 1869 | } 1870 | ] 1871 | }, 1872 | { 1873 | "id": "83f66abe-d49c-4574-81ea-0058c60b1766", 1874 | "alias": "clients", 1875 | "description": "Base authentication for clients", 1876 | "providerId": "client-flow", 1877 | "topLevel": true, 1878 | "builtIn": true, 1879 | "authenticationExecutions": [ 1880 | { 1881 | "authenticator": "client-secret", 1882 | "authenticatorFlow": false, 1883 | "requirement": "ALTERNATIVE", 1884 | "priority": 10, 1885 | "userSetupAllowed": false, 1886 | "autheticatorFlow": false 1887 | }, 1888 | { 1889 | "authenticator": "client-jwt", 1890 | "authenticatorFlow": false, 1891 | "requirement": "ALTERNATIVE", 1892 | "priority": 20, 1893 | "userSetupAllowed": false, 1894 | "autheticatorFlow": false 1895 | }, 1896 | { 1897 | "authenticator": "client-secret-jwt", 1898 | "authenticatorFlow": false, 1899 | "requirement": "ALTERNATIVE", 1900 | "priority": 30, 1901 | "userSetupAllowed": false, 1902 | "autheticatorFlow": false 1903 | }, 1904 | { 1905 | "authenticator": "client-x509", 1906 | "authenticatorFlow": false, 1907 | "requirement": "ALTERNATIVE", 1908 | "priority": 40, 1909 | "userSetupAllowed": false, 1910 | "autheticatorFlow": false 1911 | } 1912 | ] 1913 | }, 1914 | { 1915 | "id": "170f3f34-cae1-43ed-a567-b3202e772d93", 1916 | "alias": "direct grant", 1917 | "description": "OpenID Connect Resource Owner Grant", 1918 | "providerId": "basic-flow", 1919 | "topLevel": true, 1920 | "builtIn": true, 1921 | "authenticationExecutions": [ 1922 | { 1923 | "authenticator": "direct-grant-validate-username", 1924 | "authenticatorFlow": false, 1925 | "requirement": "REQUIRED", 1926 | "priority": 10, 1927 | "userSetupAllowed": false, 1928 | "autheticatorFlow": false 1929 | }, 1930 | { 1931 | "authenticator": "direct-grant-validate-password", 1932 | "authenticatorFlow": false, 1933 | "requirement": "REQUIRED", 1934 | "priority": 20, 1935 | "userSetupAllowed": false, 1936 | "autheticatorFlow": false 1937 | }, 1938 | { 1939 | "authenticatorFlow": true, 1940 | "requirement": "CONDITIONAL", 1941 | "priority": 30, 1942 | "flowAlias": "Direct Grant - Conditional OTP", 1943 | "userSetupAllowed": false, 1944 | "autheticatorFlow": true 1945 | } 1946 | ] 1947 | }, 1948 | { 1949 | "id": "09b16c6f-d109-41b6-a4c0-18826fd3ebfa", 1950 | "alias": "docker auth", 1951 | "description": "Used by Docker clients to authenticate against the IDP", 1952 | "providerId": "basic-flow", 1953 | "topLevel": true, 1954 | "builtIn": true, 1955 | "authenticationExecutions": [ 1956 | { 1957 | "authenticator": "docker-http-basic-authenticator", 1958 | "authenticatorFlow": false, 1959 | "requirement": "REQUIRED", 1960 | "priority": 10, 1961 | "userSetupAllowed": false, 1962 | "autheticatorFlow": false 1963 | } 1964 | ] 1965 | }, 1966 | { 1967 | "id": "0cdfed69-584f-49b1-a2b8-dc1468a1c1d7", 1968 | "alias": "first broker login", 1969 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account", 1970 | "providerId": "basic-flow", 1971 | "topLevel": true, 1972 | "builtIn": true, 1973 | "authenticationExecutions": [ 1974 | { 1975 | "authenticatorConfig": "review profile config", 1976 | "authenticator": "idp-review-profile", 1977 | "authenticatorFlow": false, 1978 | "requirement": "REQUIRED", 1979 | "priority": 10, 1980 | "userSetupAllowed": false, 1981 | "autheticatorFlow": false 1982 | }, 1983 | { 1984 | "authenticatorFlow": true, 1985 | "requirement": "REQUIRED", 1986 | "priority": 20, 1987 | "flowAlias": "User creation or linking", 1988 | "userSetupAllowed": false, 1989 | "autheticatorFlow": true 1990 | } 1991 | ] 1992 | }, 1993 | { 1994 | "id": "97ff2626-802d-4fc2-9862-6c589d6976a7", 1995 | "alias": "forms", 1996 | "description": "Username, password, otp and other auth forms.", 1997 | "providerId": "basic-flow", 1998 | "topLevel": false, 1999 | "builtIn": true, 2000 | "authenticationExecutions": [ 2001 | { 2002 | "authenticator": "auth-username-password-form", 2003 | "authenticatorFlow": false, 2004 | "requirement": "REQUIRED", 2005 | "priority": 10, 2006 | "userSetupAllowed": false, 2007 | "autheticatorFlow": false 2008 | }, 2009 | { 2010 | "authenticatorFlow": true, 2011 | "requirement": "CONDITIONAL", 2012 | "priority": 20, 2013 | "flowAlias": "Browser - Conditional OTP", 2014 | "userSetupAllowed": false, 2015 | "autheticatorFlow": true 2016 | } 2017 | ] 2018 | }, 2019 | { 2020 | "id": "491a5bc4-a287-4ba4-bb94-8fb6a6054f0b", 2021 | "alias": "http challenge", 2022 | "description": "An authentication flow based on challenge-response HTTP Authentication Schemes", 2023 | "providerId": "basic-flow", 2024 | "topLevel": true, 2025 | "builtIn": true, 2026 | "authenticationExecutions": [ 2027 | { 2028 | "authenticator": "no-cookie-redirect", 2029 | "authenticatorFlow": false, 2030 | "requirement": "REQUIRED", 2031 | "priority": 10, 2032 | "userSetupAllowed": false, 2033 | "autheticatorFlow": false 2034 | }, 2035 | { 2036 | "authenticatorFlow": true, 2037 | "requirement": "REQUIRED", 2038 | "priority": 20, 2039 | "flowAlias": "Authentication Options", 2040 | "userSetupAllowed": false, 2041 | "autheticatorFlow": true 2042 | } 2043 | ] 2044 | }, 2045 | { 2046 | "id": "f0dd9335-3cc6-45ab-8ed0-ade854ac5315", 2047 | "alias": "registration", 2048 | "description": "registration flow", 2049 | "providerId": "basic-flow", 2050 | "topLevel": true, 2051 | "builtIn": true, 2052 | "authenticationExecutions": [ 2053 | { 2054 | "authenticator": "registration-page-form", 2055 | "authenticatorFlow": true, 2056 | "requirement": "REQUIRED", 2057 | "priority": 10, 2058 | "flowAlias": "registration form", 2059 | "userSetupAllowed": false, 2060 | "autheticatorFlow": true 2061 | } 2062 | ] 2063 | }, 2064 | { 2065 | "id": "331a6669-8708-4b42-b8c8-c6ee2b76eaad", 2066 | "alias": "registration form", 2067 | "description": "registration form", 2068 | "providerId": "form-flow", 2069 | "topLevel": false, 2070 | "builtIn": true, 2071 | "authenticationExecutions": [ 2072 | { 2073 | "authenticator": "registration-user-creation", 2074 | "authenticatorFlow": false, 2075 | "requirement": "REQUIRED", 2076 | "priority": 20, 2077 | "userSetupAllowed": false, 2078 | "autheticatorFlow": false 2079 | }, 2080 | { 2081 | "authenticator": "registration-profile-action", 2082 | "authenticatorFlow": false, 2083 | "requirement": "REQUIRED", 2084 | "priority": 40, 2085 | "userSetupAllowed": false, 2086 | "autheticatorFlow": false 2087 | }, 2088 | { 2089 | "authenticator": "registration-password-action", 2090 | "authenticatorFlow": false, 2091 | "requirement": "REQUIRED", 2092 | "priority": 50, 2093 | "userSetupAllowed": false, 2094 | "autheticatorFlow": false 2095 | }, 2096 | { 2097 | "authenticator": "registration-recaptcha-action", 2098 | "authenticatorFlow": false, 2099 | "requirement": "DISABLED", 2100 | "priority": 60, 2101 | "userSetupAllowed": false, 2102 | "autheticatorFlow": false 2103 | } 2104 | ] 2105 | }, 2106 | { 2107 | "id": "04b7b566-86d6-4a58-8075-17e8af02aa19", 2108 | "alias": "reset credentials", 2109 | "description": "Reset credentials for a user if they forgot their password or something", 2110 | "providerId": "basic-flow", 2111 | "topLevel": true, 2112 | "builtIn": true, 2113 | "authenticationExecutions": [ 2114 | { 2115 | "authenticator": "reset-credentials-choose-user", 2116 | "authenticatorFlow": false, 2117 | "requirement": "REQUIRED", 2118 | "priority": 10, 2119 | "userSetupAllowed": false, 2120 | "autheticatorFlow": false 2121 | }, 2122 | { 2123 | "authenticator": "reset-credential-email", 2124 | "authenticatorFlow": false, 2125 | "requirement": "REQUIRED", 2126 | "priority": 20, 2127 | "userSetupAllowed": false, 2128 | "autheticatorFlow": false 2129 | }, 2130 | { 2131 | "authenticator": "reset-password", 2132 | "authenticatorFlow": false, 2133 | "requirement": "REQUIRED", 2134 | "priority": 30, 2135 | "userSetupAllowed": false, 2136 | "autheticatorFlow": false 2137 | }, 2138 | { 2139 | "authenticatorFlow": true, 2140 | "requirement": "CONDITIONAL", 2141 | "priority": 40, 2142 | "flowAlias": "Reset - Conditional OTP", 2143 | "userSetupAllowed": false, 2144 | "autheticatorFlow": true 2145 | } 2146 | ] 2147 | }, 2148 | { 2149 | "id": "a387278c-e808-4885-86c6-5211e672adeb", 2150 | "alias": "saml ecp", 2151 | "description": "SAML ECP Profile Authentication Flow", 2152 | "providerId": "basic-flow", 2153 | "topLevel": true, 2154 | "builtIn": true, 2155 | "authenticationExecutions": [ 2156 | { 2157 | "authenticator": "http-basic-authenticator", 2158 | "authenticatorFlow": false, 2159 | "requirement": "REQUIRED", 2160 | "priority": 10, 2161 | "userSetupAllowed": false, 2162 | "autheticatorFlow": false 2163 | } 2164 | ] 2165 | } 2166 | ], 2167 | "authenticatorConfig": [ 2168 | { 2169 | "id": "aaa03aba-3025-4ffd-bc7c-af276ddb477c", 2170 | "alias": "create unique user config", 2171 | "config": { 2172 | "require.password.update.after.registration": "false" 2173 | } 2174 | }, 2175 | { 2176 | "id": "01e73a8a-abc0-40a9-af25-ae301b4a61df", 2177 | "alias": "review profile config", 2178 | "config": { 2179 | "update.profile.on.first.login": "missing" 2180 | } 2181 | } 2182 | ], 2183 | "requiredActions": [ 2184 | { 2185 | "alias": "CONFIGURE_TOTP", 2186 | "name": "Configure OTP", 2187 | "providerId": "CONFIGURE_TOTP", 2188 | "enabled": true, 2189 | "defaultAction": false, 2190 | "priority": 10, 2191 | "config": {} 2192 | }, 2193 | { 2194 | "alias": "terms_and_conditions", 2195 | "name": "Terms and Conditions", 2196 | "providerId": "terms_and_conditions", 2197 | "enabled": false, 2198 | "defaultAction": false, 2199 | "priority": 20, 2200 | "config": {} 2201 | }, 2202 | { 2203 | "alias": "UPDATE_PASSWORD", 2204 | "name": "Update Password", 2205 | "providerId": "UPDATE_PASSWORD", 2206 | "enabled": true, 2207 | "defaultAction": false, 2208 | "priority": 30, 2209 | "config": {} 2210 | }, 2211 | { 2212 | "alias": "UPDATE_PROFILE", 2213 | "name": "Update Profile", 2214 | "providerId": "UPDATE_PROFILE", 2215 | "enabled": true, 2216 | "defaultAction": false, 2217 | "priority": 40, 2218 | "config": {} 2219 | }, 2220 | { 2221 | "alias": "VERIFY_EMAIL", 2222 | "name": "Verify Email", 2223 | "providerId": "VERIFY_EMAIL", 2224 | "enabled": true, 2225 | "defaultAction": false, 2226 | "priority": 50, 2227 | "config": {} 2228 | }, 2229 | { 2230 | "alias": "delete_account", 2231 | "name": "Delete Account", 2232 | "providerId": "delete_account", 2233 | "enabled": false, 2234 | "defaultAction": false, 2235 | "priority": 60, 2236 | "config": {} 2237 | }, 2238 | { 2239 | "alias": "update_user_locale", 2240 | "name": "Update User Locale", 2241 | "providerId": "update_user_locale", 2242 | "enabled": true, 2243 | "defaultAction": false, 2244 | "priority": 1000, 2245 | "config": {} 2246 | } 2247 | ], 2248 | "browserFlow": "browser", 2249 | "registrationFlow": "registration", 2250 | "directGrantFlow": "direct grant", 2251 | "resetCredentialsFlow": "reset credentials", 2252 | "clientAuthenticationFlow": "clients", 2253 | "dockerAuthenticationFlow": "docker auth", 2254 | "attributes": { 2255 | "cibaBackchannelTokenDeliveryMode": "poll", 2256 | "cibaExpiresIn": "120", 2257 | "cibaAuthRequestedUserHint": "login_hint", 2258 | "oauth2DeviceCodeLifespan": "600", 2259 | "clientOfflineSessionMaxLifespan": "0", 2260 | "oauth2DevicePollingInterval": "5", 2261 | "clientSessionIdleTimeout": "0", 2262 | "clientSessionMaxLifespan": "0", 2263 | "parRequestUriLifespan": "60", 2264 | "clientOfflineSessionIdleTimeout": "0", 2265 | "cibaInterval": "5" 2266 | }, 2267 | "keycloakVersion": "15.0.2", 2268 | "userManagedAccessAllowed": false, 2269 | "clientProfiles": { 2270 | "profiles": [] 2271 | }, 2272 | "clientPolicies": { 2273 | "policies": [] 2274 | } 2275 | } -------------------------------------------------------------------------------- /mvnw: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | # ---------------------------------------------------------------------------- 3 | # Licensed to the Apache Software Foundation (ASF) under one 4 | # or more contributor license agreements. See the NOTICE file 5 | # distributed with this work for additional information 6 | # regarding copyright ownership. The ASF licenses this file 7 | # to you under the Apache License, Version 2.0 (the 8 | # "License"); you may not use this file except in compliance 9 | # with the License. You may obtain a copy of the License at 10 | # 11 | # https://www.apache.org/licenses/LICENSE-2.0 12 | # 13 | # Unless required by applicable law or agreed to in writing, 14 | # software distributed under the License is distributed on an 15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 16 | # KIND, either express or implied. See the License for the 17 | # specific language governing permissions and limitations 18 | # under the License. 19 | # ---------------------------------------------------------------------------- 20 | 21 | # ---------------------------------------------------------------------------- 22 | # Maven Start Up Batch script 23 | # 24 | # Required ENV vars: 25 | # ------------------ 26 | # JAVA_HOME - location of a JDK home dir 27 | # 28 | # Optional ENV vars 29 | # ----------------- 30 | # M2_HOME - location of maven2's installed home dir 31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven 32 | # e.g. to debug Maven itself, use 33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files 35 | # ---------------------------------------------------------------------------- 36 | 37 | if [ -z "$MAVEN_SKIP_RC" ] ; then 38 | 39 | if [ -f /etc/mavenrc ] ; then 40 | . /etc/mavenrc 41 | fi 42 | 43 | if [ -f "$HOME/.mavenrc" ] ; then 44 | . "$HOME/.mavenrc" 45 | fi 46 | 47 | fi 48 | 49 | # OS specific support. $var _must_ be set to either true or false. 50 | cygwin=false; 51 | darwin=false; 52 | mingw=false 53 | case "`uname`" in 54 | CYGWIN*) cygwin=true ;; 55 | MINGW*) mingw=true;; 56 | Darwin*) darwin=true 57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home 58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html 59 | if [ -z "$JAVA_HOME" ]; then 60 | if [ -x "/usr/libexec/java_home" ]; then 61 | export JAVA_HOME="`/usr/libexec/java_home`" 62 | else 63 | export JAVA_HOME="/Library/Java/Home" 64 | fi 65 | fi 66 | ;; 67 | esac 68 | 69 | if [ -z "$JAVA_HOME" ] ; then 70 | if [ -r /etc/gentoo-release ] ; then 71 | JAVA_HOME=`java-config --jre-home` 72 | fi 73 | fi 74 | 75 | if [ -z "$M2_HOME" ] ; then 76 | ## resolve links - $0 may be a link to maven's home 77 | PRG="$0" 78 | 79 | # need this for relative symlinks 80 | while [ -h "$PRG" ] ; do 81 | ls=`ls -ld "$PRG"` 82 | link=`expr "$ls" : '.*-> \(.*\)$'` 83 | if expr "$link" : '/.*' > /dev/null; then 84 | PRG="$link" 85 | else 86 | PRG="`dirname "$PRG"`/$link" 87 | fi 88 | done 89 | 90 | saveddir=`pwd` 91 | 92 | M2_HOME=`dirname "$PRG"`/.. 93 | 94 | # make it fully qualified 95 | M2_HOME=`cd "$M2_HOME" && pwd` 96 | 97 | cd "$saveddir" 98 | # echo Using m2 at $M2_HOME 99 | fi 100 | 101 | # For Cygwin, ensure paths are in UNIX format before anything is touched 102 | if $cygwin ; then 103 | [ -n "$M2_HOME" ] && 104 | M2_HOME=`cygpath --unix "$M2_HOME"` 105 | [ -n "$JAVA_HOME" ] && 106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"` 107 | [ -n "$CLASSPATH" ] && 108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"` 109 | fi 110 | 111 | # For Mingw, ensure paths are in UNIX format before anything is touched 112 | if $mingw ; then 113 | [ -n "$M2_HOME" ] && 114 | M2_HOME="`(cd "$M2_HOME"; pwd)`" 115 | [ -n "$JAVA_HOME" ] && 116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" 117 | fi 118 | 119 | if [ -z "$JAVA_HOME" ]; then 120 | javaExecutable="`which javac`" 121 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then 122 | # readlink(1) is not available as standard on Solaris 10. 123 | readLink=`which readlink` 124 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then 125 | if $darwin ; then 126 | javaHome="`dirname \"$javaExecutable\"`" 127 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" 128 | else 129 | javaExecutable="`readlink -f \"$javaExecutable\"`" 130 | fi 131 | javaHome="`dirname \"$javaExecutable\"`" 132 | javaHome=`expr "$javaHome" : '\(.*\)/bin'` 133 | JAVA_HOME="$javaHome" 134 | export JAVA_HOME 135 | fi 136 | fi 137 | fi 138 | 139 | if [ -z "$JAVACMD" ] ; then 140 | if [ -n "$JAVA_HOME" ] ; then 141 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 142 | # IBM's JDK on AIX uses strange locations for the executables 143 | JAVACMD="$JAVA_HOME/jre/sh/java" 144 | else 145 | JAVACMD="$JAVA_HOME/bin/java" 146 | fi 147 | else 148 | JAVACMD="`which java`" 149 | fi 150 | fi 151 | 152 | if [ ! -x "$JAVACMD" ] ; then 153 | echo "Error: JAVA_HOME is not defined correctly." >&2 154 | echo " We cannot execute $JAVACMD" >&2 155 | exit 1 156 | fi 157 | 158 | if [ -z "$JAVA_HOME" ] ; then 159 | echo "Warning: JAVA_HOME environment variable is not set." 160 | fi 161 | 162 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher 163 | 164 | # traverses directory structure from process work directory to filesystem root 165 | # first directory with .mvn subdirectory is considered project base directory 166 | find_maven_basedir() { 167 | 168 | if [ -z "$1" ] 169 | then 170 | echo "Path not specified to find_maven_basedir" 171 | return 1 172 | fi 173 | 174 | basedir="$1" 175 | wdir="$1" 176 | while [ "$wdir" != '/' ] ; do 177 | if [ -d "$wdir"/.mvn ] ; then 178 | basedir=$wdir 179 | break 180 | fi 181 | # workaround for JBEAP-8937 (on Solaris 10/Sparc) 182 | if [ -d "${wdir}" ]; then 183 | wdir=`cd "$wdir/.."; pwd` 184 | fi 185 | # end of workaround 186 | done 187 | echo "${basedir}" 188 | } 189 | 190 | # concatenates all lines of a file 191 | concat_lines() { 192 | if [ -f "$1" ]; then 193 | echo "$(tr -s '\n' ' ' < "$1")" 194 | fi 195 | } 196 | 197 | BASE_DIR=`find_maven_basedir "$(pwd)"` 198 | if [ -z "$BASE_DIR" ]; then 199 | exit 1; 200 | fi 201 | 202 | ########################################################################################## 203 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 204 | # This allows using the maven wrapper in projects that prohibit checking in binary data. 205 | ########################################################################################## 206 | if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then 207 | if [ "$MVNW_VERBOSE" = true ]; then 208 | echo "Found .mvn/wrapper/maven-wrapper.jar" 209 | fi 210 | else 211 | if [ "$MVNW_VERBOSE" = true ]; then 212 | echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." 213 | fi 214 | if [ -n "$MVNW_REPOURL" ]; then 215 | jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 216 | else 217 | jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 218 | fi 219 | while IFS="=" read key value; do 220 | case "$key" in (wrapperUrl) jarUrl="$value"; break ;; 221 | esac 222 | done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" 223 | if [ "$MVNW_VERBOSE" = true ]; then 224 | echo "Downloading from: $jarUrl" 225 | fi 226 | wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" 227 | if $cygwin; then 228 | wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` 229 | fi 230 | 231 | if command -v wget > /dev/null; then 232 | if [ "$MVNW_VERBOSE" = true ]; then 233 | echo "Found wget ... using wget" 234 | fi 235 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 236 | wget "$jarUrl" -O "$wrapperJarPath" 237 | else 238 | wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" 239 | fi 240 | elif command -v curl > /dev/null; then 241 | if [ "$MVNW_VERBOSE" = true ]; then 242 | echo "Found curl ... using curl" 243 | fi 244 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then 245 | curl -o "$wrapperJarPath" "$jarUrl" -f 246 | else 247 | curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f 248 | fi 249 | 250 | else 251 | if [ "$MVNW_VERBOSE" = true ]; then 252 | echo "Falling back to using Java to download" 253 | fi 254 | javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" 255 | # For Cygwin, switch paths to Windows format before running javac 256 | if $cygwin; then 257 | javaClass=`cygpath --path --windows "$javaClass"` 258 | fi 259 | if [ -e "$javaClass" ]; then 260 | if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 261 | if [ "$MVNW_VERBOSE" = true ]; then 262 | echo " - Compiling MavenWrapperDownloader.java ..." 263 | fi 264 | # Compiling the Java class 265 | ("$JAVA_HOME/bin/javac" "$javaClass") 266 | fi 267 | if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then 268 | # Running the downloader 269 | if [ "$MVNW_VERBOSE" = true ]; then 270 | echo " - Running MavenWrapperDownloader.java ..." 271 | fi 272 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") 273 | fi 274 | fi 275 | fi 276 | fi 277 | ########################################################################################## 278 | # End of extension 279 | ########################################################################################## 280 | 281 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} 282 | if [ "$MVNW_VERBOSE" = true ]; then 283 | echo $MAVEN_PROJECTBASEDIR 284 | fi 285 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" 286 | 287 | # For Cygwin, switch paths to Windows format before running java 288 | if $cygwin; then 289 | [ -n "$M2_HOME" ] && 290 | M2_HOME=`cygpath --path --windows "$M2_HOME"` 291 | [ -n "$JAVA_HOME" ] && 292 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` 293 | [ -n "$CLASSPATH" ] && 294 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"` 295 | [ -n "$MAVEN_PROJECTBASEDIR" ] && 296 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` 297 | fi 298 | 299 | # Provide a "standardized" way to retrieve the CLI args that will 300 | # work with both Windows and non-Windows executions. 301 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" 302 | export MAVEN_CMD_LINE_ARGS 303 | 304 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 305 | 306 | exec "$JAVACMD" \ 307 | $MAVEN_OPTS \ 308 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ 309 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ 310 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" 311 | -------------------------------------------------------------------------------- /mvnw.cmd: -------------------------------------------------------------------------------- 1 | @REM ---------------------------------------------------------------------------- 2 | @REM Licensed to the Apache Software Foundation (ASF) under one 3 | @REM or more contributor license agreements. See the NOTICE file 4 | @REM distributed with this work for additional information 5 | @REM regarding copyright ownership. The ASF licenses this file 6 | @REM to you under the Apache License, Version 2.0 (the 7 | @REM "License"); you may not use this file except in compliance 8 | @REM with the License. You may obtain a copy of the License at 9 | @REM 10 | @REM https://www.apache.org/licenses/LICENSE-2.0 11 | @REM 12 | @REM Unless required by applicable law or agreed to in writing, 13 | @REM software distributed under the License is distributed on an 14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 15 | @REM KIND, either express or implied. See the License for the 16 | @REM specific language governing permissions and limitations 17 | @REM under the License. 18 | @REM ---------------------------------------------------------------------------- 19 | 20 | @REM ---------------------------------------------------------------------------- 21 | @REM Maven Start Up Batch script 22 | @REM 23 | @REM Required ENV vars: 24 | @REM JAVA_HOME - location of a JDK home dir 25 | @REM 26 | @REM Optional ENV vars 27 | @REM M2_HOME - location of maven2's installed home dir 28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands 29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending 30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven 31 | @REM e.g. to debug Maven itself, use 32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files 34 | @REM ---------------------------------------------------------------------------- 35 | 36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' 37 | @echo off 38 | @REM set title of command window 39 | title %0 40 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' 41 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% 42 | 43 | @REM set %HOME% to equivalent of $HOME 44 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") 45 | 46 | @REM Execute a user defined script before this one 47 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre 48 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending 49 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" 50 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" 51 | :skipRcPre 52 | 53 | @setlocal 54 | 55 | set ERROR_CODE=0 56 | 57 | @REM To isolate internal variables from possible post scripts, we use another setlocal 58 | @setlocal 59 | 60 | @REM ==== START VALIDATION ==== 61 | if not "%JAVA_HOME%" == "" goto OkJHome 62 | 63 | echo. 64 | echo Error: JAVA_HOME not found in your environment. >&2 65 | echo Please set the JAVA_HOME variable in your environment to match the >&2 66 | echo location of your Java installation. >&2 67 | echo. 68 | goto error 69 | 70 | :OkJHome 71 | if exist "%JAVA_HOME%\bin\java.exe" goto init 72 | 73 | echo. 74 | echo Error: JAVA_HOME is set to an invalid directory. >&2 75 | echo JAVA_HOME = "%JAVA_HOME%" >&2 76 | echo Please set the JAVA_HOME variable in your environment to match the >&2 77 | echo location of your Java installation. >&2 78 | echo. 79 | goto error 80 | 81 | @REM ==== END VALIDATION ==== 82 | 83 | :init 84 | 85 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn". 86 | @REM Fallback to current working directory if not found. 87 | 88 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% 89 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir 90 | 91 | set EXEC_DIR=%CD% 92 | set WDIR=%EXEC_DIR% 93 | :findBaseDir 94 | IF EXIST "%WDIR%"\.mvn goto baseDirFound 95 | cd .. 96 | IF "%WDIR%"=="%CD%" goto baseDirNotFound 97 | set WDIR=%CD% 98 | goto findBaseDir 99 | 100 | :baseDirFound 101 | set MAVEN_PROJECTBASEDIR=%WDIR% 102 | cd "%EXEC_DIR%" 103 | goto endDetectBaseDir 104 | 105 | :baseDirNotFound 106 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR% 107 | cd "%EXEC_DIR%" 108 | 109 | :endDetectBaseDir 110 | 111 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig 112 | 113 | @setlocal EnableExtensions EnableDelayedExpansion 114 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a 115 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% 116 | 117 | :endReadAdditionalConfig 118 | 119 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" 120 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" 121 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain 122 | 123 | set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 124 | 125 | FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( 126 | IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B 127 | ) 128 | 129 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central 130 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data. 131 | if exist %WRAPPER_JAR% ( 132 | if "%MVNW_VERBOSE%" == "true" ( 133 | echo Found %WRAPPER_JAR% 134 | ) 135 | ) else ( 136 | if not "%MVNW_REPOURL%" == "" ( 137 | SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar" 138 | ) 139 | if "%MVNW_VERBOSE%" == "true" ( 140 | echo Couldn't find %WRAPPER_JAR%, downloading it ... 141 | echo Downloading from: %DOWNLOAD_URL% 142 | ) 143 | 144 | powershell -Command "&{"^ 145 | "$webclient = new-object System.Net.WebClient;"^ 146 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ 147 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ 148 | "}"^ 149 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ 150 | "}" 151 | if "%MVNW_VERBOSE%" == "true" ( 152 | echo Finished downloading %WRAPPER_JAR% 153 | ) 154 | ) 155 | @REM End of extension 156 | 157 | @REM Provide a "standardized" way to retrieve the CLI args that will 158 | @REM work with both Windows and non-Windows executions. 159 | set MAVEN_CMD_LINE_ARGS=%* 160 | 161 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* 162 | if ERRORLEVEL 1 goto error 163 | goto end 164 | 165 | :error 166 | set ERROR_CODE=1 167 | 168 | :end 169 | @endlocal & set ERROR_CODE=%ERROR_CODE% 170 | 171 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost 172 | @REM check for post script, once with legacy .bat ending and once with .cmd ending 173 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" 174 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" 175 | :skipRcPost 176 | 177 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' 178 | if "%MAVEN_BATCH_PAUSE%" == "on" pause 179 | 180 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% 181 | 182 | exit /B %ERROR_CODE% 183 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4 | 4.0.0 5 | 6 | org.springframework.boot 7 | spring-boot-starter-parent 8 | 2.6.3 9 | 10 | 11 | in.keepgrowing 12 | spring-boot-swagger-ui-keycloak 13 | 0.0.1-SNAPSHOT 14 | spring-boot-swagger-ui-keycloak 15 | Demo Spring Boot app showing Swagger UI secured with Keycloak 16 | 17 | 17 18 | 0.9.0 19 | 1.6.6 20 | 16.1.1 21 | 22 | 23 | 24 | 25 | 26 | org.keycloak.bom 27 | keycloak-adapter-bom 28 | ${keycloak-adapter.version} 29 | pom 30 | import 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | 43 | 44 | 45 | org.keycloak 46 | keycloak-spring-boot-starter 47 | 48 | 49 | org.springframework.boot 50 | spring-boot-starter-security 51 | 52 | 53 | 54 | 55 | 56 | org.springdoc 57 | springdoc-openapi-ui 58 | ${springdoc.version} 59 | 60 | 61 | 62 | 63 | 64 | org.springframework.boot 65 | spring-boot-starter-test 66 | test 67 | 68 | 69 | org.springframework.security 70 | spring-security-test 71 | test 72 | 73 | 74 | 75 | 76 | 77 | org.projectlombok 78 | lombok 79 | true 80 | 81 | 82 | dev.codesoapbox 83 | dummy4j 84 | ${dummy4j.version} 85 | 86 | 87 | 88 | 89 | org.springframework.boot 90 | spring-boot-configuration-processor 91 | true 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | org.springframework.boot 101 | spring-boot-maven-plugin 102 | 103 | 104 | 105 | org.projectlombok 106 | lombok 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | -------------------------------------------------------------------------------- /readme-images/logo_250x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/logo_250x60.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-open-id-discovery.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-open-id-discovery.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-with-auth-code-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-with-auth-code-flow.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-with-bearer-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-with-bearer-scheme.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-with-keycloak-auth-for-endpoints.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-with-keycloak-auth-for-endpoints.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-with-openid-discovery-scheme.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-with-openid-discovery-scheme.png -------------------------------------------------------------------------------- /readme-images/swagger-ui-with-password-flow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/swagger-ui-with-password-flow.png -------------------------------------------------------------------------------- /readme-images/token-with-realm-roles.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/little-pinecone/spring-boot-swagger-ui-keycloak/1047ded82a53752a853dedd1e97fbe9785b944ea/readme-images/token-with-realm-roles.png -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/SpringBootSwaggerUiKeycloakApplication.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.context.properties.ConfigurationPropertiesScan; 6 | 7 | @SpringBootApplication 8 | @ConfigurationPropertiesScan 9 | public class SpringBootSwaggerUiKeycloakApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(SpringBootSwaggerUiKeycloakApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/domain/model/Product.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.domain.model; 2 | 3 | import lombok.Builder; 4 | import lombok.Data; 5 | 6 | import java.util.UUID; 7 | 8 | @Data 9 | @Builder 10 | public class Product { 11 | 12 | private UUID id; 13 | private String name; 14 | private String color; 15 | private String ean; 16 | private String countryOfOrigin; 17 | private String price; 18 | private int availableQuantity; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/domain/repositories/ProductRepository.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.domain.repositories; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.Product; 4 | 5 | import java.util.List; 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface ProductRepository { 10 | 11 | List findAll(); 12 | 13 | Optional findById(UUID productId); 14 | 15 | Optional save(Product productDetails); 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/infrastructure/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.config; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.SpringBootSwaggerUiKeycloakApplication; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.web.bind.annotation.RestController; 6 | import org.springframework.web.method.HandlerTypePredicate; 7 | import org.springframework.web.servlet.config.annotation.PathMatchConfigurer; 8 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 9 | 10 | @Configuration 11 | public class MvcConfig implements WebMvcConfigurer { 12 | 13 | public static final String API_PREFIX = "api"; 14 | 15 | @Override 16 | public void configurePathMatch(PathMatchConfigurer configurer) { 17 | var packageName = SpringBootSwaggerUiKeycloakApplication.class.getPackageName(); 18 | 19 | configurer.addPathPrefix(API_PREFIX, 20 | HandlerTypePredicate 21 | .forBasePackage(packageName) 22 | .and(HandlerTypePredicate.forAnnotation(RestController.class)) 23 | ); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/infrastructure/config/ProductBeanConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.config; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.repositories.ProductRepository; 4 | import in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.repositories.InMemoryProductRepository; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class ProductBeanConfig { 10 | 11 | @Bean 12 | public ProductRepository productRepository() { 13 | return new InMemoryProductRepository(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/infrastructure/repositories/InMemoryProductRepository.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.repositories; 2 | 3 | import dev.codesoapbox.dummy4j.Dummy4j; 4 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.Product; 5 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.repositories.ProductRepository; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | import java.util.UUID; 10 | 11 | public class InMemoryProductRepository implements ProductRepository { 12 | 13 | private final Dummy4j dummy; 14 | private final List products; 15 | 16 | public InMemoryProductRepository() { 17 | this.dummy = new Dummy4j(123L, null, null); 18 | this.products = dummy.listOf(20, this::generateProduct); 19 | } 20 | 21 | private Product generateProduct() { 22 | return Product.builder() 23 | .id(dummy.identifier().uuid()) 24 | .name(dummy.lorem().word() + " " + dummy.lorem().word()) 25 | .color(dummy.color().name()) 26 | .ean(dummy.identifier().ean13()) 27 | .countryOfOrigin(dummy.nation().country()) 28 | .price(dummy.finance().priceBuilder().withCurrency("USD").build()) 29 | .availableQuantity(dummy.number().nextInt(1, 200)) 30 | .build(); 31 | } 32 | 33 | @Override 34 | public List findAll() { 35 | return products; 36 | } 37 | 38 | @Override 39 | public Optional findById(UUID productId) { 40 | return products.stream() 41 | .filter(p -> p.getId().equals(productId)) 42 | .findFirst(); 43 | } 44 | 45 | @Override 46 | public Optional save(Product productDetails) { 47 | productDetails.setId(dummy.identifier().uuid()); 48 | products.add(productDetails); 49 | 50 | return findById(productDetails.getId()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/presentation/controllers/ProductController.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.presentation.controllers; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.Product; 4 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.repositories.ProductRepository; 5 | import io.swagger.v3.oas.annotations.Operation; 6 | import io.swagger.v3.oas.annotations.tags.Tag; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.http.MediaType; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.security.access.prepost.PreAuthorize; 11 | import org.springframework.web.bind.annotation.*; 12 | 13 | import java.util.List; 14 | import java.util.UUID; 15 | 16 | @RestController 17 | @RequestMapping(value = ProductControllerPaths.PRODUCTS_PATH, produces = MediaType.APPLICATION_JSON_VALUE) 18 | @Tag(name = "Products") 19 | public class ProductController { 20 | 21 | private final ProductRepository productRepository; 22 | 23 | public ProductController(ProductRepository productRepository) { 24 | this.productRepository = productRepository; 25 | } 26 | 27 | @GetMapping() 28 | @Operation(summary = "Return all products") 29 | public ResponseEntity> findAll() { 30 | var products = productRepository.findAll(); 31 | 32 | return new ResponseEntity<>(products, HttpStatus.OK); 33 | } 34 | 35 | @GetMapping("{productId}") 36 | @Operation(summary = "Return a product by its id") 37 | public ResponseEntity findById(@PathVariable UUID productId) { 38 | return productRepository.findById(productId) 39 | .map(ResponseEntity::ok) 40 | .orElse(ResponseEntity.status(HttpStatus.NOT_FOUND).build()); 41 | } 42 | 43 | @PostMapping() 44 | @PreAuthorize("hasAuthority('chief-operating-officer')") 45 | @Operation(summary = "Save a product (available for the chief-operating-officer role, default user: 'christina')") 46 | public ResponseEntity save(@RequestBody Product productDetails) { 47 | return productRepository.save(productDetails) 48 | .map(ResponseEntity::ok) 49 | .orElse(ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).build()); 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/products/presentation/controllers/ProductControllerPaths.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.presentation.controllers; 2 | 3 | public final class ProductControllerPaths { 4 | 5 | public final static String PRODUCTS_PATH = "products"; 6 | 7 | private ProductControllerPaths() { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/security/CustomMethodSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security; 2 | 3 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 4 | 5 | @EnableMethodSecurity 6 | public class CustomMethodSecurityConfig { 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/security/KeycloakConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security; 2 | 3 | import org.keycloak.adapters.KeycloakConfigResolver; 4 | import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class KeycloakConfig { 10 | 11 | @Bean 12 | public KeycloakConfigResolver keycloakConfigResolver() { 13 | return new KeycloakSpringBootConfigResolver(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/security/KeycloakProperties.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.boot.context.properties.ConstructorBinding; 7 | 8 | @ConfigurationProperties(prefix = "keycloak") 9 | @ConstructorBinding 10 | @AllArgsConstructor 11 | @Getter 12 | public class KeycloakProperties { 13 | 14 | private final String authServerUrl; 15 | private final String realm; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security; 2 | 3 | import org.keycloak.adapters.springsecurity.KeycloakConfiguration; 4 | import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider; 5 | import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper; 12 | import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper; 13 | import org.springframework.security.core.session.SessionRegistryImpl; 14 | import org.springframework.security.web.authentication.session.RegisterSessionAuthenticationStrategy; 15 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy; 16 | import org.springframework.security.web.csrf.CookieCsrfTokenRepository; 17 | 18 | @KeycloakConfiguration 19 | @ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "true", matchIfMissing = true) 20 | public class SecurityConfig extends KeycloakWebSecurityConfigurerAdapter { 21 | 22 | private static final String[] SWAGGER_WHITELIST = { 23 | "/v3/api-docs/**", 24 | "/swagger-ui/**", 25 | "/swagger-ui.html", 26 | }; 27 | 28 | public static void configureApiSecurity(HttpSecurity http) throws Exception { 29 | http 30 | .csrf().csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()) 31 | .and() 32 | .authorizeRequests() 33 | .antMatchers(SWAGGER_WHITELIST).permitAll() 34 | .anyRequest().authenticated(); 35 | } 36 | 37 | @Override 38 | protected void configure(HttpSecurity http) throws Exception { 39 | super.configure(http); 40 | configureApiSecurity(http); 41 | } 42 | 43 | @Autowired 44 | public void configureGlobal(AuthenticationManagerBuilder auth) { 45 | auth.authenticationProvider(getKeycloakAuthenticationProvider()); 46 | } 47 | 48 | private KeycloakAuthenticationProvider getKeycloakAuthenticationProvider() { 49 | KeycloakAuthenticationProvider authenticationProvider = keycloakAuthenticationProvider(); 50 | authenticationProvider.setGrantedAuthoritiesMapper(getAuthoritiesMapper()); 51 | 52 | return authenticationProvider; 53 | } 54 | 55 | private GrantedAuthoritiesMapper getAuthoritiesMapper() { 56 | SimpleAuthorityMapper mapper = new SimpleAuthorityMapper(); 57 | mapper.setPrefix(""); 58 | 59 | return mapper; 60 | } 61 | 62 | @Bean 63 | @Override 64 | protected SessionAuthenticationStrategy sessionAuthenticationStrategy() { 65 | return new RegisterSessionAuthenticationStrategy(new SessionRegistryImpl()); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/ApiInfoProvider.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger; 2 | 3 | import io.swagger.v3.oas.models.info.Info; 4 | import io.swagger.v3.oas.models.info.License; 5 | 6 | public class ApiInfoProvider { 7 | 8 | private final SwaggerProperties properties; 9 | 10 | public ApiInfoProvider(SwaggerProperties properties) { 11 | this.properties = properties; 12 | } 13 | 14 | public Info provide() { 15 | return new Info() 16 | .title(properties.getProjectTitle()) 17 | .description(properties.getProjectDescription()) 18 | .version(properties.getProjectVersion()) 19 | .license(getLicense()); 20 | } 21 | 22 | private License getLicense() { 23 | return new License() 24 | .name("Unlicense") 25 | .url("https://unlicense.org/"); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/SwaggerBeanConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | 6 | @Configuration 7 | public class SwaggerBeanConfig { 8 | 9 | @Bean 10 | ApiInfoProvider apiInfoProvider(SwaggerProperties swaggerProperties) { 11 | return new ApiInfoProvider(swaggerProperties); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/SwaggerProperties.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | import org.springframework.boot.context.properties.ConstructorBinding; 7 | 8 | @ConfigurationProperties(prefix = "swagger") 9 | @ConstructorBinding 10 | @AllArgsConstructor 11 | @Getter 12 | public class SwaggerProperties { 13 | 14 | private final String projectTitle; 15 | private final String projectDescription; 16 | private final String projectVersion; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/authorization/SwaggerAuthorizationCodeConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.KeycloakProperties; 4 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider; 5 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 6 | import io.swagger.v3.oas.models.Components; 7 | import io.swagger.v3.oas.models.OpenAPI; 8 | import io.swagger.v3.oas.models.security.*; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @OpenAPIDefinition 15 | @ConditionalOnProperty(name = "security.config.authcode-flow", havingValue = "true") 16 | public class SwaggerAuthorizationCodeConfig { 17 | 18 | private static final String OAUTH_SCHEME_NAME = "oAuth"; 19 | private static final String PROTOCOL_URL_FORMAT = "%s/realms/%s/protocol/openid-connect"; 20 | 21 | @Bean 22 | OpenAPI customOpenApi(KeycloakProperties keycloakProperties, ApiInfoProvider infoProvider) { 23 | return new OpenAPI() 24 | .info(infoProvider.provide()) 25 | .components(new Components() 26 | .addSecuritySchemes(OAUTH_SCHEME_NAME, createOAuthScheme(keycloakProperties))) 27 | .addSecurityItem(new SecurityRequirement().addList(OAUTH_SCHEME_NAME)); 28 | } 29 | 30 | private SecurityScheme createOAuthScheme(KeycloakProperties properties) { 31 | OAuthFlows flows = createOAuthFlows(properties); 32 | 33 | return new SecurityScheme() 34 | .type(SecurityScheme.Type.OAUTH2) 35 | .flows(flows); 36 | } 37 | 38 | private OAuthFlows createOAuthFlows(KeycloakProperties properties) { 39 | OAuthFlow authorizationCodeFlow = createAuthorizationCodeFlow(properties); 40 | 41 | return new OAuthFlows() 42 | .authorizationCode(authorizationCodeFlow); 43 | } 44 | 45 | private OAuthFlow createAuthorizationCodeFlow(KeycloakProperties properties) { 46 | var protocolUrl = String.format(PROTOCOL_URL_FORMAT, properties.getAuthServerUrl(), properties.getRealm()); 47 | 48 | return new OAuthFlow() 49 | .authorizationUrl(protocolUrl + "/auth") 50 | .tokenUrl(protocolUrl + "/token") 51 | .scopes(new Scopes()); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/authorization/SwaggerBearerConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider; 4 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 5 | import io.swagger.v3.oas.models.Components; 6 | import io.swagger.v3.oas.models.OpenAPI; 7 | import io.swagger.v3.oas.models.security.SecurityRequirement; 8 | import io.swagger.v3.oas.models.security.SecurityScheme; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @OpenAPIDefinition 15 | @ConditionalOnProperty(name = "security.config.bearer", havingValue = "true") 16 | public class SwaggerBearerConfig { 17 | 18 | private static final String SCHEME_NAME = "bearerAuth"; 19 | private static final String SCHEME = "bearer"; 20 | 21 | @Bean 22 | OpenAPI customOpenApi(ApiInfoProvider infoProvider) { 23 | return new OpenAPI() 24 | .info(infoProvider.provide()) 25 | .components(new Components() 26 | .addSecuritySchemes(SCHEME_NAME, createBearerScheme())) 27 | .addSecurityItem(new SecurityRequirement().addList(SCHEME_NAME)); 28 | } 29 | 30 | private SecurityScheme createBearerScheme() { 31 | return new SecurityScheme() 32 | .type(SecurityScheme.Type.HTTP) 33 | .scheme(SCHEME); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/authorization/SwaggerImplicitConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.KeycloakProperties; 4 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider; 5 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 6 | import io.swagger.v3.oas.models.Components; 7 | import io.swagger.v3.oas.models.OpenAPI; 8 | import io.swagger.v3.oas.models.security.*; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @OpenAPIDefinition 15 | @ConditionalOnProperty(name = "security.config.implicit-flow", havingValue = "true") 16 | public class SwaggerImplicitConfig { 17 | 18 | private static final String OAUTH_SCHEME_NAME = "oAuth"; 19 | private static final String AUTH_URL_FORMAT = "%s/realms/%s/protocol/openid-connect/auth"; 20 | 21 | @Bean 22 | OpenAPI customOpenApi(KeycloakProperties keycloakProperties, ApiInfoProvider infoProvider) { 23 | return new OpenAPI() 24 | .info(infoProvider.provide()) 25 | .components(new Components() 26 | .addSecuritySchemes(OAUTH_SCHEME_NAME, createOAuthScheme(keycloakProperties))) 27 | .addSecurityItem(new SecurityRequirement().addList(OAUTH_SCHEME_NAME)); 28 | } 29 | 30 | private SecurityScheme createOAuthScheme(KeycloakProperties properties) { 31 | OAuthFlows flows = createOAuthFlows(properties); 32 | 33 | return new SecurityScheme() 34 | .type(SecurityScheme.Type.OAUTH2) 35 | .flows(flows); 36 | } 37 | 38 | private OAuthFlows createOAuthFlows(KeycloakProperties properties) { 39 | OAuthFlow implicitFlow = createImplicitFlow(properties); 40 | 41 | return new OAuthFlows() 42 | .implicit(implicitFlow); 43 | } 44 | 45 | private OAuthFlow createImplicitFlow(KeycloakProperties properties) { 46 | var authUrl = String.format(AUTH_URL_FORMAT, properties.getAuthServerUrl(), properties.getRealm()); 47 | 48 | return new OAuthFlow() 49 | .authorizationUrl(authUrl) 50 | .scopes(new Scopes()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/authorization/SwaggerOpenIdConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.KeycloakProperties; 4 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider; 5 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 6 | import io.swagger.v3.oas.models.Components; 7 | import io.swagger.v3.oas.models.OpenAPI; 8 | import io.swagger.v3.oas.models.security.SecurityRequirement; 9 | import io.swagger.v3.oas.models.security.SecurityScheme; 10 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 11 | import org.springframework.context.annotation.Bean; 12 | import org.springframework.context.annotation.Configuration; 13 | 14 | @Configuration 15 | @OpenAPIDefinition 16 | @ConditionalOnProperty(name = "security.config.openid-discovery", havingValue = "true") 17 | public class SwaggerOpenIdConfig { 18 | 19 | private static final String OPEN_ID_SCHEME_NAME = "openId"; 20 | private static final String OPENID_CONFIG_FORMAT = "%s/realms/%s/.well-known/openid-configuration"; 21 | 22 | @Bean 23 | OpenAPI customOpenApi(KeycloakProperties keycloakProperties, ApiInfoProvider infoProvider) { 24 | return new OpenAPI() 25 | .info(infoProvider.provide()) 26 | .components(new Components() 27 | .addSecuritySchemes(OPEN_ID_SCHEME_NAME, createOpenIdScheme(keycloakProperties))) 28 | .addSecurityItem(new SecurityRequirement().addList(OPEN_ID_SCHEME_NAME)); 29 | } 30 | 31 | private SecurityScheme createOpenIdScheme(KeycloakProperties properties) { 32 | String connectUrl = String.format(OPENID_CONFIG_FORMAT, properties.getAuthServerUrl(), properties.getRealm()); 33 | 34 | return new SecurityScheme() 35 | .type(SecurityScheme.Type.OPENIDCONNECT) 36 | .openIdConnectUrl(connectUrl); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/swagger/authorization/SwaggerPasswordFlowConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.authorization; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.KeycloakProperties; 4 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.swagger.ApiInfoProvider; 5 | import io.swagger.v3.oas.annotations.OpenAPIDefinition; 6 | import io.swagger.v3.oas.models.Components; 7 | import io.swagger.v3.oas.models.OpenAPI; 8 | import io.swagger.v3.oas.models.security.*; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.context.annotation.Bean; 11 | import org.springframework.context.annotation.Configuration; 12 | 13 | @Configuration 14 | @OpenAPIDefinition 15 | @ConditionalOnProperty(name = "security.config.password-flow", havingValue = "true") 16 | public class SwaggerPasswordFlowConfig { 17 | 18 | private static final String OAUTH_SCHEME_NAME = "oAuth"; 19 | private static final String TOKEN_URL_FORMAT = "%s/realms/%s/protocol/openid-connect/token"; 20 | 21 | @Bean 22 | OpenAPI customOpenApi(KeycloakProperties keycloakProperties, ApiInfoProvider infoProvider) { 23 | return new OpenAPI() 24 | .info(infoProvider.provide()) 25 | .components(new Components() 26 | .addSecuritySchemes(OAUTH_SCHEME_NAME, createOAuthScheme(keycloakProperties))) 27 | .addSecurityItem(new SecurityRequirement().addList(OAUTH_SCHEME_NAME)); 28 | } 29 | 30 | private SecurityScheme createOAuthScheme(KeycloakProperties properties) { 31 | OAuthFlows flows = createOAuthFlows(properties); 32 | 33 | return new SecurityScheme() 34 | .type(SecurityScheme.Type.OAUTH2) 35 | .flows(flows); 36 | } 37 | 38 | private OAuthFlows createOAuthFlows(KeycloakProperties properties) { 39 | OAuthFlow passwordFlow = createPasswordFlow(properties); 40 | 41 | return new OAuthFlows() 42 | .password(passwordFlow); 43 | } 44 | 45 | private OAuthFlow createPasswordFlow(KeycloakProperties properties) { 46 | var tokenUrl = String.format(TOKEN_URL_FORMAT, properties.getAuthServerUrl(), properties.getRealm()); 47 | 48 | return new OAuthFlow() 49 | .tokenUrl(tokenUrl) 50 | .scopes(new Scopes()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | security.config.use-keycloak=false -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | swagger.project-title=@project.name@ 2 | swagger.project-version=@project.version@ 3 | swagger.project-description=@project.description@ 4 | 5 | keycloak.realm = Spring-Boot-Example 6 | keycloak.auth-server-url = http://localhost:8024/auth 7 | keycloak.ssl-required = external 8 | keycloak.resource = spring-boot-example-app 9 | 10 | springdoc.swagger-ui.csrf.enabled=true 11 | 12 | security.config.authcode-flow=true -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/SpringBootSwaggerUiKeycloakApplicationTests.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class SpringBootSwaggerUiKeycloakApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/products/domain/model/TestProductProvider.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.domain.model; 2 | 3 | import dev.codesoapbox.dummy4j.Dummy4j; 4 | 5 | public class TestProductProvider { 6 | 7 | private final Dummy4j dummy; 8 | 9 | public TestProductProvider() { 10 | this.dummy = new Dummy4j(); 11 | } 12 | 13 | public Product full() { 14 | var product = withoutId(); 15 | product.setId(dummy.identifier().uuid()); 16 | 17 | return product; 18 | } 19 | 20 | public Product withoutId() { 21 | return Product.builder() 22 | .name(dummy.lorem().word() + " " + dummy.lorem().word()) 23 | .color(dummy.color().name()) 24 | .ean(dummy.identifier().ean13()) 25 | .countryOfOrigin(dummy.nation().country()) 26 | .price(dummy.finance().priceBuilder().withCurrency("USD").build()) 27 | .availableQuantity(dummy.number().nextInt(1, 200)) 28 | .build(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/products/infrastructure/repositories/InMemoryProductRepositoryTest.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.repositories; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.TestProductProvider; 4 | import org.junit.jupiter.api.BeforeEach; 5 | import org.junit.jupiter.api.Test; 6 | 7 | import java.util.UUID; 8 | 9 | import static org.junit.jupiter.api.Assertions.*; 10 | 11 | class InMemoryProductRepositoryTest { 12 | 13 | private InMemoryProductRepository productRepository; 14 | 15 | @BeforeEach 16 | void setUp() { 17 | productRepository = new InMemoryProductRepository(); 18 | } 19 | 20 | @Test 21 | void shouldReturnListOfProducts() { 22 | var actual = productRepository.findAll(); 23 | 24 | assertNotNull(actual); 25 | 26 | assertAll( 27 | () -> assertFalse(actual.isEmpty(), "Empty list"), 28 | () -> assertNotNull(actual.get(0), "List element is null"), 29 | () -> assertFalse(actual.get(0).getName().isBlank(), "Blank element field") 30 | ); 31 | } 32 | 33 | @Test 34 | void shouldReturnProductById() { 35 | var products = productRepository.findAll(); 36 | 37 | var actual = productRepository.findById(products.get(0).getId()); 38 | 39 | assertNotNull(actual); 40 | assertTrue(actual.isPresent()); 41 | assertNotNull(actual.get().getId()); 42 | } 43 | 44 | @Test 45 | void shouldReturnEmptyOptionalForNotExistingProduct() { 46 | var invalidId = UUID.fromString("11111111-1111-1111-1111-111111111111"); 47 | 48 | var actual = productRepository.findById(invalidId); 49 | 50 | assertNotNull(actual); 51 | assertTrue(actual.isEmpty()); 52 | } 53 | 54 | @Test 55 | void shouldSaveProduct() { 56 | var originalSize = productRepository.findAll().size(); 57 | var productProvider = new TestProductProvider(); 58 | var productDetails = productProvider.withoutId(); 59 | 60 | var actual = productRepository.save(productDetails); 61 | 62 | assertNotNull(actual); 63 | assertTrue(actual.isPresent()); 64 | assertNotNull(actual.get().getId()); 65 | assertEquals(originalSize + 1, productRepository.findAll().size()); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/products/presentation/controllers/ProductControllerTest.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.products.presentation.controllers; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.Product; 5 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.model.TestProductProvider; 6 | import in.keepgrowing.springbootswaggeruikeycloak.products.domain.repositories.ProductRepository; 7 | import in.keepgrowing.springbootswaggeruikeycloak.products.infrastructure.config.MvcConfig; 8 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.annotations.RestControllerIntegrationTest; 9 | import org.junit.jupiter.api.BeforeEach; 10 | import org.junit.jupiter.api.Test; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.http.MediaType; 14 | import org.springframework.security.test.context.support.WithAnonymousUser; 15 | import org.springframework.security.test.context.support.WithMockUser; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | 18 | import java.util.List; 19 | import java.util.Optional; 20 | import java.util.UUID; 21 | 22 | import static org.junit.jupiter.api.Assertions.assertNotNull; 23 | import static org.mockito.Mockito.when; 24 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 25 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 26 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 27 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 | 30 | @RestControllerIntegrationTest(value = ProductController.class) 31 | class ProductControllerTest { 32 | 33 | private static final String BASE_PATH = "/" + MvcConfig.API_PREFIX + "/" + ProductControllerPaths.PRODUCTS_PATH; 34 | private static final String TEST_UUID = "a25fd1a8-b2e2-3b40-97a5-cead9ec87986"; 35 | 36 | private TestProductProvider productProvider; 37 | 38 | @Autowired 39 | private MockMvc mvc; 40 | 41 | @Autowired 42 | private ObjectMapper objectMapper; 43 | 44 | @Autowired 45 | private ProductController controller; 46 | 47 | @MockBean 48 | private ProductRepository productRepository; 49 | 50 | @BeforeEach 51 | void setUp() { 52 | productProvider = new TestProductProvider(); 53 | } 54 | 55 | @Test 56 | void contextLoads() { 57 | assertNotNull(controller); 58 | } 59 | 60 | @Test 61 | @WithMockUser 62 | void shouldReturnAllProducts() throws Exception { 63 | Product product = productProvider.full(); 64 | List products = List.of(product); 65 | 66 | when(productRepository.findAll()) 67 | .thenReturn(products); 68 | 69 | String expectedResponse = objectMapper.writeValueAsString(products); 70 | 71 | mvc.perform(get(BASE_PATH) 72 | .contentType(MediaType.APPLICATION_JSON)) 73 | .andExpect(status().isOk()) 74 | .andExpect(content().json(expectedResponse)); 75 | } 76 | 77 | @Test 78 | void shouldNotReturnAllProductForUnauthenticatedUser() throws Exception { 79 | Product productDetails = productProvider.withoutId(); 80 | 81 | mvc.perform(post(BASE_PATH) 82 | .contentType(MediaType.APPLICATION_JSON) 83 | .content(objectMapper.writeValueAsString(productDetails)) 84 | .with(csrf())) 85 | .andExpect(status().isForbidden()); 86 | } 87 | 88 | @Test 89 | @WithMockUser 90 | void shouldReturnProductById() throws Exception { 91 | Product product = productProvider.full(); 92 | 93 | when(productRepository.findById(UUID.fromString(TEST_UUID))) 94 | .thenReturn(Optional.of(product)); 95 | 96 | String expectedResponse = objectMapper.writeValueAsString(product); 97 | 98 | mvc.perform(get(BASE_PATH + "/" + TEST_UUID) 99 | .contentType(MediaType.APPLICATION_JSON)) 100 | .andExpect(status().isOk()) 101 | .andExpect(content().json(expectedResponse)); 102 | } 103 | 104 | @Test 105 | @WithMockUser 106 | void shouldReturnNotFoundForNonExistingId() throws Exception { 107 | when(productRepository.findById(UUID.fromString(TEST_UUID))) 108 | .thenReturn(Optional.empty()); 109 | 110 | mvc.perform(get(BASE_PATH + "/" + TEST_UUID) 111 | .contentType(MediaType.APPLICATION_JSON)) 112 | .andExpect(status().isNotFound()); 113 | } 114 | 115 | @Test 116 | void shouldNotReturnProductByIdForUnauthenticatedUser() throws Exception { 117 | mvc.perform(get(BASE_PATH + "/" + TEST_UUID) 118 | .contentType(MediaType.APPLICATION_JSON)) 119 | .andExpect(status().isForbidden()); 120 | } 121 | 122 | @Test 123 | @WithMockUser(authorities = {"chief-operating-officer"}) 124 | void shouldSaveNewProduct() throws Exception { 125 | Product productDetails = productProvider.withoutId(); 126 | Product expected = productProvider.full(); 127 | 128 | when(productRepository.save(productDetails)) 129 | .thenReturn(Optional.of(expected)); 130 | 131 | String expectedResponse = objectMapper.writeValueAsString(expected); 132 | 133 | mvc.perform(post(BASE_PATH) 134 | .contentType(MediaType.APPLICATION_JSON) 135 | .content(objectMapper.writeValueAsString(productDetails)) 136 | .with(csrf())) 137 | .andExpect(status().isOk()) 138 | .andExpect(content().json(expectedResponse)); 139 | } 140 | 141 | @Test 142 | @WithAnonymousUser() 143 | void shouldNotSaveNewProductForAnonymousUser() throws Exception { 144 | Product productDetails = productProvider.withoutId(); 145 | 146 | mvc.perform(post(BASE_PATH) 147 | .contentType(MediaType.APPLICATION_JSON) 148 | .content(objectMapper.writeValueAsString(productDetails)) 149 | .with(csrf())) 150 | .andExpect(status().isForbidden()); 151 | } 152 | 153 | @Test 154 | void shouldNotSaveNewProductForUnauthenticatedUser() throws Exception { 155 | Product productDetails = productProvider.withoutId(); 156 | 157 | mvc.perform(post(BASE_PATH) 158 | .contentType(MediaType.APPLICATION_JSON) 159 | .content(objectMapper.writeValueAsString(productDetails)) 160 | .with(csrf())) 161 | .andExpect(status().isForbidden()); 162 | } 163 | } -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/annotations/RestControllerIntegrationTest.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.annotations; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.ControllerIntegrationTestConfig; 4 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; 5 | import org.springframework.core.annotation.AliasFor; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.ContextConfiguration; 8 | 9 | import java.lang.annotation.ElementType; 10 | import java.lang.annotation.Retention; 11 | import java.lang.annotation.RetentionPolicy; 12 | import java.lang.annotation.Target; 13 | 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.TYPE) 16 | @WebMvcTest 17 | @ActiveProfiles("test") 18 | @ContextConfiguration(classes = ControllerIntegrationTestConfig.class) 19 | public @interface RestControllerIntegrationTest { 20 | 21 | /** 22 | * @see WebMvcTest#value 23 | */ 24 | @AliasFor(annotation = WebMvcTest.class, attribute = "value") 25 | Class[] value() default {}; 26 | 27 | /** 28 | * @see WebMvcTest#controllers 29 | */ 30 | @AliasFor(annotation = WebMvcTest.class, attribute = "controllers") 31 | Class[] controllers() default {}; 32 | } 33 | -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/ControllerIntegrationTestConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity; 5 | 6 | @TestConfiguration 7 | @EnableMethodSecurity 8 | public class ControllerIntegrationTestConfig { 9 | } 10 | -------------------------------------------------------------------------------- /src/test/java/in/keepgrowing/springbootswaggeruikeycloak/shared/infrastructure/config/TestSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config; 2 | 3 | import in.keepgrowing.springbootswaggeruikeycloak.shared.infrastructure.config.security.SecurityConfig; 4 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | 10 | @Configuration 11 | @EnableWebSecurity 12 | @ConditionalOnProperty(name = "security.config.use-keycloak", havingValue = "false") 13 | public class TestSecurityConfig extends WebSecurityConfigurerAdapter { 14 | 15 | @Override 16 | protected void configure(HttpSecurity http) throws Exception { 17 | SecurityConfig.configureApiSecurity(http); 18 | } 19 | } 20 | --------------------------------------------------------------------------------