├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── README.md
├── mvnw
├── mvnw.cmd
├── pom.xml
└── src
└── main
├── java
└── com
│ └── example
│ └── springsecurityrestclient
│ ├── Application.java
│ ├── ClientConfig.java
│ ├── CronService.java
│ ├── OAuth2ClientInterceptor.java
│ ├── SecurityConfiguration.java
│ ├── TestController.java
│ └── jose
│ ├── JwkSetController.java
│ ├── JwkUtils.java
│ └── JwtClientConfig.java
└── resources
├── application.yaml
├── docker-compose.yaml
└── realm-import
└── my-realm.json
/.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/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/mjeffrey/spring-security-oauth2-restclient-interceptor/b4b9bc97110c1c8b4074634adedb8ec5074b7e91/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
3 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # POC Interceptor for RestClient (and RestTemplate)
2 |
3 | This is a Spring Boot application that demonstrates the use of an interceptor to allow RestClient to be used for making Oauth2 calls.
4 |
5 | It is related to this issue:
6 | https://github.com/spring-projects/spring-security/issues/13588
7 |
8 | It only demonstrates the client_credentials flow.
9 | The interceptor for RestTemplate has the same signature so this can also be used for RestTemplate
10 |
11 | I am mainly interested in microservice authn/authz in a financial environment which is why I wanted to use private_jwt (which is FAPI compliant) rather than password authentication for the client_credentials flow.
12 |
13 | ## How it works
14 | When starting the application Spring Boot docker compose is used to start keycloak. Note: you need the "docker compose" plugin (v2), not "docker-compose" (v1).
15 | Docker Compose is set to import my-realm with two clients: my-client and my-client-jwt
16 | - my-client
17 | - client credentials flow
18 | - password based key
19 | - my-client-jwt
20 | - client credentials flow (service account)
21 | - private_jwt see https://www.rfc-editor.org/rfc/rfc7523
22 | - uses callback Json Web Key Set: http:/8081/localhost/jwks to be able to fetch the public key to authenticate (keycloak also supports loading a public key but using a jwks endpoint is more flexible and allows rotating the key at any time)
23 |
24 | You can see the clients by logging into keycloak on http://localhost/8080 (username is admin, password is admin)
25 |
26 | ## interesting classes
27 | ### Client Config
28 | All the beans for the Oauth2 clients. There are 6 clients 2 each (jwt and password) for WebClient, RestClient and RestTemplate.
29 |
30 | ### TestController
31 | Has all the endpoints for making manual "tests". The 6 clients are injected here and each one may be called with a GET on a URL.
32 | The clients each call the /target endpoint which logs the "authorization" header access token.
33 |
34 | ### OAuth2ClientInterceptor
35 | The interceptor itself.
36 |
37 | ### jose/*
38 | Anything in here is related to private jwt auth. Ignore it if you are not using this.
39 |
40 |
41 | ## Trying it out
42 | Run the application (uses maven). Will pull docker (may take some time)
43 | ```shell
44 | ./mvnw spring-boot:run
45 | ```
46 | The call each endpoint.
47 | **There is no actual authentication anywhere**, we just log the Access Token on the `/target` endpoint.
48 |
49 | ```shell
50 | curl http://localhost:8081/restClientPassword
51 | curl http://localhost:8081/restClientJwt
52 | curl http://localhost:8081/restTemplatePassword
53 | curl http://localhost:8081/restTemplateJwt
54 | ```
55 |
56 | Note there is also a scheduled task uses restClientJwt (previously it only worked from a serlet context and token caching was not working).
57 | see https://github.com/mjeffrey/spring-security-oauth2-restclient-interceptor/issues/1
58 |
--------------------------------------------------------------------------------
/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 | # Apache Maven Wrapper startup batch script, version 3.2.0
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | # e.g. to debug Maven itself, use
32 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | # ----------------------------------------------------------------------------
35 |
36 | if [ -z "$MAVEN_SKIP_RC" ] ; then
37 |
38 | if [ -f /usr/local/etc/mavenrc ] ; then
39 | . /usr/local/etc/mavenrc
40 | fi
41 |
42 | if [ -f /etc/mavenrc ] ; then
43 | . /etc/mavenrc
44 | fi
45 |
46 | if [ -f "$HOME/.mavenrc" ] ; then
47 | . "$HOME/.mavenrc"
48 | fi
49 |
50 | fi
51 |
52 | # OS specific support. $var _must_ be set to either true or false.
53 | cygwin=false;
54 | darwin=false;
55 | mingw=false
56 | case "$(uname)" in
57 | CYGWIN*) cygwin=true ;;
58 | MINGW*) mingw=true;;
59 | Darwin*) darwin=true
60 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
61 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
62 | if [ -z "$JAVA_HOME" ]; then
63 | if [ -x "/usr/libexec/java_home" ]; then
64 | JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
65 | else
66 | JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
67 | fi
68 | fi
69 | ;;
70 | esac
71 |
72 | if [ -z "$JAVA_HOME" ] ; then
73 | if [ -r /etc/gentoo-release ] ; then
74 | JAVA_HOME=$(java-config --jre-home)
75 | fi
76 | fi
77 |
78 | # For Cygwin, ensure paths are in UNIX format before anything is touched
79 | if $cygwin ; then
80 | [ -n "$JAVA_HOME" ] &&
81 | JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
82 | [ -n "$CLASSPATH" ] &&
83 | CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
84 | fi
85 |
86 | # For Mingw, ensure paths are in UNIX format before anything is touched
87 | if $mingw ; then
88 | [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
89 | JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
90 | fi
91 |
92 | if [ -z "$JAVA_HOME" ]; then
93 | javaExecutable="$(which javac)"
94 | if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
95 | # readlink(1) is not available as standard on Solaris 10.
96 | readLink=$(which readlink)
97 | if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
98 | if $darwin ; then
99 | javaHome="$(dirname "\"$javaExecutable\"")"
100 | javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
101 | else
102 | javaExecutable="$(readlink -f "\"$javaExecutable\"")"
103 | fi
104 | javaHome="$(dirname "\"$javaExecutable\"")"
105 | javaHome=$(expr "$javaHome" : '\(.*\)/bin')
106 | JAVA_HOME="$javaHome"
107 | export JAVA_HOME
108 | fi
109 | fi
110 | fi
111 |
112 | if [ -z "$JAVACMD" ] ; then
113 | if [ -n "$JAVA_HOME" ] ; then
114 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
115 | # IBM's JDK on AIX uses strange locations for the executables
116 | JAVACMD="$JAVA_HOME/jre/sh/java"
117 | else
118 | JAVACMD="$JAVA_HOME/bin/java"
119 | fi
120 | else
121 | JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
122 | fi
123 | fi
124 |
125 | if [ ! -x "$JAVACMD" ] ; then
126 | echo "Error: JAVA_HOME is not defined correctly." >&2
127 | echo " We cannot execute $JAVACMD" >&2
128 | exit 1
129 | fi
130 |
131 | if [ -z "$JAVA_HOME" ] ; then
132 | echo "Warning: JAVA_HOME environment variable is not set."
133 | fi
134 |
135 | # traverses directory structure from process work directory to filesystem root
136 | # first directory with .mvn subdirectory is considered project base directory
137 | find_maven_basedir() {
138 | if [ -z "$1" ]
139 | then
140 | echo "Path not specified to find_maven_basedir"
141 | return 1
142 | fi
143 |
144 | basedir="$1"
145 | wdir="$1"
146 | while [ "$wdir" != '/' ] ; do
147 | if [ -d "$wdir"/.mvn ] ; then
148 | basedir=$wdir
149 | break
150 | fi
151 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
152 | if [ -d "${wdir}" ]; then
153 | wdir=$(cd "$wdir/.." || exit 1; pwd)
154 | fi
155 | # end of workaround
156 | done
157 | printf '%s' "$(cd "$basedir" || exit 1; pwd)"
158 | }
159 |
160 | # concatenates all lines of a file
161 | concat_lines() {
162 | if [ -f "$1" ]; then
163 | # Remove \r in case we run on Windows within Git Bash
164 | # and check out the repository with auto CRLF management
165 | # enabled. Otherwise, we may read lines that are delimited with
166 | # \r\n and produce $'-Xarg\r' rather than -Xarg due to word
167 | # splitting rules.
168 | tr -s '\r\n' ' ' < "$1"
169 | fi
170 | }
171 |
172 | log() {
173 | if [ "$MVNW_VERBOSE" = true ]; then
174 | printf '%s\n' "$1"
175 | fi
176 | }
177 |
178 | BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
179 | if [ -z "$BASE_DIR" ]; then
180 | exit 1;
181 | fi
182 |
183 | MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
184 | log "$MAVEN_PROJECTBASEDIR"
185 |
186 | ##########################################################################################
187 | # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
188 | # This allows using the maven wrapper in projects that prohibit checking in binary data.
189 | ##########################################################################################
190 | wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
191 | if [ -r "$wrapperJarPath" ]; then
192 | log "Found $wrapperJarPath"
193 | else
194 | log "Couldn't find $wrapperJarPath, downloading it ..."
195 |
196 | if [ -n "$MVNW_REPOURL" ]; then
197 | wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
198 | else
199 | wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
200 | fi
201 | while IFS="=" read -r key value; do
202 | # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' )
203 | safeValue=$(echo "$value" | tr -d '\r')
204 | case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
205 | esac
206 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
207 | log "Downloading from: $wrapperUrl"
208 |
209 | if $cygwin; then
210 | wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
211 | fi
212 |
213 | if command -v wget > /dev/null; then
214 | log "Found wget ... using wget"
215 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
216 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
217 | wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
218 | else
219 | wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
220 | fi
221 | elif command -v curl > /dev/null; then
222 | log "Found curl ... using curl"
223 | [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
224 | if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
225 | curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
226 | else
227 | curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
228 | fi
229 | else
230 | log "Falling back to using Java to download"
231 | javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
232 | javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
233 | # For Cygwin, switch paths to Windows format before running javac
234 | if $cygwin; then
235 | javaSource=$(cygpath --path --windows "$javaSource")
236 | javaClass=$(cygpath --path --windows "$javaClass")
237 | fi
238 | if [ -e "$javaSource" ]; then
239 | if [ ! -e "$javaClass" ]; then
240 | log " - Compiling MavenWrapperDownloader.java ..."
241 | ("$JAVA_HOME/bin/javac" "$javaSource")
242 | fi
243 | if [ -e "$javaClass" ]; then
244 | log " - Running MavenWrapperDownloader.java ..."
245 | ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
246 | fi
247 | fi
248 | fi
249 | fi
250 | ##########################################################################################
251 | # End of extension
252 | ##########################################################################################
253 |
254 | # If specified, validate the SHA-256 sum of the Maven wrapper jar file
255 | wrapperSha256Sum=""
256 | while IFS="=" read -r key value; do
257 | case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
258 | esac
259 | done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
260 | if [ -n "$wrapperSha256Sum" ]; then
261 | wrapperSha256Result=false
262 | if command -v sha256sum > /dev/null; then
263 | if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
264 | wrapperSha256Result=true
265 | fi
266 | elif command -v shasum > /dev/null; then
267 | if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
268 | wrapperSha256Result=true
269 | fi
270 | else
271 | echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
272 | echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
273 | exit 1
274 | fi
275 | if [ $wrapperSha256Result = false ]; then
276 | echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
277 | echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
278 | echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
279 | exit 1
280 | fi
281 | fi
282 |
283 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
284 |
285 | # For Cygwin, switch paths to Windows format before running java
286 | if $cygwin; then
287 | [ -n "$JAVA_HOME" ] &&
288 | JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
289 | [ -n "$CLASSPATH" ] &&
290 | CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
291 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
292 | MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
293 | fi
294 |
295 | # Provide a "standardized" way to retrieve the CLI args that will
296 | # work with both Windows and non-Windows executions.
297 | MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
298 | export MAVEN_CMD_LINE_ARGS
299 |
300 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
301 |
302 | # shellcheck disable=SC2086 # safe args
303 | exec "$JAVACMD" \
304 | $MAVEN_OPTS \
305 | $MAVEN_DEBUG_OPTS \
306 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
307 | "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
308 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
309 |
--------------------------------------------------------------------------------
/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 Apache Maven Wrapper startup batch script, version 3.2.0
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 MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
28 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
29 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
30 | @REM e.g. to debug Maven itself, use
31 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
32 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
33 | @REM ----------------------------------------------------------------------------
34 |
35 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
36 | @echo off
37 | @REM set title of command window
38 | title %0
39 | @REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
40 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
41 |
42 | @REM set %HOME% to equivalent of $HOME
43 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
44 |
45 | @REM Execute a user defined script before this one
46 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
47 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
48 | if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
49 | if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
50 | :skipRcPre
51 |
52 | @setlocal
53 |
54 | set ERROR_CODE=0
55 |
56 | @REM To isolate internal variables from possible post scripts, we use another setlocal
57 | @setlocal
58 |
59 | @REM ==== START VALIDATION ====
60 | if not "%JAVA_HOME%" == "" goto OkJHome
61 |
62 | echo.
63 | echo Error: JAVA_HOME not found in your environment. >&2
64 | echo Please set the JAVA_HOME variable in your environment to match the >&2
65 | echo location of your Java installation. >&2
66 | echo.
67 | goto error
68 |
69 | :OkJHome
70 | if exist "%JAVA_HOME%\bin\java.exe" goto init
71 |
72 | echo.
73 | echo Error: JAVA_HOME is set to an invalid directory. >&2
74 | echo JAVA_HOME = "%JAVA_HOME%" >&2
75 | echo Please set the JAVA_HOME variable in your environment to match the >&2
76 | echo location of your Java installation. >&2
77 | echo.
78 | goto error
79 |
80 | @REM ==== END VALIDATION ====
81 |
82 | :init
83 |
84 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
85 | @REM Fallback to current working directory if not found.
86 |
87 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
88 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
89 |
90 | set EXEC_DIR=%CD%
91 | set WDIR=%EXEC_DIR%
92 | :findBaseDir
93 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
94 | cd ..
95 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
96 | set WDIR=%CD%
97 | goto findBaseDir
98 |
99 | :baseDirFound
100 | set MAVEN_PROJECTBASEDIR=%WDIR%
101 | cd "%EXEC_DIR%"
102 | goto endDetectBaseDir
103 |
104 | :baseDirNotFound
105 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
106 | cd "%EXEC_DIR%"
107 |
108 | :endDetectBaseDir
109 |
110 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
111 |
112 | @setlocal EnableExtensions EnableDelayedExpansion
113 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
114 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
115 |
116 | :endReadAdditionalConfig
117 |
118 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
123 |
124 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
125 | IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
126 | )
127 |
128 | @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
129 | @REM This allows using the maven wrapper in projects that prohibit checking in binary data.
130 | if exist %WRAPPER_JAR% (
131 | if "%MVNW_VERBOSE%" == "true" (
132 | echo Found %WRAPPER_JAR%
133 | )
134 | ) else (
135 | if not "%MVNW_REPOURL%" == "" (
136 | SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
137 | )
138 | if "%MVNW_VERBOSE%" == "true" (
139 | echo Couldn't find %WRAPPER_JAR%, downloading it ...
140 | echo Downloading from: %WRAPPER_URL%
141 | )
142 |
143 | powershell -Command "&{"^
144 | "$webclient = new-object System.Net.WebClient;"^
145 | "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
146 | "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
147 | "}"^
148 | "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
149 | "}"
150 | if "%MVNW_VERBOSE%" == "true" (
151 | echo Finished downloading %WRAPPER_JAR%
152 | )
153 | )
154 | @REM End of extension
155 |
156 | @REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
157 | SET WRAPPER_SHA_256_SUM=""
158 | FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
159 | IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
160 | )
161 | IF NOT %WRAPPER_SHA_256_SUM%=="" (
162 | powershell -Command "&{"^
163 | "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
164 | "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
165 | " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
166 | " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
167 | " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
168 | " exit 1;"^
169 | "}"^
170 | "}"
171 | if ERRORLEVEL 1 goto error
172 | )
173 |
174 | @REM Provide a "standardized" way to retrieve the CLI args that will
175 | @REM work with both Windows and non-Windows executions.
176 | set MAVEN_CMD_LINE_ARGS=%*
177 |
178 | %MAVEN_JAVA_EXE% ^
179 | %JVM_CONFIG_MAVEN_PROPS% ^
180 | %MAVEN_OPTS% ^
181 | %MAVEN_DEBUG_OPTS% ^
182 | -classpath %WRAPPER_JAR% ^
183 | "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
184 | %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
185 | if ERRORLEVEL 1 goto error
186 | goto end
187 |
188 | :error
189 | set ERROR_CODE=1
190 |
191 | :end
192 | @endlocal & set ERROR_CODE=%ERROR_CODE%
193 |
194 | if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
195 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
196 | if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
197 | if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
198 | :skipRcPost
199 |
200 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
201 | if "%MAVEN_BATCH_PAUSE%"=="on" pause
202 |
203 | if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
204 |
205 | cmd /C exit /B %ERROR_CODE%
206 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 |
6 | org.springframework.boot
7 | spring-boot-starter-parent
8 | 3.2.2
9 |
10 |
11 | com.example
12 | spring-security-restclient
13 | 0.0.1-SNAPSHOT
14 | spring-security-restclient
15 | spring-security-restclient
16 |
17 | 17
18 |
19 |
20 |
21 | org.springframework.boot
22 | spring-boot-starter-security
23 |
24 |
25 | org.springframework.boot
26 | spring-boot-starter-oauth2-client
27 |
28 |
29 | org.springframework.boot
30 | spring-boot-starter-web
31 |
32 |
33 | org.springframework.boot
34 | spring-boot-docker-compose
35 |
36 |
37 |
38 | org.projectlombok
39 | lombok
40 |
41 |
42 |
43 |
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-maven-plugin
48 |
49 |
50 |
51 |
52 |
53 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/Application.java:
--------------------------------------------------------------------------------
1 | package com.example.springsecurityrestclient;
2 |
3 | import org.springframework.boot.SpringApplication;
4 | import org.springframework.boot.autoconfigure.SpringBootApplication;
5 | import org.springframework.scheduling.annotation.EnableScheduling;
6 |
7 | @SpringBootApplication
8 | @EnableScheduling
9 | public class Application {
10 |
11 | public static void main(String[] args) {
12 | SpringApplication.run(Application.class, args);
13 | }
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/ClientConfig.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020-2022 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 | package com.example.springsecurityrestclient;
17 |
18 | import org.springframework.boot.web.client.RestTemplateBuilder;
19 | import org.springframework.context.annotation.Bean;
20 | import org.springframework.context.annotation.Configuration;
21 | import org.springframework.http.client.ClientHttpRequestInitializer;
22 | import org.springframework.http.client.ClientHttpRequestInterceptor;
23 | import org.springframework.security.oauth2.client.AuthorizedClientServiceOAuth2AuthorizedClientManager;
24 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
25 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider;
26 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder;
27 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService;
28 | import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
29 | import org.springframework.security.oauth2.client.endpoint.OAuth2ClientCredentialsGrantRequest;
30 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
31 | import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository;
32 | import org.springframework.web.client.RestClient;
33 | import org.springframework.web.client.RestTemplate;
34 |
35 | @Configuration
36 | public class ClientConfig {
37 |
38 | /**
39 | * {@link RestClient} with {@link ClientHttpRequestInterceptor}
40 | */
41 | @Bean
42 | RestClient restClientPassword(RestClient.Builder builder,
43 | OAuth2AuthorizedClientManager authorizedClientManager,
44 | ClientRegistrationRepository clientRegistrationRepository) {
45 | ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-secret-client");
46 |
47 | ClientHttpRequestInterceptor interceptor = new OAuth2ClientInterceptor(authorizedClientManager, clientRegistration);
48 | return builder.requestInterceptor(interceptor).build();
49 | }
50 |
51 | /**
52 | * {@link RestClient} with {@link ClientHttpRequestInitializer}
53 | */
54 | @Bean
55 | RestClient restClientJwt(RestClient.Builder builder,
56 | OAuth2AuthorizedClientManager authorizedClientManager,
57 | ClientRegistrationRepository clientRegistrationRepository) {
58 | ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-jwt-client");
59 |
60 | ClientHttpRequestInitializer initializer = new OAuth2ClientInterceptor(authorizedClientManager, clientRegistration);
61 | return builder.requestInitializer(initializer).build();
62 | }
63 |
64 |
65 | /**
66 | * {@link RestTemplate} with {@link ClientHttpRequestInitializer}
67 | *
68 | * Note that this needs to be added after construction.
69 | */
70 | @Bean
71 | RestTemplate restTemplatePassword(RestTemplateBuilder restTemplateBuilder,
72 | OAuth2AuthorizedClientManager authorizedClientManager,
73 | ClientRegistrationRepository clientRegistrationRepository) {
74 | ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-secret-client");
75 |
76 | ClientHttpRequestInitializer initializer = new OAuth2ClientInterceptor(authorizedClientManager, clientRegistration);
77 |
78 | RestTemplate restTemplate = restTemplateBuilder.build(); // Need to build it then add the initializer (not supported by RestTemplateBuilder)
79 | restTemplate.getClientHttpRequestInitializers().add(initializer);
80 | return restTemplate;
81 | }
82 |
83 | /**
84 | * {@link RestTemplate} with {@link ClientHttpRequestInterceptor}.
85 | */
86 | @Bean
87 | RestTemplate restTemplateJwt(RestTemplateBuilder restTemplateBuilder,
88 | OAuth2AuthorizedClientManager authorizedClientManager,
89 | ClientRegistrationRepository clientRegistrationRepository) {
90 | ClientRegistration clientRegistration = clientRegistrationRepository.findByRegistrationId("my-jwt-client");
91 |
92 | ClientHttpRequestInterceptor interceptor = new OAuth2ClientInterceptor(authorizedClientManager, clientRegistration);
93 | return restTemplateBuilder
94 | .requestCustomizers()
95 | .additionalInterceptors(interceptor)
96 | .build();
97 | }
98 |
99 | @Bean
100 | OAuth2AuthorizedClientManager authorizedClientManager(
101 | OAuth2AccessTokenResponseClient responseClient,
102 | ClientRegistrationRepository clientRegistrationRepository,
103 | OAuth2AuthorizedClientService clientService) {
104 |
105 | OAuth2AuthorizedClientProvider authorizedClientProvider =
106 | OAuth2AuthorizedClientProviderBuilder.builder()
107 | .clientCredentials(clientCredentials ->
108 | clientCredentials.accessTokenResponseClient(responseClient))
109 | .build();
110 |
111 | AuthorizedClientServiceOAuth2AuthorizedClientManager clientManager =
112 | new AuthorizedClientServiceOAuth2AuthorizedClientManager(clientRegistrationRepository, clientService);
113 | clientManager.setAuthorizedClientProvider(authorizedClientProvider);
114 | return clientManager;
115 | }
116 |
117 | }
118 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/CronService.java:
--------------------------------------------------------------------------------
1 | package com.example.springsecurityrestclient;
2 |
3 | import lombok.AllArgsConstructor;
4 | import lombok.extern.slf4j.Slf4j;
5 | import org.springframework.scheduling.annotation.Scheduled;
6 | import org.springframework.stereotype.Service;
7 | import org.springframework.web.client.RestClient;
8 |
9 | @Slf4j
10 | @Service
11 | @AllArgsConstructor
12 | public class CronService {
13 |
14 | private static final String TARGET = "http://localhost:8081/target";
15 |
16 | private RestClient restClientJwt;
17 |
18 | @Scheduled(cron = "0 */1 * * * *")
19 | public void performCronTask() {
20 | log.info("cronJob called");
21 | restClientJwt.get().uri(TARGET).retrieve().body(String.class);
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/OAuth2ClientInterceptor.java:
--------------------------------------------------------------------------------
1 | package com.example.springsecurityrestclient;
2 |
3 | import org.springframework.http.HttpRequest;
4 | import org.springframework.http.client.ClientHttpRequest;
5 | import org.springframework.http.client.ClientHttpRequestExecution;
6 | import org.springframework.http.client.ClientHttpRequestInitializer;
7 | import org.springframework.http.client.ClientHttpRequestInterceptor;
8 | import org.springframework.http.client.ClientHttpResponse;
9 | import org.springframework.security.oauth2.client.OAuth2AuthorizeRequest;
10 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClient;
11 | import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager;
12 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
13 | import org.springframework.util.Assert;
14 |
15 | import java.io.IOException;
16 |
17 | public class OAuth2ClientInterceptor implements ClientHttpRequestInterceptor, ClientHttpRequestInitializer {
18 |
19 | private final OAuth2AuthorizedClientManager manager;
20 | private final ClientRegistration clientRegistration;
21 |
22 | public OAuth2ClientInterceptor(OAuth2AuthorizedClientManager manager, ClientRegistration clientRegistration) {
23 | this.manager = manager;
24 | this.clientRegistration = clientRegistration;
25 | }
26 |
27 | @Override
28 | public ClientHttpResponse intercept(HttpRequest request, byte[] body, ClientHttpRequestExecution execution) throws IOException {
29 | request.getHeaders().setBearerAuth(getBearerToken());
30 | return execution.execute(request, body);
31 | }
32 |
33 | @Override
34 | public void initialize(ClientHttpRequest request) {
35 | request.getHeaders().setBearerAuth(getBearerToken());
36 | }
37 |
38 | private String getBearerToken() {
39 | OAuth2AuthorizeRequest oAuth2AuthorizeRequest = OAuth2AuthorizeRequest
40 | .withClientRegistrationId(clientRegistration.getRegistrationId())
41 | .principal(clientRegistration.getClientId())
42 | .build();
43 |
44 | OAuth2AuthorizedClient client = manager.authorize(oAuth2AuthorizeRequest);
45 | Assert.notNull(client, () -> "Authorized client failed for Registration id: '" + clientRegistration.getRegistrationId() + "', returned client is null");
46 | return client.getAccessToken().getTokenValue();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/SecurityConfiguration.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020 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 |
17 | package com.example.springsecurityrestclient;
18 |
19 | import org.springframework.context.annotation.Bean;
20 | import org.springframework.context.annotation.Configuration;
21 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
22 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
23 | import org.springframework.security.web.SecurityFilterChain;
24 |
25 | import static org.springframework.security.config.Customizer.withDefaults;
26 |
27 | @Configuration
28 | @EnableWebSecurity
29 | public class SecurityConfiguration {
30 |
31 | @Bean
32 | public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
33 | http
34 | .authorizeHttpRequests((authorize) -> authorize
35 | .requestMatchers("/", "/public/**").permitAll()
36 | .anyRequest().permitAll()
37 | )
38 | .oauth2Client(withDefaults());
39 | return http.build();
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/TestController.java:
--------------------------------------------------------------------------------
1 | package com.example.springsecurityrestclient;
2 |
3 | import com.nimbusds.jwt.SignedJWT;
4 | import lombok.AllArgsConstructor;
5 | import lombok.SneakyThrows;
6 | import lombok.extern.slf4j.Slf4j;
7 | import org.springframework.http.HttpHeaders;
8 | import org.springframework.web.bind.annotation.GetMapping;
9 | import org.springframework.web.bind.annotation.RequestHeader;
10 | import org.springframework.web.bind.annotation.RestController;
11 | import org.springframework.web.client.RestClient;
12 | import org.springframework.web.client.RestTemplate;
13 |
14 | @RestController
15 | @Slf4j
16 | @AllArgsConstructor
17 | public class TestController {
18 |
19 | private static final String TARGET = "http://localhost:8081/target";
20 |
21 | private RestClient restClientPassword;
22 | private RestClient restClientJwt;
23 |
24 | private RestTemplate restTemplatePassword;
25 | private RestTemplate restTemplateJwt;
26 |
27 | @GetMapping("/restClientPassword")
28 | public String restClientPassword() {
29 | log.info("restClientPassword called");
30 | return restClientPassword.get().uri(TARGET).retrieve().body(String.class);
31 | }
32 |
33 | @GetMapping("/restClientJwt")
34 | public String restClientJwt() {
35 | log.info("restClientJwt called");
36 | return restClientJwt.get().uri(TARGET).retrieve().body(String.class);
37 | }
38 |
39 | @GetMapping("/restTemplatePassword")
40 | public String callerRestTemplate() {
41 | log.info("restTemplatePassword called");
42 | return restTemplatePassword.getForObject(TARGET, String.class);
43 | }
44 |
45 | @GetMapping("/restTemplateJwt")
46 | public String restTemplateJwt() {
47 | log.info("restTemplateJwt called");
48 | return restTemplateJwt.getForObject(TARGET, String.class);
49 | }
50 |
51 | @SneakyThrows
52 | @GetMapping("/target")
53 | public String target(@RequestHeader HttpHeaders headers) {
54 | headers.forEach((key, value) -> {
55 | log.debug("Header '{}' = {}", key, value);
56 | });
57 | String token = headers.getFirst(HttpHeaders.AUTHORIZATION.toLowerCase()).substring(7);
58 | SignedJWT signedJWT = SignedJWT.parse(token);
59 | log.info("jwt {} {}", signedJWT.getHeader(), signedJWT.getJWTClaimsSet());
60 | return "called OK\n" ;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/jose/JwkSetController.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020-2022 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 | package com.example.springsecurityrestclient.jose;
17 |
18 | import com.nimbusds.jose.KeySourceException;
19 | import com.nimbusds.jose.jwk.JWKMatcher;
20 | import com.nimbusds.jose.jwk.JWKSelector;
21 | import com.nimbusds.jose.jwk.JWKSet;
22 | import com.nimbusds.jose.jwk.source.JWKSource;
23 | import com.nimbusds.jose.proc.SecurityContext;
24 | import lombok.extern.slf4j.Slf4j;
25 | import org.springframework.util.Assert;
26 | import org.springframework.web.bind.annotation.GetMapping;
27 | import org.springframework.web.bind.annotation.RestController;
28 |
29 | import java.util.Map;
30 |
31 | @RestController
32 | @Slf4j
33 | public class JwkSetController {
34 | private final JWKSource jwkSource;
35 | private final JWKSelector jwkSelector;
36 |
37 | public JwkSetController(JWKSource jwkSource) {
38 | Assert.notNull(jwkSource, "jwkSource cannot be null");
39 | this.jwkSource = jwkSource;
40 | this.jwkSelector = new JWKSelector(new JWKMatcher.Builder().build());
41 | }
42 |
43 | @GetMapping("/jwks")
44 | public Map getJwkSet() throws KeySourceException {
45 | log.warn("JWKS called - cached by Keycloak so this will only be called once unless the key is changed.");
46 | return new JWKSet(this.jwkSource.get(this.jwkSelector, null)).toJSONObject();
47 | }
48 |
49 | }
50 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/jose/JwkUtils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2020-2021 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 | package com.example.springsecurityrestclient.jose;
17 |
18 | import com.nimbusds.jose.jwk.Curve;
19 | import com.nimbusds.jose.jwk.ECKey;
20 | import com.nimbusds.jose.jwk.KeyUse;
21 | import com.nimbusds.jose.jwk.RSAKey;
22 | import lombok.SneakyThrows;
23 |
24 | import java.security.KeyPair;
25 | import java.security.KeyPairGenerator;
26 | import java.security.interfaces.ECPrivateKey;
27 | import java.security.interfaces.ECPublicKey;
28 | import java.security.interfaces.RSAPrivateKey;
29 | import java.security.interfaces.RSAPublicKey;
30 | import java.util.UUID;
31 |
32 | public final class JwkUtils {
33 |
34 | private JwkUtils() {
35 | }
36 |
37 | public static RSAKey generateRsa() {
38 | KeyPair keyPair = JwkUtils.generateRsaKey();
39 | RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic();
40 | RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate();
41 | return new RSAKey.Builder(publicKey)
42 | .keyUse(KeyUse.SIGNATURE)
43 | .privateKey(privateKey)
44 | .keyID(UUID.randomUUID().toString())
45 | .build();
46 | }
47 |
48 | public static ECKey generateEc() {
49 | KeyPair keyPair = JwkUtils.generateEcKey();
50 | ECPublicKey publicKey = (ECPublicKey) keyPair.getPublic();
51 | ECPrivateKey privateKey = (ECPrivateKey) keyPair.getPrivate();
52 | Curve curve = Curve.forECParameterSpec(publicKey.getParams());
53 |
54 | return new ECKey.Builder(curve, publicKey)
55 | .keyUse(KeyUse.SIGNATURE)
56 | .privateKey(privateKey)
57 | .keyID(UUID.randomUUID().toString())
58 | .build();
59 | }
60 |
61 | @SneakyThrows
62 | private static KeyPair generateRsaKey() {
63 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
64 | keyPairGenerator.initialize(2048);
65 | return keyPairGenerator.generateKeyPair();
66 | }
67 |
68 | @SneakyThrows
69 | private static KeyPair generateEcKey() {
70 | KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("EC");
71 | return keyPairGenerator.generateKeyPair();
72 | }
73 |
74 | }
75 |
--------------------------------------------------------------------------------
/src/main/java/com/example/springsecurityrestclient/jose/JwtClientConfig.java:
--------------------------------------------------------------------------------
1 | package com.example.springsecurityrestclient.jose;
2 |
3 | import com.nimbusds.jose.KeySourceException;
4 | import com.nimbusds.jose.jwk.JWK;
5 | import com.nimbusds.jose.jwk.JWKMatcher;
6 | import com.nimbusds.jose.jwk.JWKSelector;
7 | import com.nimbusds.jose.jwk.JWKSet;
8 | import com.nimbusds.jose.jwk.source.JWKSource;
9 | import com.nimbusds.jose.proc.SecurityContext;
10 | import lombok.extern.slf4j.Slf4j;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Configuration;
13 | import org.springframework.security.oauth2.client.endpoint.*;
14 | import org.springframework.security.oauth2.client.registration.ClientRegistration;
15 | import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames;
16 | import org.springframework.util.LinkedMultiValueMap;
17 | import org.springframework.util.MultiValueMap;
18 |
19 | import java.util.function.Function;
20 |
21 | @Configuration
22 | @Slf4j
23 | public class JwtClientConfig {
24 |
25 | @Bean
26 | JWKSource jwkSource() {
27 | JWK key = JwkUtils.generateEc();
28 | JWKSet jwkSet = new JWKSet(key);
29 | return (jwkSelector, securityContext) -> jwkSelector.select(jwkSet);
30 | }
31 |
32 | @Bean
33 | Function jwkResolver(JWKSource jwkSource) {
34 | JWKSelector jwkSelector = new JWKSelector(new JWKMatcher.Builder().privateOnly(true).build());
35 | return (registration) -> getJwk(jwkSource, jwkSelector);
36 | }
37 |
38 | private JWK getJwk(JWKSource jwkSource, JWKSelector jwkSelector) {
39 | JWKSet jwkSet = null;
40 | try {
41 | jwkSet = new JWKSet(jwkSource.get(jwkSelector, null));
42 | } catch (KeySourceException ex) {
43 | log.error("cannot locate private key", ex);
44 | }
45 | return jwkSet != null ? jwkSet.getKeys().iterator().next() : null;
46 | }
47 |
48 | @Bean
49 | OAuth2AccessTokenResponseClient clientCredentialsTokenResponseClient(
50 | Function jwkResolver) {
51 |
52 | OAuth2ClientCredentialsGrantRequestEntityConverter clientCredentialsGrantRequestEntityConverter = new OAuth2ClientCredentialsGrantRequestEntityConverter();
53 | clientCredentialsGrantRequestEntityConverter.addParametersConverter(new NimbusJwtClientAuthenticationParametersConverter<>(jwkResolver));
54 |
55 | clientCredentialsGrantRequestEntityConverter.addParametersConverter(authorizationGrantRequest -> {
56 | MultiValueMap parameters = new LinkedMultiValueMap<>();
57 | parameters.add(OAuth2ParameterNames.CLIENT_ID, authorizationGrantRequest.getClientRegistration().getClientId());
58 | return parameters;
59 | });
60 |
61 | DefaultClientCredentialsTokenResponseClient clientCredentialsTokenResponseClient = new DefaultClientCredentialsTokenResponseClient();
62 | clientCredentialsTokenResponseClient.setRequestEntityConverter(clientCredentialsGrantRequestEntityConverter);
63 |
64 | return clientCredentialsTokenResponseClient;
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/src/main/resources/application.yaml:
--------------------------------------------------------------------------------
1 | server:
2 | port: 8081
3 |
4 | #keycloak-url: "http://nuc:8080"
5 | keycloak-url: "http://localhost:8080"
6 |
7 | spring:
8 | docker:
9 | compose:
10 | enabled: true
11 | file: docker-compose.yaml
12 | security:
13 | oauth2:
14 | client:
15 | registration:
16 | my-secret-client:
17 | client-id: "my-client"
18 | client-secret: "secret"
19 | authorization-grant-type: client_credentials
20 | my-jwt-client:
21 | client-id: "my-jwt-client"
22 | client-authentication-method: private_key_jwt
23 | authorization-grant-type: client_credentials
24 | provider:
25 | my-secret-client:
26 | token-uri: "${keycloak-url}/realms/my-realm/protocol/openid-connect/token"
27 | my-jwt-client:
28 | token-uri: "${keycloak-url}/realms/my-realm/protocol/openid-connect/token"
29 |
--------------------------------------------------------------------------------
/src/main/resources/docker-compose.yaml:
--------------------------------------------------------------------------------
1 | version: '3.8'
2 | services:
3 | keycloak:
4 | image: quay.io/keycloak/keycloak:23.0.4
5 | container_name: keycloak-restclient
6 | environment:
7 | KEYCLOAK_ADMIN: admin
8 | KEYCLOAK_ADMIN_PASSWORD: admin
9 |
10 | restart: unless-stopped
11 |
12 | entrypoint: /opt/keycloak/bin/kc.sh start-dev --import-realm # --log-level=debug
13 | ports:
14 | - "8080:8080"
15 | extra_hosts:
16 | - "host.docker.internal:host-gateway"
17 | networks:
18 | - keycloak-network
19 | volumes:
20 | - "./realm-import:/opt/keycloak/data/import"
21 |
22 | networks:
23 | keycloak-network:
24 | driver: bridge
25 |
--------------------------------------------------------------------------------
/src/main/resources/realm-import/my-realm.json:
--------------------------------------------------------------------------------
1 | {
2 | "id": "9fd3954a-5071-4694-9f3c-33bd2d5e42ef",
3 | "realm": "my-realm",
4 | "notBefore": 0,
5 | "defaultSignatureAlgorithm": "RS256",
6 | "revokeRefreshToken": false,
7 | "refreshTokenMaxReuse": 0,
8 | "accessTokenLifespan": 300,
9 | "accessTokenLifespanForImplicitFlow": 900,
10 | "ssoSessionIdleTimeout": 1800,
11 | "ssoSessionMaxLifespan": 36000,
12 | "ssoSessionIdleTimeoutRememberMe": 0,
13 | "ssoSessionMaxLifespanRememberMe": 0,
14 | "offlineSessionIdleTimeout": 2592000,
15 | "offlineSessionMaxLifespanEnabled": false,
16 | "offlineSessionMaxLifespan": 5184000,
17 | "clientSessionIdleTimeout": 0,
18 | "clientSessionMaxLifespan": 0,
19 | "clientOfflineSessionIdleTimeout": 0,
20 | "clientOfflineSessionMaxLifespan": 0,
21 | "accessCodeLifespan": 60,
22 | "accessCodeLifespanUserAction": 300,
23 | "accessCodeLifespanLogin": 1800,
24 | "actionTokenGeneratedByAdminLifespan": 43200,
25 | "actionTokenGeneratedByUserLifespan": 300,
26 | "oauth2DeviceCodeLifespan": 600,
27 | "oauth2DevicePollingInterval": 5,
28 | "enabled": true,
29 | "sslRequired": "external",
30 | "registrationAllowed": false,
31 | "registrationEmailAsUsername": false,
32 | "rememberMe": false,
33 | "verifyEmail": false,
34 | "loginWithEmailAllowed": true,
35 | "duplicateEmailsAllowed": false,
36 | "resetPasswordAllowed": false,
37 | "editUsernameAllowed": false,
38 | "bruteForceProtected": false,
39 | "permanentLockout": false,
40 | "maxFailureWaitSeconds": 900,
41 | "minimumQuickLoginWaitSeconds": 60,
42 | "waitIncrementSeconds": 60,
43 | "quickLoginCheckMilliSeconds": 1000,
44 | "maxDeltaTimeSeconds": 43200,
45 | "failureFactor": 30,
46 | "defaultRole": {
47 | "id": "a76e6614-24fc-45f5-9886-62f917ab8f53",
48 | "name": "default-roles-my-realm",
49 | "description": "${role_default-roles}",
50 | "composite": true,
51 | "clientRole": false,
52 | "containerId": "9fd3954a-5071-4694-9f3c-33bd2d5e42ef"
53 | },
54 | "requiredCredentials": [
55 | "password"
56 | ],
57 | "otpPolicyType": "totp",
58 | "otpPolicyAlgorithm": "HmacSHA1",
59 | "otpPolicyInitialCounter": 0,
60 | "otpPolicyDigits": 6,
61 | "otpPolicyLookAheadWindow": 1,
62 | "otpPolicyPeriod": 30,
63 | "otpPolicyCodeReusable": false,
64 | "otpSupportedApplications": [
65 | "totpAppFreeOTPName",
66 | "totpAppGoogleName",
67 | "totpAppMicrosoftAuthenticatorName"
68 | ],
69 | "localizationTexts": {},
70 | "webAuthnPolicyRpEntityName": "keycloak",
71 | "webAuthnPolicySignatureAlgorithms": [
72 | "ES256"
73 | ],
74 | "webAuthnPolicyRpId": "",
75 | "webAuthnPolicyAttestationConveyancePreference": "not specified",
76 | "webAuthnPolicyAuthenticatorAttachment": "not specified",
77 | "webAuthnPolicyRequireResidentKey": "not specified",
78 | "webAuthnPolicyUserVerificationRequirement": "not specified",
79 | "webAuthnPolicyCreateTimeout": 0,
80 | "webAuthnPolicyAvoidSameAuthenticatorRegister": false,
81 | "webAuthnPolicyAcceptableAaguids": [],
82 | "webAuthnPolicyExtraOrigins": [],
83 | "webAuthnPolicyPasswordlessRpEntityName": "keycloak",
84 | "webAuthnPolicyPasswordlessSignatureAlgorithms": [
85 | "ES256"
86 | ],
87 | "webAuthnPolicyPasswordlessRpId": "",
88 | "webAuthnPolicyPasswordlessAttestationConveyancePreference": "not specified",
89 | "webAuthnPolicyPasswordlessAuthenticatorAttachment": "not specified",
90 | "webAuthnPolicyPasswordlessRequireResidentKey": "not specified",
91 | "webAuthnPolicyPasswordlessUserVerificationRequirement": "not specified",
92 | "webAuthnPolicyPasswordlessCreateTimeout": 0,
93 | "webAuthnPolicyPasswordlessAvoidSameAuthenticatorRegister": false,
94 | "webAuthnPolicyPasswordlessAcceptableAaguids": [],
95 | "webAuthnPolicyPasswordlessExtraOrigins": [],
96 | "users": [
97 | {
98 | "id": "c2e48adb-c7b0-487e-a51f-39484fa223fb",
99 | "createdTimestamp": 1706362781714,
100 | "username": "service-account-my-client",
101 | "enabled": true,
102 | "totp": false,
103 | "emailVerified": false,
104 | "serviceAccountClientId": "my-client",
105 | "disableableCredentialTypes": [],
106 | "requiredActions": [],
107 | "notBefore": 0
108 | },
109 | {
110 | "id": "ed878d88-af12-41db-bd45-5a3eff8dff1b",
111 | "createdTimestamp": 1706432054034,
112 | "username": "service-account-my-jwt-client",
113 | "enabled": true,
114 | "totp": false,
115 | "emailVerified": false,
116 | "serviceAccountClientId": "my-jwt-client",
117 | "disableableCredentialTypes": [],
118 | "requiredActions": [],
119 | "notBefore": 0
120 | }
121 | ],
122 | "scopeMappings": [
123 | {
124 | "clientScope": "offline_access",
125 | "roles": [
126 | "offline_access"
127 | ]
128 | }
129 | ],
130 | "clientScopeMappings": {
131 | "account": [
132 | {
133 | "client": "account-console",
134 | "roles": [
135 | "manage-account",
136 | "view-groups"
137 | ]
138 | }
139 | ]
140 | },
141 | "clients": [
142 | {
143 | "id": "5c7c6e59-eb6d-4a6f-b8c1-d5b965e7d63e",
144 | "clientId": "account",
145 | "name": "${client_account}",
146 | "rootUrl": "${authBaseUrl}",
147 | "baseUrl": "/realms/my-realm/account/",
148 | "surrogateAuthRequired": false,
149 | "enabled": true,
150 | "alwaysDisplayInConsole": false,
151 | "clientAuthenticatorType": "client-secret",
152 | "redirectUris": [
153 | "/realms/my-realm/account/*"
154 | ],
155 | "webOrigins": [],
156 | "notBefore": 0,
157 | "bearerOnly": false,
158 | "consentRequired": false,
159 | "standardFlowEnabled": true,
160 | "implicitFlowEnabled": false,
161 | "directAccessGrantsEnabled": false,
162 | "serviceAccountsEnabled": false,
163 | "publicClient": true,
164 | "frontchannelLogout": false,
165 | "protocol": "openid-connect",
166 | "attributes": {
167 | "post.logout.redirect.uris": "+"
168 | },
169 | "authenticationFlowBindingOverrides": {},
170 | "fullScopeAllowed": false,
171 | "nodeReRegistrationTimeout": 0,
172 | "defaultClientScopes": [
173 | "web-origins",
174 | "acr",
175 | "roles",
176 | "profile",
177 | "email"
178 | ],
179 | "optionalClientScopes": [
180 | "address",
181 | "phone",
182 | "offline_access",
183 | "microprofile-jwt"
184 | ]
185 | },
186 | {
187 | "id": "54bd0123-c9a8-463e-a4c6-c57f5e8d1409",
188 | "clientId": "account-console",
189 | "name": "${client_account-console}",
190 | "rootUrl": "${authBaseUrl}",
191 | "baseUrl": "/realms/my-realm/account/",
192 | "surrogateAuthRequired": false,
193 | "enabled": true,
194 | "alwaysDisplayInConsole": false,
195 | "clientAuthenticatorType": "client-secret",
196 | "redirectUris": [
197 | "/realms/my-realm/account/*"
198 | ],
199 | "webOrigins": [],
200 | "notBefore": 0,
201 | "bearerOnly": false,
202 | "consentRequired": false,
203 | "standardFlowEnabled": true,
204 | "implicitFlowEnabled": false,
205 | "directAccessGrantsEnabled": false,
206 | "serviceAccountsEnabled": false,
207 | "publicClient": true,
208 | "frontchannelLogout": false,
209 | "protocol": "openid-connect",
210 | "attributes": {
211 | "post.logout.redirect.uris": "+",
212 | "pkce.code.challenge.method": "S256"
213 | },
214 | "authenticationFlowBindingOverrides": {},
215 | "fullScopeAllowed": false,
216 | "nodeReRegistrationTimeout": 0,
217 | "protocolMappers": [
218 | {
219 | "id": "47296e9e-9f0f-4bdf-8cc3-1c765b4c38fc",
220 | "name": "audience resolve",
221 | "protocol": "openid-connect",
222 | "protocolMapper": "oidc-audience-resolve-mapper",
223 | "consentRequired": false,
224 | "config": {}
225 | }
226 | ],
227 | "defaultClientScopes": [
228 | "web-origins",
229 | "acr",
230 | "roles",
231 | "profile",
232 | "email"
233 | ],
234 | "optionalClientScopes": [
235 | "address",
236 | "phone",
237 | "offline_access",
238 | "microprofile-jwt"
239 | ]
240 | },
241 | {
242 | "id": "cdbf3081-0d51-4a6a-bce3-39c02d91e4f0",
243 | "clientId": "admin-cli",
244 | "name": "${client_admin-cli}",
245 | "surrogateAuthRequired": false,
246 | "enabled": true,
247 | "alwaysDisplayInConsole": false,
248 | "clientAuthenticatorType": "client-secret",
249 | "redirectUris": [],
250 | "webOrigins": [],
251 | "notBefore": 0,
252 | "bearerOnly": false,
253 | "consentRequired": false,
254 | "standardFlowEnabled": false,
255 | "implicitFlowEnabled": false,
256 | "directAccessGrantsEnabled": true,
257 | "serviceAccountsEnabled": false,
258 | "publicClient": true,
259 | "frontchannelLogout": false,
260 | "protocol": "openid-connect",
261 | "attributes": {
262 | "post.logout.redirect.uris": "+"
263 | },
264 | "authenticationFlowBindingOverrides": {},
265 | "fullScopeAllowed": false,
266 | "nodeReRegistrationTimeout": 0,
267 | "defaultClientScopes": [
268 | "web-origins",
269 | "acr",
270 | "roles",
271 | "profile",
272 | "email"
273 | ],
274 | "optionalClientScopes": [
275 | "address",
276 | "phone",
277 | "offline_access",
278 | "microprofile-jwt"
279 | ]
280 | },
281 | {
282 | "id": "cbb16a12-0198-4ef1-b087-36174f12351f",
283 | "clientId": "broker",
284 | "name": "${client_broker}",
285 | "surrogateAuthRequired": false,
286 | "enabled": true,
287 | "alwaysDisplayInConsole": false,
288 | "clientAuthenticatorType": "client-secret",
289 | "redirectUris": [],
290 | "webOrigins": [],
291 | "notBefore": 0,
292 | "bearerOnly": true,
293 | "consentRequired": false,
294 | "standardFlowEnabled": true,
295 | "implicitFlowEnabled": false,
296 | "directAccessGrantsEnabled": false,
297 | "serviceAccountsEnabled": false,
298 | "publicClient": false,
299 | "frontchannelLogout": false,
300 | "protocol": "openid-connect",
301 | "attributes": {
302 | "post.logout.redirect.uris": "+"
303 | },
304 | "authenticationFlowBindingOverrides": {},
305 | "fullScopeAllowed": false,
306 | "nodeReRegistrationTimeout": 0,
307 | "defaultClientScopes": [
308 | "web-origins",
309 | "acr",
310 | "roles",
311 | "profile",
312 | "email"
313 | ],
314 | "optionalClientScopes": [
315 | "address",
316 | "phone",
317 | "offline_access",
318 | "microprofile-jwt"
319 | ]
320 | },
321 | {
322 | "id": "a43e12c3-eddb-47f7-893b-c0e8f1745c6b",
323 | "clientId": "my-client",
324 | "name": "",
325 | "description": "",
326 | "rootUrl": "",
327 | "adminUrl": "",
328 | "baseUrl": "",
329 | "surrogateAuthRequired": false,
330 | "enabled": true,
331 | "alwaysDisplayInConsole": false,
332 | "clientAuthenticatorType": "client-secret",
333 | "secret": "secret",
334 | "redirectUris": [
335 | "/*"
336 | ],
337 | "webOrigins": [
338 | "/*"
339 | ],
340 | "notBefore": 0,
341 | "bearerOnly": false,
342 | "consentRequired": false,
343 | "standardFlowEnabled": true,
344 | "implicitFlowEnabled": false,
345 | "directAccessGrantsEnabled": true,
346 | "serviceAccountsEnabled": true,
347 | "publicClient": false,
348 | "frontchannelLogout": true,
349 | "protocol": "openid-connect",
350 | "attributes": {
351 | "client.secret.creation.time": "1706343483",
352 | "post.logout.redirect.uris": "+",
353 | "oauth2.device.authorization.grant.enabled": "false",
354 | "backchannel.logout.revoke.offline.tokens": "false",
355 | "use.refresh.tokens": "true",
356 | "oidc.ciba.grant.enabled": "false",
357 | "backchannel.logout.session.required": "true",
358 | "client_credentials.use_refresh_token": "false",
359 | "tls.client.certificate.bound.access.tokens": "false",
360 | "require.pushed.authorization.requests": "false",
361 | "acr.loa.map": "{}",
362 | "display.on.consent.screen": "false",
363 | "token.response.type.bearer.lower-case": "false"
364 | },
365 | "authenticationFlowBindingOverrides": {},
366 | "fullScopeAllowed": true,
367 | "nodeReRegistrationTimeout": -1,
368 | "protocolMappers": [
369 | {
370 | "id": "021e6cab-4935-42b1-b9b1-87f22e2a205b",
371 | "name": "Client IP Address",
372 | "protocol": "openid-connect",
373 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
374 | "consentRequired": false,
375 | "config": {
376 | "user.session.note": "clientAddress",
377 | "introspection.token.claim": "true",
378 | "userinfo.token.claim": "true",
379 | "id.token.claim": "true",
380 | "access.token.claim": "true",
381 | "claim.name": "clientAddress",
382 | "jsonType.label": "String"
383 | }
384 | },
385 | {
386 | "id": "44da5662-c161-4960-8730-a1023f14a121",
387 | "name": "Client ID",
388 | "protocol": "openid-connect",
389 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
390 | "consentRequired": false,
391 | "config": {
392 | "user.session.note": "client_id",
393 | "introspection.token.claim": "true",
394 | "userinfo.token.claim": "true",
395 | "id.token.claim": "true",
396 | "access.token.claim": "true",
397 | "claim.name": "client_id",
398 | "jsonType.label": "String"
399 | }
400 | },
401 | {
402 | "id": "d23f0026-0843-4cbd-a931-b4d9e4d4ac9c",
403 | "name": "Client Host",
404 | "protocol": "openid-connect",
405 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
406 | "consentRequired": false,
407 | "config": {
408 | "user.session.note": "clientHost",
409 | "introspection.token.claim": "true",
410 | "userinfo.token.claim": "true",
411 | "id.token.claim": "true",
412 | "access.token.claim": "true",
413 | "claim.name": "clientHost",
414 | "jsonType.label": "String"
415 | }
416 | }
417 | ],
418 | "defaultClientScopes": [
419 | "web-origins",
420 | "acr",
421 | "roles",
422 | "profile",
423 | "email"
424 | ],
425 | "optionalClientScopes": [
426 | "address",
427 | "phone",
428 | "offline_access",
429 | "microprofile-jwt"
430 | ]
431 | },
432 | {
433 | "id": "482ee2ab-8e65-4aab-afe0-24896c638a2e",
434 | "clientId": "my-jwt-client",
435 | "name": "",
436 | "description": "",
437 | "rootUrl": "",
438 | "adminUrl": "",
439 | "baseUrl": "",
440 | "surrogateAuthRequired": false,
441 | "enabled": true,
442 | "alwaysDisplayInConsole": false,
443 | "clientAuthenticatorType": "client-jwt",
444 | "secret": "q4pDzgvJBnzh9qpNWELUuKFCWMx4xdWY",
445 | "redirectUris": [
446 | "/*"
447 | ],
448 | "webOrigins": [
449 | "/*"
450 | ],
451 | "notBefore": 0,
452 | "bearerOnly": false,
453 | "consentRequired": false,
454 | "standardFlowEnabled": false,
455 | "implicitFlowEnabled": false,
456 | "directAccessGrantsEnabled": false,
457 | "serviceAccountsEnabled": true,
458 | "publicClient": false,
459 | "frontchannelLogout": true,
460 | "protocol": "openid-connect",
461 | "attributes": {
462 | "client.secret.creation.time": "1706432054",
463 | "post.logout.redirect.uris": "+",
464 | "oauth2.device.authorization.grant.enabled": "false",
465 | "use.jwks.url": "true",
466 | "backchannel.logout.revoke.offline.tokens": "false",
467 | "use.refresh.tokens": "true",
468 | "oidc.ciba.grant.enabled": "false",
469 | "backchannel.logout.session.required": "true",
470 | "jwks.url": "http://host.docker.internal:8081/jwks",
471 | "client_credentials.use_refresh_token": "false",
472 | "tls.client.certificate.bound.access.tokens": "false",
473 | "require.pushed.authorization.requests": "false",
474 | "acr.loa.map": "{}",
475 | "display.on.consent.screen": "false",
476 | "token.response.type.bearer.lower-case": "false"
477 | },
478 | "authenticationFlowBindingOverrides": {},
479 | "fullScopeAllowed": true,
480 | "nodeReRegistrationTimeout": -1,
481 | "protocolMappers": [
482 | {
483 | "id": "9f9bdaf7-dc60-45ec-8c93-a825da5b9024",
484 | "name": "Client ID",
485 | "protocol": "openid-connect",
486 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
487 | "consentRequired": false,
488 | "config": {
489 | "user.session.note": "client_id",
490 | "introspection.token.claim": "true",
491 | "userinfo.token.claim": "true",
492 | "id.token.claim": "true",
493 | "access.token.claim": "true",
494 | "claim.name": "client_id",
495 | "jsonType.label": "String"
496 | }
497 | },
498 | {
499 | "id": "58a3403e-51d7-456b-822a-d22345ce9505",
500 | "name": "Client IP Address",
501 | "protocol": "openid-connect",
502 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
503 | "consentRequired": false,
504 | "config": {
505 | "user.session.note": "clientAddress",
506 | "introspection.token.claim": "true",
507 | "userinfo.token.claim": "true",
508 | "id.token.claim": "true",
509 | "access.token.claim": "true",
510 | "claim.name": "clientAddress",
511 | "jsonType.label": "String"
512 | }
513 | },
514 | {
515 | "id": "4b4f4f7a-c787-40a0-9894-b4afdd226bd0",
516 | "name": "Client Host",
517 | "protocol": "openid-connect",
518 | "protocolMapper": "oidc-usersessionmodel-note-mapper",
519 | "consentRequired": false,
520 | "config": {
521 | "user.session.note": "clientHost",
522 | "introspection.token.claim": "true",
523 | "userinfo.token.claim": "true",
524 | "id.token.claim": "true",
525 | "access.token.claim": "true",
526 | "claim.name": "clientHost",
527 | "jsonType.label": "String"
528 | }
529 | }
530 | ],
531 | "defaultClientScopes": [
532 | "web-origins",
533 | "acr",
534 | "roles",
535 | "profile",
536 | "email"
537 | ],
538 | "optionalClientScopes": [
539 | "address",
540 | "phone",
541 | "offline_access",
542 | "microprofile-jwt"
543 | ]
544 | },
545 | {
546 | "id": "994b93a2-ce29-48c4-8886-ad13021e5d51",
547 | "clientId": "realm-management",
548 | "name": "${client_realm-management}",
549 | "surrogateAuthRequired": false,
550 | "enabled": true,
551 | "alwaysDisplayInConsole": false,
552 | "clientAuthenticatorType": "client-secret",
553 | "redirectUris": [],
554 | "webOrigins": [],
555 | "notBefore": 0,
556 | "bearerOnly": true,
557 | "consentRequired": false,
558 | "standardFlowEnabled": true,
559 | "implicitFlowEnabled": false,
560 | "directAccessGrantsEnabled": false,
561 | "serviceAccountsEnabled": false,
562 | "publicClient": false,
563 | "frontchannelLogout": false,
564 | "protocol": "openid-connect",
565 | "attributes": {
566 | "post.logout.redirect.uris": "+"
567 | },
568 | "authenticationFlowBindingOverrides": {},
569 | "fullScopeAllowed": false,
570 | "nodeReRegistrationTimeout": 0,
571 | "defaultClientScopes": [
572 | "web-origins",
573 | "acr",
574 | "roles",
575 | "profile",
576 | "email"
577 | ],
578 | "optionalClientScopes": [
579 | "address",
580 | "phone",
581 | "offline_access",
582 | "microprofile-jwt"
583 | ]
584 | },
585 | {
586 | "id": "68ae1e44-9264-489e-a734-225e53ae9053",
587 | "clientId": "security-admin-console",
588 | "name": "${client_security-admin-console}",
589 | "rootUrl": "${authAdminUrl}",
590 | "baseUrl": "/admin/my-realm/console/",
591 | "surrogateAuthRequired": false,
592 | "enabled": true,
593 | "alwaysDisplayInConsole": false,
594 | "clientAuthenticatorType": "client-secret",
595 | "redirectUris": [
596 | "/admin/my-realm/console/*"
597 | ],
598 | "webOrigins": [
599 | "+"
600 | ],
601 | "notBefore": 0,
602 | "bearerOnly": false,
603 | "consentRequired": false,
604 | "standardFlowEnabled": true,
605 | "implicitFlowEnabled": false,
606 | "directAccessGrantsEnabled": false,
607 | "serviceAccountsEnabled": false,
608 | "publicClient": true,
609 | "frontchannelLogout": false,
610 | "protocol": "openid-connect",
611 | "attributes": {
612 | "post.logout.redirect.uris": "+",
613 | "pkce.code.challenge.method": "S256"
614 | },
615 | "authenticationFlowBindingOverrides": {},
616 | "fullScopeAllowed": false,
617 | "nodeReRegistrationTimeout": 0,
618 | "protocolMappers": [
619 | {
620 | "id": "e4e3b6aa-1071-4b4d-a3d5-60a7d35de9da",
621 | "name": "locale",
622 | "protocol": "openid-connect",
623 | "protocolMapper": "oidc-usermodel-attribute-mapper",
624 | "consentRequired": false,
625 | "config": {
626 | "introspection.token.claim": "true",
627 | "userinfo.token.claim": "true",
628 | "user.attribute": "locale",
629 | "id.token.claim": "true",
630 | "access.token.claim": "true",
631 | "claim.name": "locale",
632 | "jsonType.label": "String"
633 | }
634 | }
635 | ],
636 | "defaultClientScopes": [
637 | "web-origins",
638 | "acr",
639 | "roles",
640 | "profile",
641 | "email"
642 | ],
643 | "optionalClientScopes": [
644 | "address",
645 | "phone",
646 | "offline_access",
647 | "microprofile-jwt"
648 | ]
649 | }
650 | ],
651 | "clientScopes": [
652 | {
653 | "id": "5c3df1ff-ef21-4132-b97c-bfa8b20a8699",
654 | "name": "web-origins",
655 | "description": "OpenID Connect scope for add allowed web origins to the access token",
656 | "protocol": "openid-connect",
657 | "attributes": {
658 | "include.in.token.scope": "false",
659 | "display.on.consent.screen": "false",
660 | "consent.screen.text": ""
661 | },
662 | "protocolMappers": [
663 | {
664 | "id": "f8160a98-1726-408f-867b-1b3751814681",
665 | "name": "allowed web origins",
666 | "protocol": "openid-connect",
667 | "protocolMapper": "oidc-allowed-origins-mapper",
668 | "consentRequired": false,
669 | "config": {
670 | "introspection.token.claim": "true",
671 | "access.token.claim": "true"
672 | }
673 | }
674 | ]
675 | },
676 | {
677 | "id": "f5040fa9-47b0-40b9-b0a3-8ed2f090e792",
678 | "name": "roles",
679 | "description": "OpenID Connect scope for add user roles to the access token",
680 | "protocol": "openid-connect",
681 | "attributes": {
682 | "include.in.token.scope": "false",
683 | "display.on.consent.screen": "true",
684 | "consent.screen.text": "${rolesScopeConsentText}"
685 | },
686 | "protocolMappers": [
687 | {
688 | "id": "aa013b36-0370-4ea6-89ed-cc75294f0029",
689 | "name": "audience resolve",
690 | "protocol": "openid-connect",
691 | "protocolMapper": "oidc-audience-resolve-mapper",
692 | "consentRequired": false,
693 | "config": {
694 | "introspection.token.claim": "true",
695 | "access.token.claim": "true"
696 | }
697 | },
698 | {
699 | "id": "42ad11a5-faa1-4db3-bfb7-b9b65213706f",
700 | "name": "client roles",
701 | "protocol": "openid-connect",
702 | "protocolMapper": "oidc-usermodel-client-role-mapper",
703 | "consentRequired": false,
704 | "config": {
705 | "introspection.token.claim": "true",
706 | "multivalued": "true",
707 | "user.attribute": "foo",
708 | "access.token.claim": "true",
709 | "claim.name": "resource_access.${client_id}.roles",
710 | "jsonType.label": "String"
711 | }
712 | },
713 | {
714 | "id": "c906ff83-e739-4bdb-8ad4-afe2672f3430",
715 | "name": "realm roles",
716 | "protocol": "openid-connect",
717 | "protocolMapper": "oidc-usermodel-realm-role-mapper",
718 | "consentRequired": false,
719 | "config": {
720 | "introspection.token.claim": "true",
721 | "multivalued": "true",
722 | "user.attribute": "foo",
723 | "access.token.claim": "true",
724 | "claim.name": "realm_access.roles",
725 | "jsonType.label": "String"
726 | }
727 | }
728 | ]
729 | },
730 | {
731 | "id": "ac0cc088-5fd1-49c8-9a7a-a340747635ab",
732 | "name": "acr",
733 | "description": "OpenID Connect scope for add acr (authentication context class reference) to the token",
734 | "protocol": "openid-connect",
735 | "attributes": {
736 | "include.in.token.scope": "false",
737 | "display.on.consent.screen": "false"
738 | },
739 | "protocolMappers": [
740 | {
741 | "id": "a092fa8f-e214-49b1-ab08-fba704815554",
742 | "name": "acr loa level",
743 | "protocol": "openid-connect",
744 | "protocolMapper": "oidc-acr-mapper",
745 | "consentRequired": false,
746 | "config": {
747 | "id.token.claim": "true",
748 | "introspection.token.claim": "true",
749 | "access.token.claim": "true",
750 | "userinfo.token.claim": "true"
751 | }
752 | }
753 | ]
754 | },
755 | {
756 | "id": "50e9b4f6-cad1-4a22-ba8f-a0494f526687",
757 | "name": "offline_access",
758 | "description": "OpenID Connect built-in scope: offline_access",
759 | "protocol": "openid-connect",
760 | "attributes": {
761 | "consent.screen.text": "${offlineAccessScopeConsentText}",
762 | "display.on.consent.screen": "true"
763 | }
764 | },
765 | {
766 | "id": "cc69d404-023a-4851-87a3-cdfe755c6088",
767 | "name": "role_list",
768 | "description": "SAML role list",
769 | "protocol": "saml",
770 | "attributes": {
771 | "consent.screen.text": "${samlRoleListScopeConsentText}",
772 | "display.on.consent.screen": "true"
773 | },
774 | "protocolMappers": [
775 | {
776 | "id": "a0a2419a-96dc-4556-b2e3-30faf98190a8",
777 | "name": "role list",
778 | "protocol": "saml",
779 | "protocolMapper": "saml-role-list-mapper",
780 | "consentRequired": false,
781 | "config": {
782 | "single": "false",
783 | "attribute.nameformat": "Basic",
784 | "attribute.name": "Role"
785 | }
786 | }
787 | ]
788 | },
789 | {
790 | "id": "7cea4641-bee4-4505-8e16-f18109e2f592",
791 | "name": "email",
792 | "description": "OpenID Connect built-in scope: email",
793 | "protocol": "openid-connect",
794 | "attributes": {
795 | "include.in.token.scope": "true",
796 | "display.on.consent.screen": "true",
797 | "consent.screen.text": "${emailScopeConsentText}"
798 | },
799 | "protocolMappers": [
800 | {
801 | "id": "801f1275-e706-44cc-8099-6039f15307a3",
802 | "name": "email",
803 | "protocol": "openid-connect",
804 | "protocolMapper": "oidc-usermodel-attribute-mapper",
805 | "consentRequired": false,
806 | "config": {
807 | "introspection.token.claim": "true",
808 | "userinfo.token.claim": "true",
809 | "user.attribute": "email",
810 | "id.token.claim": "true",
811 | "access.token.claim": "true",
812 | "claim.name": "email",
813 | "jsonType.label": "String"
814 | }
815 | },
816 | {
817 | "id": "ce3587ed-a29e-4956-bfe9-6fe334ab79bf",
818 | "name": "email verified",
819 | "protocol": "openid-connect",
820 | "protocolMapper": "oidc-usermodel-property-mapper",
821 | "consentRequired": false,
822 | "config": {
823 | "introspection.token.claim": "true",
824 | "userinfo.token.claim": "true",
825 | "user.attribute": "emailVerified",
826 | "id.token.claim": "true",
827 | "access.token.claim": "true",
828 | "claim.name": "email_verified",
829 | "jsonType.label": "boolean"
830 | }
831 | }
832 | ]
833 | },
834 | {
835 | "id": "736c5219-bbfb-4f32-ae7b-0e11cde5962c",
836 | "name": "microprofile-jwt",
837 | "description": "Microprofile - JWT built-in scope",
838 | "protocol": "openid-connect",
839 | "attributes": {
840 | "include.in.token.scope": "true",
841 | "display.on.consent.screen": "false"
842 | },
843 | "protocolMappers": [
844 | {
845 | "id": "e20bd8c1-d4c6-4321-bb3f-925a2f5c123d",
846 | "name": "groups",
847 | "protocol": "openid-connect",
848 | "protocolMapper": "oidc-usermodel-realm-role-mapper",
849 | "consentRequired": false,
850 | "config": {
851 | "introspection.token.claim": "true",
852 | "multivalued": "true",
853 | "userinfo.token.claim": "true",
854 | "user.attribute": "foo",
855 | "id.token.claim": "true",
856 | "access.token.claim": "true",
857 | "claim.name": "groups",
858 | "jsonType.label": "String"
859 | }
860 | },
861 | {
862 | "id": "8e4ad0f1-2a22-4266-8776-c67721c88941",
863 | "name": "upn",
864 | "protocol": "openid-connect",
865 | "protocolMapper": "oidc-usermodel-attribute-mapper",
866 | "consentRequired": false,
867 | "config": {
868 | "introspection.token.claim": "true",
869 | "userinfo.token.claim": "true",
870 | "user.attribute": "username",
871 | "id.token.claim": "true",
872 | "access.token.claim": "true",
873 | "claim.name": "upn",
874 | "jsonType.label": "String"
875 | }
876 | }
877 | ]
878 | },
879 | {
880 | "id": "c64ac7f3-380d-46f2-ab23-fa925e0a7fa5",
881 | "name": "phone",
882 | "description": "OpenID Connect built-in scope: phone",
883 | "protocol": "openid-connect",
884 | "attributes": {
885 | "include.in.token.scope": "true",
886 | "display.on.consent.screen": "true",
887 | "consent.screen.text": "${phoneScopeConsentText}"
888 | },
889 | "protocolMappers": [
890 | {
891 | "id": "7533c6d4-c922-4fbd-aac5-d3e18512aa67",
892 | "name": "phone number verified",
893 | "protocol": "openid-connect",
894 | "protocolMapper": "oidc-usermodel-attribute-mapper",
895 | "consentRequired": false,
896 | "config": {
897 | "introspection.token.claim": "true",
898 | "userinfo.token.claim": "true",
899 | "user.attribute": "phoneNumberVerified",
900 | "id.token.claim": "true",
901 | "access.token.claim": "true",
902 | "claim.name": "phone_number_verified",
903 | "jsonType.label": "boolean"
904 | }
905 | },
906 | {
907 | "id": "776f69cd-c10f-4b20-ab5a-98af82888d43",
908 | "name": "phone number",
909 | "protocol": "openid-connect",
910 | "protocolMapper": "oidc-usermodel-attribute-mapper",
911 | "consentRequired": false,
912 | "config": {
913 | "introspection.token.claim": "true",
914 | "userinfo.token.claim": "true",
915 | "user.attribute": "phoneNumber",
916 | "id.token.claim": "true",
917 | "access.token.claim": "true",
918 | "claim.name": "phone_number",
919 | "jsonType.label": "String"
920 | }
921 | }
922 | ]
923 | },
924 | {
925 | "id": "9f3fa2df-4e3d-4e57-9cf0-0fa294af529b",
926 | "name": "profile",
927 | "description": "OpenID Connect built-in scope: profile",
928 | "protocol": "openid-connect",
929 | "attributes": {
930 | "include.in.token.scope": "true",
931 | "display.on.consent.screen": "true",
932 | "consent.screen.text": "${profileScopeConsentText}"
933 | },
934 | "protocolMappers": [
935 | {
936 | "id": "0cb98916-d6d5-4c7c-9fea-ccc12c1f887b",
937 | "name": "middle name",
938 | "protocol": "openid-connect",
939 | "protocolMapper": "oidc-usermodel-attribute-mapper",
940 | "consentRequired": false,
941 | "config": {
942 | "introspection.token.claim": "true",
943 | "userinfo.token.claim": "true",
944 | "user.attribute": "middleName",
945 | "id.token.claim": "true",
946 | "access.token.claim": "true",
947 | "claim.name": "middle_name",
948 | "jsonType.label": "String"
949 | }
950 | },
951 | {
952 | "id": "b4b0529e-72eb-4fc8-a043-6aedd6443f9d",
953 | "name": "username",
954 | "protocol": "openid-connect",
955 | "protocolMapper": "oidc-usermodel-attribute-mapper",
956 | "consentRequired": false,
957 | "config": {
958 | "introspection.token.claim": "true",
959 | "userinfo.token.claim": "true",
960 | "user.attribute": "username",
961 | "id.token.claim": "true",
962 | "access.token.claim": "true",
963 | "claim.name": "preferred_username",
964 | "jsonType.label": "String"
965 | }
966 | },
967 | {
968 | "id": "82120568-6268-45e7-b0c3-fa95e486f54e",
969 | "name": "family name",
970 | "protocol": "openid-connect",
971 | "protocolMapper": "oidc-usermodel-attribute-mapper",
972 | "consentRequired": false,
973 | "config": {
974 | "introspection.token.claim": "true",
975 | "userinfo.token.claim": "true",
976 | "user.attribute": "lastName",
977 | "id.token.claim": "true",
978 | "access.token.claim": "true",
979 | "claim.name": "family_name",
980 | "jsonType.label": "String"
981 | }
982 | },
983 | {
984 | "id": "d9678539-5a43-45ae-8667-40f6e529852a",
985 | "name": "nickname",
986 | "protocol": "openid-connect",
987 | "protocolMapper": "oidc-usermodel-attribute-mapper",
988 | "consentRequired": false,
989 | "config": {
990 | "introspection.token.claim": "true",
991 | "userinfo.token.claim": "true",
992 | "user.attribute": "nickname",
993 | "id.token.claim": "true",
994 | "access.token.claim": "true",
995 | "claim.name": "nickname",
996 | "jsonType.label": "String"
997 | }
998 | },
999 | {
1000 | "id": "2c6c4bd8-f1f6-4d0e-bab5-99372c461c25",
1001 | "name": "locale",
1002 | "protocol": "openid-connect",
1003 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1004 | "consentRequired": false,
1005 | "config": {
1006 | "introspection.token.claim": "true",
1007 | "userinfo.token.claim": "true",
1008 | "user.attribute": "locale",
1009 | "id.token.claim": "true",
1010 | "access.token.claim": "true",
1011 | "claim.name": "locale",
1012 | "jsonType.label": "String"
1013 | }
1014 | },
1015 | {
1016 | "id": "12fdb9ee-bc63-4a14-834a-9b5e19340fab",
1017 | "name": "gender",
1018 | "protocol": "openid-connect",
1019 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1020 | "consentRequired": false,
1021 | "config": {
1022 | "introspection.token.claim": "true",
1023 | "userinfo.token.claim": "true",
1024 | "user.attribute": "gender",
1025 | "id.token.claim": "true",
1026 | "access.token.claim": "true",
1027 | "claim.name": "gender",
1028 | "jsonType.label": "String"
1029 | }
1030 | },
1031 | {
1032 | "id": "afe2b3f0-8189-4f03-85a1-f2468077016a",
1033 | "name": "profile",
1034 | "protocol": "openid-connect",
1035 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1036 | "consentRequired": false,
1037 | "config": {
1038 | "introspection.token.claim": "true",
1039 | "userinfo.token.claim": "true",
1040 | "user.attribute": "profile",
1041 | "id.token.claim": "true",
1042 | "access.token.claim": "true",
1043 | "claim.name": "profile",
1044 | "jsonType.label": "String"
1045 | }
1046 | },
1047 | {
1048 | "id": "f670ce97-4a7f-45f8-a36f-f0d822716bda",
1049 | "name": "zoneinfo",
1050 | "protocol": "openid-connect",
1051 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1052 | "consentRequired": false,
1053 | "config": {
1054 | "introspection.token.claim": "true",
1055 | "userinfo.token.claim": "true",
1056 | "user.attribute": "zoneinfo",
1057 | "id.token.claim": "true",
1058 | "access.token.claim": "true",
1059 | "claim.name": "zoneinfo",
1060 | "jsonType.label": "String"
1061 | }
1062 | },
1063 | {
1064 | "id": "25e5425c-f98a-4ffc-8c47-c14d768e2878",
1065 | "name": "birthdate",
1066 | "protocol": "openid-connect",
1067 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1068 | "consentRequired": false,
1069 | "config": {
1070 | "introspection.token.claim": "true",
1071 | "userinfo.token.claim": "true",
1072 | "user.attribute": "birthdate",
1073 | "id.token.claim": "true",
1074 | "access.token.claim": "true",
1075 | "claim.name": "birthdate",
1076 | "jsonType.label": "String"
1077 | }
1078 | },
1079 | {
1080 | "id": "fc1fb4ba-3d33-43f9-aecb-00b31c28cab3",
1081 | "name": "picture",
1082 | "protocol": "openid-connect",
1083 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1084 | "consentRequired": false,
1085 | "config": {
1086 | "introspection.token.claim": "true",
1087 | "userinfo.token.claim": "true",
1088 | "user.attribute": "picture",
1089 | "id.token.claim": "true",
1090 | "access.token.claim": "true",
1091 | "claim.name": "picture",
1092 | "jsonType.label": "String"
1093 | }
1094 | },
1095 | {
1096 | "id": "c9a11b44-f896-4424-8298-8330a4291d1d",
1097 | "name": "given name",
1098 | "protocol": "openid-connect",
1099 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1100 | "consentRequired": false,
1101 | "config": {
1102 | "introspection.token.claim": "true",
1103 | "userinfo.token.claim": "true",
1104 | "user.attribute": "firstName",
1105 | "id.token.claim": "true",
1106 | "access.token.claim": "true",
1107 | "claim.name": "given_name",
1108 | "jsonType.label": "String"
1109 | }
1110 | },
1111 | {
1112 | "id": "c05b519f-9dcd-47f1-a604-8cb94e57488c",
1113 | "name": "updated at",
1114 | "protocol": "openid-connect",
1115 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1116 | "consentRequired": false,
1117 | "config": {
1118 | "introspection.token.claim": "true",
1119 | "userinfo.token.claim": "true",
1120 | "user.attribute": "updatedAt",
1121 | "id.token.claim": "true",
1122 | "access.token.claim": "true",
1123 | "claim.name": "updated_at",
1124 | "jsonType.label": "long"
1125 | }
1126 | },
1127 | {
1128 | "id": "5588e32b-c844-44f1-a756-d3aae83e2ae7",
1129 | "name": "full name",
1130 | "protocol": "openid-connect",
1131 | "protocolMapper": "oidc-full-name-mapper",
1132 | "consentRequired": false,
1133 | "config": {
1134 | "id.token.claim": "true",
1135 | "introspection.token.claim": "true",
1136 | "access.token.claim": "true",
1137 | "userinfo.token.claim": "true"
1138 | }
1139 | },
1140 | {
1141 | "id": "96cd26d4-c905-4252-a2f7-a7ee8a7fb87d",
1142 | "name": "website",
1143 | "protocol": "openid-connect",
1144 | "protocolMapper": "oidc-usermodel-attribute-mapper",
1145 | "consentRequired": false,
1146 | "config": {
1147 | "introspection.token.claim": "true",
1148 | "userinfo.token.claim": "true",
1149 | "user.attribute": "website",
1150 | "id.token.claim": "true",
1151 | "access.token.claim": "true",
1152 | "claim.name": "website",
1153 | "jsonType.label": "String"
1154 | }
1155 | }
1156 | ]
1157 | },
1158 | {
1159 | "id": "15a00a4c-165f-47f9-b362-7a5cc0893145",
1160 | "name": "address",
1161 | "description": "OpenID Connect built-in scope: address",
1162 | "protocol": "openid-connect",
1163 | "attributes": {
1164 | "include.in.token.scope": "true",
1165 | "display.on.consent.screen": "true",
1166 | "consent.screen.text": "${addressScopeConsentText}"
1167 | },
1168 | "protocolMappers": [
1169 | {
1170 | "id": "3207bd29-6f86-42e8-ac2c-3e559c7cbf41",
1171 | "name": "address",
1172 | "protocol": "openid-connect",
1173 | "protocolMapper": "oidc-address-mapper",
1174 | "consentRequired": false,
1175 | "config": {
1176 | "user.attribute.formatted": "formatted",
1177 | "user.attribute.country": "country",
1178 | "introspection.token.claim": "true",
1179 | "user.attribute.postal_code": "postal_code",
1180 | "userinfo.token.claim": "true",
1181 | "user.attribute.street": "street",
1182 | "id.token.claim": "true",
1183 | "user.attribute.region": "region",
1184 | "access.token.claim": "true",
1185 | "user.attribute.locality": "locality"
1186 | }
1187 | }
1188 | ]
1189 | }
1190 | ],
1191 | "defaultDefaultClientScopes": [
1192 | "role_list",
1193 | "profile",
1194 | "email",
1195 | "roles",
1196 | "web-origins",
1197 | "acr"
1198 | ],
1199 | "defaultOptionalClientScopes": [
1200 | "offline_access",
1201 | "address",
1202 | "phone",
1203 | "microprofile-jwt"
1204 | ],
1205 | "browserSecurityHeaders": {
1206 | "contentSecurityPolicyReportOnly": "",
1207 | "xContentTypeOptions": "nosniff",
1208 | "referrerPolicy": "no-referrer",
1209 | "xRobotsTag": "none",
1210 | "xFrameOptions": "SAMEORIGIN",
1211 | "contentSecurityPolicy": "frame-src 'self'; frame-ancestors 'self'; object-src 'none';",
1212 | "xXSSProtection": "1; mode=block",
1213 | "strictTransportSecurity": "max-age=31536000; includeSubDomains"
1214 | },
1215 | "smtpServer": {},
1216 | "eventsEnabled": true,
1217 | "eventsListeners": [
1218 | "jboss-logging"
1219 | ],
1220 | "enabledEventTypes": [
1221 | "SEND_RESET_PASSWORD",
1222 | "UPDATE_CONSENT_ERROR",
1223 | "GRANT_CONSENT",
1224 | "VERIFY_PROFILE_ERROR",
1225 | "REMOVE_TOTP",
1226 | "REVOKE_GRANT",
1227 | "UPDATE_TOTP",
1228 | "LOGIN_ERROR",
1229 | "CLIENT_LOGIN",
1230 | "RESET_PASSWORD_ERROR",
1231 | "IMPERSONATE_ERROR",
1232 | "CODE_TO_TOKEN_ERROR",
1233 | "CUSTOM_REQUIRED_ACTION",
1234 | "OAUTH2_DEVICE_CODE_TO_TOKEN_ERROR",
1235 | "RESTART_AUTHENTICATION",
1236 | "IMPERSONATE",
1237 | "UPDATE_PROFILE_ERROR",
1238 | "LOGIN",
1239 | "OAUTH2_DEVICE_VERIFY_USER_CODE",
1240 | "UPDATE_PASSWORD_ERROR",
1241 | "CLIENT_INITIATED_ACCOUNT_LINKING",
1242 | "USER_DISABLED_BY_PERMANENT_LOCKOUT",
1243 | "TOKEN_EXCHANGE",
1244 | "AUTHREQID_TO_TOKEN",
1245 | "LOGOUT",
1246 | "REGISTER",
1247 | "DELETE_ACCOUNT_ERROR",
1248 | "CLIENT_REGISTER",
1249 | "IDENTITY_PROVIDER_LINK_ACCOUNT",
1250 | "DELETE_ACCOUNT",
1251 | "UPDATE_PASSWORD",
1252 | "CLIENT_DELETE",
1253 | "FEDERATED_IDENTITY_LINK_ERROR",
1254 | "IDENTITY_PROVIDER_FIRST_LOGIN",
1255 | "CLIENT_DELETE_ERROR",
1256 | "VERIFY_EMAIL",
1257 | "CLIENT_LOGIN_ERROR",
1258 | "RESTART_AUTHENTICATION_ERROR",
1259 | "EXECUTE_ACTIONS",
1260 | "REMOVE_FEDERATED_IDENTITY_ERROR",
1261 | "TOKEN_EXCHANGE_ERROR",
1262 | "PERMISSION_TOKEN",
1263 | "SEND_IDENTITY_PROVIDER_LINK_ERROR",
1264 | "EXECUTE_ACTION_TOKEN_ERROR",
1265 | "SEND_VERIFY_EMAIL",
1266 | "OAUTH2_DEVICE_AUTH",
1267 | "EXECUTE_ACTIONS_ERROR",
1268 | "REMOVE_FEDERATED_IDENTITY",
1269 | "OAUTH2_DEVICE_CODE_TO_TOKEN",
1270 | "IDENTITY_PROVIDER_POST_LOGIN",
1271 | "IDENTITY_PROVIDER_LINK_ACCOUNT_ERROR",
1272 | "OAUTH2_DEVICE_VERIFY_USER_CODE_ERROR",
1273 | "UPDATE_EMAIL",
1274 | "REGISTER_ERROR",
1275 | "REVOKE_GRANT_ERROR",
1276 | "EXECUTE_ACTION_TOKEN",
1277 | "LOGOUT_ERROR",
1278 | "UPDATE_EMAIL_ERROR",
1279 | "CLIENT_UPDATE_ERROR",
1280 | "AUTHREQID_TO_TOKEN_ERROR",
1281 | "UPDATE_PROFILE",
1282 | "CLIENT_REGISTER_ERROR",
1283 | "FEDERATED_IDENTITY_LINK",
1284 | "SEND_IDENTITY_PROVIDER_LINK",
1285 | "SEND_VERIFY_EMAIL_ERROR",
1286 | "RESET_PASSWORD",
1287 | "CLIENT_INITIATED_ACCOUNT_LINKING_ERROR",
1288 | "OAUTH2_DEVICE_AUTH_ERROR",
1289 | "UPDATE_CONSENT",
1290 | "REMOVE_TOTP_ERROR",
1291 | "VERIFY_EMAIL_ERROR",
1292 | "SEND_RESET_PASSWORD_ERROR",
1293 | "CLIENT_UPDATE",
1294 | "CUSTOM_REQUIRED_ACTION_ERROR",
1295 | "IDENTITY_PROVIDER_POST_LOGIN_ERROR",
1296 | "UPDATE_TOTP_ERROR",
1297 | "CODE_TO_TOKEN",
1298 | "VERIFY_PROFILE",
1299 | "GRANT_CONSENT_ERROR",
1300 | "IDENTITY_PROVIDER_FIRST_LOGIN_ERROR"
1301 | ],
1302 | "adminEventsEnabled": false,
1303 | "adminEventsDetailsEnabled": false,
1304 | "identityProviders": [],
1305 | "identityProviderMappers": [],
1306 | "components": {
1307 | "org.keycloak.services.clientregistration.policy.ClientRegistrationPolicy": [
1308 | {
1309 | "id": "1b3e8653-e9f4-431e-abf2-bddf5c27b049",
1310 | "name": "Max Clients Limit",
1311 | "providerId": "max-clients",
1312 | "subType": "anonymous",
1313 | "subComponents": {},
1314 | "config": {
1315 | "max-clients": [
1316 | "200"
1317 | ]
1318 | }
1319 | },
1320 | {
1321 | "id": "60a8ef10-8a1b-45bc-acc9-a2b1ba07d1d7",
1322 | "name": "Trusted Hosts",
1323 | "providerId": "trusted-hosts",
1324 | "subType": "anonymous",
1325 | "subComponents": {},
1326 | "config": {
1327 | "host-sending-registration-request-must-match": [
1328 | "true"
1329 | ],
1330 | "client-uris-must-match": [
1331 | "true"
1332 | ]
1333 | }
1334 | },
1335 | {
1336 | "id": "051e3d18-bb7a-4428-a1ec-638e9fac9038",
1337 | "name": "Allowed Client Scopes",
1338 | "providerId": "allowed-client-templates",
1339 | "subType": "authenticated",
1340 | "subComponents": {},
1341 | "config": {
1342 | "allow-default-scopes": [
1343 | "true"
1344 | ]
1345 | }
1346 | },
1347 | {
1348 | "id": "c015de02-be6b-41ed-852f-787f33460828",
1349 | "name": "Allowed Client Scopes",
1350 | "providerId": "allowed-client-templates",
1351 | "subType": "anonymous",
1352 | "subComponents": {},
1353 | "config": {
1354 | "allow-default-scopes": [
1355 | "true"
1356 | ]
1357 | }
1358 | },
1359 | {
1360 | "id": "67d0efde-4688-4573-9407-d6a0679ff033",
1361 | "name": "Allowed Protocol Mapper Types",
1362 | "providerId": "allowed-protocol-mappers",
1363 | "subType": "authenticated",
1364 | "subComponents": {},
1365 | "config": {
1366 | "allowed-protocol-mapper-types": [
1367 | "saml-user-property-mapper",
1368 | "oidc-full-name-mapper",
1369 | "saml-user-attribute-mapper",
1370 | "saml-role-list-mapper",
1371 | "oidc-usermodel-property-mapper",
1372 | "oidc-usermodel-attribute-mapper",
1373 | "oidc-sha256-pairwise-sub-mapper",
1374 | "oidc-address-mapper"
1375 | ]
1376 | }
1377 | },
1378 | {
1379 | "id": "fbb610e3-672b-425f-bced-cd80932ee47f",
1380 | "name": "Allowed Protocol Mapper Types",
1381 | "providerId": "allowed-protocol-mappers",
1382 | "subType": "anonymous",
1383 | "subComponents": {},
1384 | "config": {
1385 | "allowed-protocol-mapper-types": [
1386 | "oidc-full-name-mapper",
1387 | "oidc-sha256-pairwise-sub-mapper",
1388 | "saml-user-attribute-mapper",
1389 | "oidc-usermodel-property-mapper",
1390 | "saml-role-list-mapper",
1391 | "saml-user-property-mapper",
1392 | "oidc-usermodel-attribute-mapper",
1393 | "oidc-address-mapper"
1394 | ]
1395 | }
1396 | },
1397 | {
1398 | "id": "2a8a6284-481e-4526-b67c-70e54190dc29",
1399 | "name": "Consent Required",
1400 | "providerId": "consent-required",
1401 | "subType": "anonymous",
1402 | "subComponents": {},
1403 | "config": {}
1404 | },
1405 | {
1406 | "id": "1264c1d7-1080-4ce5-a58a-aa0dd94fcd8c",
1407 | "name": "Full Scope Disabled",
1408 | "providerId": "scope",
1409 | "subType": "anonymous",
1410 | "subComponents": {},
1411 | "config": {}
1412 | }
1413 | ],
1414 | "org.keycloak.keys.KeyProvider": [
1415 | {
1416 | "id": "f47efe36-c0ca-4508-a8d8-9eb5968ad772",
1417 | "name": "aes-generated",
1418 | "providerId": "aes-generated",
1419 | "subComponents": {},
1420 | "config": {
1421 | "priority": [
1422 | "100"
1423 | ]
1424 | }
1425 | },
1426 | {
1427 | "id": "be29f023-fc2f-4154-9484-f1a390d43029",
1428 | "name": "hmac-generated",
1429 | "providerId": "hmac-generated",
1430 | "subComponents": {},
1431 | "config": {
1432 | "priority": [
1433 | "100"
1434 | ],
1435 | "algorithm": [
1436 | "HS256"
1437 | ]
1438 | }
1439 | },
1440 | {
1441 | "id": "617c23a4-9743-409c-bc6b-7d8c433d6451",
1442 | "name": "rsa-enc-generated",
1443 | "providerId": "rsa-enc-generated",
1444 | "subComponents": {},
1445 | "config": {
1446 | "priority": [
1447 | "100"
1448 | ],
1449 | "algorithm": [
1450 | "RSA-OAEP"
1451 | ]
1452 | }
1453 | },
1454 | {
1455 | "id": "44eb1f2c-10c6-465c-91e6-74028c7cf25f",
1456 | "name": "rsa-generated",
1457 | "providerId": "rsa-generated",
1458 | "subComponents": {},
1459 | "config": {
1460 | "priority": [
1461 | "100"
1462 | ]
1463 | }
1464 | }
1465 | ]
1466 | },
1467 | "internationalizationEnabled": false,
1468 | "supportedLocales": [],
1469 | "authenticationFlows": [
1470 | {
1471 | "id": "5ca5df6e-e0fc-481b-b433-faecf01f5829",
1472 | "alias": "Account verification options",
1473 | "description": "Method with which to verity the existing account",
1474 | "providerId": "basic-flow",
1475 | "topLevel": false,
1476 | "builtIn": true,
1477 | "authenticationExecutions": [
1478 | {
1479 | "authenticator": "idp-email-verification",
1480 | "authenticatorFlow": false,
1481 | "requirement": "ALTERNATIVE",
1482 | "priority": 10,
1483 | "autheticatorFlow": false,
1484 | "userSetupAllowed": false
1485 | },
1486 | {
1487 | "authenticatorFlow": true,
1488 | "requirement": "ALTERNATIVE",
1489 | "priority": 20,
1490 | "autheticatorFlow": true,
1491 | "flowAlias": "Verify Existing Account by Re-authentication",
1492 | "userSetupAllowed": false
1493 | }
1494 | ]
1495 | },
1496 | {
1497 | "id": "744a6b89-3aa7-468f-9031-f166b0a79740",
1498 | "alias": "Browser - Conditional OTP",
1499 | "description": "Flow to determine if the OTP is required for the authentication",
1500 | "providerId": "basic-flow",
1501 | "topLevel": false,
1502 | "builtIn": true,
1503 | "authenticationExecutions": [
1504 | {
1505 | "authenticator": "conditional-user-configured",
1506 | "authenticatorFlow": false,
1507 | "requirement": "REQUIRED",
1508 | "priority": 10,
1509 | "autheticatorFlow": false,
1510 | "userSetupAllowed": false
1511 | },
1512 | {
1513 | "authenticator": "auth-otp-form",
1514 | "authenticatorFlow": false,
1515 | "requirement": "REQUIRED",
1516 | "priority": 20,
1517 | "autheticatorFlow": false,
1518 | "userSetupAllowed": false
1519 | }
1520 | ]
1521 | },
1522 | {
1523 | "id": "d421510e-5da1-448a-9aab-70ee18fcd724",
1524 | "alias": "Direct Grant - Conditional OTP",
1525 | "description": "Flow to determine if the OTP is required for the authentication",
1526 | "providerId": "basic-flow",
1527 | "topLevel": false,
1528 | "builtIn": true,
1529 | "authenticationExecutions": [
1530 | {
1531 | "authenticator": "conditional-user-configured",
1532 | "authenticatorFlow": false,
1533 | "requirement": "REQUIRED",
1534 | "priority": 10,
1535 | "autheticatorFlow": false,
1536 | "userSetupAllowed": false
1537 | },
1538 | {
1539 | "authenticator": "direct-grant-validate-otp",
1540 | "authenticatorFlow": false,
1541 | "requirement": "REQUIRED",
1542 | "priority": 20,
1543 | "autheticatorFlow": false,
1544 | "userSetupAllowed": false
1545 | }
1546 | ]
1547 | },
1548 | {
1549 | "id": "d323af0b-02ac-4d79-aaf9-851cc9f2b5bc",
1550 | "alias": "First broker login - Conditional OTP",
1551 | "description": "Flow to determine if the OTP is required for the authentication",
1552 | "providerId": "basic-flow",
1553 | "topLevel": false,
1554 | "builtIn": true,
1555 | "authenticationExecutions": [
1556 | {
1557 | "authenticator": "conditional-user-configured",
1558 | "authenticatorFlow": false,
1559 | "requirement": "REQUIRED",
1560 | "priority": 10,
1561 | "autheticatorFlow": false,
1562 | "userSetupAllowed": false
1563 | },
1564 | {
1565 | "authenticator": "auth-otp-form",
1566 | "authenticatorFlow": false,
1567 | "requirement": "REQUIRED",
1568 | "priority": 20,
1569 | "autheticatorFlow": false,
1570 | "userSetupAllowed": false
1571 | }
1572 | ]
1573 | },
1574 | {
1575 | "id": "34ece470-c8a9-455e-b5f6-fbebb9856bc6",
1576 | "alias": "Handle Existing Account",
1577 | "description": "Handle what to do if there is existing account with same email/username like authenticated identity provider",
1578 | "providerId": "basic-flow",
1579 | "topLevel": false,
1580 | "builtIn": true,
1581 | "authenticationExecutions": [
1582 | {
1583 | "authenticator": "idp-confirm-link",
1584 | "authenticatorFlow": false,
1585 | "requirement": "REQUIRED",
1586 | "priority": 10,
1587 | "autheticatorFlow": false,
1588 | "userSetupAllowed": false
1589 | },
1590 | {
1591 | "authenticatorFlow": true,
1592 | "requirement": "REQUIRED",
1593 | "priority": 20,
1594 | "autheticatorFlow": true,
1595 | "flowAlias": "Account verification options",
1596 | "userSetupAllowed": false
1597 | }
1598 | ]
1599 | },
1600 | {
1601 | "id": "7fa52f58-01b0-43d7-86d6-3a44f42f22d5",
1602 | "alias": "Reset - Conditional OTP",
1603 | "description": "Flow to determine if the OTP should be reset or not. Set to REQUIRED to force.",
1604 | "providerId": "basic-flow",
1605 | "topLevel": false,
1606 | "builtIn": true,
1607 | "authenticationExecutions": [
1608 | {
1609 | "authenticator": "conditional-user-configured",
1610 | "authenticatorFlow": false,
1611 | "requirement": "REQUIRED",
1612 | "priority": 10,
1613 | "autheticatorFlow": false,
1614 | "userSetupAllowed": false
1615 | },
1616 | {
1617 | "authenticator": "reset-otp",
1618 | "authenticatorFlow": false,
1619 | "requirement": "REQUIRED",
1620 | "priority": 20,
1621 | "autheticatorFlow": false,
1622 | "userSetupAllowed": false
1623 | }
1624 | ]
1625 | },
1626 | {
1627 | "id": "64ad5539-7646-4acd-9eaf-a9b5d3d34490",
1628 | "alias": "User creation or linking",
1629 | "description": "Flow for the existing/non-existing user alternatives",
1630 | "providerId": "basic-flow",
1631 | "topLevel": false,
1632 | "builtIn": true,
1633 | "authenticationExecutions": [
1634 | {
1635 | "authenticatorConfig": "create unique user config",
1636 | "authenticator": "idp-create-user-if-unique",
1637 | "authenticatorFlow": false,
1638 | "requirement": "ALTERNATIVE",
1639 | "priority": 10,
1640 | "autheticatorFlow": false,
1641 | "userSetupAllowed": false
1642 | },
1643 | {
1644 | "authenticatorFlow": true,
1645 | "requirement": "ALTERNATIVE",
1646 | "priority": 20,
1647 | "autheticatorFlow": true,
1648 | "flowAlias": "Handle Existing Account",
1649 | "userSetupAllowed": false
1650 | }
1651 | ]
1652 | },
1653 | {
1654 | "id": "8875666b-a33c-42a3-8980-91802886fc68",
1655 | "alias": "Verify Existing Account by Re-authentication",
1656 | "description": "Reauthentication of existing account",
1657 | "providerId": "basic-flow",
1658 | "topLevel": false,
1659 | "builtIn": true,
1660 | "authenticationExecutions": [
1661 | {
1662 | "authenticator": "idp-username-password-form",
1663 | "authenticatorFlow": false,
1664 | "requirement": "REQUIRED",
1665 | "priority": 10,
1666 | "autheticatorFlow": false,
1667 | "userSetupAllowed": false
1668 | },
1669 | {
1670 | "authenticatorFlow": true,
1671 | "requirement": "CONDITIONAL",
1672 | "priority": 20,
1673 | "autheticatorFlow": true,
1674 | "flowAlias": "First broker login - Conditional OTP",
1675 | "userSetupAllowed": false
1676 | }
1677 | ]
1678 | },
1679 | {
1680 | "id": "5e65a74c-544c-47f7-9299-8c18aa15431a",
1681 | "alias": "browser",
1682 | "description": "browser based authentication",
1683 | "providerId": "basic-flow",
1684 | "topLevel": true,
1685 | "builtIn": true,
1686 | "authenticationExecutions": [
1687 | {
1688 | "authenticator": "auth-cookie",
1689 | "authenticatorFlow": false,
1690 | "requirement": "ALTERNATIVE",
1691 | "priority": 10,
1692 | "autheticatorFlow": false,
1693 | "userSetupAllowed": false
1694 | },
1695 | {
1696 | "authenticator": "auth-spnego",
1697 | "authenticatorFlow": false,
1698 | "requirement": "DISABLED",
1699 | "priority": 20,
1700 | "autheticatorFlow": false,
1701 | "userSetupAllowed": false
1702 | },
1703 | {
1704 | "authenticator": "identity-provider-redirector",
1705 | "authenticatorFlow": false,
1706 | "requirement": "ALTERNATIVE",
1707 | "priority": 25,
1708 | "autheticatorFlow": false,
1709 | "userSetupAllowed": false
1710 | },
1711 | {
1712 | "authenticatorFlow": true,
1713 | "requirement": "ALTERNATIVE",
1714 | "priority": 30,
1715 | "autheticatorFlow": true,
1716 | "flowAlias": "forms",
1717 | "userSetupAllowed": false
1718 | }
1719 | ]
1720 | },
1721 | {
1722 | "id": "c0ab0739-e829-49da-8724-35be6f01a0d1",
1723 | "alias": "clients",
1724 | "description": "Base authentication for clients",
1725 | "providerId": "client-flow",
1726 | "topLevel": true,
1727 | "builtIn": true,
1728 | "authenticationExecutions": [
1729 | {
1730 | "authenticator": "client-secret",
1731 | "authenticatorFlow": false,
1732 | "requirement": "ALTERNATIVE",
1733 | "priority": 10,
1734 | "autheticatorFlow": false,
1735 | "userSetupAllowed": false
1736 | },
1737 | {
1738 | "authenticator": "client-jwt",
1739 | "authenticatorFlow": false,
1740 | "requirement": "ALTERNATIVE",
1741 | "priority": 20,
1742 | "autheticatorFlow": false,
1743 | "userSetupAllowed": false
1744 | },
1745 | {
1746 | "authenticator": "client-secret-jwt",
1747 | "authenticatorFlow": false,
1748 | "requirement": "ALTERNATIVE",
1749 | "priority": 30,
1750 | "autheticatorFlow": false,
1751 | "userSetupAllowed": false
1752 | },
1753 | {
1754 | "authenticator": "client-x509",
1755 | "authenticatorFlow": false,
1756 | "requirement": "ALTERNATIVE",
1757 | "priority": 40,
1758 | "autheticatorFlow": false,
1759 | "userSetupAllowed": false
1760 | }
1761 | ]
1762 | },
1763 | {
1764 | "id": "a9ba83e9-584e-4aab-ad3b-85a76a648916",
1765 | "alias": "direct grant",
1766 | "description": "OpenID Connect Resource Owner Grant",
1767 | "providerId": "basic-flow",
1768 | "topLevel": true,
1769 | "builtIn": true,
1770 | "authenticationExecutions": [
1771 | {
1772 | "authenticator": "direct-grant-validate-username",
1773 | "authenticatorFlow": false,
1774 | "requirement": "REQUIRED",
1775 | "priority": 10,
1776 | "autheticatorFlow": false,
1777 | "userSetupAllowed": false
1778 | },
1779 | {
1780 | "authenticator": "direct-grant-validate-password",
1781 | "authenticatorFlow": false,
1782 | "requirement": "REQUIRED",
1783 | "priority": 20,
1784 | "autheticatorFlow": false,
1785 | "userSetupAllowed": false
1786 | },
1787 | {
1788 | "authenticatorFlow": true,
1789 | "requirement": "CONDITIONAL",
1790 | "priority": 30,
1791 | "autheticatorFlow": true,
1792 | "flowAlias": "Direct Grant - Conditional OTP",
1793 | "userSetupAllowed": false
1794 | }
1795 | ]
1796 | },
1797 | {
1798 | "id": "567c7f50-7d34-495a-810b-a77cb6f1f0b5",
1799 | "alias": "docker auth",
1800 | "description": "Used by Docker clients to authenticate against the IDP",
1801 | "providerId": "basic-flow",
1802 | "topLevel": true,
1803 | "builtIn": true,
1804 | "authenticationExecutions": [
1805 | {
1806 | "authenticator": "docker-http-basic-authenticator",
1807 | "authenticatorFlow": false,
1808 | "requirement": "REQUIRED",
1809 | "priority": 10,
1810 | "autheticatorFlow": false,
1811 | "userSetupAllowed": false
1812 | }
1813 | ]
1814 | },
1815 | {
1816 | "id": "bef77f53-e990-4331-b96e-ea13e8678734",
1817 | "alias": "first broker login",
1818 | "description": "Actions taken after first broker login with identity provider account, which is not yet linked to any Keycloak account",
1819 | "providerId": "basic-flow",
1820 | "topLevel": true,
1821 | "builtIn": true,
1822 | "authenticationExecutions": [
1823 | {
1824 | "authenticatorConfig": "review profile config",
1825 | "authenticator": "idp-review-profile",
1826 | "authenticatorFlow": false,
1827 | "requirement": "REQUIRED",
1828 | "priority": 10,
1829 | "autheticatorFlow": false,
1830 | "userSetupAllowed": false
1831 | },
1832 | {
1833 | "authenticatorFlow": true,
1834 | "requirement": "REQUIRED",
1835 | "priority": 20,
1836 | "autheticatorFlow": true,
1837 | "flowAlias": "User creation or linking",
1838 | "userSetupAllowed": false
1839 | }
1840 | ]
1841 | },
1842 | {
1843 | "id": "ebaff817-19d7-4da0-9fe7-93b799363a1b",
1844 | "alias": "forms",
1845 | "description": "Username, password, otp and other auth forms.",
1846 | "providerId": "basic-flow",
1847 | "topLevel": false,
1848 | "builtIn": true,
1849 | "authenticationExecutions": [
1850 | {
1851 | "authenticator": "auth-username-password-form",
1852 | "authenticatorFlow": false,
1853 | "requirement": "REQUIRED",
1854 | "priority": 10,
1855 | "autheticatorFlow": false,
1856 | "userSetupAllowed": false
1857 | },
1858 | {
1859 | "authenticatorFlow": true,
1860 | "requirement": "CONDITIONAL",
1861 | "priority": 20,
1862 | "autheticatorFlow": true,
1863 | "flowAlias": "Browser - Conditional OTP",
1864 | "userSetupAllowed": false
1865 | }
1866 | ]
1867 | },
1868 | {
1869 | "id": "03a94dd4-1555-4ac8-93c5-d445d01e9714",
1870 | "alias": "registration",
1871 | "description": "registration flow",
1872 | "providerId": "basic-flow",
1873 | "topLevel": true,
1874 | "builtIn": true,
1875 | "authenticationExecutions": [
1876 | {
1877 | "authenticator": "registration-page-form",
1878 | "authenticatorFlow": true,
1879 | "requirement": "REQUIRED",
1880 | "priority": 10,
1881 | "autheticatorFlow": true,
1882 | "flowAlias": "registration form",
1883 | "userSetupAllowed": false
1884 | }
1885 | ]
1886 | },
1887 | {
1888 | "id": "3b5367e7-f4c6-4902-8c5e-3d5200eb7538",
1889 | "alias": "registration form",
1890 | "description": "registration form",
1891 | "providerId": "form-flow",
1892 | "topLevel": false,
1893 | "builtIn": true,
1894 | "authenticationExecutions": [
1895 | {
1896 | "authenticator": "registration-user-creation",
1897 | "authenticatorFlow": false,
1898 | "requirement": "REQUIRED",
1899 | "priority": 20,
1900 | "autheticatorFlow": false,
1901 | "userSetupAllowed": false
1902 | },
1903 | {
1904 | "authenticator": "registration-password-action",
1905 | "authenticatorFlow": false,
1906 | "requirement": "REQUIRED",
1907 | "priority": 50,
1908 | "autheticatorFlow": false,
1909 | "userSetupAllowed": false
1910 | },
1911 | {
1912 | "authenticator": "registration-recaptcha-action",
1913 | "authenticatorFlow": false,
1914 | "requirement": "DISABLED",
1915 | "priority": 60,
1916 | "autheticatorFlow": false,
1917 | "userSetupAllowed": false
1918 | }
1919 | ]
1920 | },
1921 | {
1922 | "id": "4b354399-d0b8-4f42-aa73-4785f5e97815",
1923 | "alias": "reset credentials",
1924 | "description": "Reset credentials for a user if they forgot their password or something",
1925 | "providerId": "basic-flow",
1926 | "topLevel": true,
1927 | "builtIn": true,
1928 | "authenticationExecutions": [
1929 | {
1930 | "authenticator": "reset-credentials-choose-user",
1931 | "authenticatorFlow": false,
1932 | "requirement": "REQUIRED",
1933 | "priority": 10,
1934 | "autheticatorFlow": false,
1935 | "userSetupAllowed": false
1936 | },
1937 | {
1938 | "authenticator": "reset-credential-email",
1939 | "authenticatorFlow": false,
1940 | "requirement": "REQUIRED",
1941 | "priority": 20,
1942 | "autheticatorFlow": false,
1943 | "userSetupAllowed": false
1944 | },
1945 | {
1946 | "authenticator": "reset-password",
1947 | "authenticatorFlow": false,
1948 | "requirement": "REQUIRED",
1949 | "priority": 30,
1950 | "autheticatorFlow": false,
1951 | "userSetupAllowed": false
1952 | },
1953 | {
1954 | "authenticatorFlow": true,
1955 | "requirement": "CONDITIONAL",
1956 | "priority": 40,
1957 | "autheticatorFlow": true,
1958 | "flowAlias": "Reset - Conditional OTP",
1959 | "userSetupAllowed": false
1960 | }
1961 | ]
1962 | },
1963 | {
1964 | "id": "1335f55a-5d16-4cc1-9e6f-3329a5e8514b",
1965 | "alias": "saml ecp",
1966 | "description": "SAML ECP Profile Authentication Flow",
1967 | "providerId": "basic-flow",
1968 | "topLevel": true,
1969 | "builtIn": true,
1970 | "authenticationExecutions": [
1971 | {
1972 | "authenticator": "http-basic-authenticator",
1973 | "authenticatorFlow": false,
1974 | "requirement": "REQUIRED",
1975 | "priority": 10,
1976 | "autheticatorFlow": false,
1977 | "userSetupAllowed": false
1978 | }
1979 | ]
1980 | }
1981 | ],
1982 | "authenticatorConfig": [
1983 | {
1984 | "id": "98c13d0e-e30a-4947-b4f9-5506f56108af",
1985 | "alias": "create unique user config",
1986 | "config": {
1987 | "require.password.update.after.registration": "false"
1988 | }
1989 | },
1990 | {
1991 | "id": "a6fa87cd-a9a0-48ab-baf0-b0082deb6cf6",
1992 | "alias": "review profile config",
1993 | "config": {
1994 | "update.profile.on.first.login": "missing"
1995 | }
1996 | }
1997 | ],
1998 | "requiredActions": [
1999 | {
2000 | "alias": "CONFIGURE_TOTP",
2001 | "name": "Configure OTP",
2002 | "providerId": "CONFIGURE_TOTP",
2003 | "enabled": true,
2004 | "defaultAction": false,
2005 | "priority": 10,
2006 | "config": {}
2007 | },
2008 | {
2009 | "alias": "TERMS_AND_CONDITIONS",
2010 | "name": "Terms and Conditions",
2011 | "providerId": "TERMS_AND_CONDITIONS",
2012 | "enabled": false,
2013 | "defaultAction": false,
2014 | "priority": 20,
2015 | "config": {}
2016 | },
2017 | {
2018 | "alias": "UPDATE_PASSWORD",
2019 | "name": "Update Password",
2020 | "providerId": "UPDATE_PASSWORD",
2021 | "enabled": true,
2022 | "defaultAction": false,
2023 | "priority": 30,
2024 | "config": {}
2025 | },
2026 | {
2027 | "alias": "UPDATE_PROFILE",
2028 | "name": "Update Profile",
2029 | "providerId": "UPDATE_PROFILE",
2030 | "enabled": true,
2031 | "defaultAction": false,
2032 | "priority": 40,
2033 | "config": {}
2034 | },
2035 | {
2036 | "alias": "VERIFY_EMAIL",
2037 | "name": "Verify Email",
2038 | "providerId": "VERIFY_EMAIL",
2039 | "enabled": true,
2040 | "defaultAction": false,
2041 | "priority": 50,
2042 | "config": {}
2043 | },
2044 | {
2045 | "alias": "delete_account",
2046 | "name": "Delete Account",
2047 | "providerId": "delete_account",
2048 | "enabled": false,
2049 | "defaultAction": false,
2050 | "priority": 60,
2051 | "config": {}
2052 | },
2053 | {
2054 | "alias": "webauthn-register",
2055 | "name": "Webauthn Register",
2056 | "providerId": "webauthn-register",
2057 | "enabled": true,
2058 | "defaultAction": false,
2059 | "priority": 70,
2060 | "config": {}
2061 | },
2062 | {
2063 | "alias": "webauthn-register-passwordless",
2064 | "name": "Webauthn Register Passwordless",
2065 | "providerId": "webauthn-register-passwordless",
2066 | "enabled": true,
2067 | "defaultAction": false,
2068 | "priority": 80,
2069 | "config": {}
2070 | },
2071 | {
2072 | "alias": "update_user_locale",
2073 | "name": "Update User Locale",
2074 | "providerId": "update_user_locale",
2075 | "enabled": true,
2076 | "defaultAction": false,
2077 | "priority": 1000,
2078 | "config": {}
2079 | }
2080 | ],
2081 | "browserFlow": "browser",
2082 | "registrationFlow": "registration",
2083 | "directGrantFlow": "direct grant",
2084 | "resetCredentialsFlow": "reset credentials",
2085 | "clientAuthenticationFlow": "clients",
2086 | "dockerAuthenticationFlow": "docker auth",
2087 | "attributes": {
2088 | "cibaBackchannelTokenDeliveryMode": "poll",
2089 | "cibaExpiresIn": "120",
2090 | "cibaAuthRequestedUserHint": "login_hint",
2091 | "oauth2DeviceCodeLifespan": "600",
2092 | "clientOfflineSessionMaxLifespan": "0",
2093 | "oauth2DevicePollingInterval": "5",
2094 | "clientSessionIdleTimeout": "0",
2095 | "parRequestUriLifespan": "60",
2096 | "clientSessionMaxLifespan": "0",
2097 | "clientOfflineSessionIdleTimeout": "0",
2098 | "cibaInterval": "5",
2099 | "realmReusableOtpCode": "false"
2100 | },
2101 | "keycloakVersion": "23.0.4",
2102 | "userManagedAccessAllowed": false,
2103 | "clientProfiles": {
2104 | "profiles": []
2105 | },
2106 | "clientPolicies": {
2107 | "policies": []
2108 | }
2109 | }
2110 |
--------------------------------------------------------------------------------