├── .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 | --------------------------------------------------------------------------------