├── .github
└── ISSUE_TEMPLATE
│ ├── bug_report.md
│ └── feature_request.md
├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
├── postman_collection_v2.json
├── realm-export.json
└── src
├── main
├── java
│ └── com
│ │ └── ineat
│ │ └── tutorials
│ │ └── springkeycloaktutorials
│ │ ├── SpringKeycloakTutorialsApisController.java
│ │ ├── SpringKeycloakTutorialsApplication.java
│ │ └── SpringKeycloakTutorialsSecurityConfiguration.java
└── resources
│ └── application.properties
└── test
└── java
└── com
└── ineat
└── tutorials
└── springkeycloaktutorials
└── SpringKeycloakTutorialsSecurityTests.java
/.github/ISSUE_TEMPLATE/bug_report.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug report
3 | about: Create a report to help us improve
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Describe the bug**
11 | A clear and concise description of what the bug is.
12 |
13 | **To Reproduce**
14 | Steps to reproduce the behavior:
15 | 1. Go to '...'
16 | 2. Click on '....'
17 | 3. Scroll down to '....'
18 | 4. See error
19 |
20 | **Expected behavior**
21 | A clear and concise description of what you expected to happen.
22 |
23 | **Screenshots**
24 | If applicable, add screenshots to help explain your problem.
25 |
26 | **Desktop (please complete the following information):**
27 | - OS: [e.g. iOS]
28 | - Browser [e.g. chrome, safari]
29 | - Version [e.g. 22]
30 |
31 | **Smartphone (please complete the following information):**
32 | - Device: [e.g. iPhone6]
33 | - OS: [e.g. iOS8.1]
34 | - Browser [e.g. stock browser, safari]
35 | - Version [e.g. 22]
36 |
37 | **Additional context**
38 | Add any other context about the problem here.
39 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature_request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Suggest an idea for this project
4 | title: ''
5 | labels: ''
6 | assignees: ''
7 |
8 | ---
9 |
10 | **Is your feature request related to a problem? Please describe.**
11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
12 |
13 | **Describe the solution you'd like**
14 | A clear and concise description of what you want to happen.
15 |
16 | **Describe alternatives you've considered**
17 | A clear and concise description of any alternative solutions or features you've considered.
18 |
19 | **Additional context**
20 | Add any other context or screenshots about the feature request here.
21 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ineat/spring-keycloak-tutorials/f0356c698b5a16bafdcae1e473c791b148a9432e/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
2 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Spring / Keycloak Tutorials
2 |
3 | This is a sandbox / sample project to illustrate [this blog article](https://blog.ineat-group.com/2017/12/securisez-vos-apis-spring-avec-keycloak-3-utilisation-des-connecteurs-spring-de-keycloak/), part of our [Keycloak series](https://blog.ineat-group.com/tag/keycloak-series/).
4 |
5 | ## Technologies
6 |
7 | * Java 11
8 | * JUnit 5
9 | * Spring Boot 2.4.2
10 | * Keycloak 12.0.2
11 |
12 | ## Pre-requisites
13 |
14 | * Start your Keycloak server before running the app
15 | > 👉 [How to install & start Keycloak](https://blog.ineat-group.com/2017/11/securisez-vos-apis-spring-avec-keycloak-1-installation-de-keycloak/)
16 |
17 | * Create a demo realm working with this app
18 |
19 | > * Import the provided [realm-export.json](realm-export.json)
20 | > * 👉 Or follow [How to create a realm for this sample project](https://blog.ineat-group.com/2017/11/securisez-vos-apis-spring-avec-keycloak-2-parametrage-dun-domaine-keycloak/)
21 |
22 | * Create 2 users
23 | * One `ineat-admin` / `password` with `ADMIN` role associated
24 | * One `ineat-user` / `password` With `USER` role associated
25 |
26 | ## How to start
27 |
28 | * For test :
29 |
30 | ```shell
31 | mvn clean test
32 | ```
33 |
34 | * To run the app :
35 |
36 | ```shell
37 | mvn spring-boot:run
38 | ```
39 |
40 | ## How to test the Keycloak security
41 |
42 | * Use the provided [postman_collection_v2.json](postman_collection_v2.json)
43 | * Generate an `access_token` thanks to the `KEYCLOAK request TOKEN for USER` or `KEYCLOAK request TOKEN for ADMIN`
44 | * Use this `access_token` as `Bearer` to call the protected `/user` or `/admin` endpoints thanks to the `Request /user path` or `Request /admin path`
45 |
46 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 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 Migwn, 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 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/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 http://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 Maven2 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 key stroke 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 enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | com.ineat.tutorials
7 | spring-keycloak-tutorials
8 | 0.0.1-SNAPSHOT
9 | jar
10 |
11 | spring-keycloak-tutorials
12 | Sample project about SPRING API securisation with Keycloak connectors
13 |
14 |
15 | org.springframework.boot
16 | spring-boot-starter-parent
17 | 2.4.2
18 |
19 |
20 |
21 |
22 | UTF-8
23 | UTF-8
24 | 11
25 | 12.0.2
26 |
27 |
28 |
29 |
30 |
31 | org.springframework.boot
32 | spring-boot-starter-security
33 |
34 |
35 | org.springframework.boot
36 | spring-boot-starter-validation
37 |
38 |
39 | org.springframework.boot
40 | spring-boot-starter-web
41 |
42 |
43 | org.keycloak
44 | keycloak-spring-boot-starter
45 |
46 |
47 | org.springframework.boot
48 | spring-boot-devtools
49 | runtime
50 |
51 |
52 | org.springframework.boot
53 | spring-boot-configuration-processor
54 | true
55 |
56 |
57 | org.projectlombok
58 | lombok
59 | true
60 |
61 |
62 | org.springframework.boot
63 | spring-boot-starter-test
64 | test
65 |
66 |
67 | org.junit.vintage
68 | junit-vintage-engine
69 |
70 |
71 |
72 |
73 | org.springframework.security
74 | spring-security-test
75 | test
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 | org.keycloak.bom
84 | keycloak-adapter-bom
85 | ${keycloak.version}
86 | pom
87 | import
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 | org.springframework.boot
96 | spring-boot-maven-plugin
97 |
98 |
99 |
100 |
101 |
102 |
103 |
--------------------------------------------------------------------------------
/postman_collection_v2.json:
--------------------------------------------------------------------------------
1 | {
2 | "variables": [],
3 | "info": {
4 | "name": "Spring Keycloak Tutorials",
5 | "_postman_id": "162c7aaa-a946-8e7c-4655-3e02be3c811d",
6 | "description": "",
7 | "schema": "https://schema.getpostman.com/json/collection/v2.0.0/collection.json"
8 | },
9 | "item": [
10 | {
11 | "name": "KEYCLOAK request TOKEN for USER",
12 | "request": {
13 | "url": "http://localhost:8180/auth/realms/ineat-realm/protocol/openid-connect/token",
14 | "method": "POST",
15 | "header": [
16 | {
17 | "key": "Accept",
18 | "value": "application/json",
19 | "description": ""
20 | },
21 | {
22 | "key": "Content-Type",
23 | "value": "application/x-www-form-urlencoded",
24 | "description": ""
25 | },
26 | {
27 | "key": "X-CSRF-TOKEN",
28 | "value": "1",
29 | "description": "",
30 | "disabled": true
31 | }
32 | ],
33 | "body": {
34 | "mode": "urlencoded",
35 | "urlencoded": [
36 | {
37 | "key": "grant_type",
38 | "value": "password",
39 | "description": "",
40 | "type": "text"
41 | },
42 | {
43 | "key": "username",
44 | "value": "ineat-user",
45 | "description": "",
46 | "type": "text"
47 | },
48 | {
49 | "key": "password",
50 | "value": "password",
51 | "description": "",
52 | "type": "text"
53 | },
54 | {
55 | "key": "client_id",
56 | "value": "ineat-web",
57 | "description": "",
58 | "type": "text"
59 | },
60 | {
61 | "key": "client_secret",
62 | "value": "038c7378-c478-4c5c-a616-c1ddf294cdb7",
63 | "description": "",
64 | "type": "text"
65 | },
66 | {
67 | "key": "grant_type",
68 | "value": "client_credentials",
69 | "description": "",
70 | "type": "text",
71 | "disabled": true
72 | }
73 | ]
74 | },
75 | "description": "Dont forget to change client-secret with yours"
76 | },
77 | "response": []
78 | },
79 | {
80 | "name": "KEYCLOAK request TOKEN for ADMIN",
81 | "request": {
82 | "url": "http://localhost:8180/auth/realms/ineat-realm/protocol/openid-connect/token",
83 | "method": "POST",
84 | "header": [
85 | {
86 | "key": "Accept",
87 | "value": "application/json",
88 | "description": ""
89 | },
90 | {
91 | "key": "Content-Type",
92 | "value": "application/x-www-form-urlencoded",
93 | "description": ""
94 | },
95 | {
96 | "key": "X-CSRF-TOKEN",
97 | "value": "1",
98 | "description": "",
99 | "disabled": true
100 | }
101 | ],
102 | "body": {
103 | "mode": "urlencoded",
104 | "urlencoded": [
105 | {
106 | "key": "grant_type",
107 | "value": "password",
108 | "description": "",
109 | "type": "text"
110 | },
111 | {
112 | "key": "username",
113 | "value": "ineat-admin",
114 | "description": "",
115 | "type": "text"
116 | },
117 | {
118 | "key": "password",
119 | "value": "password",
120 | "description": "",
121 | "type": "text"
122 | },
123 | {
124 | "key": "client_id",
125 | "value": "ineat-web",
126 | "description": "",
127 | "type": "text"
128 | },
129 | {
130 | "key": "client_secret",
131 | "value": "038c7378-c478-4c5c-a616-c1ddf294cdb7",
132 | "description": "",
133 | "type": "text"
134 | },
135 | {
136 | "key": "grant_type",
137 | "value": "client_credentials",
138 | "description": "",
139 | "type": "text",
140 | "disabled": true
141 | }
142 | ]
143 | },
144 | "description": "Dont forget to change client-secret with yours"
145 | },
146 | "response": []
147 | },
148 | {
149 | "name": "Request /user path",
150 | "request": {
151 | "url": "http://localhost:8080/user",
152 | "method": "GET",
153 | "header": [
154 | {
155 | "key": "Authorization",
156 | "value": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3bzc0OVZFNHZnbHpfeGRpUEM1aUlmZFdtamg4ZktnTHEzMzRuQmxxRDQ0In0.eyJqdGkiOiJlOTdmYWIyZC00N2EyLTQ5MWQtYWY5NC04ZDIxNjYyNTc5OTAiLCJleHAiOjE1MTI1OTg4NjYsIm5iZiI6MCwiaWF0IjoxNTEyNTk4NTY2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxODAvYXV0aC9yZWFsbXMvaW5lYXQtcmVhbG0iLCJhdWQiOiJpbmVhdC13ZWIiLCJzdWIiOiJjZTI1OTc1OC01Y2Q2LTQzMzktYmY0MC1iYzllZTEzZTk0ZTkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJpbmVhdC13ZWIiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMGIyNTQwMC1jNWQwLTQzMWUtYmI1My0xZDhlM2YxNDNmNDEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInByZWZlcnJlZF91c2VybmFtZSI6ImluZWF0LXVzZXIifQ.ojhOlyFDRSWpDyefgsPwv4N0CcQBDHIJwtVPTZIUQhxaZ0jvn5d3lQKWOtoeCwDXQ4woreh0jK3nBJjFmhdzTPCtZeDToMRMn1U-LgFpL8SXe6vXqXrVYE5_0kSv50Nx6dTPdA2Nzb5qwD445F5EVgweOEoyFAzIDakzInqNfbN745l998HLJTtpwp38fa6vKiZP2r6POoHt2jlNv6uKfYvQPUykgfPfrEY6b_j5z-NecH4aHfBrpGd27T2_xH5_oplIWUlJ_2P2o19ipt0uFIbLplCskODwGhXaQFIaeENUSDAxPBpuHM_9-rdHJRCqoUGX7Qvz8bqTBptP_Oh4AQ",
157 | "description": ""
158 | }
159 | ],
160 | "body": {},
161 | "description": "Dont forget to set Bearer value obtains from token request"
162 | },
163 | "response": []
164 | },
165 | {
166 | "name": "Request /admin path",
167 | "request": {
168 | "url": "http://localhost:8080/admin",
169 | "method": "GET",
170 | "header": [
171 | {
172 | "key": "Authorization",
173 | "value": "Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICI3bzc0OVZFNHZnbHpfeGRpUEM1aUlmZFdtamg4ZktnTHEzMzRuQmxxRDQ0In0.eyJqdGkiOiJlOTdmYWIyZC00N2EyLTQ5MWQtYWY5NC04ZDIxNjYyNTc5OTAiLCJleHAiOjE1MTI1OTg4NjYsIm5iZiI6MCwiaWF0IjoxNTEyNTk4NTY2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjgxODAvYXV0aC9yZWFsbXMvaW5lYXQtcmVhbG0iLCJhdWQiOiJpbmVhdC13ZWIiLCJzdWIiOiJjZTI1OTc1OC01Y2Q2LTQzMzktYmY0MC1iYzllZTEzZTk0ZTkiLCJ0eXAiOiJCZWFyZXIiLCJhenAiOiJpbmVhdC13ZWIiLCJhdXRoX3RpbWUiOjAsInNlc3Npb25fc3RhdGUiOiJkMGIyNTQwMC1jNWQwLTQzMWUtYmI1My0xZDhlM2YxNDNmNDEiLCJhY3IiOiIxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbInVtYV9hdXRob3JpemF0aW9uIiwiVVNFUiJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJtYW5hZ2UtYWNjb3VudC1saW5rcyIsInZpZXctcHJvZmlsZSJdfX0sInByZWZlcnJlZF91c2VybmFtZSI6ImluZWF0LXVzZXIifQ.ojhOlyFDRSWpDyefgsPwv4N0CcQBDHIJwtVPTZIUQhxaZ0jvn5d3lQKWOtoeCwDXQ4woreh0jK3nBJjFmhdzTPCtZeDToMRMn1U-LgFpL8SXe6vXqXrVYE5_0kSv50Nx6dTPdA2Nzb5qwD445F5EVgweOEoyFAzIDakzInqNfbN745l998HLJTtpwp38fa6vKiZP2r6POoHt2jlNv6uKfYvQPUykgfPfrEY6b_j5z-NecH4aHfBrpGd27T2_xH5_oplIWUlJ_2P2o19ipt0uFIbLplCskODwGhXaQFIaeENUSDAxPBpuHM_9-rdHJRCqoUGX7Qvz8bqTBptP_Oh4AQ",
174 | "description": ""
175 | }
176 | ],
177 | "body": {},
178 | "description": "Dont forget to set Bearer value obtains from token request"
179 | },
180 | "response": []
181 | }
182 | ]
183 | }
--------------------------------------------------------------------------------
/realm-export.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "ineat-realm",
3 | "realm": "ineat-realm",
4 | "notBefore": 0,
5 | "revokeRefreshToken": false,
6 | "refreshTokenMaxReuse": 0,
7 | "accessTokenLifespan": 300,
8 | "accessTokenLifespanForImplicitFlow": 900,
9 | "ssoSessionIdleTimeout": 1800,
10 | "ssoSessionMaxLifespan": 36000,
11 | "offlineSessionIdleTimeout": 2592000,
12 | "offlineSessionMaxLifespanEnabled": false,
13 | "offlineSessionMaxLifespan": 5184000,
14 | "accessCodeLifespan": 60,
15 | "accessCodeLifespanUserAction": 300,
16 | "accessCodeLifespanLogin": 1800,
17 | "actionTokenGeneratedByAdminLifespan": 43200,
18 | "actionTokenGeneratedByUserLifespan": 300,
19 | "enabled": true,
20 | "sslRequired": "external",
21 | "registrationAllowed": false,
22 | "registrationEmailAsUsername": false,
23 | "rememberMe": false,
24 | "verifyEmail": false,
25 | "loginWithEmailAllowed": true,
26 | "duplicateEmailsAllowed": false,
27 | "resetPasswordAllowed": false,
28 | "editUsernameAllowed": false,
29 | "bruteForceProtected": false,
30 | "permanentLockout": false,
31 | "maxFailureWaitSeconds": 900,
32 | "minimumQuickLoginWaitSeconds": 60,
33 | "waitIncrementSeconds": 60,
34 | "quickLoginCheckMilliSeconds": 1000,
35 | "maxDeltaTimeSeconds": 43200,
36 | "failureFactor": 30,
37 | "roles": {
38 | "realm": [
39 | {
40 | "id": "5b8b457e-8f6e-4d6a-b144-03c0e121c19d",
41 | "name": "offline_access",
42 | "description": "${role_offline-access}",
43 | "composite": false,
44 | "clientRole": false,
45 | "containerId": "ineat-realm"
46 | },
47 | {
48 | "id": "beac463c-7db5-4da0-b45d-97558d001b81",
49 | "name": "uma_authorization",
50 | "description": "${role_uma_authorization}",
51 | "composite": false,
52 | "clientRole": false,
53 | "containerId": "ineat-realm"
54 | },
55 | {
56 | "id": "f506295d-5da0-4e3c-a04f-fd5bcf58decd",
57 | "name": "USER",
58 | "composite": false,
59 | "clientRole": false,
60 | "containerId": "ineat-realm"
61 | },
62 | {
63 | "id": "b066e9a0-7c5f-4aea-8476-2590d70bce64",
64 | "name": "ADMIN",
65 | "composite": false,
66 | "clientRole": false,
67 | "containerId": "ineat-realm"
68 | }
69 | ],
70 | "client": {
71 | "realm-management": [
72 | {
73 | "id": "88410ae9-ad5a-4616-af9c-f0a81f7eb9f3",
74 | "name": "view-clients",
75 | "description": "${role_view-clients}",
76 | "composite": true,
77 | "composites": {
78 | "client": {
79 | "realm-management": [
80 | "query-clients"
81 | ]
82 | }
83 | },
84 | "clientRole": true,
85 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
86 | },
87 | {
88 | "id": "b3df9a4b-b5e5-467b-bb5e-80e8e618c483",
89 | "name": "manage-realm",
90 | "description": "${role_manage-realm}",
91 | "composite": false,
92 | "clientRole": true,
93 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
94 | },
95 | {
96 | "id": "c56177fb-c1e0-43ba-9549-6a54f45e184c",
97 | "name": "manage-events",
98 | "description": "${role_manage-events}",
99 | "composite": false,
100 | "clientRole": true,
101 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
102 | },
103 | {
104 | "id": "603c20d8-85e8-43c6-88fe-aa8cd2aa9be3",
105 | "name": "manage-authorization",
106 | "description": "${role_manage-authorization}",
107 | "composite": false,
108 | "clientRole": true,
109 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
110 | },
111 | {
112 | "id": "7d96b081-594d-437a-b9b1-59975b7023de",
113 | "name": "query-groups",
114 | "description": "${role_query-groups}",
115 | "composite": false,
116 | "clientRole": true,
117 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
118 | },
119 | {
120 | "id": "848544c3-bb3d-44b2-ada5-6c9da7a56693",
121 | "name": "view-identity-providers",
122 | "description": "${role_view-identity-providers}",
123 | "composite": false,
124 | "clientRole": true,
125 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
126 | },
127 | {
128 | "id": "9b6ac21a-d695-4f97-87b2-72402b28f15d",
129 | "name": "query-users",
130 | "description": "${role_query-users}",
131 | "composite": false,
132 | "clientRole": true,
133 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
134 | },
135 | {
136 | "id": "49d0121a-7348-44b4-9cc5-e0790f3fc515",
137 | "name": "manage-users",
138 | "description": "${role_manage-users}",
139 | "composite": false,
140 | "clientRole": true,
141 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
142 | },
143 | {
144 | "id": "fa1c34e8-c5d0-4152-8725-6ad2db9ad268",
145 | "name": "view-authorization",
146 | "description": "${role_view-authorization}",
147 | "composite": false,
148 | "clientRole": true,
149 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
150 | },
151 | {
152 | "id": "52e7e7c8-ee20-4106-ae64-cd194e427186",
153 | "name": "manage-clients",
154 | "description": "${role_manage-clients}",
155 | "composite": false,
156 | "clientRole": true,
157 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
158 | },
159 | {
160 | "id": "1a281575-a3e6-4ee3-b7af-983a4c93709b",
161 | "name": "query-realms",
162 | "description": "${role_query-realms}",
163 | "composite": false,
164 | "clientRole": true,
165 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
166 | },
167 | {
168 | "id": "e39f3ab3-2474-45f9-ba6d-bbe21dce088a",
169 | "name": "view-realm",
170 | "description": "${role_view-realm}",
171 | "composite": false,
172 | "clientRole": true,
173 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
174 | },
175 | {
176 | "id": "8668c37e-40fd-43d0-a535-3bd7c20eccbf",
177 | "name": "view-users",
178 | "description": "${role_view-users}",
179 | "composite": true,
180 | "composites": {
181 | "client": {
182 | "realm-management": [
183 | "query-groups",
184 | "query-users"
185 | ]
186 | }
187 | },
188 | "clientRole": true,
189 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
190 | },
191 | {
192 | "id": "b0ae0074-decb-46eb-bc1f-45c83b2d3014",
193 | "name": "create-client",
194 | "description": "${role_create-client}",
195 | "composite": false,
196 | "clientRole": true,
197 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
198 | },
199 | {
200 | "id": "edd09692-259c-4e23-a46e-e1a6b1e3a5e6",
201 | "name": "view-events",
202 | "description": "${role_view-events}",
203 | "composite": false,
204 | "clientRole": true,
205 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
206 | },
207 | {
208 | "id": "b59258a9-0de4-457d-90c5-d611701416ba",
209 | "name": "query-clients",
210 | "description": "${role_query-clients}",
211 | "composite": false,
212 | "clientRole": true,
213 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
214 | },
215 | {
216 | "id": "69db6dd7-5b48-4ac3-bb32-505d4871c1e1",
217 | "name": "manage-identity-providers",
218 | "description": "${role_manage-identity-providers}",
219 | "composite": false,
220 | "clientRole": true,
221 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
222 | },
223 | {
224 | "id": "c6886cce-cd83-4f4e-a7cc-185ba0764076",
225 | "name": "realm-admin",
226 | "description": "${role_realm-admin}",
227 | "composite": true,
228 | "composites": {
229 | "client": {
230 | "realm-management": [
231 | "view-clients",
232 | "manage-realm",
233 | "manage-events",
234 | "manage-authorization",
235 | "query-groups",
236 | "view-identity-providers",
237 | "query-users",
238 | "manage-users",
239 | "view-authorization",
240 | "manage-clients",
241 | "query-realms",
242 | "view-realm",
243 | "view-users",
244 | "create-client",
245 | "view-events",
246 | "query-clients",
247 | "manage-identity-providers",
248 | "impersonation"
249 | ]
250 | }
251 | },
252 | "clientRole": true,
253 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
254 | },
255 | {
256 | "id": "3b75da35-da7b-47e3-a505-16cc904597ce",
257 | "name": "impersonation",
258 | "description": "${role_impersonation}",
259 | "composite": false,
260 | "clientRole": true,
261 | "containerId": "0dbf06ab-7468-4239-87e1-1435342e193d"
262 | }
263 | ],
264 | "ineat-api": [],
265 | "security-admin-console": [],
266 | "admin-cli": [],
267 | "broker": [
268 | {
269 | "id": "85f87bba-455d-4ae3-918f-8c7fa1ada2de",
270 | "name": "read-token",
271 | "description": "${role_read-token}",
272 | "composite": false,
273 | "clientRole": true,
274 | "containerId": "f7524dfc-e6cd-42a5-a095-ee0270524d6b"
275 | }
276 | ],
277 | "ineat-web": [],
278 | "account": [
279 | {
280 | "id": "232a872c-7d9f-4cbe-ae7b-fa556cde3f6e",
281 | "name": "manage-account-links",
282 | "description": "${role_manage-account-links}",
283 | "composite": false,
284 | "clientRole": true,
285 | "containerId": "0ac4cbb2-d365-4fd7-a30f-61838ff979b5"
286 | },
287 | {
288 | "id": "d3e5c12d-da1c-4dbd-9776-bb0ebdd6cf0b",
289 | "name": "manage-account",
290 | "description": "${role_manage-account}",
291 | "composite": true,
292 | "composites": {
293 | "client": {
294 | "account": [
295 | "manage-account-links"
296 | ]
297 | }
298 | },
299 | "clientRole": true,
300 | "containerId": "0ac4cbb2-d365-4fd7-a30f-61838ff979b5"
301 | },
302 | {
303 | "id": "6aeeb2de-210f-4dfa-b905-d7294d3f710c",
304 | "name": "view-profile",
305 | "description": "${role_view-profile}",
306 | "composite": false,
307 | "clientRole": true,
308 | "containerId": "0ac4cbb2-d365-4fd7-a30f-61838ff979b5"
309 | }
310 | ]
311 | }
312 | },
313 | "groups": [],
314 | "defaultRoles": [
315 | "offline_access",
316 | "uma_authorization"
317 | ],
318 | "requiredCredentials": [
319 | "password"
320 | ],
321 | "otpPolicyType": "totp",
322 | "otpPolicyAlgorithm": "HmacSHA1",
323 | "otpPolicyInitialCounter": 0,
324 | "otpPolicyDigits": 6,
325 | "otpPolicyLookAheadWindow": 1,
326 | "otpPolicyPeriod": 30,
327 | "otpSupportedApplications": [
328 | "FreeOTP",
329 | "Google Authenticator"
330 | ],
331 | "scopeMappings": [
332 | {
333 | "clientScope": "offline_access",
334 | "roles": [
335 | "offline_access"
336 | ]
337 | }
338 | ],
339 | "clients": [
340 | {
341 | "id": "cc75d1fa-349f-4be9-b1c5-c48bd2b09bf1",
342 | "clientId": "ineat-api",
343 | "surrogateAuthRequired": false,
344 | "enabled": true,
345 | "clientAuthenticatorType": "client-secret",
346 | "secret": "**********",
347 | "redirectUris": [],
348 | "webOrigins": [],
349 | "notBefore": 0,
350 | "bearerOnly": true,
351 | "consentRequired": false,
352 | "standardFlowEnabled": true,
353 | "implicitFlowEnabled": false,
354 | "directAccessGrantsEnabled": true,
355 | "serviceAccountsEnabled": false,
356 | "publicClient": false,
357 | "frontchannelLogout": false,
358 | "protocol": "openid-connect",
359 | "attributes": {
360 | "saml.assertion.signature": "false",
361 | "saml.force.post.binding": "false",
362 | "saml.multivalued.roles": "false",
363 | "saml.encrypt": "false",
364 | "saml.server.signature": "false",
365 | "saml.server.signature.keyinfo.ext": "false",
366 | "exclude.session.state.from.auth.response": "false",
367 | "saml_force_name_id_format": "false",
368 | "saml.client.signature": "false",
369 | "tls.client.certificate.bound.access.tokens": "false",
370 | "saml.authnstatement": "false",
371 | "display.on.consent.screen": "false",
372 | "saml.onetimeuse.condition": "false"
373 | },
374 | "authenticationFlowBindingOverrides": {},
375 | "fullScopeAllowed": true,
376 | "nodeReRegistrationTimeout": -1,
377 | "defaultClientScopes": [
378 | "role_list",
379 | "profile",
380 | "email"
381 | ],
382 | "optionalClientScopes": [
383 | "address",
384 | "phone",
385 | "offline_access"
386 | ]
387 | },
388 | {
389 | "id": "0dbf06ab-7468-4239-87e1-1435342e193d",
390 | "clientId": "realm-management",
391 | "name": "${client_realm-management}",
392 | "surrogateAuthRequired": false,
393 | "enabled": true,
394 | "clientAuthenticatorType": "client-secret",
395 | "secret": "**********",
396 | "redirectUris": [],
397 | "webOrigins": [],
398 | "notBefore": 0,
399 | "bearerOnly": true,
400 | "consentRequired": false,
401 | "standardFlowEnabled": true,
402 | "implicitFlowEnabled": false,
403 | "directAccessGrantsEnabled": false,
404 | "serviceAccountsEnabled": false,
405 | "publicClient": false,
406 | "frontchannelLogout": false,
407 | "protocol": "openid-connect",
408 | "attributes": {},
409 | "authenticationFlowBindingOverrides": {},
410 | "fullScopeAllowed": false,
411 | "nodeReRegistrationTimeout": 0,
412 | "defaultClientScopes": [
413 | "role_list",
414 | "profile",
415 | "email"
416 | ],
417 | "optionalClientScopes": [
418 | "address",
419 | "phone",
420 | "offline_access"
421 | ]
422 | },
423 | {
424 | "id": "aa169386-5dd9-4631-b24b-0c5d7ec2df4a",
425 | "clientId": "security-admin-console",
426 | "name": "${client_security-admin-console}",
427 | "baseUrl": "/auth/admin/ineat-realm/console/index.html",
428 | "surrogateAuthRequired": false,
429 | "enabled": true,
430 | "clientAuthenticatorType": "client-secret",
431 | "secret": "**********",
432 | "redirectUris": [
433 | "/auth/admin/ineat-realm/console/*"
434 | ],
435 | "webOrigins": [],
436 | "notBefore": 0,
437 | "bearerOnly": false,
438 | "consentRequired": false,
439 | "standardFlowEnabled": true,
440 | "implicitFlowEnabled": false,
441 | "directAccessGrantsEnabled": false,
442 | "serviceAccountsEnabled": false,
443 | "publicClient": true,
444 | "frontchannelLogout": false,
445 | "protocol": "openid-connect",
446 | "attributes": {},
447 | "authenticationFlowBindingOverrides": {},
448 | "fullScopeAllowed": false,
449 | "nodeReRegistrationTimeout": 0,
450 | "protocolMappers": [
451 | {
452 | "id": "ef5d2047-05f5-49a0-aed2-15f197a9cfd1",
453 | "name": "locale",
454 | "protocol": "openid-connect",
455 | "protocolMapper": "oidc-usermodel-attribute-mapper",
456 | "consentRequired": false,
457 | "config": {
458 | "userinfo.token.claim": "true",
459 | "user.attribute": "locale",
460 | "id.token.claim": "true",
461 | "access.token.claim": "true",
462 | "claim.name": "locale",
463 | "jsonType.label": "String"
464 | }
465 | }
466 | ],
467 | "defaultClientScopes": [
468 | "role_list",
469 | "profile",
470 | "email"
471 | ],
472 | "optionalClientScopes": [
473 | "address",
474 | "phone",
475 | "offline_access"
476 | ]
477 | },
478 | {
479 | "id": "ac687b0f-5277-47d0-af94-b10fe2b95de5",
480 | "clientId": "admin-cli",
481 | "name": "${client_admin-cli}",
482 | "surrogateAuthRequired": false,
483 | "enabled": true,
484 | "clientAuthenticatorType": "client-secret",
485 | "secret": "**********",
486 | "redirectUris": [],
487 | "webOrigins": [],
488 | "notBefore": 0,
489 | "bearerOnly": false,
490 | "consentRequired": false,
491 | "standardFlowEnabled": false,
492 | "implicitFlowEnabled": false,
493 | "directAccessGrantsEnabled": true,
494 | "serviceAccountsEnabled": false,
495 | "publicClient": true,
496 | "frontchannelLogout": false,
497 | "protocol": "openid-connect",
498 | "attributes": {},
499 | "authenticationFlowBindingOverrides": {},
500 | "fullScopeAllowed": false,
501 | "nodeReRegistrationTimeout": 0,
502 | "defaultClientScopes": [
503 | "role_list",
504 | "profile",
505 | "email"
506 | ],
507 | "optionalClientScopes": [
508 | "address",
509 | "phone",
510 | "offline_access"
511 | ]
512 | },
513 | {
514 | "id": "75c9b822-b52b-47a3-879d-1bb56a568ef2",
515 | "clientId": "ineat-web",
516 | "surrogateAuthRequired": false,
517 | "enabled": true,
518 | "clientAuthenticatorType": "client-secret",
519 | "secret": "**********",
520 | "redirectUris": [],
521 | "webOrigins": [],
522 | "notBefore": 0,
523 | "bearerOnly": false,
524 | "consentRequired": false,
525 | "standardFlowEnabled": true,
526 | "implicitFlowEnabled": false,
527 | "directAccessGrantsEnabled": true,
528 | "serviceAccountsEnabled": false,
529 | "publicClient": true,
530 | "frontchannelLogout": false,
531 | "protocol": "openid-connect",
532 | "attributes": {},
533 | "authenticationFlowBindingOverrides": {},
534 | "fullScopeAllowed": true,
535 | "nodeReRegistrationTimeout": -1,
536 | "defaultClientScopes": [
537 | "role_list",
538 | "profile",
539 | "email"
540 | ],
541 | "optionalClientScopes": [
542 | "address",
543 | "phone",
544 | "offline_access"
545 | ]
546 | },
547 | {
548 | "id": "f7524dfc-e6cd-42a5-a095-ee0270524d6b",
549 | "clientId": "broker",
550 | "name": "${client_broker}",
551 | "surrogateAuthRequired": false,
552 | "enabled": true,
553 | "clientAuthenticatorType": "client-secret",
554 | "secret": "**********",
555 | "redirectUris": [],
556 | "webOrigins": [],
557 | "notBefore": 0,
558 | "bearerOnly": false,
559 | "consentRequired": false,
560 | "standardFlowEnabled": true,
561 | "implicitFlowEnabled": false,
562 | "directAccessGrantsEnabled": false,
563 | "serviceAccountsEnabled": false,
564 | "publicClient": false,
565 | "frontchannelLogout": false,
566 | "protocol": "openid-connect",
567 | "attributes": {},
568 | "authenticationFlowBindingOverrides": {},
569 | "fullScopeAllowed": false,
570 | "nodeReRegistrationTimeout": 0,
571 | "defaultClientScopes": [
572 | "role_list",
573 | "profile",
574 | "email"
575 | ],
576 | "optionalClientScopes": [
577 | "address",
578 | "phone",
579 | "offline_access"
580 | ]
581 | },
582 | {
583 | "id": "0ac4cbb2-d365-4fd7-a30f-61838ff979b5",
584 | "clientId": "account",
585 | "name": "${client_account}",
586 | "baseUrl": "/auth/realms/ineat-realm/account",
587 | "surrogateAuthRequired": false,
588 | "enabled": true,
589 | "clientAuthenticatorType": "client-secret",
590 | "secret": "**********",
591 | "defaultRoles": [
592 | "view-profile",
593 | "manage-account"
594 | ],
595 | "redirectUris": [
596 | "/auth/realms/ineat-realm/account/*"
597 | ],
598 | "webOrigins": [],
599 | "notBefore": 0,
600 | "bearerOnly": false,
601 | "consentRequired": false,
602 | "standardFlowEnabled": true,
603 | "implicitFlowEnabled": false,
604 | "directAccessGrantsEnabled": false,
605 | "serviceAccountsEnabled": false,
606 | "publicClient": false,
607 | "frontchannelLogout": false,
608 | "protocol": "openid-connect",
609 | "attributes": {},
610 | "authenticationFlowBindingOverrides": {},
611 | "fullScopeAllowed": false,
612 | "nodeReRegistrationTimeout": 0,
613 | "defaultClientScopes": [
614 | "role_list",
615 | "profile",
616 | "email"
617 | ],
618 | "optionalClientScopes": [
619 | "address",
620 | "phone",
621 | "offline_access"
622 | ]
623 | }
624 | ],
625 | "clientScopes": [
626 | {
627 | "id": "7d50bcfc-6173-4fac-80b1-86f4b67abecb",
628 | "name": "phone",
629 | "description": "OpenID Connect built-in scope: phone",
630 | "protocol": "openid-connect",
631 | "attributes": {
632 | "consent.screen.text": "${phoneScopeConsentText}",
633 | "display.on.consent.screen": "true"
634 | },
635 | "protocolMappers": [
636 | {
637 | "id": "d976029b-de72-4bbf-a083-06ffe7e27d7f",
638 | "name": "phone number verified",
639 | "protocol": "openid-connect",
640 | "protocolMapper": "oidc-usermodel-attribute-mapper",
641 | "consentRequired": false,
642 | "config": {
643 | "userinfo.token.claim": "true",
644 | "user.attribute": "phoneNumberVerified",
645 | "id.token.claim": "true",
646 | "access.token.claim": "true",
647 | "claim.name": "phone_number_verified",
648 | "jsonType.label": "boolean"
649 | }
650 | },
651 | {
652 | "id": "c66c3208-5b34-4fc7-9f3c-c934d9ac5c96",
653 | "name": "phone number",
654 | "protocol": "openid-connect",
655 | "protocolMapper": "oidc-usermodel-attribute-mapper",
656 | "consentRequired": false,
657 | "config": {
658 | "userinfo.token.claim": "true",
659 | "user.attribute": "phoneNumber",
660 | "id.token.claim": "true",
661 | "access.token.claim": "true",
662 | "claim.name": "phone_number",
663 | "jsonType.label": "String"
664 | }
665 | }
666 | ]
667 | },
668 | {
669 | "id": "d4a68cdd-4de0-4466-9727-cb3767789f3c",
670 | "name": "address",
671 | "description": "OpenID Connect built-in scope: address",
672 | "protocol": "openid-connect",
673 | "attributes": {
674 | "consent.screen.text": "${addressScopeConsentText}",
675 | "display.on.consent.screen": "true"
676 | },
677 | "protocolMappers": [
678 | {
679 | "id": "d1165f9f-5626-4d37-ab54-b4b37d781fb0",
680 | "name": "address",
681 | "protocol": "openid-connect",
682 | "protocolMapper": "oidc-address-mapper",
683 | "consentRequired": false,
684 | "config": {
685 | "user.attribute.formatted": "formatted",
686 | "user.attribute.country": "country",
687 | "user.attribute.postal_code": "postal_code",
688 | "userinfo.token.claim": "true",
689 | "user.attribute.street": "street",
690 | "id.token.claim": "true",
691 | "user.attribute.region": "region",
692 | "access.token.claim": "true",
693 | "user.attribute.locality": "locality"
694 | }
695 | }
696 | ]
697 | },
698 | {
699 | "id": "b23309d9-f607-442e-b2df-b470a4ece264",
700 | "name": "email",
701 | "description": "OpenID Connect built-in scope: email",
702 | "protocol": "openid-connect",
703 | "attributes": {
704 | "consent.screen.text": "${emailScopeConsentText}",
705 | "display.on.consent.screen": "true"
706 | },
707 | "protocolMappers": [
708 | {
709 | "id": "5105d834-fbee-46d0-b01c-819a4ac28724",
710 | "name": "email",
711 | "protocol": "openid-connect",
712 | "protocolMapper": "oidc-usermodel-property-mapper",
713 | "consentRequired": false,
714 | "config": {
715 | "userinfo.token.claim": "true",
716 | "user.attribute": "email",
717 | "id.token.claim": "true",
718 | "access.token.claim": "true",
719 | "claim.name": "email",
720 | "jsonType.label": "String"
721 | }
722 | },
723 | {
724 | "id": "72ebaf77-685b-4985-9dbf-570e72409fc2",
725 | "name": "email verified",
726 | "protocol": "openid-connect",
727 | "protocolMapper": "oidc-usermodel-property-mapper",
728 | "consentRequired": false,
729 | "config": {
730 | "userinfo.token.claim": "true",
731 | "user.attribute": "emailVerified",
732 | "id.token.claim": "true",
733 | "access.token.claim": "true",
734 | "claim.name": "email_verified",
735 | "jsonType.label": "boolean"
736 | }
737 | }
738 | ]
739 | },
740 | {
741 | "id": "34e45a9f-a301-44e1-b8ab-a4a422ae4ceb",
742 | "name": "profile",
743 | "description": "OpenID Connect built-in scope: profile",
744 | "protocol": "openid-connect",
745 | "attributes": {
746 | "consent.screen.text": "${profileScopeConsentText}",
747 | "display.on.consent.screen": "true"
748 | },
749 | "protocolMappers": [
750 | {
751 | "id": "3786a734-77e6-4790-b4c0-241acfd95b42",
752 | "name": "full name",
753 | "protocol": "openid-connect",
754 | "protocolMapper": "oidc-full-name-mapper",
755 | "consentRequired": false,
756 | "config": {
757 | "id.token.claim": "true",
758 | "access.token.claim": "true",
759 | "userinfo.token.claim": "true"
760 | }
761 | },
762 | {
763 | "id": "b3aecb97-cd80-479c-a286-656742704e7f",
764 | "name": "nickname",
765 | "protocol": "openid-connect",
766 | "protocolMapper": "oidc-usermodel-attribute-mapper",
767 | "consentRequired": false,
768 | "config": {
769 | "userinfo.token.claim": "true",
770 | "user.attribute": "nickname",
771 | "id.token.claim": "true",
772 | "access.token.claim": "true",
773 | "claim.name": "nickname",
774 | "jsonType.label": "String"
775 | }
776 | },
777 | {
778 | "id": "8f9b2a32-9759-4c10-8e39-d3bcfc62fe3a",
779 | "name": "given name",
780 | "protocol": "openid-connect",
781 | "protocolMapper": "oidc-usermodel-property-mapper",
782 | "consentRequired": false,
783 | "config": {
784 | "userinfo.token.claim": "true",
785 | "user.attribute": "firstName",
786 | "id.token.claim": "true",
787 | "access.token.claim": "true",
788 | "claim.name": "given_name",
789 | "jsonType.label": "String"
790 | }
791 | },
792 | {
793 | "id": "8a8a4bf5-2ee2-410b-a4ee-2b90758bbbf9",
794 | "name": "profile",
795 | "protocol": "openid-connect",
796 | "protocolMapper": "oidc-usermodel-attribute-mapper",
797 | "consentRequired": false,
798 | "config": {
799 | "userinfo.token.claim": "true",
800 | "user.attribute": "profile",
801 | "id.token.claim": "true",
802 | "access.token.claim": "true",
803 | "claim.name": "profile",
804 | "jsonType.label": "String"
805 | }
806 | },
807 | {
808 | "id": "5dc01f9e-b166-4f12-a912-cacba85d8494",
809 | "name": "gender",
810 | "protocol": "openid-connect",
811 | "protocolMapper": "oidc-usermodel-attribute-mapper",
812 | "consentRequired": false,
813 | "config": {
814 | "userinfo.token.claim": "true",
815 | "user.attribute": "gender",
816 | "id.token.claim": "true",
817 | "access.token.claim": "true",
818 | "claim.name": "gender",
819 | "jsonType.label": "String"
820 | }
821 | },
822 | {
823 | "id": "86e66905-0ec0-4cc3-8b53-d095abba2cdb",
824 | "name": "family name",
825 | "protocol": "openid-connect",
826 | "protocolMapper": "oidc-usermodel-property-mapper",
827 | "consentRequired": false,
828 | "config": {
829 | "userinfo.token.claim": "true",
830 | "user.attribute": "lastName",
831 | "id.token.claim": "true",
832 | "access.token.claim": "true",
833 | "claim.name": "family_name",
834 | "jsonType.label": "String"
835 | }
836 | },
837 | {
838 | "id": "f6ae74c9-fdd0-4001-b124-4730f7b01fe9",
839 | "name": "website",
840 | "protocol": "openid-connect",
841 | "protocolMapper": "oidc-usermodel-attribute-mapper",
842 | "consentRequired": false,
843 | "config": {
844 | "userinfo.token.claim": "true",
845 | "user.attribute": "website",
846 | "id.token.claim": "true",
847 | "access.token.claim": "true",
848 | "claim.name": "website",
849 | "jsonType.label": "String"
850 | }
851 | },
852 | {
853 | "id": "ea6a907d-c28d-4d76-b25a-f84d4b4ad840",
854 | "name": "locale",
855 | "protocol": "openid-connect",
856 | "protocolMapper": "oidc-usermodel-attribute-mapper",
857 | "consentRequired": false,
858 | "config": {
859 | "userinfo.token.claim": "true",
860 | "user.attribute": "locale",
861 | "id.token.claim": "true",
862 | "access.token.claim": "true",
863 | "claim.name": "locale",
864 | "jsonType.label": "String"
865 | }
866 | },
867 | {
868 | "id": "8b2a1d8d-852a-4d3e-ba14-f14d20864d1f",
869 | "name": "zoneinfo",
870 | "protocol": "openid-connect",
871 | "protocolMapper": "oidc-usermodel-attribute-mapper",
872 | "consentRequired": false,
873 | "config": {
874 | "userinfo.token.claim": "true",
875 | "user.attribute": "zoneinfo",
876 | "id.token.claim": "true",
877 | "access.token.claim": "true",
878 | "claim.name": "zoneinfo",
879 | "jsonType.label": "String"
880 | }
881 | },
882 | {
883 | "id": "de90ced9-1330-4c84-aaac-4b7e37752f7f",
884 | "name": "picture",
885 | "protocol": "openid-connect",
886 | "protocolMapper": "oidc-usermodel-attribute-mapper",
887 | "consentRequired": false,
888 | "config": {
889 | "userinfo.token.claim": "true",
890 | "user.attribute": "picture",
891 | "id.token.claim": "true",
892 | "access.token.claim": "true",
893 | "claim.name": "picture",
894 | "jsonType.label": "String"
895 | }
896 | },
897 | {
898 | "id": "350b45fc-160a-416a-87d4-0142350e8997",
899 | "name": "birthdate",
900 | "protocol": "openid-connect",
901 | "protocolMapper": "oidc-usermodel-attribute-mapper",
902 | "consentRequired": false,
903 | "config": {
904 | "userinfo.token.claim": "true",
905 | "user.attribute": "birthdate",
906 | "id.token.claim": "true",
907 | "access.token.claim": "true",
908 | "claim.name": "birthdate",
909 | "jsonType.label": "String"
910 | }
911 | },
912 | {
913 | "id": "13e8221c-4d7d-4169-b30d-3e3a434c6649",
914 | "name": "middle name",
915 | "protocol": "openid-connect",
916 | "protocolMapper": "oidc-usermodel-attribute-mapper",
917 | "consentRequired": false,
918 | "config": {
919 | "userinfo.token.claim": "true",
920 | "user.attribute": "middleName",
921 | "id.token.claim": "true",
922 | "access.token.claim": "true",
923 | "claim.name": "middle_name",
924 | "jsonType.label": "String"
925 | }
926 | },
927 | {
928 | "id": "fd2475d2-12b9-4267-859e-70ced6cbd4b1",
929 | "name": "username",
930 | "protocol": "openid-connect",
931 | "protocolMapper": "oidc-usermodel-property-mapper",
932 | "consentRequired": false,
933 | "config": {
934 | "userinfo.token.claim": "true",
935 | "user.attribute": "username",
936 | "id.token.claim": "true",
937 | "access.token.claim": "true",
938 | "claim.name": "preferred_username",
939 | "jsonType.label": "String"
940 | }
941 | },
942 | {
943 | "id": "cb7e0faf-78ee-47a5-a883-b8fc50924533",
944 | "name": "updated at",
945 | "protocol": "openid-connect",
946 | "protocolMapper": "oidc-usermodel-attribute-mapper",
947 | "consentRequired": false,
948 | "config": {
949 | "userinfo.token.claim": "true",
950 | "user.attribute": "updatedAt",
951 | "id.token.claim": "true",
952 | "access.token.claim": "true",
953 | "claim.name": "updated_at",
954 | "jsonType.label": "String"
955 | }
956 | }
957 | ]
958 | },
959 | {
960 | "id": "2cf94826-10b4-4e18-8b75-9a113df82015",
961 | "name": "role_list",
962 | "description": "SAML role list",
963 | "protocol": "saml",
964 | "attributes": {
965 | "consent.screen.text": "${samlRoleListScopeConsentText}",
966 | "display.on.consent.screen": "true"
967 | },
968 | "protocolMappers": [
969 | {
970 | "id": "750bb5ee-41f1-467b-8520-70dba9cc90fc",
971 | "name": "role list",
972 | "protocol": "saml",
973 | "protocolMapper": "saml-role-list-mapper",
974 | "consentRequired": false,
975 | "config": {
976 | "single": "false",
977 | "attribute.nameformat": "Basic",
978 | "attribute.name": "Role"
979 | }
980 | }
981 | ]
982 | },
983 | {
984 | "id": "1dee9b55-1eb5-4cfe-a3ab-c54d740f148b",
985 | "name": "offline_access",
986 | "description": "OpenID Connect built-in scope: offline_access",
987 | "protocol": "openid-connect",
988 | "attributes": {
989 | "consent.screen.text": "${offlineAccessScopeConsentText}",
990 | "display.on.consent.screen": "true"
991 | }
992 | }
993 | ],
994 | "defaultDefaultClientScopes": [
995 | "role_list",
996 | "profile",
997 | "email"
998 | ],
999 | "defaultOptionalClientScopes": [
1000 | "offline_access",
1001 | "address",
1002 | "phone"
1003 | ],
1004 | "browserSecurityHeaders": {
1005 | "xContentTypeOptions": "nosniff",
1006 | "xRobotsTag": "none",
1007 | "xFrameOptions": "SAMEORIGIN",
1008 | "xXSSProtection": "1; mode=block",
1009 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
1010 | "strictTransportSecurity": "max-age=31536000; includeSubDomains"
1011 | },
1012 | "smtpServer": {},
1013 | "eventsEnabled": false,
1014 | "eventsListeners": [
1015 | "jboss-logging"
1016 | ],
1017 | "enabledEventTypes": [],
1018 | "adminEventsEnabled": false,
1019 | "adminEventsDetailsEnabled": false,
1020 | "components": {
1021 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
1022 | {
1023 | "id": "78d64391-7650-4c68-be72-486a4cfc2bfa",
1024 | "name": "Max Clients Limit",
1025 | "providerId": "max-clients",
1026 | "subType": "anonymous",
1027 | "subComponents": {},
1028 | "config": {
1029 | "max-clients": [
1030 | "200"
1031 | ]
1032 | }
1033 | },
1034 | {
1035 | "id": "5da9b0ae-d234-47b1-8584-39d861fb3c8a",
1036 | "name": "Allowed Client Scopes",
1037 | "providerId": "allowed-client-templates",
1038 | "subType": "authenticated",
1039 | "subComponents": {},
1040 | "config": {
1041 | "allow-default-scopes": [
1042 | "true"
1043 | ]
1044 | }
1045 | },
1046 | {
1047 | "id": "12890219-9ad4-4000-8c46-3dc20f150ed6",
1048 | "name": "Allowed Protocol Mapper Types",
1049 | "providerId": "allowed-protocol-mappers",
1050 | "subType": "anonymous",
1051 | "subComponents": {},
1052 | "config": {
1053 | "allowed-protocol-mapper-types": [
1054 | "oidc-sha256-pairwise-sub-mapper",
1055 | "oidc-full-name-mapper",
1056 | "saml-role-list-mapper",
1057 | "oidc-usermodel-attribute-mapper",
1058 | "oidc-address-mapper",
1059 | "saml-user-attribute-mapper",
1060 | "saml-user-property-mapper",
1061 | "oidc-usermodel-property-mapper"
1062 | ]
1063 | }
1064 | },
1065 | {
1066 | "id": "e7e4067b-a086-4a1b-a996-f5d9be04ff07",
1067 | "name": "Consent Required",
1068 | "providerId": "consent-required",
1069 | "subType": "anonymous",
1070 | "subComponents": {},
1071 | "config": {}
1072 | },
1073 | {
1074 | "id": "ed7c0e07-4cf5-4cf1-a211-5642fbbd3212",
1075 | "name": "Full Scope Disabled",
1076 | "providerId": "scope",
1077 | "subType": "anonymous",
1078 | "subComponents": {},
1079 | "config": {}
1080 | },
1081 | {
1082 | "id": "7c406530-802b-4d75-a218-c15cfef438c4",
1083 | "name": "Trusted Hosts",
1084 | "providerId": "trusted-hosts",
1085 | "subType": "anonymous",
1086 | "subComponents": {},
1087 | "config": {
1088 | "host-sending-registration-request-must-match": [
1089 | "true"
1090 | ],
1091 | "client-uris-must-match": [
1092 | "true"
1093 | ]
1094 | }
1095 | },
1096 | {
1097 | "id": "5271168d-298d-4a33-87d4-c45f02fbd260",
1098 | "name": "Allowed Client Scopes",
1099 | "providerId": "allowed-client-templates",
1100 | "subType": "anonymous",
1101 | "subComponents": {},
1102 | "config": {
1103 | "allow-default-scopes": [
1104 | "true"
1105 | ]
1106 | }
1107 | },
1108 | {
1109 | "id": "c7bce6d0-bbd2-4664-9e53-d71ba30aa2ea",
1110 | "name": "Allowed Protocol Mapper Types",
1111 | "providerId": "allowed-protocol-mappers",
1112 | "subType": "authenticated",
1113 | "subComponents": {},
1114 | "config": {
1115 | "allowed-protocol-mapper-types": [
1116 | "oidc-sha256-pairwise-sub-mapper",
1117 | "saml-user-attribute-mapper",
1118 | "saml-user-property-mapper",
1119 | "oidc-usermodel-property-mapper",
1120 | "oidc-address-mapper",
1121 | "oidc-full-name-mapper",
1122 | "oidc-usermodel-attribute-mapper",
1123 | "saml-role-list-mapper"
1124 | ]
1125 | }
1126 | }
1127 | ],
1128 | "org.keycloak.keys.KeyProvider": [
1129 | {
1130 | "id": "f5007221-c201-435f-bcce-179d51e5e457",
1131 | "name": "aes-generated",
1132 | "providerId": "aes-generated",
1133 | "subComponents": {},
1134 | "config": {
1135 | "priority": [
1136 | "100"
1137 | ]
1138 | }
1139 | },
1140 | {
1141 | "id": "ad7031df-2ba6-4298-852e-7bcf56a59b91",
1142 | "name": "hmac-generated",
1143 | "providerId": "hmac-generated",
1144 | "subComponents": {},
1145 | "config": {
1146 | "priority": [
1147 | "100"
1148 | ]
1149 | }
1150 | },
1151 | {
1152 | "id": "32046dc7-17a0-4a2b-8f18-05dd44a7d7d2",
1153 | "name": "rsa-generated",
1154 | "providerId": "rsa-generated",
1155 | "subComponents": {},
1156 | "config": {
1157 | "priority": [
1158 | "100"
1159 | ]
1160 | }
1161 | }
1162 | ]
1163 | },
1164 | "internationalizationEnabled": false,
1165 | "supportedLocales": [],
1166 | "authenticationFlows": [
1167 | {
1168 | "id": "76290a2e-c255-40c5-b45d-3441dc487a03",
1169 | "alias": "Handle Existing Account",
1170 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
1171 | "providerId": "basic-flow",
1172 | "topLevel": false,
1173 | "builtIn": true,
1174 | "authenticationExecutions": [
1175 | {
1176 | "authenticator": "idp-confirm-link",
1177 | "requirement": "REQUIRED",
1178 | "priority": 10,
1179 | "userSetupAllowed": false,
1180 | "autheticatorFlow": false
1181 | },
1182 | {
1183 | "authenticator": "idp-email-verification",
1184 | "requirement": "ALTERNATIVE",
1185 | "priority": 20,
1186 | "userSetupAllowed": false,
1187 | "autheticatorFlow": false
1188 | },
1189 | {
1190 | "requirement": "ALTERNATIVE",
1191 | "priority": 30,
1192 | "flowAlias": "Verify Existing Account by Re-authentication",
1193 | "userSetupAllowed": false,
1194 | "autheticatorFlow": true
1195 | }
1196 | ]
1197 | },
1198 | {
1199 | "id": "4d00a58d-e388-4423-8dcc-b7e12d9fbdbf",
1200 | "alias": "Verify Existing Account by Re-authentication",
1201 | "description": "Reauthentication of existing account",
1202 | "providerId": "basic-flow",
1203 | "topLevel": false,
1204 | "builtIn": true,
1205 | "authenticationExecutions": [
1206 | {
1207 | "authenticator": "idp-username-password-form",
1208 | "requirement": "REQUIRED",
1209 | "priority": 10,
1210 | "userSetupAllowed": false,
1211 | "autheticatorFlow": false
1212 | },
1213 | {
1214 | "authenticator": "auth-otp-form",
1215 | "requirement": "OPTIONAL",
1216 | "priority": 20,
1217 | "userSetupAllowed": false,
1218 | "autheticatorFlow": false
1219 | }
1220 | ]
1221 | },
1222 | {
1223 | "id": "7e72a413-b3b8-4f61-942c-86c1088805c2",
1224 | "alias": "browser",
1225 | "description": "browser based authentication",
1226 | "providerId": "basic-flow",
1227 | "topLevel": true,
1228 | "builtIn": true,
1229 | "authenticationExecutions": [
1230 | {
1231 | "authenticator": "auth-cookie",
1232 | "requirement": "ALTERNATIVE",
1233 | "priority": 10,
1234 | "userSetupAllowed": false,
1235 | "autheticatorFlow": false
1236 | },
1237 | {
1238 | "authenticator": "auth-spnego",
1239 | "requirement": "DISABLED",
1240 | "priority": 20,
1241 | "userSetupAllowed": false,
1242 | "autheticatorFlow": false
1243 | },
1244 | {
1245 | "authenticator": "identity-provider-redirector",
1246 | "requirement": "ALTERNATIVE",
1247 | "priority": 25,
1248 | "userSetupAllowed": false,
1249 | "autheticatorFlow": false
1250 | },
1251 | {
1252 | "requirement": "ALTERNATIVE",
1253 | "priority": 30,
1254 | "flowAlias": "forms",
1255 | "userSetupAllowed": false,
1256 | "autheticatorFlow": true
1257 | }
1258 | ]
1259 | },
1260 | {
1261 | "id": "c35f3f5f-9e99-4599-a6b7-9e4d0ceef03b",
1262 | "alias": "clients",
1263 | "description": "Base authentication for clients",
1264 | "providerId": "client-flow",
1265 | "topLevel": true,
1266 | "builtIn": true,
1267 | "authenticationExecutions": [
1268 | {
1269 | "authenticator": "client-secret",
1270 | "requirement": "ALTERNATIVE",
1271 | "priority": 10,
1272 | "userSetupAllowed": false,
1273 | "autheticatorFlow": false
1274 | },
1275 | {
1276 | "authenticator": "client-jwt",
1277 | "requirement": "ALTERNATIVE",
1278 | "priority": 20,
1279 | "userSetupAllowed": false,
1280 | "autheticatorFlow": false
1281 | },
1282 | {
1283 | "authenticator": "client-secret-jwt",
1284 | "requirement": "ALTERNATIVE",
1285 | "priority": 30,
1286 | "userSetupAllowed": false,
1287 | "autheticatorFlow": false
1288 | },
1289 | {
1290 | "authenticator": "client-x509",
1291 | "requirement": "ALTERNATIVE",
1292 | "priority": 40,
1293 | "userSetupAllowed": false,
1294 | "autheticatorFlow": false
1295 | }
1296 | ]
1297 | },
1298 | {
1299 | "id": "5e2ec938-33ec-4d69-81bd-4248aef724ff",
1300 | "alias": "direct grant",
1301 | "description": "OpenID Connect Resource Owner Grant",
1302 | "providerId": "basic-flow",
1303 | "topLevel": true,
1304 | "builtIn": true,
1305 | "authenticationExecutions": [
1306 | {
1307 | "authenticator": "direct-grant-validate-username",
1308 | "requirement": "REQUIRED",
1309 | "priority": 10,
1310 | "userSetupAllowed": false,
1311 | "autheticatorFlow": false
1312 | },
1313 | {
1314 | "authenticator": "direct-grant-validate-password",
1315 | "requirement": "REQUIRED",
1316 | "priority": 20,
1317 | "userSetupAllowed": false,
1318 | "autheticatorFlow": false
1319 | },
1320 | {
1321 | "authenticator": "direct-grant-validate-otp",
1322 | "requirement": "OPTIONAL",
1323 | "priority": 30,
1324 | "userSetupAllowed": false,
1325 | "autheticatorFlow": false
1326 | }
1327 | ]
1328 | },
1329 | {
1330 | "id": "b9e4f1e7-4e10-4584-b055-7dc3124aadd6",
1331 | "alias": "docker auth",
1332 | "description": "Used by Docker clients to authenticate against the IDP",
1333 | "providerId": "basic-flow",
1334 | "topLevel": true,
1335 | "builtIn": true,
1336 | "authenticationExecutions": [
1337 | {
1338 | "authenticator": "docker-http-basic-authenticator",
1339 | "requirement": "REQUIRED",
1340 | "priority": 10,
1341 | "userSetupAllowed": false,
1342 | "autheticatorFlow": false
1343 | }
1344 | ]
1345 | },
1346 | {
1347 | "id": "5d2b49f7-7c5c-4120-afd9-4688213445ac",
1348 | "alias": "first broker login",
1349 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
1350 | "providerId": "basic-flow",
1351 | "topLevel": true,
1352 | "builtIn": true,
1353 | "authenticationExecutions": [
1354 | {
1355 | "authenticatorConfig": "review profile config",
1356 | "authenticator": "idp-review-profile",
1357 | "requirement": "REQUIRED",
1358 | "priority": 10,
1359 | "userSetupAllowed": false,
1360 | "autheticatorFlow": false
1361 | },
1362 | {
1363 | "authenticatorConfig": "create unique user config",
1364 | "authenticator": "idp-create-user-if-unique",
1365 | "requirement": "ALTERNATIVE",
1366 | "priority": 20,
1367 | "userSetupAllowed": false,
1368 | "autheticatorFlow": false
1369 | },
1370 | {
1371 | "requirement": "ALTERNATIVE",
1372 | "priority": 30,
1373 | "flowAlias": "Handle Existing Account",
1374 | "userSetupAllowed": false,
1375 | "autheticatorFlow": true
1376 | }
1377 | ]
1378 | },
1379 | {
1380 | "id": "e2fa9fe0-28ae-404e-960f-61e5cd26abf0",
1381 | "alias": "forms",
1382 | "description": "Username, password, otp and other auth forms.",
1383 | "providerId": "basic-flow",
1384 | "topLevel": false,
1385 | "builtIn": true,
1386 | "authenticationExecutions": [
1387 | {
1388 | "authenticator": "auth-username-password-form",
1389 | "requirement": "REQUIRED",
1390 | "priority": 10,
1391 | "userSetupAllowed": false,
1392 | "autheticatorFlow": false
1393 | },
1394 | {
1395 | "authenticator": "auth-otp-form",
1396 | "requirement": "OPTIONAL",
1397 | "priority": 20,
1398 | "userSetupAllowed": false,
1399 | "autheticatorFlow": false
1400 | }
1401 | ]
1402 | },
1403 | {
1404 | "id": "f9cc120f-ecde-427f-9164-7fdfe437cee3",
1405 | "alias": "registration",
1406 | "description": "registration flow",
1407 | "providerId": "basic-flow",
1408 | "topLevel": true,
1409 | "builtIn": true,
1410 | "authenticationExecutions": [
1411 | {
1412 | "authenticator": "registration-page-form",
1413 | "requirement": "REQUIRED",
1414 | "priority": 10,
1415 | "flowAlias": "registration form",
1416 | "userSetupAllowed": false,
1417 | "autheticatorFlow": true
1418 | }
1419 | ]
1420 | },
1421 | {
1422 | "id": "7764b484-f9f8-4a95-bdfe-9d5f372274dc",
1423 | "alias": "registration form",
1424 | "description": "registration form",
1425 | "providerId": "form-flow",
1426 | "topLevel": false,
1427 | "builtIn": true,
1428 | "authenticationExecutions": [
1429 | {
1430 | "authenticator": "registration-user-creation",
1431 | "requirement": "REQUIRED",
1432 | "priority": 20,
1433 | "userSetupAllowed": false,
1434 | "autheticatorFlow": false
1435 | },
1436 | {
1437 | "authenticator": "registration-profile-action",
1438 | "requirement": "REQUIRED",
1439 | "priority": 40,
1440 | "userSetupAllowed": false,
1441 | "autheticatorFlow": false
1442 | },
1443 | {
1444 | "authenticator": "registration-password-action",
1445 | "requirement": "REQUIRED",
1446 | "priority": 50,
1447 | "userSetupAllowed": false,
1448 | "autheticatorFlow": false
1449 | },
1450 | {
1451 | "authenticator": "registration-recaptcha-action",
1452 | "requirement": "DISABLED",
1453 | "priority": 60,
1454 | "userSetupAllowed": false,
1455 | "autheticatorFlow": false
1456 | }
1457 | ]
1458 | },
1459 | {
1460 | "id": "9f69bc63-bc31-44f0-bbf7-f7a8cd63008a",
1461 | "alias": "reset credentials",
1462 | "description": "Reset credentials for a user if they forgot their password or something",
1463 | "providerId": "basic-flow",
1464 | "topLevel": true,
1465 | "builtIn": true,
1466 | "authenticationExecutions": [
1467 | {
1468 | "authenticator": "reset-credentials-choose-user",
1469 | "requirement": "REQUIRED",
1470 | "priority": 10,
1471 | "userSetupAllowed": false,
1472 | "autheticatorFlow": false
1473 | },
1474 | {
1475 | "authenticator": "reset-credential-email",
1476 | "requirement": "REQUIRED",
1477 | "priority": 20,
1478 | "userSetupAllowed": false,
1479 | "autheticatorFlow": false
1480 | },
1481 | {
1482 | "authenticator": "reset-password",
1483 | "requirement": "REQUIRED",
1484 | "priority": 30,
1485 | "userSetupAllowed": false,
1486 | "autheticatorFlow": false
1487 | },
1488 | {
1489 | "authenticator": "reset-otp",
1490 | "requirement": "OPTIONAL",
1491 | "priority": 40,
1492 | "userSetupAllowed": false,
1493 | "autheticatorFlow": false
1494 | }
1495 | ]
1496 | },
1497 | {
1498 | "id": "aea8b6ca-514f-469e-a4d4-ddcb31766d94",
1499 | "alias": "saml ecp",
1500 | "description": "SAML ECP Profile Authentication Flow",
1501 | "providerId": "basic-flow",
1502 | "topLevel": true,
1503 | "builtIn": true,
1504 | "authenticationExecutions": [
1505 | {
1506 | "authenticator": "http-basic-authenticator",
1507 | "requirement": "REQUIRED",
1508 | "priority": 10,
1509 | "userSetupAllowed": false,
1510 | "autheticatorFlow": false
1511 | }
1512 | ]
1513 | }
1514 | ],
1515 | "authenticatorConfig": [
1516 | {
1517 | "id": "0c423954-e011-4014-b51a-87d561d82f0d",
1518 | "alias": "create unique user config",
1519 | "config": {
1520 | "require.password.update.after.registration": "false"
1521 | }
1522 | },
1523 | {
1524 | "id": "6156398e-990b-4bd3-8ad3-f3195ec742d6",
1525 | "alias": "review profile config",
1526 | "config": {
1527 | "update.profile.on.first.login": "missing"
1528 | }
1529 | }
1530 | ],
1531 | "requiredActions": [
1532 | {
1533 | "alias": "CONFIGURE_TOTP",
1534 | "name": "Configure OTP",
1535 | "providerId": "CONFIGURE_TOTP",
1536 | "enabled": true,
1537 | "defaultAction": false,
1538 | "priority": 0,
1539 | "config": {}
1540 | },
1541 | {
1542 | "alias": "UPDATE_PASSWORD",
1543 | "name": "Update Password",
1544 | "providerId": "UPDATE_PASSWORD",
1545 | "enabled": true,
1546 | "defaultAction": false,
1547 | "priority": 0,
1548 | "config": {}
1549 | },
1550 | {
1551 | "alias": "UPDATE_PROFILE",
1552 | "name": "Update Profile",
1553 | "providerId": "UPDATE_PROFILE",
1554 | "enabled": true,
1555 | "defaultAction": false,
1556 | "priority": 0,
1557 | "config": {}
1558 | },
1559 | {
1560 | "alias": "VERIFY_EMAIL",
1561 | "name": "Verify Email",
1562 | "providerId": "VERIFY_EMAIL",
1563 | "enabled": true,
1564 | "defaultAction": false,
1565 | "priority": 0,
1566 | "config": {}
1567 | },
1568 | {
1569 | "alias": "terms_and_conditions",
1570 | "name": "Terms and Conditions",
1571 | "providerId": "terms_and_conditions",
1572 | "enabled": false,
1573 | "defaultAction": false,
1574 | "priority": 0,
1575 | "config": {}
1576 | }
1577 | ],
1578 | "browserFlow": "browser",
1579 | "registrationFlow": "registration",
1580 | "directGrantFlow": "direct grant",
1581 | "resetCredentialsFlow": "reset credentials",
1582 | "clientAuthenticationFlow": "clients",
1583 | "dockerAuthenticationFlow": "docker auth",
1584 | "attributes": {
1585 | "_browser_header.xXSSProtection": "1; mode=block",
1586 | "_browser_header.strictTransportSecurity": "max-age=31536000; includeSubDomains",
1587 | "_browser_header.xFrameOptions": "SAMEORIGIN",
1588 | "quickLoginCheckMilliSeconds": "1000",
1589 | "permanentLockout": "false",
1590 | "_browser_header.xRobotsTag": "none",
1591 | "maxFailureWaitSeconds": "900",
1592 | "minimumQuickLoginWaitSeconds": "60",
1593 | "failureFactor": "30",
1594 | "actionTokenGeneratedByUserLifespan": "300",
1595 | "maxDeltaTimeSeconds": "43200",
1596 | "_browser_header.xContentTypeOptions": "nosniff",
1597 | "actionTokenGeneratedByAdminLifespan": "43200",
1598 | "offlineSessionMaxLifespan": "5184000",
1599 | "bruteForceProtected": "false",
1600 | "_browser_header.contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
1601 | "offlineSessionMaxLifespanEnabled": "false",
1602 | "waitIncrementSeconds": "60"
1603 | },
1604 | "keycloakVersion": "4.3.0.Final",
1605 | "userManagedAccessAllowed": false
1606 | }
--------------------------------------------------------------------------------
/src/main/java/com/ineat/tutorials/springkeycloaktutorials/SpringKeycloakTutorialsApisController.java:
--------------------------------------------------------------------------------
1 | package com.ineat.tutorials.springkeycloaktutorials;
2 |
3 | import org.springframework.http.MediaType;
4 | import org.springframework.web.bind.annotation.RequestMapping;
5 | import org.springframework.web.bind.annotation.RequestMethod;
6 | import org.springframework.web.bind.annotation.RestController;
7 |
8 | /**
9 | * A Sample controller used to expose Keycloak Secured routes
10 | */
11 | @RestController
12 | public class SpringKeycloakTutorialsApisController {
13 |
14 | @RequestMapping(path = {"/", "/unsecured"})
15 | public String noSecuredEndpoint(){
16 | return "This is an unsecured endpoint payload";
17 | }
18 |
19 |
20 | @RequestMapping(
21 | path = "/admin",
22 | method = RequestMethod.GET, // @RequestMapping default assignment
23 | produces = MediaType.APPLICATION_JSON_VALUE // TIP : use org.springframework.http.MediaType for MimeType instead of hard coded value
24 | )
25 | public String adminSecuredEndpoint(){
26 | return "This is an ADMIN endpoint payload";
27 | }
28 |
29 | @RequestMapping("/user")
30 | public String userSecuredEndpoint(){
31 | return "This is an USER endpoint payload";
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/src/main/java/com/ineat/tutorials/springkeycloaktutorials/SpringKeycloakTutorialsApplication.java:
--------------------------------------------------------------------------------
1 | package com.ineat.tutorials.springkeycloaktutorials;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 |
6 | @SpringBootApplication
7 | public class SpringKeycloakTutorialsApplication {
8 |
9 | public static void main(String[] args) {
10 | SpringApplication.run(SpringKeycloakTutorialsApplication.class, args);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/com/ineat/tutorials/springkeycloaktutorials/SpringKeycloakTutorialsSecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | package com.ineat.tutorials.springkeycloaktutorials;
2 |
3 | import org.keycloak.adapters.KeycloakConfigResolver;
4 | import org.keycloak.adapters.springboot.KeycloakSpringBootConfigResolver;
5 | import org.keycloak.adapters.springsecurity.KeycloakConfiguration;
6 | import org.keycloak.adapters.springsecurity.authentication.KeycloakAuthenticationProvider;
7 | import org.keycloak.adapters.springsecurity.config.KeycloakWebSecurityConfigurerAdapter;
8 | import org.springframework.beans.factory.annotation.Autowired;
9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
10 | import org.springframework.context.annotation.Bean;
11 | import org.springframework.http.HttpMethod;
12 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
13 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
14 | import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
15 | import org.springframework.security.config.http.SessionCreationPolicy;
16 | import org.springframework.security.core.Authentication;
17 | import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
18 | import org.springframework.security.web.authentication.logout.LogoutFilter;
19 | import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
20 | import org.springframework.security.web.authentication.session.NullAuthenticatedSessionStrategy;
21 | import org.springframework.security.web.authentication.session.SessionAuthenticationStrategy;
22 | import org.springframework.web.cors.CorsConfiguration;
23 | import org.springframework.web.cors.CorsConfigurationSource;
24 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
25 |
26 | import javax.servlet.http.HttpServletRequest;
27 | import javax.servlet.http.HttpServletResponse;
28 | import java.util.Arrays;
29 |
30 | public class SpringKeycloakTutorialsSecurityConfiguration {
31 |
32 | @KeycloakConfiguration
33 | @ConditionalOnProperty(name = "keycloak.enabled", havingValue = "true", matchIfMissing = true)
34 | public static class KeycloakConfigurationAdapter extends KeycloakWebSecurityConfigurerAdapter {
35 | /**
36 | * Defines the session authentication strategy.
37 | */
38 | @Bean
39 | @Override
40 | protected SessionAuthenticationStrategy sessionAuthenticationStrategy() {
41 | // required for bearer-only applications.
42 | return new NullAuthenticatedSessionStrategy();
43 | }
44 |
45 | /**
46 | * Registers the KeycloakAuthenticationProvider with the authentication manager.
47 | */
48 | @Autowired
49 | public void configureGlobal(AuthenticationManagerBuilder auth) throws Exception {
50 | KeycloakAuthenticationProvider keycloakAuthenticationProvider = keycloakAuthenticationProvider();
51 | // simple Authority Mapper to avoid ROLE_
52 | keycloakAuthenticationProvider.setGrantedAuthoritiesMapper(new SimpleAuthorityMapper());
53 | auth.authenticationProvider(keycloakAuthenticationProvider);
54 | }
55 |
56 | /**
57 | * Required to handle spring boot configurations
58 | * @return
59 | */
60 | @Bean
61 | public KeycloakConfigResolver KeycloakConfigResolver() {
62 | return new KeycloakSpringBootConfigResolver();
63 | }
64 |
65 | /**
66 | * Configuration spécifique à keycloak (ajouts de filtres, etc)
67 | * @param http
68 | * @throws Exception
69 | */
70 | @Override
71 | protected void configure(HttpSecurity http) throws Exception
72 | {
73 | http.httpBasic().disable();
74 |
75 | http
76 | // disable csrf because of API mode
77 | .csrf().disable()
78 | .formLogin().disable()
79 | .sessionManagement()
80 | // use previously declared bean
81 | .sessionAuthenticationStrategy(sessionAuthenticationStrategy())
82 |
83 |
84 | // keycloak filters for securisation
85 | .and()
86 | .addFilterBefore(keycloakPreAuthActionsFilter(), LogoutFilter.class)
87 | .addFilterBefore(keycloakAuthenticationProcessingFilter(), X509AuthenticationFilter.class)
88 | .exceptionHandling().authenticationEntryPoint(authenticationEntryPoint())
89 |
90 | // add cors options
91 | .and().cors()
92 | // delegate logout endpoint to spring security
93 |
94 | .and()
95 | .logout()
96 | .addLogoutHandler(keycloakLogoutHandler())
97 | .logoutUrl("/logout").logoutSuccessHandler(
98 | // logout handler for API
99 | (HttpServletRequest request, HttpServletResponse response, Authentication authentication) ->
100 | response.setStatus(HttpServletResponse.SC_OK)
101 | )
102 | .and().apply(new CommonSpringKeycloakTutorialsSecuritAdapter());
103 |
104 |
105 | }
106 |
107 |
108 | @Bean
109 | public CorsConfigurationSource corsConfigurationSource() {
110 | CorsConfiguration configuration = new CorsConfiguration();
111 | configuration.setAllowedOrigins(Arrays.asList("*"));
112 | configuration.setAllowedMethods(Arrays.asList(HttpMethod.OPTIONS.name(), "GET","POST"));
113 | configuration.setAllowedHeaders(Arrays.asList("Access-Control-Allow-Headers", "Access-Control-Allow-Origin", "Authorization"));
114 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
115 | source.registerCorsConfiguration("/**", configuration);
116 | return source;
117 | }
118 | }
119 |
120 |
121 |
122 | /**
123 | * See https://docs.spring.io/spring-security/site/docs/current/reference/htmlsingle/#jc-custom-dsls
124 | *
- Manage paths securisation here !You must use this configuration in tests to validate routes securisation
125 | * - Use with .and().apply(new CommonVitodocSecuritAdapter()) on http dsl
126 | *
127 | */
128 | public static class CommonSpringKeycloakTutorialsSecuritAdapter extends AbstractHttpConfigurer {
129 |
130 | @Override
131 | public void init(HttpSecurity http) throws Exception {
132 | // any method that adds another configurer
133 | // must be done in the init method
134 | http
135 | // disable csrf because of API mode
136 | .csrf().disable()
137 | .sessionManagement()
138 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS)
139 |
140 | .and()
141 | // manage routes securisation here
142 | .authorizeRequests().antMatchers(HttpMethod.OPTIONS).permitAll()
143 |
144 | // manage routes securisation here
145 | .and()
146 | .authorizeRequests()
147 | .antMatchers(HttpMethod.OPTIONS).permitAll()
148 |
149 |
150 | .antMatchers("/logout", "/", "/unsecured").permitAll()
151 | .antMatchers("/user").hasRole("USER")
152 | .antMatchers("/admin").hasRole("ADMIN")
153 |
154 | .anyRequest().denyAll();
155 | }
156 | }
157 | }
158 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | ########################################
2 | # Spring Boot / Keycloak Configuration
3 | ########################################
4 | keycloak.auth-server-url=http://localhost:8180/auth
5 | keycloak.realm=ineat-realm
6 | keycloak.resource=ineat-api
7 | #we do not write a web-app - so no login page and redirects are necessary
8 | keycloak.bearer-only=true
9 |
10 | # You can use configuration facilities instead of KeycloakWebSecurityConfigurerAdapter
11 |
12 | #keycloak.securityConstraints[0].securityCollections[0].name = insecure endpoint
13 | #keycloak.securityConstraints[0].securityCollections[0].patterns[0] = /unsecured
14 | #keycloak.securityConstraints[0].securityCollections[0].patterns[1] = /
15 |
16 | #keycloak.security-constraints[1].authRoles[0]=USER
17 | #keycloak.security-constraints[1].securityCollections[0].patterns[0]=/user
18 |
19 | #keycloak.security-constraints[2].authRoles[0]=ADMIN
20 | #keycloak.security-constraints[2].securityCollections[0].patterns[0]=/admin
21 |
--------------------------------------------------------------------------------
/src/test/java/com/ineat/tutorials/springkeycloaktutorials/SpringKeycloakTutorialsSecurityTests.java:
--------------------------------------------------------------------------------
1 | package com.ineat.tutorials.springkeycloaktutorials;
2 |
3 | import org.junit.jupiter.api.Test;
4 | import org.springframework.beans.factory.annotation.Autowired;
5 | import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
6 | import org.springframework.boot.test.context.TestConfiguration;
7 | import org.springframework.context.annotation.Import;
8 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
9 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
10 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
11 | import org.springframework.security.test.context.support.WithMockUser;
12 | import org.springframework.test.context.TestPropertySource;
13 | import org.springframework.test.web.servlet.MockMvc;
14 | import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
15 |
16 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
18 |
19 | @WebMvcTest(value = SpringKeycloakTutorialsApisController.class)
20 | @Import(SpringKeycloakTutorialsSecurityTestConfiguration.class)
21 | // Disable Keycloak configuration processing
22 | @TestPropertySource(properties = {"keycloak.enabled=false"})
23 | public class SpringKeycloakTutorialsSecurityTests {
24 |
25 | @Autowired
26 | MockMvc mockMvc;
27 |
28 |
29 | @Test
30 | public void testUnsecuredPathIsAllowedForAll() throws Exception {
31 | mockMvc.perform( MockMvcRequestBuilders.get("/unsecured"))
32 | .andExpect(status().isOk())
33 | .andExpect(jsonPath("$").value("This is an unsecured endpoint payload"));
34 | }
35 |
36 |
37 |
38 | @Test
39 | @WithMockUser
40 | public void testAdminPathIsNotAllowedForAll() throws Exception {
41 | mockMvc.perform( MockMvcRequestBuilders.get("/admin"))
42 | .andExpect(status().isForbidden());
43 | }
44 |
45 | @Test
46 | @WithMockUser(roles = "ADMIN")
47 | public void testAdmindPathIsOnlyAllowedForAdminProfil() throws Exception {
48 | mockMvc.perform( MockMvcRequestBuilders.get("/admin"))
49 | .andExpect(status().isOk())
50 | .andExpect(jsonPath("$").value("This is an ADMIN endpoint payload"));
51 | }
52 |
53 | @Test
54 | @WithMockUser(roles = "USER")
55 | public void testUserPathIsOnlyAllowedForUserProfil() throws Exception {
56 | mockMvc.perform( MockMvcRequestBuilders.get("/user"))
57 | .andExpect(status().isOk())
58 | .andExpect(jsonPath("$").value("This is an USER endpoint payload"));
59 | }
60 |
61 | @Test
62 | @WithMockUser(roles = "USER")
63 | public void testAdmindPathIsNotAllowedForUserProfil() throws Exception {
64 | mockMvc.perform( MockMvcRequestBuilders.get("/admin"))
65 | .andExpect(status().isForbidden());
66 | }
67 | }
68 |
69 | /***
70 | * Use this configuration class to test if your path is secured
71 | * his class use {@link SpringKeycloakTutorialsSecurityConfiguration.CommonSpringKeycloakTutorialsSecuritAdapter} which define all
72 | * security matchers
73 | */
74 | @TestConfiguration
75 | @EnableWebSecurity
76 | class SpringKeycloakTutorialsSecurityTestConfiguration extends WebSecurityConfigurerAdapter {
77 | @Override
78 | protected void configure(HttpSecurity http) throws Exception {
79 | // use the common configuration to validate matchers
80 | http.apply(new SpringKeycloakTutorialsSecurityConfiguration.CommonSpringKeycloakTutorialsSecuritAdapter());
81 |
82 | }
83 | }
84 |
--------------------------------------------------------------------------------