================================================
FILE: .vscode/settings.json
================================================
{
"java.compile.nullAnalysis.mode": "disabled"
}
================================================
FILE: LICENSE
================================================
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
================================================
FILE: apps/account-svc/.dockerignore
================================================
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*
================================================
FILE: apps/account-svc/.gitignore
================================================
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
.flattened-pom.xml
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
# Plugin directory
/.quarkus/cli/plugins/
================================================
FILE: apps/account-svc/.mvn/wrapper/.gitignore
================================================
maven-wrapper.jar
================================================
FILE: apps/account-svc/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import java.io.IOException;
import java.io.InputStream;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
public final class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "3.2.0";
private static final boolean VERBOSE = Boolean.parseBoolean(System.getenv("MVNW_VERBOSE"));
public static void main(String[] args) {
log("Apache Maven Wrapper Downloader " + WRAPPER_VERSION);
if (args.length != 2) {
System.err.println(" - ERROR wrapperUrl or wrapperJarPath parameter missing");
System.exit(1);
}
try {
log(" - Downloader started");
final URL wrapperUrl = new URL(args[0]);
final String jarPath = args[1].replace("..", ""); // Sanitize path
final Path wrapperJarPath = Paths.get(jarPath).toAbsolutePath().normalize();
downloadFileFromURL(wrapperUrl, wrapperJarPath);
log("Done");
} catch (IOException e) {
System.err.println("- Error downloading: " + e.getMessage());
if (VERBOSE) {
e.printStackTrace();
}
System.exit(1);
}
}
private static void downloadFileFromURL(URL wrapperUrl, Path wrapperJarPath)
throws IOException {
log(" - Downloading to: " + wrapperJarPath);
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
final String username = System.getenv("MVNW_USERNAME");
final char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
try (InputStream inStream = wrapperUrl.openStream()) {
Files.copy(inStream, wrapperJarPath, StandardCopyOption.REPLACE_EXISTING);
}
log(" - Downloader complete");
}
private static void log(String msg) {
if (VERBOSE) {
System.out.println(msg);
}
}
}
================================================
FILE: apps/account-svc/.mvn/wrapper/maven-wrapper.properties
================================================
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar
================================================
FILE: apps/account-svc/README.md
================================================
# account-svc
Minimal quarkus backend for a custom remote user storage.
================================================
FILE: apps/account-svc/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Apache Maven Wrapper startup batch script, version 3.2.0
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "$(uname)" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME
else
JAVA_HOME="/Library/Java/Home"; export JAVA_HOME
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=$(java-config --jre-home)
fi
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --unix "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --unix "$CLASSPATH")
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] &&
JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="$(which javac)"
if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=$(which readlink)
if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then
if $darwin ; then
javaHome="$(dirname "\"$javaExecutable\"")"
javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac"
else
javaExecutable="$(readlink -f "\"$javaExecutable\"")"
fi
javaHome="$(dirname "\"$javaExecutable\"")"
javaHome=$(expr "$javaHome" : '\(.*\)/bin')
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=$(cd "$wdir/.." || exit 1; pwd)
fi
# end of workaround
done
printf '%s' "$(cd "$basedir" || exit 1; pwd)"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
# Remove \r in case we run on Windows within Git Bash
# and check out the repository with auto CRLF management
# enabled. Otherwise, we may read lines that are delimited with
# \r\n and produce $'-Xarg\r' rather than -Xarg due to word
# splitting rules.
tr -s '\r\n' ' ' < "$1"
fi
}
log() {
if [ "$MVNW_VERBOSE" = true ]; then
printf '%s\n' "$1"
fi
}
BASE_DIR=$(find_maven_basedir "$(dirname "$0")")
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR
log "$MAVEN_PROJECTBASEDIR"
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar"
if [ -r "$wrapperJarPath" ]; then
log "Found $wrapperJarPath"
else
log "Couldn't find $wrapperJarPath, downloading it ..."
if [ -n "$MVNW_REPOURL" ]; then
wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
else
wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
fi
while IFS="=" read -r key value; do
# 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 '=' )
safeValue=$(echo "$value" | tr -d '\r')
case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
log "Downloading from: $wrapperUrl"
if $cygwin; then
wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath")
fi
if command -v wget > /dev/null; then
log "Found wget ... using wget"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
log "Found curl ... using curl"
[ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent"
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
else
curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath"
fi
else
log "Falling back to using Java to download"
javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java"
javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaSource=$(cygpath --path --windows "$javaSource")
javaClass=$(cygpath --path --windows "$javaClass")
fi
if [ -e "$javaSource" ]; then
if [ ! -e "$javaClass" ]; then
log " - Compiling MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/javac" "$javaSource")
fi
if [ -e "$javaClass" ]; then
log " - Running MavenWrapperDownloader.java ..."
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath"
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
# If specified, validate the SHA-256 sum of the Maven wrapper jar file
wrapperSha256Sum=""
while IFS="=" read -r key value; do
case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;;
esac
done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties"
if [ -n "$wrapperSha256Sum" ]; then
wrapperSha256Result=false
if command -v sha256sum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
elif command -v shasum > /dev/null; then
if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then
wrapperSha256Result=true
fi
else
echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available."
echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties."
exit 1
fi
if [ $wrapperSha256Result = false ]; then
echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2
echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2
echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2
exit 1
fi
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME")
[ -n "$CLASSPATH" ] &&
CLASSPATH=$(cygpath --path --windows "$CLASSPATH")
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR")
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
# shellcheck disable=SC2086 # safe args
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/account-svc/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Apache Maven Wrapper startup batch script, version 3.2.0
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %WRAPPER_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file
SET WRAPPER_SHA_256_SUM=""
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B
)
IF NOT %WRAPPER_SHA_256_SUM%=="" (
powershell -Command "&{"^
"$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^
"If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^
" Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^
" Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^
" Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^
" exit 1;"^
"}"^
"}"
if ERRORLEVEL 1 goto error
)
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
================================================
FILE: apps/account-svc/pom.xml
================================================
4.0.0com.thomasdarimont.training.keycloakaccount-svc1.0-SNAPSHOT3.11.021UTF-8UTF-8quarkus-bomio.quarkus.platform3.5.3true3.1.2${quarkus.platform.group-id}${quarkus.platform.artifact-id}${quarkus.platform.version}pomimportio.quarkusquarkus-resteasyio.quarkusquarkus-resteasy-jacksonio.quarkusquarkus-arcio.quarkusquarkus-container-image-jibio.quarkusquarkus-junit5testorg.projectlomboklombok1.18.34true${quarkus.platform.group-id}quarkus-maven-plugin${quarkus.platform.version}truebuildgenerate-codegenerate-code-testsmaven-compiler-plugin${compiler-plugin.version}-parametersmaven-surefire-plugin${surefire-plugin.version}org.jboss.logmanager.LogManager${maven.home}maven-failsafe-plugin${surefire-plugin.version}integration-testverify${project.build.directory}/${project.build.finalName}-runnerorg.jboss.logmanager.LogManager${maven.home}nativenativefalsenative
================================================
FILE: apps/account-svc/src/main/docker/Dockerfile.jvm
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/account-svc-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc-jvm
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.17
ENV LANGUAGE='en_US:en'
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=185 target/quarkus-app/*.jar /deployments/
COPY --chown=185 target/quarkus-app/app/ /deployments/app/
COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
================================================
FILE: apps/account-svc/src/main/docker/Dockerfile.legacy-jar
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/account-svc-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005 being the default) like this : EXPOSE 8080 5005.
# Additionally you will have to set -e JAVA_DEBUG=true and -e JAVA_DEBUG_PORT=*:5005
# when running the container
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc-legacy-jar
#
# This image uses the `run-java.sh` script to run the application.
# This scripts computes the command line to execute your Java application, and
# includes memory/GC tuning.
# You can configure the behavior using the following environment properties:
# - JAVA_OPTS: JVM options passed to the `java` command (example: "-verbose:class")
# - JAVA_OPTS_APPEND: User specified Java options to be appended to generated options
# in JAVA_OPTS (example: "-Dsome.property=foo")
# - JAVA_MAX_MEM_RATIO: Is used when no `-Xmx` option is given in JAVA_OPTS. This is
# used to calculate a default maximal heap memory based on a containers restriction.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xmx` is set to a ratio
# of the container available memory as set here. The default is `50` which means 50%
# of the available memory is used as an upper boundary. You can skip this mechanism by
# setting this value to `0` in which case no `-Xmx` option is added.
# - JAVA_INITIAL_MEM_RATIO: Is used when no `-Xms` option is given in JAVA_OPTS. This
# is used to calculate a default initial heap memory based on the maximum heap memory.
# If used in a container without any memory constraints for the container then this
# option has no effect. If there is a memory constraint then `-Xms` is set to a ratio
# of the `-Xmx` memory as set here. The default is `25` which means 25% of the `-Xmx`
# is used as the initial heap size. You can skip this mechanism by setting this value
# to `0` in which case no `-Xms` option is added (example: "25")
# - JAVA_MAX_INITIAL_MEM: Is used when no `-Xms` option is given in JAVA_OPTS.
# This is used to calculate the maximum value of the initial heap memory. If used in
# a container without any memory constraints for the container then this option has
# no effect. If there is a memory constraint then `-Xms` is limited to the value set
# here. The default is 4096MB which means the calculated value of `-Xms` never will
# be greater than 4096MB. The value of this variable is expressed in MB (example: "4096")
# - JAVA_DIAGNOSTICS: Set this to get some diagnostics information to standard output
# when things are happening. This option, if set to true, will set
# `-XX:+UnlockDiagnosticVMOptions`. Disabled by default (example: "true").
# - JAVA_DEBUG: If set remote debugging will be switched on. Disabled by default (example:
# true").
# - JAVA_DEBUG_PORT: Port used for remote debugging. Defaults to 5005 (example: "8787").
# - CONTAINER_CORE_LIMIT: A calculated core limit as described in
# https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt. (example: "2")
# - CONTAINER_MAX_MEMORY: Memory limit given to the container (example: "1024").
# - GC_MIN_HEAP_FREE_RATIO: Minimum percentage of heap free after GC to avoid expansion.
# (example: "20")
# - GC_MAX_HEAP_FREE_RATIO: Maximum percentage of heap free after GC to avoid shrinking.
# (example: "40")
# - GC_TIME_RATIO: Specifies the ratio of the time spent outside the garbage collection.
# (example: "4")
# - GC_ADAPTIVE_SIZE_POLICY_WEIGHT: The weighting given to the current GC time versus
# previous GC times. (example: "90")
# - GC_METASPACE_SIZE: The initial metaspace size. (example: "20")
# - GC_MAX_METASPACE_SIZE: The maximum metaspace size. (example: "100")
# - GC_CONTAINER_OPTIONS: Specify Java GC to use. The value of this variable should
# contain the necessary JRE command-line options to specify the required GC, which
# will override the default of `-XX:+UseParallelGC` (example: -XX:+UseG1GC).
# - HTTPS_PROXY: The location of the https proxy. (example: "myuser@127.0.0.1:8080")
# - HTTP_PROXY: The location of the http proxy. (example: "myuser@127.0.0.1:8080")
# - NO_PROXY: A comma separated lists of hosts, IP addresses or domains that can be
# accessed directly. (example: "foo.example.com,bar.example.com")
#
###
FROM registry.access.redhat.com/ubi8/openjdk-17:1.17
ENV LANGUAGE='en_US:en'
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/quarkus-run.jar
EXPOSE 8080
USER 185
ENV JAVA_OPTS_APPEND="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
ENV JAVA_APP_JAR="/deployments/quarkus-run.jar"
ENTRYPOINT [ "/opt/jboss/container/java/run/run-java.sh" ]
================================================
FILE: apps/account-svc/src/main/docker/Dockerfile.native
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/account-svc .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.8
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
================================================
FILE: apps/account-svc/src/main/docker/Dockerfile.native-micro
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode.
# It uses a micro base image, tuned for Quarkus native executables.
# It reduces the size of the resulting container image.
# Check https://quarkus.io/guides/quarkus-runtime-base-image for further information about this image.
#
# Before building the container image run:
#
# ./mvnw package -Dnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-micro -t quarkus/account-svc .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/account-svc
#
###
FROM quay.io/quarkus/quarkus-micro-image:2.0
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
ENTRYPOINT ["./application", "-Dquarkus.http.host=0.0.0.0"]
================================================
FILE: apps/account-svc/src/main/java/com/thomasdarimont/keycloak/training/accounts/User.java
================================================
package com.thomasdarimont.keycloak.training.accounts;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
@Data
@NoArgsConstructor
public class User implements Cloneable {
private String id;
private String username;
private String email;
private boolean emailVerified;
private String firstname;
private String lastname;
private String password;
private boolean enabled;
private long created;
private List roles;
public User(String id, String username, String password, String email, boolean emailVerified, String firstname, String lastname, boolean enabled, List roles) {
this.id = id;
this.username = username;
this.email = email;
this.emailVerified = emailVerified;
this.firstname = firstname;
this.lastname = lastname;
this.password = password;
this.enabled = enabled;
this.created = System.currentTimeMillis();
this.roles = roles;
}
@JsonIgnore
public String getPassword() {
return password;
}
}
================================================
FILE: apps/account-svc/src/main/java/com/thomasdarimont/keycloak/training/accounts/UserRepository.java
================================================
package com.thomasdarimont.keycloak.training.accounts;
import com.thomasdarimont.keycloak.training.accounts.UserResource.UserSearchInput;
import jakarta.inject.Singleton;
import lombok.extern.jbosslog.JBossLog;
import java.util.ArrayList;
import java.util.EnumSet;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
import java.util.stream.Stream;
@Singleton
@JBossLog
class UserRepository {
private static final List USERS = new ArrayList<>();
public UserRepository() {
USERS.add(newUser("1", "bugs", "password", "bugs.bunny@acme.com", true, "Bugs", "Bunny", true, List.of("staff")));
USERS.add(newUser("2", "daffy", "password", "duffy.duck@acme.com", true, "Duffy", "Duck", true, List.of("staff")));
USERS.add(newUser("3", "porky", "password", "porky.pig@acme.com", false, "Porky", "Pig", false, List.of("staff")));
USERS.add(newUser("4", "taz", "password", "taz.devil@acme.com", false, "Taz", "Devil", true, List.of("staff")));
USERS.add(newUser("5", "sylvester", "password", "sylvester.cat@acme.com",false, "Sylvester", "Cat", false, List.of("staff")));
USERS.add(newUser("6", "marvin", "password", "marvin.martian@acme.com", false, "Marvin", "Martian", false, List.of("staff")));
USERS.add(newUser("7", "wile", "password", "wile.e.coyote@acme.com", false, "Wile", "Coyote", false, null));
}
private static User newUser(String idSeed, String username, String password, String email, boolean emailVerified, String firstname, String lastname, boolean enabled, List roles) {
return new User(UUID.nameUUIDFromBytes(idSeed.getBytes()).toString(), username, password, email, emailVerified, firstname, lastname, enabled, roles);
}
public List findAll() {
log.info("findAll");
return USERS;
}
public int count() {
log.info("count");
return USERS.size();
}
public User findById(String id) {
log.infof("findById id=%s", id);
return USERS.stream().filter(user -> user.getId().equalsIgnoreCase(id)).findFirst().orElse(null);
}
public User findByUsernameOrEmail(String username) {
log.infof("findByUsernameOrEmail username=%s", username);
return getByUsername(username).or(() -> getByEmail(username)).orElse(null);
}
public Optional getByUsername(String username) {
log.infof("getByUsername username=%s", username);
return USERS.stream().filter(user -> user.getUsername().equalsIgnoreCase(username)).findFirst();
}
public Optional getByEmail(String email) {
log.infof("getByEmail email=%s", email);
return USERS.stream().filter(user -> user.getEmail().equalsIgnoreCase(email)).findFirst();
}
List findUsers(String query) {
log.infof("findUsers query=%s", query);
return USERS.stream().filter(user -> query.equalsIgnoreCase("*") || user.getUsername().contains(query) || user.getEmail().contains(query)).toList();
}
public boolean validatePassword(String id, String password) {
log.infof("validatePassword id=%s password=%s", id, password);
return findById(id).getPassword().equals(password);
}
public boolean updatePassword(String id, String password) {
log.infof("updatePassword id=%s password=%s", id, password);
findById(id).setPassword(password);
return true;
}
public void createUser(User user) {
log.infof("createUser user=%s", user.toString());
user.setId(UUID.randomUUID().toString());
user.setCreated(System.currentTimeMillis());
USERS.add(user);
}
public void updateUser(User user) {
log.infof("updateUser user=%s", user.toString());
User existing = findByUsernameOrEmail(user.getUsername());
existing.setEmail(user.getEmail());
existing.setFirstname(user.getFirstname());
existing.setLastname(user.getLastname());
existing.setEnabled(user.isEnabled());
}
public boolean removeUser(String id) {
log.infof("removeUser id=%s", id);
return USERS.removeIf(p -> p.getId().equals(id));
}
public List search(String search, Integer firstResult, Integer maxResults, EnumSet options) {
log.infof("search search=%s firstResult=%s maxResults=%s options=%s", search, firstResult, maxResults, options);
return searchInternal(search, firstResult, maxResults, options).toList();
}
public int searchForCount(String search, Integer firstResult, Integer maxResults, EnumSet options) {
log.infof("searchForCount search=%s firstResult=%s maxResults=%s options=%s", search, firstResult, maxResults, options);
return (int) searchInternal(search, firstResult, maxResults, options).count();
}
private static Stream searchInternal(String search, Integer firstResult, Integer maxResults, EnumSet options) {
if (search == null) {
return Stream.empty();
}
var exact = search.startsWith("'") && search.endsWith("'");
var exactSearch = exact ? search.substring(1, search.length() - 1) : search;
final String searchMode;
if (exact) {
searchMode = "exact";
} else if (search.trim().equals("*")) {
searchMode = "wildcard";
} else {
searchMode = "contains";
}
log.infof("searchInternal search=%s firstResult=%s maxResults=%s options=%s searchMode=%s", search, firstResult, maxResults, options, searchMode);
Stream stream = USERS.stream() //
.filter(u -> switch (searchMode) {
case "exact" -> u.getUsername().equals(exactSearch) || u.getEmail().equals(exactSearch);
case "contains" -> u.getUsername().contains(search) || u.getEmail().contains(search);
default /* wildcard / null */ -> true;
}) //
.filter(user -> !user.getUsername().startsWith("service-account-") || options.contains(UserSearchInput.UserSearchOption.INCLUDE_SERVICE_ACCOUNTS)) //
;
if (firstResult != null) {
stream = stream.skip(firstResult);
}
if (maxResults != null) {
stream = stream.limit(maxResults);
}
return stream;
}
}
================================================
FILE: apps/account-svc/src/main/java/com/thomasdarimont/keycloak/training/accounts/UserResource.java
================================================
package com.thomasdarimont.keycloak.training.accounts;
import jakarta.ws.rs.Consumes;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.POST;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.jbosslog.JBossLog;
import java.util.EnumSet;
import java.util.List;
@JBossLog
@Path("/api/users")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
@RequiredArgsConstructor
public class UserResource {
private final UserRepository users;
@GET
public List userList() {
return users.findAll();
}
@GET
@Path("{userId}")
public User userById(@PathParam("userId") String userId) {
return users.findById(userId);
}
@POST
@Path("/lookup/username")
public User lookupUserByUsername(UserLookupInput search) {
return users.getByUsername(search.getUsername()).orElse(null);
}
@POST
@Path("/lookup/email")
public User lookupUserByEmail(UserLookupInput search) {
return users.getByEmail(search.getEmail()).orElse(null);
}
@POST
@Path("/{userId}/credentials/verify")
public VerifyCredentialsOutput verifyCredentials(@PathParam("userId") String userId, VerifyCredentialsInput input) {
VerifyCredentialsOutput output = verify(userId, input);
log.infof("verifyCredentials output=%s", output);
return output;
}
@POST
@Path("/search")
public UserSearchOutput searchUsers(UserSearchInput search) {
if (search.getOptions().contains(UserSearchInput.UserSearchOption.COUNT_ONLY)) {
int count = users.searchForCount(search.getSearch(), search.getFirstResult(), search.getMaxResults(), search.getOptions());
return new UserSearchOutput(null, count);
}
var output = users.search(search.getSearch(), search.getFirstResult(), search.getMaxResults(), search.getOptions());
log.infof("searchUsers output=%s", output);
return new UserSearchOutput(output, output.size());
}
private VerifyCredentialsOutput verify(String userId, VerifyCredentialsInput input) {
return new VerifyCredentialsOutput(users.validatePassword(userId, input.getPassword()));
}
@Data
public static class UserLookupInput {
String username;
String email;
}
@Data
public static class UserSearchInput {
private String search;
private Integer firstResult;
private Integer maxResults;
private EnumSet options;
public enum UserSearchOption {
COUNT_ONLY, //
INCLUDE_SERVICE_ACCOUNTS, //
}
}
@Data
public static class UserSearchOutput {
private final List users;
private final int count;
}
@Data
public static class VerifyCredentialsInput {
String password;
}
@Data
public static class VerifyCredentialsOutput {
private final boolean valid;
}
}
================================================
FILE: apps/account-svc/src/main/resources/application.properties
================================================
quarkus.container-image.build=true
quarkus.container-image.group=training
quarkus.container-image.name=account-svc
quarkus.container-image.tag=latest
quarkus.jib.ports=7070
quarkus.http.port=7070
quarkus.http.host=0.0.0.0
================================================
FILE: apps/acme-account-console/index.html
================================================
Acme Account Console
================================================
FILE: apps/acme-webapp-saml-node-express/views/partials/head.ejs
================================================
Acme Webapp with SAML and NodeJS Express
================================================
FILE: apps/acme-webapp-saml-node-express/views/partials/header.ejs
================================================
================================================
FILE: apps/backend-api-dnc/api/.dockerignore
================================================
**/.dockerignore
**/.env
**/.git
**/.gitignore
**/.project
**/.settings
**/.toolstarget
**/.vs
**/.vscode
**/.idea
**/*.*proj.user
**/*.dbmdl
**/*.jfm
**/azds.yaml
**/bin
**/charts
**/docker-compose*
**/Dockerfile*
**/node_modules
**/npm-debug.log
**/obj
**/secrets.dev.yaml
**/values.dev.yaml
LICENSE
README.md
================================================
FILE: apps/backend-api-dnc/api/.gitignore
================================================
# Visual Studio
bin/
obj/
.vs/
*.user
================================================
FILE: apps/backend-api-dnc/api/Controllers/UsersController.cs
================================================
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Api.Controllers;
[ApiController]
[Route("/api/users")]
public class UsersController
{
private readonly ILogger _logger;
private readonly IHttpContextAccessor _accessor;
public UsersController(ILogger logger, IHttpContextAccessor accessor)
{
_logger = logger;
_accessor = accessor;
}
[Authorize]
[HttpGet]
[Route("me")]
public object Me() {
_logger.LogInformation("### Accessing {}", _accessor.HttpContext?.Request.Path.Value);
// var username = _accessor.HttpContext?.User.FindFirst("preferred_username")?.Value;
var username = _accessor.HttpContext?.User?.Identity?.Name;
var data = new Dictionary
{
{ "message", "Hello " + username },
{ "backend", "AspNetCore" },
{ "datetime", DateTime.Now }
};
return data;
}
}
================================================
FILE: apps/backend-api-dnc/api/Dockerfile
================================================
FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base
WORKDIR /app
EXPOSE 80
EXPOSE 443
FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build
WORKDIR /src
COPY ["api/api.csproj", "api/"]
RUN dotnet restore "api/api.csproj"
COPY . .
WORKDIR "/src/api"
RUN dotnet build "api.csproj" -c Release -o /app/build
FROM build AS publish
RUN dotnet publish "api.csproj" -c Release -o /app/publish
FROM base AS final
WORKDIR /app
COPY --from=publish /app/publish .
ENTRYPOINT ["dotnet", "api.dll"]
================================================
FILE: apps/backend-api-dnc/api/JwtBearerOptions.cs
================================================
namespace Api;
///
/// Options for JWT Bearer authentication.
///
public class JwtBearerOptions
{
///
/// Gets or sets the authority.
///
///
/// The authority.
///
public string Authority { get; set; } = String.Empty;
///
/// Gets or sets the audience.
///
///
/// The audience.
///
public string Audience { get; set; } = String.Empty;
}
================================================
FILE: apps/backend-api-dnc/api/Program.cs
================================================
using Microsoft.AspNetCore.Authentication.JwtBearer;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddLogging(config =>
{
config.AddDebug();
config.AddConsole();
//etc
});
builder.Services.AddControllers();
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
// builder.Services.AddEndpointsApiExplorer();
// builder.Services.AddSwaggerGen();
builder.Services.AddCors(options => options.AddDefaultPolicy(b =>
{
b.AllowAnyOrigin()
.AllowAnyMethod()
.AllowAnyHeader();
}));
builder.Services.AddSingleton();
var jwtOptions = builder.Configuration.GetSection("JwtBearer").Get();
builder.Services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
options.Authority = jwtOptions.Authority;
options.Audience = jwtOptions.Audience;
options.RequireHttpsMetadata = false;
options.TokenValidationParameters.NameClaimType = "preferred_username";
options.TokenValidationParameters.RoleClaimType = "role";
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
// app.UseSwagger();
// app.UseSwaggerUI();
}
app.UseCors();
app.UseHttpsRedirection();
app.UseAuthentication();
app.UseAuthorization();
app.MapControllers();
app.Run();
================================================
FILE: apps/backend-api-dnc/api/Properties/launchSettings.json
================================================
{
"$schema": "https://json.schemastore.org/launchsettings.json",
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:43687",
"sslPort": 44332
}
},
"profiles": {
"api": {
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"launchUrl": "swagger",
"applicationUrl": "https://localhost:7229;http://localhost:5178",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development",
"ASPNETCORE_Kestrel__Certificates__Default__Path": "../../../config/stage/dev/tls/acme.test+1.pem",
"ASPNETCORE_Kestrel__Certificates__Default__KeyPath": "../../../config/stage/dev/tls/acme.test+1-key.pem"
},
"workingDirectory": "."
},
"IIS Express": {
"commandName": "IISExpress",
"launchBrowser": false,
"launchUrl": "swagger",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
}
}
}
================================================
FILE: apps/backend-api-dnc/api/api.csproj
================================================
net6.0enableenableLinux
================================================
FILE: apps/backend-api-dnc/api/appsettings.Development.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"JwtBearer": {
"Authority": "https://id.acme.test:8443/auth/realms/acme-internal",
"Audience": "account"
},
"Kestrel": {
"HttpsInlineCertAndKeyFile": {
"Url": "https://apps.acme.test:7229",
"Certificate": {
"Path": "../../../config/stage/dev/tls/acme.test+1.pem",
"KeyPath": "../../../config/stage/dev/tls/acme.test+1-key.pem",
"Password": ""
}
}
}
}
================================================
FILE: apps/backend-api-dnc/api/appsettings.json
================================================
{
"Logging": {
"LogLevel": {
"Default": "Information",
"Microsoft.AspNetCore": "Warning"
}
},
"AllowedHosts": "*",
"Kestrel": {
"HttpsInlineCertAndKeyFile": {
"Url": "https://apps.acme.test:7229",
"Certificate": {
"Path": "../../../config/stage/dev/tls/acme.test+1.pem",
"KeyPath": "../../../config/stage/dev/tls/acme.test+1-key.pem",
"Password": ""
}
}
}
}
================================================
FILE: apps/backend-api-dnc/backend-api-dnc.sln
================================================
Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "api", "api\api.csproj", "{DF1D196F-6305-4ADD-A02E-84CAF13F59C7}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{DF1D196F-6305-4ADD-A02E-84CAF13F59C7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{DF1D196F-6305-4ADD-A02E-84CAF13F59C7}.Debug|Any CPU.Build.0 = Debug|Any CPU
{DF1D196F-6305-4ADD-A02E-84CAF13F59C7}.Release|Any CPU.ActiveCfg = Release|Any CPU
{DF1D196F-6305-4ADD-A02E-84CAF13F59C7}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
================================================
FILE: apps/backend-api-micronaut/.gitignore
================================================
Thumbs.db
.DS_Store
.gradle
build/
target/
out/
.idea
*.iml
*.ipr
*.iws
.project
.settings
.classpath
.factorypath
================================================
FILE: apps/backend-api-micronaut/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.net.Authenticator;
import java.net.PasswordAuthentication;
import java.net.URL;
import java.nio.channels.Channels;
import java.nio.channels.ReadableByteChannel;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
================================================
FILE: apps/backend-api-micronaut/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
================================================
FILE: apps/backend-api-micronaut/README.md
================================================
## Micronaut 3.3.4 Documentation
- [User Guide](https://docs.micronaut.io/3.3.4/guide/index.html)
- [API Reference](https://docs.micronaut.io/3.3.4/api/index.html)
- [Configuration Reference](https://docs.micronaut.io/3.3.4/guide/configurationreference.html)
- [Micronaut Guides](https://guides.micronaut.io/index.html)
---
## Feature http-client documentation
- [Micronaut HTTP Client documentation](https://docs.micronaut.io/latest/guide/index.html#httpClient)
## Feature security-jwt documentation
- [Micronaut Security JWT documentation](https://micronaut-projects.github.io/micronaut-security/latest/guide/index.html)
================================================
FILE: apps/backend-api-micronaut/micronaut-cli.yml
================================================
applicationType: default
defaultPackage: com.acme.backend.micronaut
testFramework: junit
sourceLanguage: java
buildTool: maven
features: [annotation-api, app-name, http-client, jackson-databind, java, java-application, junit, logback, maven, netty-server, readme, security-annotations, security-jwt, shade, yaml]
================================================
FILE: apps/backend-api-micronaut/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/backend-api-micronaut/mvnw.bat
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: apps/backend-api-micronaut/pom.xml
================================================
4.0.0com.acme.backend.micronautbackend-api-micronaut0.1${packaging}io.micronautmicronaut-parent3.4.0jar1111${project.parent.version}com.acme.backend.micronaut.Applicationnettycentralhttps://repo.maven.apache.org/maven2io.micronautmicronaut-injectcompileio.micronautmicronaut-validationcompileorg.junit.jupiterjunit-jupiter-apitestorg.junit.jupiterjunit-jupiter-enginetestio.micronaut.testmicronaut-test-junit5testio.micronautmicronaut-http-clientcompileio.micronautmicronaut-http-server-nettycompileio.micronautmicronaut-jackson-databindcompileio.micronautmicronaut-runtimecompileio.micronaut.securitymicronaut-security-jwtcompilejakarta.annotationjakarta.annotation-apicompilech.qos.logbacklogback-classicruntimeio.micronaut.buildmicronaut-maven-pluginorg.apache.maven.pluginsmaven-compiler-pluginio.micronautmicronaut-http-validation${micronaut.version}io.micronaut.securitymicronaut-security-annotations${micronaut.security.version}-Amicronaut.processing.group=com.acme.backend.micronaut-Amicronaut.processing.module=backend-api-micronaut
================================================
FILE: apps/backend-api-micronaut/src/main/java/com/acme/backend/micronaut/Application.java
================================================
package com.acme.backend.micronaut;
import io.micronaut.runtime.Micronaut;
public class Application {
public static void main(String[] args) {
Micronaut.run(Application.class, args);
}
}
================================================
FILE: apps/backend-api-micronaut/src/main/java/com/acme/backend/micronaut/api/UsersResource.java
================================================
package com.acme.backend.micronaut.api;
import io.micronaut.http.HttpRequest;
import io.micronaut.http.annotation.Controller;
import io.micronaut.http.annotation.Get;
import io.micronaut.security.annotation.Secured;
import io.micronaut.security.authentication.Authentication;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
import static io.micronaut.security.rules.SecurityRule.IS_AUTHENTICATED;
@Secured(IS_AUTHENTICATED)
@Controller("/api/users")
class UsersResource {
private static final Logger log = LoggerFactory.getLogger(UsersResource.class);
@Get("/me")
public Object me(HttpRequest> request, Authentication authentication) {
log.info("### Accessing {}", request.getUri());
Object username = authentication.getName();
Map data = new HashMap<>();
data.put("message", "Hello " + username);
data.put("backend", "Micronaut");
data.put("datetime", Instant.now());
return data;
}
}
================================================
FILE: apps/backend-api-micronaut/src/main/resources/application.yml
================================================
micronaut:
application:
name: backendApiMicronaut
ssl:
enabled: true
keyStore:
path: file:config/stage/dev/tls/acme.test+1.p12 # (1)
password: changeit # (2)
type: PKCS12
security:
authentication: bearer
token:
name-key: "preferred_username"
jwt:
signatures:
jwks:
keycloak:
url: "${micronaut.security.token.jwt.claims-validators.issuer}/protocol/openid-connect/certs"
claims-validators:
issuer: "https://id.acme.test:8443/auth/realms/acme-internal"
expiration: true
subject-not-null: true
# audience: ""
server:
ssl:
port: 4953
cors:
enabled: true
configurations:
web:
allowedOrigins:
- https://apps.acme.test:4443
netty:
default:
allocator:
max-order: 3
================================================
FILE: apps/backend-api-micronaut/src/main/resources/logback.xml
================================================
true%cyan(%d{HH:mm:ss.SSS}) %gray([%thread]) %highlight(%-5level) %magenta(%logger{36}) - %msg%n
================================================
FILE: apps/backend-api-node-express/package.json
================================================
{
"name": "backend-api-node-express",
"version": "1.0.0",
"main": "src/index.js",
"author": "Thomas Darimont",
"license": "MIT",
"dependencies": {
"cors": "^2.8.5",
"es6-promisify": "^7.0.0",
"express": "^4.17.1",
"express-jwt": "^6.1.0",
"https": "^1.0.0",
"jwks-rsa": "^2.0.4",
"spdy": "^4.0.2",
"stoppable": "^1.1.0",
"winston": "^3.3.3"
},
"type": "module",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"devDependencies": {
"nodemon": "^2.0.12"
}
}
================================================
FILE: apps/backend-api-node-express/readme.md
================================================
Acme Backend API Node Express
---
# Setup
Add rootCA for self-signed certificates - required for fetching public keys from JWKS endpoint in Keycloak.
```
export NODE_EXTRA_CA_CERTS=$(mkcert -CAROOT)/rootCA.pem
```
# Build
```
yarn install
```
# Run
```
yarn run start
```
================================================
FILE: apps/backend-api-node-express/src/api.js
================================================
/**
* Initializes the API endpoints
* @param app
* @param LOG
*/
function createApiEndpoints(app, config, LOG) {
LOG.info('Create API endpoints');
// API routes can then access JWT claims in the request object via request.user
app.get('/api/users/me', (req, res) => {
let username = req.user.preferred_username;
LOG.info(`### Accessing ${req.path}`);
const data = {
datetime: new Date().toISOString(),
message: `Hello ${username}`,
backend: 'NodeJS Express',
};
res.status(200).send(JSON.stringify(data));
});
}
export default createApiEndpoints;
================================================
FILE: apps/backend-api-node-express/src/config.js
================================================
const ISSUER = process.env.ISSUER || "https://id.acme.test:8443/auth/realms/acme-internal";
const PORT = process.env.PORT || 4743;
const CORS_ALLOWED_ORIGINS = process.env.CORS_ALLOWED_ORIGINS || 'https://apps.acme.test:4443'; // * or https://domain1:4443,https://domain2:4443
const CORS_ALLOWED_METHODS = process.env.CORS_ALLOWED_METHODS || 'GET'; // or GET,POST,PUT
const TLS_CERT = process.env.TLS_CERT || '../../config/stage/dev/tls/acme.test+1.pem';
const TLS_KEY = process.env.TLS_KEY || '../../config/stage/dev/tls/acme.test+1-key.pem';
const LOG_LEVEL = process.env.LOG_LEVEL || 'info';
const LOG_FORMAT = process.env.LOG_FORMAT || 'json'; // plain / json
// see https://github.com/RisingStack/kubernetes-graceful-shutdown-example/blob/master/src/index.js
const READINESS_PROBE_DELAY = process.env.READINESS_PROBE_DELAY || 1000; // 2 * 2 * 1000; // failureThreshold: 2, periodSeconds: 2 (4s)
export default {
ISSUER,
PORT,
CORS_ALLOWED_METHODS,
CORS_ALLOWED_ORIGINS,
TLS_CERT,
TLS_KEY,
LOG_LEVEL,
LOG_FORMAT,
READINESS_PROBE_DELAY,
};
================================================
FILE: apps/backend-api-node-express/src/express.js
================================================
import express from "express";
import cors from "cors";
import jwksRsa from "jwks-rsa";
import jwt from "express-jwt";
function createExpressApp(config, LOG) {
LOG.info("Create express app");
const app = express();
configureCors(app, config, LOG);
configureJwtAuthorization(app, config, LOG);
return app;
}
function configureCors(app, config, LOG) {
LOG.info("Configure CORS");
const corsOptions = {
origin: config.CORS_ALLOWED_ORIGINS.split(","),
methods: config.CORS_ALLOWED_METHODS.split(","),
optionsSuccessStatus: 200 // For legacy browser support
};
app.use(cors(corsOptions));
}
function configureJwtAuthorization(app, config, LOG) {
LOG.info("Configure JWT Authorization");
// JWT Bearer Authorization
let jwtOptions = {
// Dynamically provide a signing key based on the kid in the header and the signing keys provided by the JWKS endpoint.
secret: jwksRsa.expressJwtSecret({
cache: true,
rateLimit: true,
jwksRequestsPerMinute: 5,
jwksUri: `${config.ISSUER}/protocol/openid-connect/certs`,
handleSigningKeyError: (err, cb) => {
if (err instanceof jwksRsa.SigningKeyNotFoundError) {
return cb(new Error('Could not fetch certs from JWKS endpoint.'));
}
return cb(err);
}
}),
// Validate the audience.
// audience: 'urn:my-resource-server',
// Validate the issuer.
issuer: config.ISSUER,
algorithms: ['RS256']
};
app.use('/api/*', jwt(jwtOptions));
}
export default createExpressApp;
================================================
FILE: apps/backend-api-node-express/src/index.js
================================================
'use strict'
import config from './config.js';
import initLogging from './logging.js';
import createExpressApp from './express.js';
import createApiEndpoints from './api.js';
import createServer from "./server.js";
const LOG = initLogging(config);
const app = createExpressApp(config, LOG);
createApiEndpoints(app, config, LOG);
createServer(app, config, LOG);
================================================
FILE: apps/backend-api-node-express/src/logging.js
================================================
import winston from "winston";
function initLogging(config) {
const loggingFormat = winston.format.combine(
winston.format.timestamp(),
'json' === config.LOG_FORMAT
? winston.format.json()
: winston.format.simple()
);
return winston.createLogger({
level: config.LOG_LEVEL,
format: loggingFormat,
defaultMeta: {service: 'acme-backend-api'},
transports: [
new winston.transports.Console(),
//
// - Write all logs with level `error` and below to `error.log`
// - Write all logs with level `info` and below to `combined.log`
//
// new winston.transports.File({ filename: 'error.log', level: 'error' }),
// new winston.transports.File({ filename: 'combined.log' }),
],
});
}
export default initLogging;
================================================
FILE: apps/backend-api-node-express/src/server.js
================================================
import fs from "fs";
import stoppable from "stoppable";
import {promisify} from "es6-promisify";
import spdy from "spdy";
function createServer(app, config, LOG) {
LOG.info("Create server");
const httpsServer = spdy.createServer({
key: fs.readFileSync(config.TLS_KEY),
cert: fs.readFileSync(config.TLS_CERT),
}, app);
// for Graceful shutdown see https://github.com/RisingStack/kubernetes-graceful-shutdown-example
configureGracefulShutdown(httpsServer, config, LOG);
// Start server
httpsServer.listen(config.PORT, () => {
LOG.info(`Listening on HTTPS port ${config.PORT}`);
});
}
function configureGracefulShutdown(httpsServer, config, LOG) {
// Keep-alive connections doesn't let the server to close in time
// Destroy extension helps to force close connections
// Because we wait READINESS_PROBE_DELAY, we expect that all requests are fulfilled
// https://en.wikipedia.org/wiki/HTTP_persistent_connection
stoppable(httpsServer);
const serverDestroy = promisify(httpsServer.stop.bind(httpsServer));
// Graceful stop
async function gracefulStop() {
LOG.info('Server is shutting down...')
try {
await serverDestroy(); // close server first (ongoing requests)
LOG.info('Successful graceful shutdown');
process.exit(0); // exit with ok code
} catch (err) {
LOG.error('Error happened during graceful shutdown', err)
process.exit(1) // exit with not ok code
}
}
// Support graceful shutdown
// do not accept more request and release resources
process.on('SIGTERM', () => {
LOG.info('Got SIGTERM. Graceful shutdown start');
// Wait a little bit to give enough time for Kubernetes readiness probe to fail (we don't want more traffic)
// Don't worry livenessProbe won't kill it until (failureThreshold: 3) => 30s
// http://www.bite-code.com/2015/07/27/implementing-graceful-shutdown-for-docker-containers-in-go-part-2/
setTimeout(gracefulStop, config.READINESS_PROBE_DELAY);
});
}
export default createServer;
================================================
FILE: apps/backend-api-quarkus/.dockerignore
================================================
*
!target/*-runner
!target/*-runner.jar
!target/lib/*
!target/quarkus-app/*
================================================
FILE: apps/backend-api-quarkus/.gitignore
================================================
#Maven
target/
pom.xml.tag
pom.xml.releaseBackup
pom.xml.versionsBackup
release.properties
# Eclipse
.project
.classpath
.settings/
bin/
# IntelliJ
.idea
*.ipr
*.iml
*.iws
# NetBeans
nb-configuration.xml
# Visual Studio Code
.vscode
.factorypath
# OSX
.DS_Store
# Vim
*.swp
*.swo
# patch
*.orig
*.rej
# Local environment
.env
================================================
FILE: apps/backend-api-quarkus/README.md
================================================
backend-api-quarkus project
---
Simple backend API example that can be access with an Access-Token from the oidc-js-spa application.
This project uses Quarkus, the Supersonic Subatomic Java Framework.
If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ .
## Running the application in dev mode
You can run your application in dev mode that enables live coding using:
```shell script
mvn compile quarkus:dev
```
> **_NOTE:_** Quarkus now ships with a Dev UI, which is available in dev mode only at http://localhost:8080/q/dev/.
## Packaging and running the application
The application can be packaged using:
```shell script
mvn package
```
It produces the `quarkus-run.jar` file in the `target/quarkus-app/` directory. Be aware that it’s not an _über-jar_ as
the dependencies are copied into the `target/quarkus-app/lib/` directory.
If you want to build an _über-jar_, execute the following command:
```shell script
mvn package -Dquarkus.package.type=uber-jar
```
The application is now runnable using `java -jar target/quarkus-app/quarkus-run.jar`.
## Creating a native executable
You can create a native executable using:
```shell script
mvn package -Pnative
```
Or, if you don't have GraalVM installed, you can run the native executable build in a container using:
```shell script
mvn package -Pnative -Dquarkus.native.container-build=true
```
You can then execute your native executable with: `./target/backend-api-quarkus-1.0-SNAPSHOT-runner`
If you want to learn more about building native executables, please consult https://quarkus.io/guides/maven-tooling.html
.
## Related guides
- RESTEasy JAX-RS ([guide](https://quarkus.io/guides/rest-json)): REST endpoint framework implementing JAX-RS and more
## Provided examples
### RESTEasy JAX-RS example
REST is easy peasy with this Hello World RESTEasy resource.
[Related guide section...](https://quarkus.io/guides/getting-started#the-jax-rs-resources)
================================================
FILE: apps/backend-api-quarkus/pom.xml
================================================
4.0.0com.github.thomasdarimont.appsbackend-api-quarkus1.0-SNAPSHOTAcme Backend API Quarkus3.11.017UTF-8UTF-8quarkus-bomio.quarkus.platform3.8.3true3.1.2${quarkus.platform.group-id}${quarkus.platform.artifact-id}${quarkus.platform.version}pomimportio.quarkusquarkus-resteasyio.quarkusquarkus-resteasy-jacksonio.quarkusquarkus-smallrye-jwtio.quarkusquarkus-arcio.quarkusquarkus-junit5testio.rest-assuredrest-assuredtest${quarkus.platform.group-id}quarkus-maven-plugin${quarkus.platform.version}truebuildgenerate-codegenerate-code-testsmaven-compiler-plugin${compiler-plugin.version}-parametersmaven-surefire-plugin${surefire-plugin.version}org.jboss.logmanager.LogManager${maven.home}maven-failsafe-plugin${surefire-plugin.version}integration-testverify${project.build.directory}/${project.build.finalName}-runnerorg.jboss.logmanager.LogManager${maven.home}nativenativefalsenative
================================================
FILE: apps/backend-api-quarkus/src/main/docker/Dockerfile.jvm
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/quarkus242test-jvm .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/quarkus242test-jvm
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/quarkus242test-jvm
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
# We make four distinct layers so if there are application changes the library layers can be re-used
COPY --chown=1001 target/quarkus-app/lib/ /deployments/lib/
COPY --chown=1001 target/quarkus-app/*.jar /deployments/
COPY --chown=1001 target/quarkus-app/app/ /deployments/app/
COPY --chown=1001 target/quarkus-app/quarkus/ /deployments/quarkus/
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]
================================================
FILE: apps/backend-api-quarkus/src/main/docker/Dockerfile.legacy-jar
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode
#
# Before building the container image run:
#
# ./mvnw package -Dquarkus.package.type=legacy-jar
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.legacy-jar -t quarkus/quarkus242test-legacy-jar .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/quarkus242test-legacy-jar
#
# If you want to include the debug port into your docker image
# you will have to expose the debug port (default 5005) like this : EXPOSE 8080 5005
#
# Then run the container using :
#
# docker run -i --rm -p 8080:8080 -p 5005:5005 -e JAVA_ENABLE_DEBUG="true" quarkus/quarkus242test-legacy-jar
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
ARG JAVA_PACKAGE=java-11-openjdk-headless
ARG RUN_JAVA_VERSION=1.3.8
ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en'
# Install java and the run-java script
# Also set up permissions for user `1001`
RUN microdnf install curl ca-certificates ${JAVA_PACKAGE} \
&& microdnf update \
&& microdnf clean all \
&& mkdir /deployments \
&& chown 1001 /deployments \
&& chmod "g+rwX" /deployments \
&& chown 1001:root /deployments \
&& curl https://repo1.maven.org/maven2/io/fabric8/run-java-sh/${RUN_JAVA_VERSION}/run-java-sh-${RUN_JAVA_VERSION}-sh.sh -o /deployments/run-java.sh \
&& chown 1001 /deployments/run-java.sh \
&& chmod 540 /deployments/run-java.sh \
&& echo "securerandom.source=file:/dev/urandom" >> /etc/alternatives/jre/conf/security/java.security
# Configure the JAVA_OPTIONS, you can add -XshowSettings:vm to also display the heap size.
ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager"
COPY target/lib/* /deployments/lib/
COPY target/*-runner.jar /deployments/app.jar
EXPOSE 8080
USER 1001
ENTRYPOINT [ "/deployments/run-java.sh" ]
================================================
FILE: apps/backend-api-quarkus/src/main/docker/Dockerfile.native
================================================
####
# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode
#
# Before building the container image run:
#
# ./mvnw package -Pnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native -t quarkus/quarkus242test .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/quarkus242test
#
###
FROM registry.access.redhat.com/ubi8/ubi-minimal:8.4
WORKDIR /work/
RUN chown 1001 /work \
&& chmod "g+rwX" /work \
&& chown 1001:root /work
COPY --chown=1001:root target/*-runner /work/application
EXPOSE 8080
USER 1001
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
================================================
FILE: apps/backend-api-quarkus/src/main/docker/Dockerfile.native-distroless
================================================
####
# This Dockerfile is used in order to build a distroless container that runs the Quarkus application in native (no JVM) mode
#
# Before building the container image run:
#
# ./mvnw package -Pnative
#
# Then, build the image with:
#
# docker build -f src/main/docker/Dockerfile.native-distroless -t quarkus/quarkus242test .
#
# Then run the container using:
#
# docker run -i --rm -p 8080:8080 quarkus/quarkus242test
#
###
FROM quay.io/quarkus/quarkus-distroless-image:1.0
COPY target/*-runner /application
EXPOSE 8080
USER nonroot
CMD ["./application", "-Dquarkus.http.host=0.0.0.0"]
================================================
FILE: apps/backend-api-quarkus/src/main/java/com/acme/backend/quarkus/users/UsersResource.java
================================================
package com.acme.backend.quarkus.users;
import io.quarkus.security.Authenticated;
import jakarta.annotation.security.RolesAllowed;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.QueryParam;
import jakarta.ws.rs.core.Context;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.SecurityContext;
import jakarta.ws.rs.core.UriInfo;
import org.eclipse.microprofile.jwt.JsonWebToken;
import org.jboss.logging.Logger;
import org.jboss.resteasy.spi.HttpRequest;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Path("/api/users")
@Produces(MediaType.APPLICATION_JSON)
@Authenticated
public class UsersResource {
@Inject
Logger log;
@Inject
JsonWebToken jwt;
@Context
SecurityContext securityContext;
@Context
UriInfo uriInfo;
@Context
HttpRequest request;
@GET
@Path("/me")
public Object me() {
log.infof("### Accessing %s", uriInfo.getPath());
// Note in order to have role information in the token, you need to add the microprofile-jwt scope
// to the token to populate the groups claim with the realm roles.
// securityContext.isUserInRole("admin");
//Object username = jwt.getClaim("preferred_username");
String username = securityContext.getUserPrincipal().getName();
Map data = new HashMap<>();
data.put("message", "Hello " + username);
data.put("backend", "Quarkus");
data.put("datetime", Instant.now());
return data;
}
@GET
@RolesAllowed("iam") // require 'iam' present in groups claim list
@Path("/claims")
public Object claims(
@QueryParam("issuer") String issuer,
@QueryParam("clientId") String clientId,
@QueryParam("userId") String userId,
@QueryParam("username") String username
) {
log.infof("### Generating dynamic claims for user. issuer=%s client_id=%s user_id=%s username=%s",
issuer, clientId, userId, username
);
var acmeData = new HashMap();
acmeData.put("hello", "world");
return Map.of("acme", acmeData);
}
}
================================================
FILE: apps/backend-api-quarkus/src/main/resources/META-INF/resources/index.html
================================================
backend-api-quarkus - 1.0-SNAPSHOT
Your new Cloud-Native application is ready!
Congratulations, you have created a new Quarkus cloud application.
What is this page?
This page is served by Quarkus. The source is in
src/main/resources/META-INF/resources/index.html.
What are your next steps?
If not already done, run the application in dev mode using: ./mvnw compile quarkus:dev.
Your static assets are located in src/main/resources/META-INF/resources.
Configure your application in src/main/resources/application.properties.
Quarkus now ships with a Dev UI (available in dev mode only)
Play with the getting started example code located in src/main/java:
RESTEasy JAX-RS example
REST is easy peasy with this Hello World RESTEasy resource.
================================================
FILE: apps/backend-api-quarkus/src/main/resources/application.properties
================================================
quarkus.http.port=4500
# Allows access via host.docker.internal from container
quarkus.http.host=0.0.0.0
quarkus.http.ssl-port=4543
quarkus.http.ssl.certificate.files=../../config/stage/dev/tls/acme.test+1.pem
quarkus.http.ssl.certificate.key-files=../../config/stage/dev/tls/acme.test+1-key.pem
quarkus.http.cors=true
quarkus.http.cors.origins=https://apps.acme.test:4443
quarkus.http.cors.headers=accept, origin, authorization, content-type, x-requested-with
quarkus.http.cors.methods=GET,POST,OPTIONS
quarkus.log.category."io.smallrye.jwt.auth.principal".level=DEBUG
# Note: If you need to fetch the certificate info from an https JWKS uri, then
# you need to import the certificate of the acme.issuer.uri into your JVM truststore, e.g:
# ~/.sdkman/candidates/java/11.0.11.hs-adpt/bin/keytool -import -noprompt -cacerts -storepass changeit -file config/stage/dev/tls/acme.test+1.pem
# see https://quarkus.io/guides/security-jwt
mp.jwt.verify.publickey.location=${acme.issuer.uri}/protocol/openid-connect/certs
mp.jwt.verify.publickey.algorithm=RS256
mp.jwt.verify.issuer=${acme.issuer.uri}
acme.issuer.uri=https://id.acme.test:8443/auth/realms/acme-internal
# see https://stackoverflow.com/questions/63347673/quarkus-native-image-load-a-pkcs12-file-at-runtime
quarkus.native.enable-all-security-services=true
quarkus.package.type=fast-jar
================================================
FILE: apps/backend-api-rust-actix/.gitignore
================================================
/target
================================================
FILE: apps/backend-api-rust-actix/Cargo.toml
================================================
[package]
name = "backend-api-rust-actix"
version = "0.1.0"
edition = "2021"
authors = ["Thomas Darimont "]
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
actix-web = { version = "4.3.1", features = ["openssl"] }
actix-cors = "0.6.4"
actix-4-jwt-auth = "0.4.2" # TODO update to 1.0.0
openssl = "0.10.48"
serde = { version = "1.0.159", features = ["derive"] }
serde_json = { version = "1.0.95" }
chrono = "0.4.24"
env_logger = "0.10.0"
[dev-dependencies]
# cargo +nightly watch --quiet --clear --exec run
cargo-watch = "8.4.0"
================================================
FILE: apps/backend-api-rust-actix/rustfmt.toml
================================================
max_width = 120
================================================
FILE: apps/backend-api-rust-actix/rustup-toolchain.toml
================================================
[toolchain]
channel = "nightly"
================================================
FILE: apps/backend-api-rust-actix/src/api/me_info.rs
================================================
use crate::middleware::jwt_auth::FoundClaims;
use actix_4_jwt_auth::AuthenticatedUser;
use actix_web::{get, HttpResponse};
use chrono::Utc;
use serde::Serialize;
#[derive(Serialize)]
struct MeInfo {
pub message: String,
pub backend: String,
pub datetime: String,
}
#[derive(Serialize)]
struct ErrorInfo {
pub code: String,
}
#[get("/api/users/me")]
pub async fn handle_me_info(user: AuthenticatedUser) -> HttpResponse {
if !user.claims.has_scope("email") {
return HttpResponse::Forbidden().json(ErrorInfo {
code: "invalid_scope".into(),
});
}
let username = user.claims.preferred_username.unwrap_or("anonymous".into());
let obj = MeInfo {
message: format!("Hello, {}!", username),
backend: "rust-actix".into(),
datetime: Utc::now().to_string(),
};
HttpResponse::Ok().json(obj)
}
================================================
FILE: apps/backend-api-rust-actix/src/api/mod.rs
================================================
pub mod me_info;
================================================
FILE: apps/backend-api-rust-actix/src/config.rs
================================================
use std::env;
pub struct Config {
pub server_bind_addr: String,
pub cert_location: String,
pub key_location: String,
pub oidc_issuer: String,
pub allowed_cors_origin: String,
pub log_level_default: String,
}
impl Config {
pub fn from_environment_with_defaults() -> Self {
Self {
server_bind_addr: env::var("SERVER_BIND_ADDRESS").unwrap_or("127.0.0.1:4863".into()),
cert_location: env::var("CERT_LOCATION").unwrap_or("../../config/stage/dev/tls/acme.test+1.pem".into()),
key_location: env::var("KEY_LOCATION").unwrap_or("../../config/stage/dev/tls/acme.test+1-key.pem".into()),
oidc_issuer: env::var("OIDC_ISSUER")
.unwrap_or("https://id.acme.test:8443/auth/realms/acme-internal".into()),
allowed_cors_origin: env::var("ALLOWED_CORS_ORIGIN").unwrap_or("https://apps.acme.test:4443".into()),
log_level_default: env::var("LOG_LEVEL_DEFAULT").unwrap_or("info".into()),
}
}
}
================================================
FILE: apps/backend-api-rust-actix/src/main.rs
================================================
#![feature(decl_macro)]
use actix_web::middleware::Logger;
use actix_web::{App, HttpServer};
mod api;
mod config;
mod middleware;
#[actix_web::main]
async fn main() -> std::io::Result<()> {
let config = config::Config::from_environment_with_defaults();
env_logger::init_from_env(env_logger::Env::new().default_filter_or(&config.log_level_default));
let ssl_acceptor_builder =
middleware::ssl::create_ssl_acceptor_builder(&config.cert_location, &config.key_location);
let oidc_jwt_validator = middleware::jwt_auth::create_oidc_jwt_validator(config.oidc_issuer).await;
HttpServer::new(move || {
let cors = middleware::cors::create_cors_config(config.allowed_cors_origin.clone());
App::new()
// see https://actix.rs/actix-web/actix_web/middleware/struct.Logger.html
.wrap(Logger::new("%a \"%r\" %s %b \"%{Referer}i\" %T"))
.wrap(cors)
.app_data(oidc_jwt_validator.clone())
.service(api::me_info::handle_me_info)
})
.bind_openssl(config.server_bind_addr, ssl_acceptor_builder)?
.run()
.await
}
================================================
FILE: apps/backend-api-rust-actix/src/middleware/cors.rs
================================================
use actix_cors::Cors;
use actix_web::http::header;
pub fn create_cors_config(allowed_origin: String) -> Cors {
Cors::default()
.allowed_origin_fn(move |header, _request| {
return header.as_bytes().ends_with(allowed_origin.as_bytes());
})
.allowed_methods(vec!["GET", "POST"])
.allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
.allowed_header(header::CONTENT_TYPE)
.supports_credentials()
.max_age(3600)
}
================================================
FILE: apps/backend-api-rust-actix/src/middleware/jwt_auth.rs
================================================
use actix_4_jwt_auth::{OIDCValidator, OIDCValidatorConfig};
use actix_web::rt::task;
use serde::Deserialize;
use serde_json::Value;
use std::collections::BTreeMap as Map;
#[derive(Debug, Deserialize)]
pub struct FoundClaims {
pub iat: usize,
pub exp: usize,
pub iss: String,
pub sub: String,
pub scope: String,
pub preferred_username: Option,
#[serde(flatten)]
pub other: Map,
}
impl FoundClaims {
pub fn has_scope(&self, scope: &str) -> bool {
self.scope.split_ascii_whitespace().any(|s| s == scope)
}
}
pub async fn create_oidc_jwt_validator(issuer: String) -> OIDCValidatorConfig {
task::spawn_blocking(move || {
let validator = OIDCValidator::new_from_issuer(issuer.clone()).unwrap();
OIDCValidatorConfig { issuer, validator }
})
.await
.unwrap()
}
================================================
FILE: apps/backend-api-rust-actix/src/middleware/mod.rs
================================================
pub mod cors;
pub mod jwt_auth;
pub mod ssl;
================================================
FILE: apps/backend-api-rust-actix/src/middleware/ssl.rs
================================================
use openssl::ssl::{SslAcceptor, SslAcceptorBuilder, SslFiletype, SslMethod};
pub fn create_ssl_acceptor_builder(cert_location: &str, key_location: &str) -> SslAcceptorBuilder {
let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
builder.set_private_key_file(key_location, SslFiletype::PEM).unwrap();
builder.set_certificate_chain_file(cert_location).unwrap();
builder
}
================================================
FILE: apps/backend-api-rust-rocket/.gitignore
================================================
/target
.idea/
.DS_Store
================================================
FILE: apps/backend-api-rust-rocket/.run/Run backend-api-rust-rocket.run.xml
================================================
================================================
FILE: apps/backend-api-rust-rocket/Cargo.toml
================================================
[package]
name = "backend-api-rust-rocket"
version = "0.1.0"
authors = ["Thomas Darimont "]
edition = "2021"
[dependencies]
serde = { version ="1.0.157", features = ["derive"]}
lazy_static = "1.4.0"
log = "0.4.17"
env_logger = "0.10.0"
chrono = "0.4.24"
jsonwebtoken = "8.3.0"
rocket = { version = "0.5.0-rc.2", features = ["tls", "json", "serde_json"] }
reqwest = { version = "0.11.14", features = ["blocking", "json"] }
[dev-dependencies]
cargo-watch = "8.4.0"
================================================
FILE: apps/backend-api-rust-rocket/README.md
================================================
# Backend API with JWK authentication based on Rocket (Rust)
## Features
- Validate JWT issued by Keycloak
- Validate JWT with JWK from JWKS endpoint
- Periodically fetch a JWKS Keyset from Keycloak
- Extract claims from JWT
## Run
```
ROCKET_PROFILE=debug cargo run
```
Browse to: https://127.0.0.1:4853
This example is inspired by [maylukas/rust_jwk_example](https://github.com/maylukas/rust_jwk_example)
================================================
FILE: apps/backend-api-rust-rocket/Rocket.toml
================================================
# see https://rocket.rs/v0.5-rc/guide/configuration/#overview
[debug]
address = "127.0.0.1"
port = 4853
workers = 2
keep_alive = 5
log_level = "normal"
limits = { forms = 32768 }
[debug.tls]
certs = "../../config/stage/dev/tls/acme.test+1.pem"
key = "../../config/stage/dev/tls/acme.test+1-key.pem"
================================================
FILE: apps/backend-api-rust-rocket/rustfmt.toml
================================================
edition = "2021"
================================================
FILE: apps/backend-api-rust-rocket/rustup-toolchain.toml
================================================
[toolchain]
channel = "nightly"
================================================
FILE: apps/backend-api-rust-rocket/src/domain/mod.rs
================================================
pub mod user;
================================================
FILE: apps/backend-api-rust-rocket/src/domain/user.rs
================================================
pub struct User {
pub uid: String,
pub username: String,
pub email: String,
// TODO add other claims
// TODO add access token
}
================================================
FILE: apps/backend-api-rust-rocket/src/main.rs
================================================
//#![feature(proc_macro_hygiene, decl_macro)]
#[macro_use]
extern crate rocket;
use rocket::routes;
use std::error::Error;
use crate::domain::user::User;
use crate::middleware::auth::jwt::JwtAuth;
use chrono::Utc;
use rocket::serde::json::Json;
use rocket::tokio::task::spawn_blocking;
use serde::Deserialize;
use serde::Serialize;
use crate::middleware::logging;
pub mod domain;
pub mod middleware;
pub mod support;
#[derive(Serialize, Deserialize)]
pub struct MeInfo {
pub message: String,
pub backend: String,
pub datetime: String,
}
#[options("/api/users/me")]
fn options_me_info() {}
#[get("/api/users/me")]
fn get_me_info(user: User) -> Json {
log::info!("Handle user info request. username={}", &user.username);
let info = MeInfo {
datetime: Utc::now().to_string(),
message: format!("Hello, {}!", user.username),
backend: String::from("rust-rocket"),
};
Json(info)
}
#[rocket::main]
async fn main() -> Result<(), Box> {
logging::init_logging();
let auth = spawn_blocking(JwtAuth::new).await?;
let _ = rocket::build()
.attach(middleware::cors::Cors)
.mount("/", routes![get_me_info, options_me_info])
.manage(auth)
.launch()
.await?;
Ok(())
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/auth.rs
================================================
use crate::middleware::auth::jwt::{fetch_jwks_keys, Claims, JwkKeys, JwtVerifier};
use crate::support::scheduling::use_repeating_job;
use jsonwebtoken::TokenData;
use std::sync::{Arc, Mutex};
use std::time::Duration;
use log;
type CleanupFn = Box;
pub struct JwtAuth {
verifier: Arc>,
cleanup: Mutex,
}
impl Drop for JwtAuth {
fn drop(&mut self) {
// Stop the update thread when the updater is destructed
let cleanup_fn = self.cleanup.lock().unwrap();
cleanup_fn();
}
}
impl JwtAuth {
pub fn new() -> JwtAuth {
let jwk_key_result = fetch_jwks_keys();
let jwk_keys: JwkKeys = match jwk_key_result {
Ok(keys) => keys,
Err(_) => {
panic!("Unable to fetch jwt keys! Cannot verify user tokens! Shutting down...")
}
};
let verifier = Arc::new(Mutex::new(JwtVerifier::new(jwk_keys.keys)));
let mut instance = JwtAuth {
verifier,
cleanup: Mutex::new(Box::new(|| {})),
};
instance.start_key_update();
instance
}
pub fn verify(&self, token: &str) -> Option> {
let verifier = self.verifier.lock().unwrap();
verifier.verify(token)
}
fn start_key_update(&mut self) {
let verifier_ref = Arc::clone(&self.verifier);
let stop = use_repeating_job(move || match fetch_jwks_keys() {
Ok(jwk_keys) => {
let mut verifier = verifier_ref.lock().unwrap();
verifier.set_keys(jwk_keys.keys);
log::info!("Updated JWK keys. Next refresh will be in {:?}", jwk_keys.validity);
jwk_keys.validity
}
Err(_) => Duration::from_secs(10),
});
let mut cleanup = self.cleanup.lock().unwrap();
*cleanup = stop;
}
}
impl Default for JwtAuth {
fn default() -> Self {
Self::new()
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/claims.rs
================================================
use rocket::serde::json::serde_json;
use std::collections::HashMap;
pub type Claims = HashMap;
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/config.rs
================================================
use crate::middleware;
#[derive(Debug)]
pub struct JwtConfig {
pub jwk_url: String,
pub audience: String,
// TODO ADD support for multiple issuers via HashSet
pub issuer: String,
}
pub fn get_config() -> JwtConfig {
JwtConfig {
jwk_url: middleware::expect_env_var(
"JWK_URL",
"https://id.acme.test:8443/auth/realms/acme-internal/protocol/openid-connect/certs",
),
audience: middleware::expect_env_var("JWK_AUDIENCE", "app-minispa"),
issuer: middleware::expect_env_var(
"JWK_ISSUER",
"https://id.acme.test:8443/auth/realms/acme-internal",
),
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/get_max_age.rs
================================================
use reqwest::blocking::Response;
use reqwest::header::HeaderValue;
use std::time::Duration;
pub enum MaxAgeParseError {
NoMaxAgeSpecified,
NoCacheControlHeader,
MaxAgeValueEmpty,
NonNumericMaxAge,
}
// Determines the max age of an HTTP response
pub fn get_max_age(response: &Response) -> Result {
let headers = response.headers();
let header = headers.get("Cache-Control");
match header {
Some(header_value) => parse_cache_control_header(header_value),
None => Err(MaxAgeParseError::NoCacheControlHeader),
}
}
fn parse_max_age_value(cache_control_value: &str) -> Result {
let tokens: Vec<&str> = cache_control_value.split(',').collect();
for token in tokens {
let key_value: Vec<&str> = token.split('=').map(|s| s.trim()).collect();
let key = key_value.first().unwrap();
let val = key_value.get(1);
if String::from("max-age").eq(&key.to_lowercase()) {
match val {
Some(value) => {
return Ok(Duration::from_secs(
value
.parse()
.map_err(|_| MaxAgeParseError::NonNumericMaxAge)?,
))
}
None => return Err(MaxAgeParseError::MaxAgeValueEmpty),
}
}
}
Err(MaxAgeParseError::NoMaxAgeSpecified)
}
fn parse_cache_control_header(header_value: &HeaderValue) -> Result {
match header_value.to_str() {
Ok(string_value) => parse_max_age_value(string_value),
Err(_) => Err(MaxAgeParseError::NoCacheControlHeader),
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/jwks.rs
================================================
use crate::middleware::auth::jwt;
use crate::middleware::auth::jwt::get_max_age::get_max_age;
use crate::middleware::auth::jwt::JwtConfig;
use serde::Deserialize;
use std::error::Error;
use std::time::Duration;
#[derive(Debug, Deserialize)]
struct KeyResponse {
keys: Vec,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct JwkKey {
pub e: String,
pub alg: String,
pub kty: String,
pub kid: String,
pub n: String,
}
#[derive(Debug, Deserialize, Eq, PartialEq)]
pub struct JwkKeys {
pub keys: Vec,
pub validity: Duration,
}
// TODO make JWKS fetch FALLBACK_TIMEOUT configurable
const FALLBACK_TIMEOUT: Duration = Duration::from_secs(300);
pub fn fetch_keys_for_config(config: &JwtConfig) -> Result> {
log::info!("Fetching JWKS Keys from URL={}", &config.jwk_url);
let http_response = reqwest::blocking::get::<>(&config.jwk_url).unwrap();
let max_age = get_max_age(&http_response).unwrap_or(FALLBACK_TIMEOUT);
let result = Ok(http_response.json::().unwrap());
result.map(|res| JwkKeys {
keys: res.keys,
validity: max_age,
})
}
pub fn fetch_jwks_keys() -> Result> {
return fetch_keys_for_config(&jwt::get_config());
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/mod.rs
================================================
mod auth;
mod claims;
mod config;
mod jwks;
mod get_max_age;
mod verifier;
pub use auth::*;
pub use claims::*;
pub use config::*;
pub use jwks::*;
pub use verifier::*;
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt/verifier.rs
================================================
use crate::middleware::auth::jwt;
use crate::middleware::auth::jwt::claims::Claims;
use crate::middleware::auth::jwt::{JwkKey, JwtConfig};
use jsonwebtoken::decode_header;
use jsonwebtoken::TokenData;
use jsonwebtoken::{decode, Algorithm, DecodingKey, Validation};
use std::collections::HashMap;
use std::str::FromStr;
enum VerificationError {
InvalidSignature,
UnknownKeyAlgorithm,
}
#[derive(Debug)]
pub struct JwtVerifier {
keys: HashMap,
config: JwtConfig,
}
fn keys_to_map(keys: Vec) -> HashMap {
let mut keys_as_map = HashMap::new();
for key in keys {
keys_as_map.insert(String::clone(&key.kid), key);
}
keys_as_map
}
impl JwtVerifier {
pub fn new(keys: Vec) -> JwtVerifier {
JwtVerifier {
keys: keys_to_map(keys),
config: jwt::get_config(),
}
}
pub fn verify(&self, token: &str) -> Option> {
let token_kid = match decode_header(token).map(|header| header.kid) {
Ok(Some(header)) => header,
_ => return None,
};
let jwk_key = match self.get_key(token_kid) {
Some(key) => key,
_ => return None,
};
match self.decode_token_with_key(jwk_key, token) {
Ok(token_data) => Some(token_data),
_ => None,
}
}
pub fn set_keys(&mut self, keys: Vec) {
self.keys = keys_to_map(keys);
}
fn get_key(&self, key_id: String) -> Option<&JwkKey> {
self.keys.get(&key_id)
}
fn decode_token_with_key(
&self,
key: &JwkKey,
token: &str,
) -> Result, VerificationError> {
// TODO ensure that "none" algorithm cannot be used!
let algorithm = match Algorithm::from_str(&key.alg) {
Ok(alg) => alg,
Err(_error) => return Err(VerificationError::UnknownKeyAlgorithm),
};
let mut validation = Validation::new(algorithm);
// TODO make audience validation configurable (enable / disable)
// TODO make allowed audience configurable
// validation.set_audience(&[&self.middleware.audience]);
// TODO adapt to support multiple issuers
let mut issuers = std::collections::HashSet::new();
issuers.insert(self.config.issuer.clone());
validation.iss = Some(issuers);
let key = DecodingKey::from_rsa_components(&key.n, &key.e).unwrap();
decode::(token, &key, &validation)
.map_err(|_| VerificationError::InvalidSignature)
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/jwt_auth_request_guard.rs
================================================
use crate::domain::user::User;
use crate::middleware::auth::jwt::JwtAuth;
use rocket::http::Status;
use rocket::outcome::Outcome;
use rocket::request;
use rocket::request::FromRequest;
use rocket::Request;
use rocket::State;
#[derive(Debug)]
pub enum AuthError {
InvalidJwt,
NoAuthorizationHeader,
MultipleKeysProvided,
NoJwkVerifier,
}
fn get_token_from_header(header: &str) -> Option {
let prefix_len = "Bearer ".len();
match header.len() {
l if l < prefix_len => None,
_ => Some(header[prefix_len..].to_string()),
}
}
fn verify_token(token: &str, auth: &JwtAuth) -> request::Outcome {
let verified_token = auth.verify(token);
// TODO externalize claims to JWT User conversion
let maybe_user = verified_token.map(|token| User {
// TODO use more idiomatic value conversion here
uid: token
.claims
.get("sub")
.unwrap()
.as_str()
.unwrap()
.to_string(),
username: token
.claims
.get("preferred_username")
.unwrap()
.as_str()
.unwrap()
.to_string(),
email: token
.claims
.get("email")
.unwrap()
.as_str()
.unwrap()
.to_string(),
});
match maybe_user {
Some(user) => Outcome::Success(user),
None => Outcome::Failure((Status::BadRequest, AuthError::InvalidJwt)),
}
}
fn parse_and_verify_auth_header(header: &str, auth: &JwtAuth) -> request::Outcome {
let maybe_token = get_token_from_header(header);
match maybe_token {
Some(token) => verify_token(&token, auth),
None => Outcome::Failure((Status::Unauthorized, AuthError::InvalidJwt)),
}
}
#[rocket::async_trait]
impl<'r> FromRequest<'r> for User {
type Error = AuthError;
async fn from_request(request: &'r Request<'_>) -> request::Outcome {
let auth_headers: Vec<_> = request.headers().get("Authorization").collect();
let configured_auth = request.guard::<&'r State>();
match configured_auth.await {
Outcome::Success(auth) => match auth_headers.len() {
0 => Outcome::Failure((Status::Unauthorized, AuthError::NoAuthorizationHeader)),
1 => parse_and_verify_auth_header(auth_headers[0], auth),
_ => Outcome::Failure((Status::BadRequest, AuthError::MultipleKeysProvided)),
},
_ => Outcome::Failure((Status::InternalServerError, AuthError::NoJwkVerifier)),
}
}
}
#[cfg(test)]
mod describe {
#[test]
fn test_extract_token() {
let token = super::get_token_from_header("Bearer token_string");
assert_eq!(Some("token_string".to_string()), token)
}
#[test]
fn test_extract_token_too_short() {
assert_eq!(None, super::get_token_from_header(&"Bear".to_string()));
assert_eq!(None, super::get_token_from_header(&"Bearer".to_string()))
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/auth/mod.rs
================================================
pub mod jwt;
mod jwt_auth_request_guard;
pub use jwt_auth_request_guard::*;
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/cors/cors.rs
================================================
use rocket::fairing::Fairing;
use rocket::fairing::Info;
use rocket::fairing::Kind;
use rocket::http::Header;
use rocket::Request;
use rocket::Response;
pub struct Cors;
#[rocket::async_trait]
impl Fairing for Cors {
fn info(&self) -> Info {
Info {
name: "Attaching CORS headers to responses",
kind: Kind::Response,
}
}
async fn on_response<'r>(&self, _request: &'r Request<'_>, response: &mut Response<'r>) {
// TODO make cors configurable
response.set_header(Header::new("Access-Control-Allow-Origin", "*"));
response.set_header(Header::new(
"Access-Control-Allow-Methods",
"POST, GET, PATCH, OPTIONS",
));
response.set_header(Header::new("Access-Control-Allow-Headers", "*"));
response.set_header(Header::new("Access-Control-Allow-Credentials", "true"));
}
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/cors/mod.rs
================================================
pub mod cors;
pub use cors::*;
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/logging/logging.rs
================================================
use chrono::Local;
use env_logger::Builder;
use log;
use log::LevelFilter;
use std::io::Write;
pub fn init_logging() {
Builder::new()
.format(|buf, record| {
writeln!(buf,
"{} [{}] - {}",
Local::now().format("%Y-%m-%dT%H:%M:%S"),
record.level(),
record.args()
)
})
.filter(None, LevelFilter::Info)
.init();
}
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/logging/mod.rs
================================================
pub mod logging;
pub use logging::*;
================================================
FILE: apps/backend-api-rust-rocket/src/middleware/mod.rs
================================================
use std::env;
pub mod auth;
pub mod cors;
pub mod logging;
#[cfg(debug_assertions)]
pub fn expect_env_var(name: &str, default: &str) -> String {
env::var(name).unwrap_or(String::from(default))
}
#[cfg(not(debug_assertions))]
pub fn expect_env_var(name: &str, _default: &str) -> String {
return env::var(name).expect(&format!(
"Environment variable {name} is not defined",
name = name
));
}
================================================
FILE: apps/backend-api-rust-rocket/src/support/mod.rs
================================================
pub mod scheduling;
================================================
FILE: apps/backend-api-rust-rocket/src/support/scheduling/mod.rs
================================================
pub mod use_repeating_job;
pub use use_repeating_job::*;
================================================
FILE: apps/backend-api-rust-rocket/src/support/scheduling/use_repeating_job.rs
================================================
use std::sync::mpsc::{self, TryRecvError};
use std::thread;
use std::time::Duration;
type Delay = Duration;
type Cancel = Box;
// Runs a given closure as a repeating job until the cancel callback is invoked.
// The jobs are run with a delay returned by the closure execution.
pub fn use_repeating_job(job: F) -> Cancel
where
F: Fn() -> Delay,
F: Send + 'static,
{
let (shutdown_tx, shutdown_rx) = mpsc::channel();
thread::spawn(move || loop {
let delay = job();
thread::sleep(delay);
if let Ok(_) | Err(TryRecvError::Disconnected) = shutdown_rx.try_recv() {
break;
}
});
Box::new(move || {
println!("Stopping...");
let _ = shutdown_tx.send("stop");
})
}
================================================
FILE: apps/backend-api-rust-rocket/tests/fetch_keys.rs
================================================
use jwk_example::fetch_keys_for_config;
use jwk_example::JwkConfiguration;
use jwk_example::JwkKey;
fn assert_is_valid_key(key: &JwkKey) {
assert!(key.kid.len() > 0);
assert!(key.n.len() > 0);
assert!(key.e.len() > 0);
assert!(key.kty.len() > 0);
assert!(key.alg.len() > 0);
}
#[test]
fn test_fetch_keys() {
let config = JwkConfiguration {
jwk_url: String::from("https://www.googleapis.com/service_accounts/v1/jwt/securetoken@system.gserviceaccount.com"),
audience: String::from("tracking-app-dev-271418"),
issuer: String::from("https://securetoken.google.com/tracking-app-dev-271418")
};
let result = fetch_keys_for_config(&config).expect("Did not fetch keys");
assert_eq!(2, result.keys.len());
assert_is_valid_key(result.keys.get(0).expect(""));
assert_is_valid_key(result.keys.get(1).expect(""));
}
================================================
FILE: apps/backend-api-springboot/.gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
================================================
FILE: apps/backend-api-springboot/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
================================================
FILE: apps/backend-api-springboot/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
================================================
FILE: apps/backend-api-springboot/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/backend-api-springboot/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: apps/backend-api-springboot/pom.xml
================================================
4.0.0org.springframework.bootspring-boot-starter-parent2.7.18com.examplebackend-api-springboot0.0.1-SNAPSHOTbackend-api-springbootbackend-api-springboot211.18.38org.springframework.bootspring-boot-starter-oauth2-resource-serverorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-devtoolsruntimetrueorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/BackendApiSpringbootApp.java
================================================
package com.acme.backend.springboot.users;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class BackendApiSpringbootApp {
public static void main(String[] args) {
SpringApplication.run(BackendApiSpringbootApp.class, args);
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/config/AcmeServiceProperties.java
================================================
package com.acme.backend.springboot.users.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "acme")
public class AcmeServiceProperties {
private KeycloakJwtProperties jwt = new KeycloakJwtProperties();
/**
* Specifies JWT client ID, issuer URI and allowed audiences
* for validation
*/
@Getter
@Setter
public static class KeycloakJwtProperties {
private String clientId;
private String issuerUri;
private List allowedAudiences;
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/config/JwtSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import com.acme.backend.springboot.users.support.keycloak.KeycloakGrantedAuthoritiesConverter;
import com.acme.backend.springboot.users.support.keycloak.KeycloakJwtAuthenticationConverter;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Configures JWT handling (decoder and validator)
*/
@Configuration
class JwtSecurityConfig {
/**
* Configures a decoder with the specified validators (validation key fetched from JWKS endpoint)
*
* @param validators validators for the given key
* @param properties key properties (provides JWK location)
* @return the decoder bean
*/
@Bean
JwtDecoder jwtDecoder(List> validators, OAuth2ResourceServerProperties properties) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder //
.withJwkSetUri(properties.getJwt().getJwkSetUri()) //
.jwsAlgorithms(algs -> algs.addAll(Set.of(SignatureAlgorithm.RS256, SignatureAlgorithm.ES256))) //
.build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
return jwtDecoder;
}
/**
* Configures the token validator. Specifies two additional validation constraints:
*
* * Timestamp on the token is still valid
* * The issuer is the expected entity
*
* @param properties JWT resource specification
* @return token validator
*/
@Bean
OAuth2TokenValidator defaultTokenValidator(OAuth2ResourceServerProperties properties) {
List> validators = new ArrayList<>();
validators.add(new JwtTimestampValidator());
validators.add(new JwtIssuerValidator(properties.getJwt().getIssuerUri()));
return new DelegatingOAuth2TokenValidator<>(validators);
}
@Bean
KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter(Converter> authoritiesConverter) {
return new KeycloakJwtAuthenticationConverter(authoritiesConverter);
}
@Bean
Converter> keycloakGrantedAuthoritiesConverter(GrantedAuthoritiesMapper authoritiesMapper, AcmeServiceProperties acmeServiceProperties) {
String clientId = acmeServiceProperties.getJwt().getClientId();
return new KeycloakGrantedAuthoritiesConverter(clientId, authoritiesMapper);
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/config/MethodSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
/**
* Enables security annotations via like {@link org.springframework.security.access.prepost.PreAuthorize} and
* {@link org.springframework.security.access.prepost.PostAuthorize} annotations per-method.
*/
@Configuration
@RequiredArgsConstructor
@EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true, proxyTargetClass = true)
class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
private final ApplicationContext applicationContext;
private final PermissionEvaluator permissionEvaluator;
@Override
protected MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
@Bean
GrantedAuthoritiesMapper keycloakAuthoritiesMapper() {
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/config/WebSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import com.acme.backend.springboot.users.support.access.AccessController;
import com.acme.backend.springboot.users.support.keycloak.KeycloakJwtAuthenticationConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
/**
* Configuration applied on all web endpoints defined for this
* application. Any configuration on specific resources is applied
* in addition to these global rules.
*/
@Configuration
@RequiredArgsConstructor
class WebSecurityConfig {
private final KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter;
/**
* Configures basic security handler per HTTP session.
*
*
*
Stateless session (no session kept server-side)
*
CORS set up
*
Require the role "ACCESS" for all api paths
*
JWT converted into Spring token
*
*
* @param http security configuration
* @throws Exception any error
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement(smc -> {
smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
});
http.cors(this::configureCors);
http.authorizeRequests(arc -> {
// declarative route configuration
// .mvcMatchers("/api").hasAuthority("ROLE_ACCESS")
arc.mvcMatchers("/api/**").access("@accessController.checkAccess()");
// add additional routes
arc.anyRequest().fullyAuthenticated(); //
});
http.oauth2ResourceServer(arsc -> {
arsc.jwt().jwtAuthenticationConverter(keycloakJwtAuthenticationConverter);
});
return http.build();
}
@Bean
AccessController accessController() {
return new AccessController();
}
/**
* Configures CORS to allow requests from localhost:30000
*
* @param cors mutable cors configuration
*/
protected void configureCors(CorsConfigurer cors) {
UrlBasedCorsConfigurationSource defaultUrlBasedCorsConfigSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.addAllowedOrigin("https://apps.acme.test:4443");
List.of("GET", "POST", "PUT", "DELETE").forEach(corsConfiguration::addAllowedMethod);
defaultUrlBasedCorsConfigSource.registerCorsConfiguration("/api/**", corsConfiguration);
cors.configurationSource(req -> {
CorsConfiguration config = new CorsConfiguration();
config = config.combine(defaultUrlBasedCorsConfigSource.getCorsConfiguration(req));
// check if request Header "origin" is in white-list -> dynamically generate cors config
return config;
});
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/access/AccessController.java
================================================
package com.acme.backend.springboot.users.support.access;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
/**
* Example for generic custom access checks on request level.
*/
@Slf4j
public class AccessController {
public boolean checkAccess() {
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
log.info("Check access for username={} path={}", auth.getName(), requestAttributes.getRequest().getRequestURI());
return true;
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakAudienceValidator.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
/**
* Example class for custom audience (aud) or authorized party (azp) claim validations.
*/
@Component
@RequiredArgsConstructor
class KeycloakAudienceValidator implements OAuth2TokenValidator {
private final OAuth2Error ERROR_INVALID_AUDIENCE = new OAuth2Error("invalid_token", "Invalid audience", null);
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
// String authorizedParty = jwt.getClaimAsString("azp");
//
// if (!keycloakDataServiceProperties.getJwt().getAllowedAudiences().contains(authorizedParty)) {
// return OAuth2TokenValidatorResult.failure(ERROR_INVALID_AUDIENCE);
// }
return OAuth2TokenValidatorResult.success();
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakGrantedAuthoritiesConverter.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Allows to extract granted authorities from a given JWT. The authorities
* are determined by combining the realm (overarching) and client (application-specific)
* roles, and normalizing them (configure them to the default format).
*/
public class KeycloakGrantedAuthoritiesConverter implements Converter> {
private static final Converter> JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER = new JwtGrantedAuthoritiesConverter();
private final String clientId;
private final GrantedAuthoritiesMapper authoritiesMapper;
public KeycloakGrantedAuthoritiesConverter(String clientId, GrantedAuthoritiesMapper authoritiesMapper) {
this.clientId = clientId;
this.authoritiesMapper = authoritiesMapper;
}
@Override
public Collection convert(Jwt jwt) {
Collection authorities = mapKeycloakRolesToAuthorities( //
getRealmRolesFrom(jwt), //
getClientRolesFrom(jwt, clientId) //
);
Collection scopeAuthorities = JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER.convert(jwt);
if(!CollectionUtils.isEmpty(scopeAuthorities)) {
authorities.addAll(scopeAuthorities);
}
return authorities;
}
protected Collection mapKeycloakRolesToAuthorities(Set realmRoles, Set clientRoles) {
List combinedAuthorities = new ArrayList<>();
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(realmRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(clientRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
return combinedAuthorities;
}
protected Set getRealmRolesFrom(Jwt jwt) {
Map realmAccess = jwt.getClaimAsMap("realm_access");
if (CollectionUtils.isEmpty(realmAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Collection realmRoles = (Collection) realmAccess.get("roles");
if (CollectionUtils.isEmpty(realmRoles)) {
return Collections.emptySet();
}
return realmRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
protected Set getClientRolesFrom(Jwt jwt, String clientId) {
Map resourceAccess = jwt.getClaimAsMap("resource_access");
if (CollectionUtils.isEmpty(resourceAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Map> clientAccess = (Map>) resourceAccess.get(clientId);
if (CollectionUtils.isEmpty(clientAccess)) {
return Collections.emptySet();
}
List clientRoles = clientAccess.get("roles");
if (CollectionUtils.isEmpty(clientRoles)) {
return Collections.emptySet();
}
return clientRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
private String normalizeRole(String role) {
return role.replace('-', '_');
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakJwtAuthenticationConverter.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import java.util.Collection;
/**
* Converts a JWT into a Spring authentication token (by extracting
* the username and roles from the claims of the token, delegating
* to the {@link KeycloakGrantedAuthoritiesConverter})
*/
@RequiredArgsConstructor
public class KeycloakJwtAuthenticationConverter implements Converter {
private Converter> grantedAuthoritiesConverter;
public KeycloakJwtAuthenticationConverter(Converter> grantedAuthoritiesConverter) {
this.grantedAuthoritiesConverter = grantedAuthoritiesConverter;
}
@Override
public JwtAuthenticationToken convert(Jwt jwt) {
Collection authorities = grantedAuthoritiesConverter.convert(jwt);
String username = getUsernameFrom(jwt);
return new JwtAuthenticationToken(jwt, authorities, username);
}
protected String getUsernameFrom(Jwt jwt) {
if (jwt.hasClaim("preferred_username")) {
return jwt.getClaimAsString("preferred_username");
}
return jwt.getSubject();
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/permissions/DefaultPermissionEvaluator.java
================================================
package com.acme.backend.springboot.users.support.permissions;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* Custom {@link PermissionEvaluator} for method level permission checks.
*
* @see com.acme.backend.springboot.users.config.MethodSecurityConfig
*/
@Slf4j
@Component
@RequiredArgsConstructor
class DefaultPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
log.info("check permission user={} target={} permission={}", auth.getName(), targetDomainObject, permission);
// TODO implement sophisticated permission check here
return true;
}
@Override
public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) {
DomainObjectReference dor = new DomainObjectReference(targetType, targetId.toString());
return hasPermission(auth, dor, permission);
}
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/support/permissions/DomainObjectReference.java
================================================
package com.acme.backend.springboot.users.support.permissions;
import lombok.Data;
/**
* Defines a single domain object by a type and name to look up
*/
@Data
public class DomainObjectReference {
private final String type;
private final String id;
}
================================================
FILE: apps/backend-api-springboot/src/main/java/com/acme/backend/springboot/users/web/UsersController.java
================================================
package com.acme.backend.springboot.users.web;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/users")
class UsersController {
@GetMapping("/me")
public Object me(ServletWebRequest request, Authentication authentication) {
log.info("### Accessing {}", request.getRequest().getRequestURI());
Object username = authentication.getName();
Map data = new HashMap<>();
data.put("message", "Hello " + username);
data.put("backend", "Spring Boot");
data.put("datetime", Instant.now());
return data;
}
}
================================================
FILE: apps/backend-api-springboot/src/main/resources/application.yml
================================================
spring:
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
# deals with single and multi-valued JWT claims
accept-single-value-as-array: true
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${acme.jwt.issuerUri}
jwk-set-uri: ${acme.jwt.issuerUri}/protocol/openid-connect/certs
# Use mock-service jwks-endpoint to obtain public key for testing
# jwk-set-uri: http://localhost:9999/jwks
acme:
jwt:
issuerUri: https://id.acme.test:8443/auth/realms/acme-internal
server:
port: 4643
ssl:
enabled: true
key-store: ../../config/stage/dev/tls/acme.test+1.p12
key-store-password: changeit
key-store-type: PKCS12
error:
include-stacktrace: never
include-exception: false
include-message: never
================================================
FILE: apps/backend-api-springboot/src/test/java/com/acme/backend/springboot/users/BackendApiSpringbootAppTests.java
================================================
package com.acme.backend.springboot.users;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BackendApiSpringbootAppTests {
@Test
void contextLoads() {
}
}
================================================
FILE: apps/backend-api-springboot-reactive/.gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
================================================
FILE: apps/backend-api-springboot-reactive/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
================================================
FILE: apps/backend-api-springboot-reactive/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/backend-api-springboot-reactive/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
================================================
FILE: apps/backend-api-springboot-reactive/pom.xml
================================================
4.0.0org.springframework.bootspring-boot-starter-parent2.7.14com.examplebackend-api-springboot-reactive0.0.1-SNAPSHOTbackend-api-springboot-reactivebackend-api-springboot-reactive171.18.38org.springframework.bootspring-boot-starter-oauth2-resource-serverorg.springframework.bootspring-boot-starter-webfluxorg.springframework.bootspring-boot-starter-actuatororg.springframework.bootspring-boot-devtoolsruntimetrueorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestio.projectreactorreactor-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/BackendApiSpringbootReactiveApp.java
================================================
package com.acme.backend.springreactive;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BackendApiSpringbootReactiveApp {
public static void main(String[] args) {
SpringApplication.run(BackendApiSpringbootReactiveApp.class, args);
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/AcmeServiceProperties.java
================================================
package com.acme.backend.springreactive.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "acme")
public class AcmeServiceProperties {
private KeycloakJwtProperties jwt = new KeycloakJwtProperties();
/**
* Specifies JWT client ID, issuer URI and allowed audiences
* for validation
*/
@Getter
@Setter
public static class KeycloakJwtProperties {
private String clientId;
private String issuerUri;
private List allowedAudiences;
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/JwtSecurityConfig.java
================================================
package com.acme.backend.springreactive.config;
import com.acme.backend.springreactive.support.keycloak.KeycloakGrantedAuthoritiesConverter;
import com.acme.backend.springreactive.support.keycloak.KeycloakJwtAuthenticationConverter;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Configures JWT handling (decoder and validator)
*/
@Configuration
class JwtSecurityConfig {
/**
* Configures a decoder with the specified validators (validation key fetched from JWKS endpoint)
*
* @param validators validators for the given key
* @param properties key properties (provides JWK location)
* @return the decoder bean
*/
@Bean
ReactiveJwtDecoder jwtDecoder(List> validators, OAuth2ResourceServerProperties properties) {
NimbusReactiveJwtDecoder jwtDecoder = NimbusReactiveJwtDecoder
.withJwkSetUri(properties.getJwt().getJwkSetUri()) //
.jwsAlgorithms(algs -> algs.addAll(Set.of(SignatureAlgorithm.RS256, SignatureAlgorithm.ES256)))
.build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
return jwtDecoder;
}
/**
* Configures the token validator. Specifies two additional validation constraints:
*
* * Timestamp on the token is still valid
* * The issuer is the expected entity
*
* @param properties JWT resource specification
* @return token validator
*/
@Bean
OAuth2TokenValidator defaultTokenValidator(OAuth2ResourceServerProperties properties) {
List> validators = new ArrayList<>();
validators.add(new JwtTimestampValidator());
validators.add(new JwtIssuerValidator(properties.getJwt().getIssuerUri()));
return new DelegatingOAuth2TokenValidator<>(validators);
}
@Bean
KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter(Converter> authoritiesConverter) {
return new KeycloakJwtAuthenticationConverter(authoritiesConverter);
}
@Bean
Converter> keycloakGrantedAuthoritiesConverter(GrantedAuthoritiesMapper authoritiesMapper, AcmeServiceProperties acmeServiceProperties) {
String clientId = acmeServiceProperties.getJwt().getClientId();
return new KeycloakGrantedAuthoritiesConverter(clientId, authoritiesMapper);
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/MethodSecurityConfig.java
================================================
package com.acme.backend.springreactive.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
/**
* Enables security annotations via like {@link org.springframework.security.access.prepost.PreAuthorize} and
* {@link org.springframework.security.access.prepost.PostAuthorize} annotations per-method.
*/
@Configuration
@RequiredArgsConstructor
@EnableReactiveMethodSecurity
class MethodSecurityConfig {
@Bean
GrantedAuthoritiesMapper keycloakAuthoritiesMapper() {
SimpleAuthorityMapper mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/WebFluxConfig.java
================================================
package com.acme.backend.springreactive.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.reactive.config.CorsRegistry;
import org.springframework.web.reactive.config.EnableWebFlux;
import org.springframework.web.reactive.config.WebFluxConfigurer;
@Configuration
@EnableWebFlux
class WebFluxConfig implements WebFluxConfigurer {
@Override
public void addCorsMappings(CorsRegistry corsRegistry) {
corsRegistry.addMapping("/api/**")
.allowedOrigins("https://apps.acme.test:4443")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.maxAge(3600);
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/WebFluxRoutes.java
================================================
package com.acme.backend.springreactive.config;
import com.acme.backend.springreactive.users.UserHandlers;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.reactive.function.server.RouterFunction;
import org.springframework.web.reactive.function.server.RouterFunctions;
import org.springframework.web.reactive.function.server.ServerResponse;
import static org.springframework.web.reactive.function.server.RequestPredicates.GET;
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
@Configuration
class WebFluxRoutes {
@Bean
public RouterFunction route(UserHandlers userHandlers) {
return RouterFunctions.route( //
GET("/api/users/me").and(accept(MediaType.APPLICATION_JSON)), userHandlers::me) //
;
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/config/WebSecurityConfig.java
================================================
package com.acme.backend.springreactive.config;
import com.acme.backend.springreactive.support.keycloak.KeycloakJwtAuthenticationConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.actuate.autoconfigure.security.reactive.EndpointRequest;
import org.springframework.boot.autoconfigure.security.reactive.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.method.configuration.EnableReactiveMethodSecurity;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
/**
* Configuration applied on all web endpoints defined for this
* application. Any configuration on specific resources is applied
* in addition to these global rules.
*/
@EnableWebFluxSecurity
@EnableReactiveMethodSecurity
@RequiredArgsConstructor
class WebSecurityConfig {
private final KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter;
/**
* Configures basic security handler per HTTP session.
*
*
*
JWT converted into Spring token
*
*
* @param http security configuration
*/
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
http
// csrf disabled for testing
.csrf() //
.disable() //
.authorizeExchange() //
// CORS requests
.pathMatchers(HttpMethod.OPTIONS, "/api/**") //
.permitAll() //
.matchers(PathRequest.toStaticResources().atCommonLocations()) //
.permitAll() //
.matchers(EndpointRequest.to("health")) //
.permitAll() //
.matchers(EndpointRequest.to("info")) //
.permitAll().matchers(EndpointRequest.toAnyEndpoint()) //
.permitAll() //
.anyExchange() //
.authenticated() //
.and() //
// Enable OAuth2 Resource Server Support
.oauth2ResourceServer() //
// Enable custom JWT handling
.jwt().jwtAuthenticationConverter(keycloakJwtAuthenticationConverter) //
;
return http.build();
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/support/keycloak/KeycloakAudienceValidator.java
================================================
package com.acme.backend.springreactive.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
/**
* Example class for custom audience (aud) or authorized party (azp) claim validations.
*/
@Component
@RequiredArgsConstructor
class KeycloakAudienceValidator implements OAuth2TokenValidator {
private final OAuth2Error ERROR_INVALID_AUDIENCE = new OAuth2Error("invalid_token", "Invalid audience", null);
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
// String authorizedParty = jwt.getClaimAsString("azp");
//
// if (!keycloakDataServiceProperties.getJwt().getAllowedAudiences().contains(authorizedParty)) {
// return OAuth2TokenValidatorResult.failure(ERROR_INVALID_AUDIENCE);
// }
return OAuth2TokenValidatorResult.success();
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/support/keycloak/KeycloakGrantedAuthoritiesConverter.java
================================================
package com.acme.backend.springreactive.support.keycloak;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Allows to extract granted authorities from a given JWT. The authorities
* are determined by combining the realm (overarching) and client (application-specific)
* roles, and normalizing them (configure them to the default format).
*/
public class KeycloakGrantedAuthoritiesConverter implements Converter> {
private static final Converter> JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER = new JwtGrantedAuthoritiesConverter();
private final String clientId;
private final GrantedAuthoritiesMapper authoritiesMapper;
public KeycloakGrantedAuthoritiesConverter(String clientId, GrantedAuthoritiesMapper authoritiesMapper) {
this.clientId = clientId;
this.authoritiesMapper = authoritiesMapper;
}
@Override
public Collection convert(Jwt jwt) {
Collection authorities = mapKeycloakRolesToAuthorities( //
getRealmRolesFrom(jwt), //
getClientRolesFrom(jwt, clientId) //
);
Collection scopeAuthorities = JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER.convert(jwt);
if(!CollectionUtils.isEmpty(scopeAuthorities)) {
authorities.addAll(scopeAuthorities);
}
return authorities;
}
protected Collection mapKeycloakRolesToAuthorities(Set realmRoles, Set clientRoles) {
List combinedAuthorities = new ArrayList<>();
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(realmRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(clientRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
return combinedAuthorities;
}
protected Set getRealmRolesFrom(Jwt jwt) {
Map realmAccess = jwt.getClaimAsMap("realm_access");
if (CollectionUtils.isEmpty(realmAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Collection realmRoles = (Collection) realmAccess.get("roles");
if (CollectionUtils.isEmpty(realmRoles)) {
return Collections.emptySet();
}
return realmRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
protected Set getClientRolesFrom(Jwt jwt, String clientId) {
Map resourceAccess = jwt.getClaimAsMap("resource_access");
if (CollectionUtils.isEmpty(resourceAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Map> clientAccess = (Map>) resourceAccess.get(clientId);
if (CollectionUtils.isEmpty(clientAccess)) {
return Collections.emptySet();
}
List clientRoles = clientAccess.get("roles");
if (CollectionUtils.isEmpty(clientRoles)) {
return Collections.emptySet();
}
return clientRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
private String normalizeRole(String role) {
return role.replace('-', '_');
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/support/keycloak/KeycloakJwtAuthenticationConverter.java
================================================
package com.acme.backend.springreactive.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import reactor.core.publisher.Mono;
import java.util.Collection;
/**
* Converts a JWT into a Spring authentication token (by extracting
* the username and roles from the claims of the token, delegating
* to the {@link KeycloakGrantedAuthoritiesConverter})
*/
@RequiredArgsConstructor
public class KeycloakJwtAuthenticationConverter implements Converter> {
private Converter> grantedAuthoritiesConverter;
public KeycloakJwtAuthenticationConverter(Converter> grantedAuthoritiesConverter) {
this.grantedAuthoritiesConverter = grantedAuthoritiesConverter;
}
@Override
public Mono convert(Jwt jwt) {
Collection authorities = grantedAuthoritiesConverter.convert(jwt);
String username = getUsernameFrom(jwt);
return Mono.just(new JwtAuthenticationToken(jwt, authorities, username));
}
protected String getUsernameFrom(Jwt jwt) {
if (jwt.hasClaim("preferred_username")) {
return jwt.getClaimAsString("preferred_username");
}
return jwt.getSubject();
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/java/com/acme/backend/springreactive/users/UserHandlers.java
================================================
package com.acme.backend.springreactive.users;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.web.reactive.function.server.ServerRequest;
import org.springframework.web.reactive.function.server.ServerResponse;
import reactor.core.publisher.Mono;
import java.time.Instant;
import java.util.HashMap;
@Slf4j
@Component
public class UserHandlers {
public Mono me(ServerRequest request) {
log.info("### Accessing {}", request.uri());
return request.principal().flatMap(auth -> {
var username = auth.getName();
var data = new HashMap();
data.put("message", "Hello " + username);
data.put("backend", "Spring Boot Reactive");
data.put("datetime", Instant.now());
return ServerResponse.ok().bodyValue(data);
});
}
}
================================================
FILE: apps/backend-api-springboot-reactive/src/main/resources/application.yml
================================================
spring:
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
# deals with single and multi-valued JWT claims
accept-single-value-as-array: true
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${acme.jwt.issuerUri}
jwk-set-uri: ${acme.jwt.issuerUri}/protocol/openid-connect/certs
# Use mock-service jwks-endpoint to obtain public key for testing
# jwk-set-uri: http://localhost:9999/jwks
acme:
jwt:
issuerUri: https://id.acme.test:8443/auth/realms/acme-internal
server:
port: 4943
ssl:
enabled: true
key-store: ../../config/stage/dev/tls/acme.test+1.p12
key-store-password: changeit
key-store-type: PKCS12
================================================
FILE: apps/backend-api-springboot-reactive/src/test/java/com/acme/backend/springreactive/BackendApiSpringbootReactiveAppTests.java
================================================
package com.acme.backend.springreactive;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BackendApiSpringbootReactiveAppTests {
@Test
void contextLoads() {
}
}
================================================
FILE: apps/backend-api-springboot3/.gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
================================================
FILE: apps/backend-api-springboot3/.mvn/wrapper/MavenWrapperDownloader.java
================================================
/*
* Copyright 2007-present the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import java.net.*;
import java.io.*;
import java.nio.channels.*;
import java.util.Properties;
public class MavenWrapperDownloader {
private static final String WRAPPER_VERSION = "0.5.6";
/**
* Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
*/
private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
/**
* Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
* use instead of the default one.
*/
private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
".mvn/wrapper/maven-wrapper.properties";
/**
* Path where the maven-wrapper.jar will be saved to.
*/
private static final String MAVEN_WRAPPER_JAR_PATH =
".mvn/wrapper/maven-wrapper.jar";
/**
* Name of the property which should be used to override the default download url for the wrapper.
*/
private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
public static void main(String args[]) {
System.out.println("- Downloader started");
File baseDirectory = new File(args[0]);
System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
// If the maven-wrapper.properties exists, read it and check if it contains a custom
// wrapperUrl parameter.
File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
String url = DEFAULT_DOWNLOAD_URL;
if(mavenWrapperPropertyFile.exists()) {
FileInputStream mavenWrapperPropertyFileInputStream = null;
try {
mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
Properties mavenWrapperProperties = new Properties();
mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
} catch (IOException e) {
System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
} finally {
try {
if(mavenWrapperPropertyFileInputStream != null) {
mavenWrapperPropertyFileInputStream.close();
}
} catch (IOException e) {
// Ignore ...
}
}
}
System.out.println("- Downloading from: " + url);
File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
if(!outputFile.getParentFile().exists()) {
if(!outputFile.getParentFile().mkdirs()) {
System.out.println(
"- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
}
}
System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
try {
downloadFileFromURL(url, outputFile);
System.out.println("Done");
System.exit(0);
} catch (Throwable e) {
System.out.println("- Error downloading");
e.printStackTrace();
System.exit(1);
}
}
private static void downloadFileFromURL(String urlString, File destination) throws Exception {
if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
String username = System.getenv("MVNW_USERNAME");
char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
Authenticator.setDefault(new Authenticator() {
@Override
protected PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, password);
}
});
}
URL website = new URL(urlString);
ReadableByteChannel rbc;
rbc = Channels.newChannel(website.openStream());
FileOutputStream fos = new FileOutputStream(destination);
fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
fos.close();
rbc.close();
}
}
================================================
FILE: apps/backend-api-springboot3/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.2/apache-maven-3.8.2-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
================================================
FILE: apps/backend-api-springboot3/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`which java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/backend-api-springboot3/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%" == "on" pause
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
exit /B %ERROR_CODE%
================================================
FILE: apps/backend-api-springboot3/pom.xml
================================================
4.0.0org.springframework.bootspring-boot-starter-parent3.4.7com.examplebackend-api-springboot30.0.1-SNAPSHOTbackend-api-springboot3backend-api-springboot317org.springframework.bootspring-boot-starter-oauth2-resource-serverorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-devtoolsruntimetrueorg.projectlomboklombokorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginspring-milestonesSpring Milestoneshttps://repo.spring.io/milestonefalsespring-milestonesSpring Milestoneshttps://repo.spring.io/milestonefalse
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/BackendApiSpringboot3App.java
================================================
package com.acme.backend.springboot.users;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.ConfigurationPropertiesScan;
@SpringBootApplication
@ConfigurationPropertiesScan
public class BackendApiSpringboot3App {
public static void main(String[] args) {
SpringApplication.run(BackendApiSpringboot3App.class, args);
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/config/AcmeServiceProperties.java
================================================
package com.acme.backend.springboot.users.config;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.util.List;
@Getter
@Setter
@Component
@ConfigurationProperties(prefix = "acme")
public class AcmeServiceProperties {
private KeycloakJwtProperties jwt = new KeycloakJwtProperties();
/**
* Specifies JWT client ID, issuer URI and allowed audiences
* for validation
*/
@Getter
@Setter
public static class KeycloakJwtProperties {
private String clientId;
private String issuerUri;
private List allowedAudiences;
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/config/JwtSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import com.acme.backend.springboot.users.support.keycloak.KeycloakGrantedAuthoritiesConverter;
import com.acme.backend.springboot.users.support.keycloak.KeycloakJwtAuthenticationConverter;
import org.springframework.boot.autoconfigure.security.oauth2.resource.OAuth2ResourceServerProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.core.DelegatingOAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.JwtIssuerValidator;
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Set;
/**
* Configures JWT handling (decoder and validator)
*/
@Configuration
class JwtSecurityConfig {
/**
* Configures a decoder with the specified validators (validation key fetched from JWKS endpoint)
*
* @param validators validators for the given key
* @param properties key properties (provides JWK location)
* @return the decoder bean
*/
@Bean
JwtDecoder jwtDecoder(List> validators, OAuth2ResourceServerProperties properties) {
NimbusJwtDecoder jwtDecoder = NimbusJwtDecoder //
.withJwkSetUri(properties.getJwt().getJwkSetUri()) //
.jwsAlgorithms(algs -> algs.addAll(Set.of(SignatureAlgorithm.RS256, SignatureAlgorithm.ES256))) //
.build();
jwtDecoder.setJwtValidator(new DelegatingOAuth2TokenValidator<>(validators));
return jwtDecoder;
}
/**
* Configures the token validator. Specifies two additional validation constraints:
*
* * Timestamp on the token is still valid
* * The issuer is the expected entity
*
* @param properties JWT resource specification
* @return token validator
*/
@Bean
OAuth2TokenValidator defaultTokenValidator(OAuth2ResourceServerProperties properties) {
List> validators = new ArrayList<>();
validators.add(new JwtTimestampValidator());
validators.add(new JwtIssuerValidator(properties.getJwt().getIssuerUri()));
return new DelegatingOAuth2TokenValidator<>(validators);
}
@Bean
KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter(Converter> authoritiesConverter) {
return new KeycloakJwtAuthenticationConverter(authoritiesConverter);
}
@Bean
Converter> keycloakGrantedAuthoritiesConverter(GrantedAuthoritiesMapper authoritiesMapper, AcmeServiceProperties acmeServiceProperties) {
String clientId = acmeServiceProperties.getJwt().getClientId();
return new KeycloakGrantedAuthoritiesConverter(clientId, authoritiesMapper);
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/config/MethodSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import lombok.RequiredArgsConstructor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.core.authority.mapping.SimpleAuthorityMapper;
/**
* Enables security annotations via like {@link org.springframework.security.access.prepost.PreAuthorize} and
* {@link org.springframework.security.access.prepost.PostAuthorize} annotations per-method.
*/
@Configuration
@RequiredArgsConstructor
@EnableMethodSecurity
class MethodSecurityConfig {
private final ApplicationContext applicationContext;
private final PermissionEvaluator permissionEvaluator;
@Bean
MethodSecurityExpressionHandler customMethodSecurityExpressionHandler() {
var expressionHandler = new DefaultMethodSecurityExpressionHandler();
expressionHandler.setApplicationContext(applicationContext);
expressionHandler.setPermissionEvaluator(permissionEvaluator);
return expressionHandler;
}
@Bean
GrantedAuthoritiesMapper keycloakAuthoritiesMapper() {
var mapper = new SimpleAuthorityMapper();
mapper.setConvertToUpperCase(true);
return mapper;
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/config/WebSecurityConfig.java
================================================
package com.acme.backend.springboot.users.config;
import com.acme.backend.springboot.users.support.access.AccessController;
import com.acme.backend.springboot.users.support.keycloak.KeycloakJwtAuthenticationConverter;
import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.List;
/**
* Configuration applied on all web endpoints defined for this
* application. Any configuration on specific resources is applied
* in addition to these global rules.
*/
@Configuration
@RequiredArgsConstructor
class WebSecurityConfig {
private final KeycloakJwtAuthenticationConverter keycloakJwtAuthenticationConverter;
/**
* Configures basic security handler per HTTP session.
*
*
*
Stateless session (no session kept server-side)
*
CORS set up
*
Require the role "ACCESS" for all api paths
*
JWT converted into Spring token
*
*
* @param http security configuration
* @throws Exception any error
*/
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.sessionManagement(smc -> {
smc.sessionCreationPolicy(SessionCreationPolicy.STATELESS);
});
http.cors(this::configureCors);
http.authorizeHttpRequests(ahrc -> {
// declarative route configuration
// .mvcMatchers("/api").hasAuthority("ROLE_ACCESS")
ahrc.requestMatchers("/api/**").access(AccessController::checkAccess);
// add additional routes
ahrc.anyRequest().fullyAuthenticated(); //
});
http.oauth2ResourceServer(arsc -> {
arsc.jwt(jc -> {
jc.jwtAuthenticationConverter(keycloakJwtAuthenticationConverter);
});
});
return http.build();
}
@Bean
AccessController accessController() {
return new AccessController();
}
/**
* Configures CORS to allow requests from localhost:30000
*
* @param cors mutable cors configuration
*/
protected void configureCors(CorsConfigurer cors) {
UrlBasedCorsConfigurationSource defaultUrlBasedCorsConfigSource = new UrlBasedCorsConfigurationSource();
CorsConfiguration corsConfiguration = new CorsConfiguration().applyPermitDefaultValues();
corsConfiguration.addAllowedOrigin("https://apps.acme.test:4443");
List.of("GET", "POST", "PUT", "DELETE").forEach(corsConfiguration::addAllowedMethod);
defaultUrlBasedCorsConfigSource.registerCorsConfiguration("/api/**", corsConfiguration);
cors.configurationSource(req -> {
CorsConfiguration config = new CorsConfiguration();
config = config.combine(defaultUrlBasedCorsConfigSource.getCorsConfiguration(req));
// check if request Header "origin" is in white-list -> dynamically generate cors config
return config;
});
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/access/AccessController.java
================================================
package com.acme.backend.springboot.users.support.access;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.authorization.AuthorizationDecision;
import org.springframework.security.core.Authentication;
import org.springframework.security.web.access.intercept.RequestAuthorizationContext;
import java.util.function.Supplier;
/**
* Example for generic custom access checks on request level.
*/
@Slf4j
public class AccessController {
private static final AuthorizationDecision GRANTED = new AuthorizationDecision(true);
private static final AuthorizationDecision DENIED = new AuthorizationDecision(false);
public static AuthorizationDecision checkAccess(Supplier authentication, RequestAuthorizationContext requestContext) {
var auth = authentication.get();
log.info("Check access for username={} path={}", auth.getName(), requestContext.getRequest().getRequestURI());
return GRANTED;
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakAudienceValidator.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.security.oauth2.core.OAuth2Error;
import org.springframework.security.oauth2.core.OAuth2TokenValidator;
import org.springframework.security.oauth2.core.OAuth2TokenValidatorResult;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.stereotype.Component;
/**
* Example class for custom audience (aud) or authorized party (azp) claim validations.
*/
@Component
@RequiredArgsConstructor
class KeycloakAudienceValidator implements OAuth2TokenValidator {
private final OAuth2Error ERROR_INVALID_AUDIENCE = new OAuth2Error("invalid_token", "Invalid audience", null);
@Override
public OAuth2TokenValidatorResult validate(Jwt jwt) {
// String authorizedParty = jwt.getClaimAsString("azp");
//
// if (!keycloakDataServiceProperties.getJwt().getAllowedAudiences().contains(authorizedParty)) {
// return OAuth2TokenValidatorResult.failure(ERROR_INVALID_AUDIENCE);
// }
return OAuth2TokenValidatorResult.success();
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakGrantedAuthoritiesConverter.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtGrantedAuthoritiesConverter;
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Allows to extract granted authorities from a given JWT. The authorities
* are determined by combining the realm (overarching) and client (application-specific)
* roles, and normalizing them (configure them to the default format).
*/
public class KeycloakGrantedAuthoritiesConverter implements Converter> {
private static final Converter> JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER = new JwtGrantedAuthoritiesConverter();
private final String clientId;
private final GrantedAuthoritiesMapper authoritiesMapper;
public KeycloakGrantedAuthoritiesConverter(String clientId, GrantedAuthoritiesMapper authoritiesMapper) {
this.clientId = clientId;
this.authoritiesMapper = authoritiesMapper;
}
@Override
public Collection convert(Jwt jwt) {
Collection authorities = mapKeycloakRolesToAuthorities( //
getRealmRolesFrom(jwt), //
getClientRolesFrom(jwt, clientId) //
);
Collection scopeAuthorities = JWT_SCOPE_GRANTED_AUTHORITIES_CONVERTER.convert(jwt);
if(!CollectionUtils.isEmpty(scopeAuthorities)) {
authorities.addAll(scopeAuthorities);
}
return authorities;
}
protected Collection mapKeycloakRolesToAuthorities(Set realmRoles, Set clientRoles) {
List combinedAuthorities = new ArrayList<>();
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(realmRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
combinedAuthorities.addAll(authoritiesMapper.mapAuthorities(clientRoles.stream() //
.map(SimpleGrantedAuthority::new) //
.collect(Collectors.toList())));
return combinedAuthorities;
}
protected Set getRealmRolesFrom(Jwt jwt) {
Map realmAccess = jwt.getClaimAsMap("realm_access");
if (CollectionUtils.isEmpty(realmAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Collection realmRoles = (Collection) realmAccess.get("roles");
if (CollectionUtils.isEmpty(realmRoles)) {
return Collections.emptySet();
}
return realmRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
protected Set getClientRolesFrom(Jwt jwt, String clientId) {
Map resourceAccess = jwt.getClaimAsMap("resource_access");
if (CollectionUtils.isEmpty(resourceAccess)) {
return Collections.emptySet();
}
@SuppressWarnings("unchecked")
Map> clientAccess = (Map>) resourceAccess.get(clientId);
if (CollectionUtils.isEmpty(clientAccess)) {
return Collections.emptySet();
}
List clientRoles = clientAccess.get("roles");
if (CollectionUtils.isEmpty(clientRoles)) {
return Collections.emptySet();
}
return clientRoles.stream().map(this::normalizeRole).collect(Collectors.toSet());
}
private String normalizeRole(String role) {
return role.replace('-', '_');
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/keycloak/KeycloakJwtAuthenticationConverter.java
================================================
package com.acme.backend.springboot.users.support.keycloak;
import lombok.RequiredArgsConstructor;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import java.util.Collection;
/**
* Converts a JWT into a Spring authentication token (by extracting
* the username and roles from the claims of the token, delegating
* to the {@link KeycloakGrantedAuthoritiesConverter})
*/
@RequiredArgsConstructor
public class KeycloakJwtAuthenticationConverter implements Converter {
private Converter> grantedAuthoritiesConverter;
public KeycloakJwtAuthenticationConverter(Converter> grantedAuthoritiesConverter) {
this.grantedAuthoritiesConverter = grantedAuthoritiesConverter;
}
@Override
public JwtAuthenticationToken convert(Jwt jwt) {
Collection authorities = grantedAuthoritiesConverter.convert(jwt);
String username = getUsernameFrom(jwt);
return new JwtAuthenticationToken(jwt, authorities, username);
}
protected String getUsernameFrom(Jwt jwt) {
if (jwt.hasClaim("preferred_username")) {
return jwt.getClaimAsString("preferred_username");
}
return jwt.getSubject();
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/permissions/DefaultPermissionEvaluator.java
================================================
package com.acme.backend.springboot.users.support.permissions;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.PermissionEvaluator;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;
import java.io.Serializable;
/**
* Custom {@link PermissionEvaluator} for method level permission checks.
*
* @see com.acme.backend.springboot.users.config.MethodSecurityConfig
*/
@Slf4j
@Component
@RequiredArgsConstructor
class DefaultPermissionEvaluator implements PermissionEvaluator {
@Override
public boolean hasPermission(Authentication auth, Object targetDomainObject, Object permission) {
log.info("check permission user={} target={} permission={}", auth.getName(), targetDomainObject, permission);
// TODO implement sophisticated permission check here
return true;
}
@Override
public boolean hasPermission(Authentication auth, Serializable targetId, String targetType, Object permission) {
DomainObjectReference dor = new DomainObjectReference(targetType, targetId.toString());
return hasPermission(auth, dor, permission);
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/support/permissions/DomainObjectReference.java
================================================
package com.acme.backend.springboot.users.support.permissions;
import lombok.Data;
/**
* Defines a single domain object by a type and name to look up
*/
@Data
public class DomainObjectReference {
private final String type;
private final String id;
}
================================================
FILE: apps/backend-api-springboot3/src/main/java/com/acme/backend/springboot/users/web/UsersController.java
================================================
package com.acme.backend.springboot.users.web;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.ServletWebRequest;
import java.time.Instant;
import java.util.HashMap;
import java.util.Map;
@Slf4j
@RestController
@RequestMapping("/api/users")
class UsersController {
@GetMapping("/me")
public Object me(ServletWebRequest request, Authentication authentication) {
log.info("### Accessing {}", request.getRequest().getRequestURI());
Object username = authentication.getName();
Map data = new HashMap<>();
data.put("message", "Hello " + username);
data.put("backend", "Spring Boot 3");
data.put("datetime", Instant.now());
return data;
}
}
================================================
FILE: apps/backend-api-springboot3/src/main/resources/application.yml
================================================
spring:
jackson:
serialization:
write-dates-as-timestamps: false
deserialization:
# deals with single and multi-valued JWT claims
accept-single-value-as-array: true
security:
oauth2:
resourceserver:
jwt:
issuer-uri: ${acme.jwt.issuerUri}
jwk-set-uri: ${acme.jwt.issuerUri}/protocol/openid-connect/certs
# Use mock-service jwks-endpoint to obtain public key for testing
# jwk-set-uri: http://localhost:9999/jwks
acme:
jwt:
issuerUri: https://id.acme.test:8443/auth/realms/acme-internal
server:
port: 4623
ssl:
enabled: true
key-store: ../../config/stage/dev/tls/acme.test+1.p12
key-store-password: changeit
key-store-type: PKCS12
================================================
FILE: apps/backend-api-springboot3/src/test/java/com/acme/backend/springboot/users/BackendApiSpringboot3AppTests.java
================================================
package com.acme.backend.springboot.users;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class BackendApiSpringboot3AppTests {
@Test
void contextLoads() {
}
}
================================================
FILE: apps/bff-springboot/.gitignore
================================================
HELP.md
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
================================================
FILE: apps/bff-springboot/.mvn/wrapper/maven-wrapper.properties
================================================
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.4/apache-maven-3.8.4-bin.zip
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar
================================================
FILE: apps/bff-springboot/mvnw
================================================
#!/bin/sh
# ----------------------------------------------------------------------------
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
# JAVA_HOME - location of a JDK home dir
#
# Optional ENV vars
# -----------------
# M2_HOME - location of maven2's installed home dir
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
# e.g. to debug Maven itself, use
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
# ----------------------------------------------------------------------------
if [ -z "$MAVEN_SKIP_RC" ] ; then
if [ -f /usr/local/etc/mavenrc ] ; then
. /usr/local/etc/mavenrc
fi
if [ -f /etc/mavenrc ] ; then
. /etc/mavenrc
fi
if [ -f "$HOME/.mavenrc" ] ; then
. "$HOME/.mavenrc"
fi
fi
# OS specific support. $var _must_ be set to either true or false.
cygwin=false;
darwin=false;
mingw=false
case "`uname`" in
CYGWIN*) cygwin=true ;;
MINGW*) mingw=true;;
Darwin*) darwin=true
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
if [ -z "$JAVA_HOME" ]; then
if [ -x "/usr/libexec/java_home" ]; then
export JAVA_HOME="`/usr/libexec/java_home`"
else
export JAVA_HOME="/Library/Java/Home"
fi
fi
;;
esac
if [ -z "$JAVA_HOME" ] ; then
if [ -r /etc/gentoo-release ] ; then
JAVA_HOME=`java-config --jre-home`
fi
fi
if [ -z "$M2_HOME" ] ; then
## resolve links - $0 may be a link to maven's home
PRG="$0"
# need this for relative symlinks
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG="`dirname "$PRG"`/$link"
fi
done
saveddir=`pwd`
M2_HOME=`dirname "$PRG"`/..
# make it fully qualified
M2_HOME=`cd "$M2_HOME" && pwd`
cd "$saveddir"
# echo Using m2 at $M2_HOME
fi
# For Cygwin, ensure paths are in UNIX format before anything is touched
if $cygwin ; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --unix "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
fi
# For Mingw, ensure paths are in UNIX format before anything is touched
if $mingw ; then
[ -n "$M2_HOME" ] &&
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
fi
if [ -z "$JAVA_HOME" ]; then
javaExecutable="`which javac`"
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
# readlink(1) is not available as standard on Solaris 10.
readLink=`which readlink`
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
if $darwin ; then
javaHome="`dirname \"$javaExecutable\"`"
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
else
javaExecutable="`readlink -f \"$javaExecutable\"`"
fi
javaHome="`dirname \"$javaExecutable\"`"
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
JAVA_HOME="$javaHome"
export JAVA_HOME
fi
fi
fi
if [ -z "$JAVACMD" ] ; then
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
else
JAVACMD="`\\unset -f command; \\command -v java`"
fi
fi
if [ ! -x "$JAVACMD" ] ; then
echo "Error: JAVA_HOME is not defined correctly." >&2
echo " We cannot execute $JAVACMD" >&2
exit 1
fi
if [ -z "$JAVA_HOME" ] ; then
echo "Warning: JAVA_HOME environment variable is not set."
fi
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
# traverses directory structure from process work directory to filesystem root
# first directory with .mvn subdirectory is considered project base directory
find_maven_basedir() {
if [ -z "$1" ]
then
echo "Path not specified to find_maven_basedir"
return 1
fi
basedir="$1"
wdir="$1"
while [ "$wdir" != '/' ] ; do
if [ -d "$wdir"/.mvn ] ; then
basedir=$wdir
break
fi
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
if [ -d "${wdir}" ]; then
wdir=`cd "$wdir/.."; pwd`
fi
# end of workaround
done
echo "${basedir}"
}
# concatenates all lines of a file
concat_lines() {
if [ -f "$1" ]; then
echo "$(tr -s '\n' ' ' < "$1")"
fi
}
BASE_DIR=`find_maven_basedir "$(pwd)"`
if [ -z "$BASE_DIR" ]; then
exit 1;
fi
##########################################################################################
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
# This allows using the maven wrapper in projects that prohibit checking in binary data.
##########################################################################################
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found .mvn/wrapper/maven-wrapper.jar"
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
fi
if [ -n "$MVNW_REPOURL" ]; then
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
else
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
fi
while IFS="=" read key value; do
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
esac
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
if [ "$MVNW_VERBOSE" = true ]; then
echo "Downloading from: $jarUrl"
fi
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
if $cygwin; then
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
fi
if command -v wget > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found wget ... using wget"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
else
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath"
fi
elif command -v curl > /dev/null; then
if [ "$MVNW_VERBOSE" = true ]; then
echo "Found curl ... using curl"
fi
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
curl -o "$wrapperJarPath" "$jarUrl" -f
else
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
fi
else
if [ "$MVNW_VERBOSE" = true ]; then
echo "Falling back to using Java to download"
fi
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
# For Cygwin, switch paths to Windows format before running javac
if $cygwin; then
javaClass=`cygpath --path --windows "$javaClass"`
fi
if [ -e "$javaClass" ]; then
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Compiling MavenWrapperDownloader.java ..."
fi
# Compiling the Java class
("$JAVA_HOME/bin/javac" "$javaClass")
fi
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
# Running the downloader
if [ "$MVNW_VERBOSE" = true ]; then
echo " - Running MavenWrapperDownloader.java ..."
fi
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
fi
fi
fi
fi
##########################################################################################
# End of extension
##########################################################################################
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
fi
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
# For Cygwin, switch paths to Windows format before running java
if $cygwin; then
[ -n "$M2_HOME" ] &&
M2_HOME=`cygpath --path --windows "$M2_HOME"`
[ -n "$JAVA_HOME" ] &&
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
[ -n "$CLASSPATH" ] &&
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
# Provide a "standardized" way to retrieve the CLI args that will
# work with both Windows and non-Windows executions.
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
export MAVEN_CMD_LINE_ARGS
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
$MAVEN_OPTS \
$MAVEN_DEBUG_OPTS \
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
"-Dmaven.home=${M2_HOME}" \
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
================================================
FILE: apps/bff-springboot/mvnw.cmd
================================================
@REM ----------------------------------------------------------------------------
@REM Licensed to the Apache Software Foundation (ASF) under one
@REM or more contributor license agreements. See the NOTICE file
@REM distributed with this work for additional information
@REM regarding copyright ownership. The ASF licenses this file
@REM to you under the Apache License, Version 2.0 (the
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
@REM https://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
@REM KIND, either express or implied. See the License for the
@REM specific language governing permissions and limitations
@REM under the License.
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@REM
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
@REM ----------------------------------------------------------------------------
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
@echo off
@REM set title of command window
title %0
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
@REM Execute a user defined script before this one
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %*
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %*
:skipRcPre
@setlocal
set ERROR_CODE=0
@REM To isolate internal variables from possible post scripts, we use another setlocal
@setlocal
@REM ==== START VALIDATION ====
if not "%JAVA_HOME%" == "" goto OkJHome
echo.
echo Error: JAVA_HOME not found in your environment. >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
:OkJHome
if exist "%JAVA_HOME%\bin\java.exe" goto init
echo.
echo Error: JAVA_HOME is set to an invalid directory. >&2
echo JAVA_HOME = "%JAVA_HOME%" >&2
echo Please set the JAVA_HOME variable in your environment to match the >&2
echo location of your Java installation. >&2
echo.
goto error
@REM ==== END VALIDATION ====
:init
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
@REM Fallback to current working directory if not found.
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
set EXEC_DIR=%CD%
set WDIR=%EXEC_DIR%
:findBaseDir
IF EXIST "%WDIR%"\.mvn goto baseDirFound
cd ..
IF "%WDIR%"=="%CD%" goto baseDirNotFound
set WDIR=%CD%
goto findBaseDir
:baseDirFound
set MAVEN_PROJECTBASEDIR=%WDIR%
cd "%EXEC_DIR%"
goto endDetectBaseDir
:baseDirNotFound
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
cd "%EXEC_DIR%"
:endDetectBaseDir
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
@setlocal EnableExtensions EnableDelayedExpansion
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
)
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
if exist %WRAPPER_JAR% (
if "%MVNW_VERBOSE%" == "true" (
echo Found %WRAPPER_JAR%
)
) else (
if not "%MVNW_REPOURL%" == "" (
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar"
)
if "%MVNW_VERBOSE%" == "true" (
echo Couldn't find %WRAPPER_JAR%, downloading it ...
echo Downloading from: %DOWNLOAD_URL%
)
powershell -Command "&{"^
"$webclient = new-object System.Net.WebClient;"^
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
"}"^
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
"}"
if "%MVNW_VERBOSE%" == "true" (
echo Finished downloading %WRAPPER_JAR%
)
)
@REM End of extension
@REM Provide a "standardized" way to retrieve the CLI args that will
@REM work with both Windows and non-Windows executions.
set MAVEN_CMD_LINE_ARGS=%*
%MAVEN_JAVA_EXE% ^
%JVM_CONFIG_MAVEN_PROPS% ^
%MAVEN_OPTS% ^
%MAVEN_DEBUG_OPTS% ^
-classpath %WRAPPER_JAR% ^
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
:error
set ERROR_CODE=1
:end
@endlocal & set ERROR_CODE=%ERROR_CODE%
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost
@REM check for post script, once with legacy .bat ending and once with .cmd ending
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat"
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd"
:skipRcPost
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
if "%MAVEN_BATCH_PAUSE%"=="on" pause
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE%
cmd /C exit /B %ERROR_CODE%
================================================
FILE: apps/bff-springboot/pom.xml
================================================
4.0.0org.springframework.bootspring-boot-starter-parent2.7.14com.github.thomasdarimont.keycloakbff-springboot0.0.1-SNAPSHOTbff-springbootbff-springboot171.18.38org.springframework.bootspring-boot-starter-oauth2-clientorg.springframework.bootspring-boot-starter-weborg.springframework.bootspring-boot-starter-thymeleaforg.thymeleaf.extrasthymeleaf-extras-springsecurity5org.springframework.bootspring-boot-starter-data-redisorg.springframework.sessionspring-session-data-redisio.lettucelettuce-coreorg.springframework.bootspring-boot-devtoolsruntimetrueio.micrometermicrometer-registry-prometheusruntimeorg.projectlomboklomboktrueorg.springframework.bootspring-boot-starter-testtestorg.springframework.bootspring-boot-maven-pluginorg.projectlomboklombok
================================================
FILE: apps/bff-springboot/src/main/java/com/github/thomasdarimont/apps/bff/BffApp.java
================================================
package com.github.thomasdarimont.apps.bff;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class BffApp {
public static void main(String[] args) {
SpringApplication.run(BffApp.class, args);
}
}
================================================
FILE: apps/bff-springboot/src/main/java/com/github/thomasdarimont/apps/bff/api/UsersResource.java
================================================
package com.github.thomasdarimont.apps.bff.api;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.oidc.OidcUserInfo;
import org.springframework.security.oauth2.core.oidc.user.OidcUserAuthority;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.client.RestTemplate;
import java.util.LinkedHashMap;
import java.util.Map;
@RestController
@RequestMapping("/api/users")
class UsersResource {
private final RestTemplate oauthRestTemplate;
public UsersResource(@Qualifier("oauth") RestTemplate oauthRestTemplate) {
this.oauthRestTemplate = oauthRestTemplate;
}
@GetMapping("/me")
public ResponseEntity