commit 0f38d49b664812bda14cb423ce7ed16b32b44ce0 Author: lingxiao865 <1060369102@qq.com> Date: Tue Feb 10 08:22:09 2026 +0800 1 diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/.gitignore @@ -0,0 +1,33 @@ +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/ diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/a-service/.gitattributes b/a-service/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/a-service/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/a-service/.gitignore b/a-service/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/a-service/.gitignore @@ -0,0 +1,33 @@ +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/ diff --git a/a-service/.mvn/wrapper/maven-wrapper.properties b/a-service/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/a-service/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/a-service/Dockerfile b/a-service/Dockerfile new file mode 100644 index 0000000..4e17874 --- /dev/null +++ b/a-service/Dockerfile @@ -0,0 +1,37 @@ +FROM openjdk:25-jdk-slim + +WORKDIR /app + +COPY target/*.jar app.jar + +EXPOSE 8091 + +ENTRYPOINT ["java","-jar", "app.jar"] + +## Perform the extraction in a separate builder container +#FROM bellsoft/liberica-openjre-debian:25-cds AS builder +#WORKDIR /builder +## This points to the built jar file in the target folder +## Adjust this to 'build/libs/*.jar' if you're using Gradle +#ARG JAR_FILE=target/*.jar +## Copy the jar file to the working directory and rename it to application.jar +#COPY ${JAR_FILE} application.jar +## Extract the jar file using an efficient layout +#RUN java -Djarmode=tools -jar application.jar extract --layers --destination extracted +# +## This is the runtime container +#FROM bellsoft/liberica-openjre-debian:25-cds +#WORKDIR /application +## Copy the extracted jar contents from the builder container into the working directory in the runtime container +## Every copy step creates a new docker layer +## This allows docker to only pull the changes it really needs +#COPY --from=builder /builder/extracted/dependencies/ ./ +#COPY --from=builder /builder/extracted/spring-boot-loader/ ./ +#COPY --from=builder /builder/extracted/snapshot-dependencies/ ./ +#COPY --from=builder /builder/extracted/application/ ./ +## Execute the AOT cache training run +#RUN java -XX:AOTCacheOutput=app.aot -Dspring.context.exit=onRefresh -jar application.jar +## Start the application jar with AOT cache enabled - this is not the uber jar used by the builder +## This jar only contains application code and references to the extracted jar files +## This layout is efficient to start up and AOT cache friendly +#ENTRYPOINT ["java", "-XX:AOTCache=app.aot", "-jar", "application.jar"] \ No newline at end of file diff --git a/a-service/mvnw b/a-service/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/a-service/mvnw @@ -0,0 +1,295 @@ +#!/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.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/a-service/mvnw.cmd b/a-service/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/a-service/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@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.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/a-service/pom.xml b/a-service/pom.xml new file mode 100644 index 0000000..ae7cfff --- /dev/null +++ b/a-service/pom.xml @@ -0,0 +1,63 @@ + + + 4.0.0 + + com + lingxiao + 0.0.1-SNAPSHOT + ../pom.xml + + + a-service + a-service + a-service + + + + + org.springframework.boot + spring-boot-starter-webflux + + + + org.projectlombok + lombok + provided + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + true + + + + org.projectlombok + lombok + + + + + + + + diff --git a/a-service/src/main/java/com/example/demo001/Demo001Application.java b/a-service/src/main/java/com/example/demo001/Demo001Application.java new file mode 100644 index 0000000..7d7ff29 --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/Demo001Application.java @@ -0,0 +1,17 @@ +package com.example.demo001; + +import com.example.demo001.service.OAuth2TokenClient; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.web.service.registry.HttpServiceGroup; +import org.springframework.web.service.registry.ImportHttpServices; + +@SpringBootApplication +@ImportHttpServices(group = "echo", types = OAuth2TokenClient.class, clientType = HttpServiceGroup.ClientType.WEB_CLIENT) +public class Demo001Application { + + public static void main(String[] args) { + SpringApplication.run(Demo001Application.class, args); + } + +} diff --git a/a-service/src/main/java/com/example/demo001/config/OAuth2ClientProperties.java b/a-service/src/main/java/com/example/demo001/config/OAuth2ClientProperties.java new file mode 100644 index 0000000..280aca4 --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/config/OAuth2ClientProperties.java @@ -0,0 +1,14 @@ +package com.example.demo001.config; + +import lombok.Data; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.context.annotation.Configuration; + +@Configuration +@ConfigurationProperties(prefix = "oauth2.client") +@Data +public class OAuth2ClientProperties { + private String clientId; + private String clientSecret; + +} \ No newline at end of file diff --git a/a-service/src/main/java/com/example/demo001/entity/CodeExchangeRequest.java b/a-service/src/main/java/com/example/demo001/entity/CodeExchangeRequest.java new file mode 100644 index 0000000..1eb98ed --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/entity/CodeExchangeRequest.java @@ -0,0 +1,12 @@ +package com.example.demo001.entity; + +import lombok.Data; + +@Data +public class CodeExchangeRequest { + + private String code; + private String redirect_uri; + private String code_verifier; + +} diff --git a/a-service/src/main/java/com/example/demo001/service/OAuth2TokenClient.java b/a-service/src/main/java/com/example/demo001/service/OAuth2TokenClient.java new file mode 100644 index 0000000..f3b86b3 --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/service/OAuth2TokenClient.java @@ -0,0 +1,34 @@ +package com.example.demo001.service; + +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.service.annotation.GetExchange; +import org.springframework.web.service.annotation.HttpExchange; +import org.springframework.web.service.annotation.PostExchange; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@HttpExchange("http://auth-service:9000") +//@HttpExchange("http://localhost:9000") +public interface OAuth2TokenClient { + + @PostExchange("/oauth2/token") + Mono> refreshToken( + @RequestHeader("Authorization") String authHeader, + @RequestBody MultiValueMap formData + ); + + @GetExchange("/userinfo") + Mono> userinfo(@RequestHeader("Authorization") String authHeader); + + @PostExchange("/oauth2/revoke") + Mono logout( + @RequestHeader("Authorization") String authHeader, + @RequestBody MultiValueMap formData + ); + +} \ No newline at end of file diff --git a/a-service/src/main/java/com/example/demo001/web/AController.java b/a-service/src/main/java/com/example/demo001/web/AController.java new file mode 100644 index 0000000..821e388 --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/web/AController.java @@ -0,0 +1,29 @@ +package com.example.demo001.web; + + +import com.example.demo001.service.OAuth2TokenClient; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.CookieValue; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import reactor.core.publisher.Mono; + +import java.util.Map; + +@RestController +@RequiredArgsConstructor +public class AController { + + private final OAuth2TokenClient tokenClient; + + @GetMapping("/userinfo") + public Mono>> userinfo(@CookieValue("access_token") String accessToken) { + return tokenClient.userinfo("Bearer " + accessToken) + .map(ResponseEntity::ok) + .onErrorReturn(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()); + } + + +} diff --git a/a-service/src/main/java/com/example/demo001/web/TestController.java b/a-service/src/main/java/com/example/demo001/web/TestController.java new file mode 100644 index 0000000..69d9c5b --- /dev/null +++ b/a-service/src/main/java/com/example/demo001/web/TestController.java @@ -0,0 +1,158 @@ +package com.example.demo001.web; + +import com.example.demo001.config.OAuth2ClientProperties; +import com.example.demo001.entity.CodeExchangeRequest; +import com.example.demo001.service.OAuth2TokenClient; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpCookie; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseCookie; +import org.springframework.http.ResponseEntity; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.http.server.reactive.ServerHttpResponse; +import org.springframework.util.LinkedMultiValueMap; +import org.springframework.util.MultiValueMap; +import org.springframework.web.bind.annotation.*; +import reactor.core.publisher.Mono; + +import java.nio.charset.StandardCharsets; +import java.util.Base64; +import java.util.Map; +import java.util.Objects; + +@Slf4j +@RestController +@RequiredArgsConstructor +public class TestController { + + + private final OAuth2TokenClient tokenClient; + private final OAuth2ClientProperties oauth2Props; // 包含 clientId/clientSecret + + @PostMapping("/logout") + public Mono> logout(ServerHttpRequest request, ServerHttpResponse response) { + // 1. 安全获取 Cookie,防止 NullPointerException + HttpCookie refreshCookie = request.getCookies().getFirst("refresh_token"); + + // 定义统一的清理动作 + Runnable clearAction = () -> { + response.addCookie(ResponseCookie.from("access_token", "").path("/").maxAge(0).build()); + // 关键:这里的路径必须与上面获取令牌时设定的 /api/auth 完全一致 + response.addCookie(ResponseCookie.from("refresh_token", "").path("/api/auth").maxAge(0).build()); + }; + if (refreshCookie == null) { + clearAction.run(); + return Mono.just(ResponseEntity.ok().build()); + } + + // 2. 构造认证头和表单数据 + String credentials = oauth2Props.getClientId() + ":" + oauth2Props.getClientSecret(); + String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); + + // 撤销授权服务器端的令牌状态 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("token", refreshCookie.getValue()); + formData.add("token_type_hint", "refresh_token"); + + // 3. 执行撤销并正确处理 Mono 链 + return tokenClient.logout(authHeader, formData) + .then(Mono.fromRunnable(() -> clearCookies(response))) // 执行清理 + .then(Mono.just(ResponseEntity.ok().build())); // 明确指定 Void 泛型 + } + + /** + * 抽取统一的 Cookie 清理逻辑 + */ + private void clearCookies(ServerHttpResponse response) { + // 注意:path 必须与登录时设置的完全一致,且不能带通配符 * + ResponseCookie clearAccess = ResponseCookie.from("access_token", "").httpOnly(true).secure(true).maxAge(0).path("/").build(); + + // 建议将 path 从 "/api/auth/*" 改为授权中心的基础路径,如 "/api/auth" + ResponseCookie clearRefresh = ResponseCookie.from("refresh_token", "").httpOnly(true).secure(true).maxAge(0).path("/api/auth").build(); + + response.addCookie(clearAccess); + response.addCookie(clearRefresh); + } + + // 前端调用此接口,传入 code 和 redirect_uri + @PostMapping("/token") + public Mono> exchangeCodeForToken(CodeExchangeRequest request, ServerHttpResponse response) { + + // 构造 Basic Auth + String credentials = oauth2Props.getClientId() + ":" + oauth2Props.getClientSecret(); + String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); + + // 构造表单 + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("grant_type", "authorization_code"); + formData.add("code", request.getCode()); + formData.add("redirect_uri", request.getRedirect_uri()); + if (request.getRedirect_uri() != null) { + formData.add("code_verifier", request.getCode_verifier()); + } + + return tokenClient.refreshToken(authHeader, formData).doOnNext(tokens -> { + String accessToken = (String) tokens.get("access_token"); + String refreshToken = (String) tokens.get("refresh_token"); + Integer expiresIn = (Integer) tokens.get("expires_in"); + + // 创建 access_token Cookie + ResponseCookie accessTokenCookie = ResponseCookie.from("access_token", accessToken).httpOnly(true).secure(true).sameSite("Strict").path("/").maxAge(expiresIn != null ? expiresIn : 900).build(); + + // 创建 refresh_token Cookie + ResponseCookie refreshTokenCookie = ResponseCookie.from("refresh_token", refreshToken).httpOnly(true).secure(true).sameSite("Strict").path("/api/auth") +// .path("/api/auth/logout") + .maxAge(60 * 60 * 24 * 7) // 7 天 + .build(); + + // 添加到响应 + response.addCookie(accessTokenCookie); + response.addCookie(refreshTokenCookie); + }).then(Mono.just(ResponseEntity.ok().build())).onErrorResume(throwable -> { + log.error(throwable.getMessage()); + // 记录错误日志 + return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()); +// return Mono.just(ResponseEntity.status(401).build()); + }); + } + + @PostMapping("/refresh") + public Mono> refreshToken(ServerHttpRequest request, ServerHttpResponse response) { + try { + String refreshToken = Objects.requireNonNull(request.getCookies().getFirst("refresh_token")).getValue(); + + String credentials = oauth2Props.getClientId() + ":" + oauth2Props.getClientSecret(); + String authHeader = "Basic " + Base64.getEncoder().encodeToString(credentials.getBytes(StandardCharsets.UTF_8)); + + MultiValueMap formData = new LinkedMultiValueMap<>(); + formData.add("grant_type", "refresh_token"); + formData.add("refresh_token", refreshToken); + return tokenClient.refreshToken(authHeader, formData).doOnNext(newTokens -> { + String newAccessToken = (String) newTokens.get("access_token"); + Integer expiresIn = (Integer) newTokens.get("expires_in"); + + ResponseCookie newAccessTokenCookie = ResponseCookie.from("access_token", newAccessToken).httpOnly(true).secure(true).sameSite("Strict").path("/").maxAge(expiresIn != null ? expiresIn : 900).build(); + + response.addCookie(newAccessTokenCookie); + + // 如果返回了新的 refresh_token,也更新它 + if (newTokens.containsKey("refresh_token")) { + String newRefreshToken = (String) newTokens.get("refresh_token"); + ResponseCookie newRefreshTokenCookie = ResponseCookie.from("refresh_token", newRefreshToken).httpOnly(true).secure(true).sameSite("Strict").path("/api/auth") +// .path("/api/auth/logout") + .maxAge(60 * 60 * 24 * 7).build(); + response.addCookie(newRefreshTokenCookie); + } + }).then(Mono.just(ResponseEntity.ok().build())).onErrorResume(throwable -> { + clearCookies(response); + return Mono.just(ResponseEntity.status(HttpStatus.UNAUTHORIZED).build()); + }); + } catch (Exception e) { + return Mono.just(ResponseEntity.status(401).build()); + } + + } + + +} diff --git a/a-service/src/main/resources/application.yml b/a-service/src/main/resources/application.yml new file mode 100644 index 0000000..ad4eed5 --- /dev/null +++ b/a-service/src/main/resources/application.yml @@ -0,0 +1,11 @@ +server: + port: 8091 +spring: + application: + name: test-service + +oauth2: + client: + client-id: oidc-client + client-secret: secret + diff --git a/auth/.gitattributes b/auth/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/auth/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/auth/.gitignore b/auth/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/auth/.gitignore @@ -0,0 +1,33 @@ +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/ diff --git a/auth/.mvn/wrapper/maven-wrapper.properties b/auth/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/auth/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/auth/Dockerfile b/auth/Dockerfile new file mode 100644 index 0000000..85b586e --- /dev/null +++ b/auth/Dockerfile @@ -0,0 +1,17 @@ +FROM openjdk:25-jdk-slim + +RUN apt-get update && \ + apt-get install -y --no-install-recommends \ + libfreetype6 \ + fontconfig \ + fonts-dejavu-core \ + fonts-wqy-microhei && \ + rm -rf /var/lib/apt/lists/* \ + +WORKDIR /app + +COPY target/*.jar app.jar + +EXPOSE 9000 + +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/auth/mvnw b/auth/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/auth/mvnw @@ -0,0 +1,295 @@ +#!/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.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/auth/mvnw.cmd b/auth/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/auth/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@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.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/auth/pom.xml b/auth/pom.xml new file mode 100644 index 0000000..094f9f7 --- /dev/null +++ b/auth/pom.xml @@ -0,0 +1,125 @@ + + + 4.0.0 + + com + lingxiao + 0.0.1-SNAPSHOT + ../pom.xml + + auth + auth + auth + + + + + + + + + + + + + + + 25 + 25 + 25 + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-tomcat + + + + + + + org.springframework.boot + spring-boot-starter-jetty + + + + org.springframework.boot + spring-boot-starter-thymeleaf + + + + org.projectlombok + lombok + true + + + org.springframework.boot + spring-boot-starter-oauth2-authorization-server + + + org.springframework.boot + spring-boot-starter-test + test + + + + org.springframework.boot + spring-boot-starter-data-redis + + + org.springframework.boot + spring-boot-starter-session-data-redis + + + com.mysql + mysql-connector-j + runtime + + + org.springframework.boot + spring-boot-starter-data-jdbc + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + 25 + 25 + + + org.projectlombok + lombok + + + + + + org.graalvm.buildtools + native-maven-plugin + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + + \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/Springboot4Application.java b/auth/src/main/java/com/example/springboot4/Springboot4Application.java new file mode 100644 index 0000000..bb96d15 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/Springboot4Application.java @@ -0,0 +1,17 @@ +package com.example.springboot4; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.client.discovery.EnableDiscoveryClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +@SpringBootApplication +@EnableDiscoveryClient // 启用 Kubernetes 服务发现 +public class Springboot4Application { + + public static void main(String[] args) { + SpringApplication.run(Springboot4Application.class, args); + } + +} diff --git a/auth/src/main/java/com/example/springboot4/config/FaviconConfig.java b/auth/src/main/java/com/example/springboot4/config/FaviconConfig.java new file mode 100644 index 0000000..59f29c6 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/config/FaviconConfig.java @@ -0,0 +1,15 @@ +package com.example.springboot4.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class FaviconConfig implements WebMvcConfigurer { + + @Override + public void addResourceHandlers(ResourceHandlerRegistry registry) { + registry.addResourceHandler("/favicon.ico") + .addResourceLocations("classpath:/static/"); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/config/RedisConfig.java b/auth/src/main/java/com/example/springboot4/config/RedisConfig.java new file mode 100644 index 0000000..1fba8f8 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/config/RedisConfig.java @@ -0,0 +1,37 @@ +/* + * Copyright 2020-2024 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. + */ +package com.example.springboot4.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.data.redis.connection.RedisConnectionFactory; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.data.redis.serializer.StringRedisSerializer; + +@Configuration(proxyBeanMethods = false) +public class RedisConfig { + + @Bean + public RedisTemplate redisTemplate(RedisConnectionFactory redisConnectionFactory) { + RedisTemplate redisTemplate = new RedisTemplate<>(); + redisTemplate.setConnectionFactory(redisConnectionFactory); + // 设置key的序列化方式为String + redisTemplate.setKeySerializer(new StringRedisSerializer()); + redisTemplate.setHashKeySerializer(new StringRedisSerializer()); + // 设置value的序列化方式为JSON + return redisTemplate; + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/config/SecurityConfig.java b/auth/src/main/java/com/example/springboot4/config/SecurityConfig.java new file mode 100644 index 0000000..06220b0 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/config/SecurityConfig.java @@ -0,0 +1,304 @@ +package com.example.springboot4.config; + +import com.example.springboot4.filter.CaptchaAuthenticationFilter; +import com.example.springboot4.service.RedisOAuth2AuthorizationConsentService; +import com.example.springboot4.service.RedisOAuth2AuthorizationService; +import com.example.springboot4.service.SmsAuthenticationProvider; +import com.example.springboot4.service.SmsUserDetailsService; +import com.example.springboot4.util.RsaKeyLoader; +import com.nimbusds.jose.jwk.JWKSet; +import com.nimbusds.jose.jwk.RSAKey; +import com.nimbusds.jose.jwk.source.ImmutableJWKSet; +import com.nimbusds.jose.jwk.source.JWKSource; +import com.nimbusds.jose.proc.SecurityContext; +import jakarta.annotation.Resource; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.annotation.Order; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.http.MediaType; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.annotation.web.configuration.OAuth2AuthorizationServerConfiguration; +import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer; +import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.core.ClientAuthenticationMethod; +import org.springframework.security.oauth2.core.oidc.OidcScopes; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; +import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; +import org.springframework.security.oauth2.jwt.JwtDecoder; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.JdbcRegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; +import org.springframework.security.oauth2.server.authorization.settings.AuthorizationServerSettings; +import org.springframework.security.oauth2.server.authorization.settings.ClientSettings; +import org.springframework.security.oauth2.server.authorization.settings.TokenSettings; +import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; +import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; +import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.CorsConfigurationSource; +import org.springframework.web.cors.UrlBasedCorsConfigurationSource; + +import java.security.KeyPair; +import java.security.KeyPairGenerator; +import java.security.interfaces.RSAPrivateKey; +import java.security.interfaces.RSAPublicKey; +import java.time.Duration; +import java.util.List; +import java.util.function.Function; + +@Configuration +@EnableWebSecurity +public class SecurityConfig { + + @Resource + private SmsAuthenticationProvider smsAuthenticationProvider; + + @Resource + private SmsUserDetailsService smsUserDetailsService; + + private static KeyPair generateRsaKey() { + // 尝试从本地文件加载密钥对 + try { + java.security.PrivateKey privateKey = RsaKeyLoader.loadPrivateKey("keys/oauth2-private.key"); + java.security.PublicKey publicKey = RsaKeyLoader.loadPublicKey("keys/oauth2-public.key"); + System.out.println("成功从文件加载RSA密钥对"); + return new KeyPair(publicKey, privateKey); + } catch (Exception ex) { + // 如果加载失败,则生成新的密钥对 + System.err.println("警告:无法从文件加载密钥对,将生成临时密钥对: " + ex.getMessage()); + KeyPair keyPair; + try { + KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA"); + keyPairGenerator.initialize(2048); + keyPair = keyPairGenerator.generateKeyPair(); + System.out.println("已生成新的临时RSA密钥对"); + } catch (Exception ex2) { + throw new IllegalStateException(ex2); + } + return keyPair; + } + } + + @Bean + @Order(1) + public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) { + OAuth2AuthorizationServerConfigurer authorizationServerConfigurer = new OAuth2AuthorizationServerConfigurer(); + authorizationServerConfigurer.authorizationEndpoint(authorizationEndpoint -> + authorizationEndpoint.consentPage("/oauth2/consent")); // 自定义授权页面 + http + .securityMatcher(authorizationServerConfigurer.getEndpointsMatcher()) + .with(authorizationServerConfigurer, (authorizationServer) -> + authorizationServer + .oidc(oidc -> oidc + .userInfoEndpoint(userInfo -> userInfo + .userInfoMapper(userInfoMapper()))) + ) + .authorizeHttpRequests((authorize) -> + authorize.anyRequest().authenticated() + ) + .exceptionHandling((exceptions) -> exceptions + .defaultAuthenticationEntryPointFor( + new LoginUrlAuthenticationEntryPoint("/login"), + new MediaTypeRequestMatcher(MediaType.TEXT_HTML) + ) + ) + // 配置CORS + .cors(cors -> cors.configurationSource(corsConfigurationSource())) + .csrf(csrf -> csrf.ignoringRequestMatchers(authorizationServerConfigurer.getEndpointsMatcher())); + + return http.build(); + } + + @Bean + @Order(2) + public SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) { + http + .authorizeHttpRequests((authorize) -> authorize + .requestMatchers("/login", "/favicon.ico", "/css/**", "/js/**", "/images/**", "/captcha/**", "/sms/**", "/login/mobile").permitAll() + .anyRequest().authenticated() + ) + .authenticationProvider(smsAuthenticationProvider) + .userDetailsService(smsUserDetailsService) + .addFilterBefore(new CaptchaAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class) // 添加验证码过滤器,不用可以注释 + .formLogin(form -> form + .loginPage("/login") + .permitAll() + ) + .sessionManagement(session -> session + .sessionFixation().migrateSession() + .maximumSessions(1) + .maxSessionsPreventsLogin(false) + ) + .logout(LogoutConfigurer::permitAll) + .cors(cors -> cors.configurationSource(corsConfigurationSource()))// 配置CORS + .csrf(csrf -> csrf.ignoringRequestMatchers("/login/mobile", "/sms/**")); + + return http.build(); + } + +// @Bean +// public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { +// RegisteredClient oidcClient = RegisteredClient.withId("7e2d5d5e-0077-4853-97a8-4e49be099956") +// .clientId("oidc-client") +// .clientSecret("{noop}secret") +// .clientAuthenticationMethod(ClientAuthenticationMethod.CLIENT_SECRET_BASIC) +// .authorizationGrantType(AuthorizationGrantType.AUTHORIZATION_CODE) +// .authorizationGrantType(AuthorizationGrantType.REFRESH_TOKEN) +// .redirectUri("http://localhost:3000/callback") +// .redirectUri("https://c.makesong.cn/callback") +// .redirectUri("https://baidu.com") +// .postLogoutRedirectUri("https://c.makesong.cn/callback") +// .scope(OidcScopes.OPENID) +// .scope(OidcScopes.PROFILE) +// .scope(OidcScopes.EMAIL) +// .scope(OidcScopes.ADDRESS) +// .scope(OidcScopes.PHONE) +// .tokenSettings(TokenSettings.builder() +// .accessTokenTimeToLive(Duration.ofSeconds(300)) +// .refreshTokenTimeToLive(Duration.ofDays(30)) +// .build()) +// .clientSettings(ClientSettings.builder() +// .requireAuthorizationConsent(true) // 启用授权同意页面 +// .requireProofKey(true) // 禁用PKCE要求,如需要可以开启 +// .build()) +// .build(); +// JdbcRegisteredClientRepository registeredClientRepository = new JdbcRegisteredClientRepository(jdbcTemplate); +// +// registeredClientRepository.save(oidcClient); //添加一条client,也可以在数据库中手动添加 +// return registeredClientRepository; +// } + + @Bean + public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { + return new JdbcRegisteredClientRepository(jdbcTemplate); + } + + // CORS配置源 + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(List.of("*")); + configuration.setAllowedMethods(List.of("*")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowCredentials(true); + configuration.setMaxAge(3600L); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + //使用JDBC存储Client信息,支持动态存储 +// @Bean +// public RegisteredClientRepository registeredClientRepository(JdbcTemplate jdbcTemplate) { +// return new JdbcRegisteredClientRepository(jdbcTemplate); +// } + + // 不再需要 @Bean 注解,因为 smsUserDetailsService 已经是 UserDetailsService bean + public UserDetailsService userDetailsService() { + return smsUserDetailsService; + } + + @Bean + public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception { + return authenticationConfiguration.getAuthenticationManager(); + } + + @Bean + public OAuth2AuthorizationConsentService authorizationConsentService( + RedisTemplate redisTemplate, RegisteredClientRepository registeredClientRepository) { + return new RedisOAuth2AuthorizationConsentService(redisTemplate, registeredClientRepository); + } + + + // 使用Redis存储授权后的信息 + @Bean + public OAuth2AuthorizationService authorizationService( + RedisTemplate redisTemplate, + RegisteredClientRepository registeredClientRepository) { + return new RedisOAuth2AuthorizationService(redisTemplate, registeredClientRepository); + } + + //使用默认地址映射 + @Bean + public AuthorizationServerSettings authorizationServerSettings() { + return AuthorizationServerSettings.builder().build(); + } + + @Bean + // 自定义ID_TOKEN信息 + public OAuth2TokenCustomizer tokenCustomizer( + UserDetailsService userInfoService) { + return (context) -> { + if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { + UserDetails userDetails = userInfoService.loadUserByUsername(context.getPrincipal().getName()); + context.getClaims().claims(claims -> { +// claims.clear(); + if (userDetails instanceof Users customUser) { + claims.put("nickname", customUser.getNickname()); + claims.put("avatar", customUser.getAvatar()); + claims.put("phone", customUser.getPhone()); + claims.put("email", customUser.getEmail()); + } + }); + } + }; + } + + // 自定义用户信息映射 + @Bean + public Function userInfoMapper() { + return (context -> { + // 获取认证信息 + Authentication authentication = context.getAuthentication(); + try { + String username = authentication.getName(); + + UserDetails userDetails = userDetailsService().loadUserByUsername(username); + + // 如果是自定义的Users类型,返回自定义的用户信息,否则返回默认的用户信息 + if (userDetails instanceof Users customUser) { + return new OidcUserInfo(customUser.getOidcUserInfo()); + } + } catch (Exception _) { + + } + JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal(); + return new OidcUserInfo(principal.getToken().getClaims()); + + }); + } + + @Bean + public JWKSource jwkSource() { + KeyPair keyPair = generateRsaKey(); + RSAPublicKey publicKey = (RSAPublicKey) keyPair.getPublic(); + RSAPrivateKey privateKey = (RSAPrivateKey) keyPair.getPrivate(); + RSAKey rsaKey = new RSAKey.Builder(publicKey) + .privateKey(privateKey) + .keyID("oauth2-jwk-key") + .build(); + JWKSet jwkSet = new JWKSet(rsaKey); + return new ImmutableJWKSet<>(jwkSet); + } + + @Bean + public JwtDecoder jwtDecoder(JWKSource jwkSource) { + return OAuth2AuthorizationServerConfiguration.jwtDecoder(jwkSource); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/config/Users.java b/auth/src/main/java/com/example/springboot4/config/Users.java new file mode 100644 index 0000000..7649f18 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/config/Users.java @@ -0,0 +1,55 @@ +package com.example.springboot4.config; + +import lombok.Data; +import lombok.EqualsAndHashCode; +import org.jspecify.annotations.Nullable; +import org.springframework.security.core.GrantedAuthority; +import org.springframework.security.core.userdetails.User; +import org.springframework.security.oauth2.core.oidc.OidcUserInfo; + +import java.util.Collection; +import java.util.Collections; +import java.util.Map; + +@EqualsAndHashCode(callSuper = true) +@Data +// 自定义用户信息 +public class Users extends User { + public String nickname; + private String email; + private String phone; + private String avatar; + + public Users(String username, @Nullable String password, Collection authorities, String nickname, String avatar, String phone) { + super(username, password, authorities); + this.nickname = nickname; + this.avatar = avatar; + this.phone = phone; + } + + public Map getOidcUserInfo() { + return OidcUserInfo.builder() + .subject(getUsername()) + .name("First Last") + .givenName("First") + .familyName("Last") + .middleName("Middle") + .nickname(nickname) + .preferredUsername(getUsername()) + .profile("https://example.com/" + nickname) + .picture(avatar) + .website("https://example.com") + .email(nickname + "@example.com") + .emailVerified(true) + .gender("female") + .birthdate("1970-01-01") + .zoneinfo("Europe/Paris") + .locale("en-US") + .phoneNumber(phone) + .phoneNumberVerified(false) + .claim("address", Collections.singletonMap("formatted", "Champ de Mars\n5 Av. Anatole France\n75007 Paris\nFrance")) + .updatedAt("1970-01-01T00:00:00Z") + .build() + .getClaims(); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/controller/CaptchaController.java b/auth/src/main/java/com/example/springboot4/controller/CaptchaController.java new file mode 100644 index 0000000..efdc519 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/controller/CaptchaController.java @@ -0,0 +1,30 @@ +package com.example.springboot4.controller; + +import com.example.springboot4.util.CaptchaUtil; +import jakarta.servlet.http.HttpSession; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import java.util.HashMap; +import java.util.Map; + +@RestController +@RequestMapping("/captcha") +public class CaptchaController { + /** + * 生成验证码 + */ + @GetMapping("/generate") + public Map generateCaptcha(HttpSession session) { + + CaptchaUtil.Captcha captcha = CaptchaUtil.generateCaptcha(); + // 将验证码文本存储在session中,也可以自定义Redis存储验证码 + session.setAttribute("captcha", captcha.getText()); + + // 返回验证码图片Base64编码 + Map response = new HashMap<>(); + response.put("image", "data:image/png;base64," + captcha.getImageBase64()); + return response; + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/controller/LoginController.java b/auth/src/main/java/com/example/springboot4/controller/LoginController.java new file mode 100644 index 0000000..86b790f --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/controller/LoginController.java @@ -0,0 +1,71 @@ +package com.example.springboot4.controller; + +import jakarta.servlet.http.HttpServletRequest; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.core.io.ClassPathResource; +import org.springframework.core.io.Resource; +import org.springframework.http.CacheControl; +import org.springframework.http.MediaType; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.core.oidc.user.OidcUser; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; +import org.springframework.stereotype.Controller; +import org.springframework.ui.Model; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestParam; + +import java.io.IOException; +import java.security.Principal; +import java.time.Duration; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +@Controller +public class LoginController { + + private static final Logger logger = LoggerFactory.getLogger(LoginController.class); + + @GetMapping("/") + public String home() { + return "home"; + } + + @GetMapping("/login") + public String login(@RequestParam(defaultValue = "false") boolean error, + @RequestParam(defaultValue = "false") boolean logout, + @RequestParam(defaultValue = "false") boolean captcha) { + return "login"; + } + + @GetMapping("/oauth2/consent") + public String consent(HttpServletRequest request, Model model) { + // 虽然不能直接拿到 code_challenge,但你可以相信框架已保存 + // 只需确保表单正确提交即可 + String clientId = request.getParameter("client_id"); + String scopeStr = request.getParameter("scope"); + String state = request.getParameter("state"); + + // 参数验证 + if (clientId == null || clientId.trim().isEmpty()) { + logger.warn("consent页面请求缺少client_id参数"); + return "error"; // 或重定向到错误页面 + } + + // 处理scope参数 + List scopes = Collections.emptyList(); + if (scopeStr != null && !scopeStr.trim().isEmpty()) { + scopes = Arrays.asList(scopeStr.trim().split("\\s+")); + } + model.addAttribute("clientId", request.getParameter("client_id")); + model.addAttribute("scopes", scopes); + model.addAttribute("state", request.getParameter("state")); + + // 注意:不要试图在这里构造 redirect_uri 或 code_challenge! + return "consent"; + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/controller/SmsController.java b/auth/src/main/java/com/example/springboot4/controller/SmsController.java new file mode 100644 index 0000000..be0f0f4 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/controller/SmsController.java @@ -0,0 +1,91 @@ +package com.example.springboot4.controller; + +import com.example.springboot4.service.SmsAuthenticationToken; +import com.example.springboot4.service.SmsCodeService; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.authentication.AuthenticationManager; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.security.web.savedrequest.HttpSessionRequestCache; +import org.springframework.security.web.savedrequest.SavedRequest; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.servlet.mvc.support.RedirectAttributes; + +@Controller +public class SmsController { + + private final SmsCodeService smsCodeService; + private final AuthenticationManager authenticationManager; + + @Autowired + public SmsController(SmsCodeService smsCodeService, AuthenticationManager authenticationManager) { + this.smsCodeService = smsCodeService; + this.authenticationManager = authenticationManager; + } + + /** + * 发送短信验证码 + */ + @PostMapping("/sms/code") + @ResponseBody + public String sendCode(@RequestParam("phone") String phone, RedirectAttributes redirectAttributes) { + // 生成验证码 + String code = smsCodeService.generateCode(); + // 发送验证码(模拟) + smsCodeService.sendCode(phone, code); + // 添加提示信息 + redirectAttributes.addFlashAttribute("message", "验证码已发送至 " + phone); + return "成功"; + } + + /** + * 短信验证码登录 + */ + @PostMapping("/login/mobile") + public String loginWithSms(@RequestParam("phone") String phone, + @RequestParam("code") String code, + HttpServletRequest request, + HttpServletResponse response, + RedirectAttributes redirectAttributes) { + // 创建未认证的Token + SmsAuthenticationToken authRequest = new SmsAuthenticationToken(phone, code); + try { + // 调用AuthenticationManager进行认证 + Authentication authentication = authenticationManager.authenticate(authRequest); + // 设置认证信息到SecurityContext + SecurityContext context = SecurityContextHolder.getContext(); + context.setAuthentication(authentication); + + // 保存SecurityContext到HttpSession,确保认证状态持久化 + HttpSession session = request.getSession(true); + session.setAttribute("SPRING_SECURITY_CONTEXT", context); + + // 获取保存的请求(如授权URL),如果存在则重定向到保存的请求 + HttpSessionRequestCache requestCache = new HttpSessionRequestCache(); + SavedRequest savedRequest = requestCache.getRequest(request, response); + if (savedRequest != null) { + String targetUrl = savedRequest.getRedirectUrl(); + // 清除保存的请求,避免重复使用 + requestCache.removeRequest(request, response); + // 确保重定向URL是安全的(只允许重定向到授权端点) + if (targetUrl.contains("/oauth2/authorize")) { + return "redirect:" + targetUrl; + } + } + + // 如果没有保存的请求或URL不安全,重定向到首页 + return "redirect:/"; + } catch (Exception e) { + // 认证失败 + redirectAttributes.addFlashAttribute("error", "短信验证码错误或已过期"); + return "redirect:/login"; + } + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/filter/CaptchaAuthenticationFilter.java b/auth/src/main/java/com/example/springboot4/filter/CaptchaAuthenticationFilter.java new file mode 100644 index 0000000..e55ed3b --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/filter/CaptchaAuthenticationFilter.java @@ -0,0 +1,65 @@ +package com.example.springboot4.filter; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import jakarta.servlet.http.HttpSession; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +/** + * 验证码验证过滤器 + * 在用户认证之前验证验证码是否正确 + */ +public class CaptchaAuthenticationFilter extends OncePerRequestFilter { + + private static final Logger logger = LoggerFactory.getLogger(CaptchaAuthenticationFilter.class); + + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + + // 只处理登录POST请求 + if (!shouldProcess(request)) { + filterChain.doFilter(request, response); + return; + } + + + // 获取用户输入的验证码 + String inputCaptcha = request.getParameter("captcha"); + + // 获取Session中的验证码 + HttpSession session = request.getSession(false); + String sessionCaptcha = (session != null) ? (String) session.getAttribute("captcha") : null; + + // 移除Session中的验证码,确保一次有效性 + if (session != null) { + session.removeAttribute("captcha"); + } + + // 验证验证码 + if (inputCaptcha == null || !inputCaptcha.equalsIgnoreCase(sessionCaptcha)) { +// logger.warn("验证码验证失败. 用户输入: {}, Session中: {}", inputCaptcha, sessionCaptcha); + // 验证码错误,重定向到登录页并携带错误参数 + response.sendRedirect("/login?captcha"); + return; + } + + logger.info("验证码验证通过,继续执行认证流程"); + // 验证码正确,继续执行过滤器链 + filterChain.doFilter(request, response); + } + + private boolean shouldProcess(HttpServletRequest request) { + return "/login".equals(request.getRequestURI()) && + "POST".equalsIgnoreCase(request.getMethod()); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/filter/CustomAuthenticationEntryPoint.java b/auth/src/main/java/com/example/springboot4/filter/CustomAuthenticationEntryPoint.java new file mode 100644 index 0000000..e82c6c6 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/filter/CustomAuthenticationEntryPoint.java @@ -0,0 +1,61 @@ +package com.example.springboot4.filter; + +import com.example.springboot4.handler.ErrorResponse; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.HttpStatus; +import org.springframework.http.MediaType; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import java.io.IOException; +import java.time.LocalDateTime; + +/** + * 自定义认证入口点,用于处理认证异常并返回统一格式的JSON响应 + */ +public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint { + + private static final Logger logger = LoggerFactory.getLogger(CustomAuthenticationEntryPoint.class); + private final ObjectMapper objectMapper = new ObjectMapper(); + + public CustomAuthenticationEntryPoint() { + // 注册JavaTimeModule以支持LocalDateTime序列化 + objectMapper.registerModule(new JavaTimeModule()); + // 禁用时间戳格式,使用字符串格式 + objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS); + } + + @Override + public void commence(HttpServletRequest request, HttpServletResponse response, + AuthenticationException authException) throws IOException, ServletException { + + logger.warn("认证失败: {}", authException.getMessage()); + + ErrorResponse errorResponse = new ErrorResponse(); + errorResponse.setTimestamp(LocalDateTime.now()); + + if (authException instanceof InvalidBearerTokenException) { + errorResponse.setMessage("Token无效或已过期: " + authException.getMessage()); + errorResponse.setCode(HttpStatus.UNAUTHORIZED.value()); + } else { +// errorResponse.setMessage("认证失败: " + authException.getMessage()); +// errorResponse.setCode(HttpStatus.UNAUTHORIZED.value()); + response.sendRedirect("/login"); + } + + response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + response.setCharacterEncoding("UTF-8"); + + String jsonResponse = objectMapper.writeValueAsString(errorResponse); + response.getWriter().write(jsonResponse); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/handler/ErrorResponse.java b/auth/src/main/java/com/example/springboot4/handler/ErrorResponse.java new file mode 100644 index 0000000..6bbf697 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/handler/ErrorResponse.java @@ -0,0 +1,36 @@ +package com.example.springboot4.handler; + +import lombok.Getter; +import lombok.Setter; + +import java.time.LocalDateTime; +import java.util.Map; + +/** + * 统一错误响应格式 + */ +@Setter +@Getter +public class ErrorResponse { + // Getter 和 Setter 方法 + private boolean success = false; + private String message; + private int code; + private LocalDateTime timestamp = LocalDateTime.now(); + private Map data; + + // 构造函数 + public ErrorResponse() {} + + public ErrorResponse(String message, int code) { + this.message = message; + this.code = code; + } + + public ErrorResponse(String message, int code, Map data) { + this.message = message; + this.code = code; + this.data = data; + } + +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationConsentService.java b/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationConsentService.java new file mode 100644 index 0000000..873e7b2 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationConsentService.java @@ -0,0 +1,59 @@ +package com.example.springboot4.service; + +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsent; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationConsentService; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; + +import java.util.concurrent.TimeUnit; + +//自定义Redis 授权同意服务 +public class RedisOAuth2AuthorizationConsentService implements OAuth2AuthorizationConsentService { + + private static final String CONSENT_KEY_PREFIX = "consent:"; + // 可选:设置授权时间,授权时间内不需要手动点击授权按钮(例如 30 天) + private static final long CONSENT_EXPIRE_DAYS = 60 * 5; + + private final RedisTemplate redisTemplate; + private final RegisteredClientRepository registeredClientRepository; // ← 新增 + + public RedisOAuth2AuthorizationConsentService(RedisTemplate redisTemplate, RegisteredClientRepository registeredClientRepository) { + this.redisTemplate = redisTemplate; + this.registeredClientRepository = registeredClientRepository; + + } + + @Override + public void save(OAuth2AuthorizationConsent authorizationConsent) { + if (authorizationConsent == null) { + return; + } + String key = getConsentKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); + redisTemplate.opsForValue().set(key, authorizationConsent, CONSENT_EXPIRE_DAYS, TimeUnit.SECONDS); + } + + @Override + public void remove(OAuth2AuthorizationConsent authorizationConsent) { + if (authorizationConsent == null) { + return; + } + String key = getConsentKey(authorizationConsent.getRegisteredClientId(), authorizationConsent.getPrincipalName()); + redisTemplate.delete(key); + } + + @Override + public OAuth2AuthorizationConsent findById(String registeredClientId, String principalName) { + RegisteredClient registeredClient = registeredClientRepository.findById(registeredClientId); + if (registeredClient == null) { + // 客户端不存在,即使 Redis 里有 consent 也视为无效 + return null; + } + String key = getConsentKey(registeredClientId, principalName); + return (OAuth2AuthorizationConsent) redisTemplate.opsForValue().get(key); + } + + private static String getConsentKey(String registeredClientId, String principalName) { + return CONSENT_KEY_PREFIX + principalName + ":" + registeredClientId; + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationService.java b/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationService.java new file mode 100644 index 0000000..5bcae48 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/RedisOAuth2AuthorizationService.java @@ -0,0 +1,143 @@ +package com.example.springboot4.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.dao.DataAccessException; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.security.oauth2.core.AuthorizationGrantType; +import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationCode; +import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; +import org.springframework.security.oauth2.server.authorization.OAuth2TokenType; +import org.springframework.security.oauth2.server.authorization.client.RegisteredClientRepository; + +import java.time.Duration; + +// RedisOAuth2AuthorizationService.java +// Redis 自定义存储授权信息 +public class RedisOAuth2AuthorizationService implements OAuth2AuthorizationService { + + private static final Logger logger = LoggerFactory.getLogger(RedisOAuth2AuthorizationService.class); + + private final RedisTemplate redisTemplate; + private final RegisteredClientRepository registeredClientRepository; + + public RedisOAuth2AuthorizationService(RedisTemplate redisTemplate, + RegisteredClientRepository registeredClientRepository) { + this.redisTemplate = redisTemplate; + this.registeredClientRepository = registeredClientRepository; + } + + @Override + public void save(OAuth2Authorization authorization) { + try { + String key = "oauth2:authorization:" + authorization.getId(); + redisTemplate.opsForValue().set(key, authorization, Duration.ofDays(30)); + // 同时按token值存储索引 + if (authorization.getAccessToken() != null) { + String tokenKey = "oauth2:token:" + authorization.getAccessToken().getToken().getTokenValue(); + redisTemplate.opsForValue().set(tokenKey, authorization.getId(), Duration.ofSeconds(300)); + } + + if (authorization.getRefreshToken() != null) { + String refreshTokenKey = "oauth2:refresh:" + authorization.getRefreshToken().getToken().getTokenValue(); + redisTemplate.opsForValue().set(refreshTokenKey, authorization.getId(), Duration.ofDays(14)); + } + + // 添加对授权码的支持 + if (authorization.getAuthorizationGrantType().getValue().equals(AuthorizationGrantType.AUTHORIZATION_CODE.getValue())) { + OAuth2Authorization.Token authorizationCode = + authorization.getToken(OAuth2AuthorizationCode.class); + if (authorizationCode != null) { + String codeKey = "oauth2:code:" + authorizationCode.getToken().getTokenValue(); + redisTemplate.opsForValue().set(codeKey, authorization.getId(), Duration.ofMinutes(5)); + } else { + //第一次授权,需要用户手动点击授权按钮,根据state存储,用于后续验证 + String stateKey = "oauth2:state:" + authorization.getAttribute("state"); + redisTemplate.opsForValue().set(stateKey, authorization.getId(), Duration.ofSeconds(300)); + } + } + } catch (DataAccessException e) { + logger.error("保存OAuth2授权信息到Redis时发生错误", e); + throw e; + } + } + + @Override + public void remove(OAuth2Authorization authorization) { + try { + String key = "oauth2:authorization:" + authorization.getId(); + redisTemplate.delete(key); + if (authorization.getAccessToken() != null) { + String tokenKey = "oauth2:token:" + authorization.getAccessToken().getToken().getTokenValue(); + redisTemplate.delete(tokenKey); + } + if (authorization.getRefreshToken() != null) { + String refreshTokenKey = "oauth2:refresh:" + authorization.getRefreshToken().getToken().getTokenValue(); + redisTemplate.delete(refreshTokenKey); + } + } catch (DataAccessException e) { + logger.error("从Redis删除OAuth2授权信息时发生错误", e); + throw e; + } + } + + @Override + public OAuth2Authorization findById(String id) { + try { + String key = "oauth2:authorization:" + id; + return (OAuth2Authorization) redisTemplate.opsForValue().get(key); + } catch (DataAccessException e) { + logger.error("从Redis查询OAuth2授权信息时发生错误", e); + throw e; + } + } + + @Override + public OAuth2Authorization findByToken(String token, OAuth2TokenType tokenType) { + try { + String authId = null; + + if (tokenType != null) { + // 1. 如果类型明确,直接获取 Key + authId = (String) redisTemplate.opsForValue().get(getTokenKey(token, tokenType)); + } else { + // 2. 如果类型为空 (revoke 流程),遍历所有可能的 Redis 前缀 + // 这里的顺序建议:Refresh Token -> Access Token -> Code + String[] possibleKeys = { + "oauth2:refresh:" + token, + "oauth2:token:" + token, + "oauth2:code:" + token + }; + + for (String key : possibleKeys) { + authId = (String) redisTemplate.opsForValue().get(key); + if (authId != null) break; + } + } + + return authId != null ? findById(authId) : null; + } catch (DataAccessException e) { + logger.error("根据Token从Redis查询OAuth2授权信息时发生错误", e); + throw e; + } + } + + private String getTokenKey(String token, OAuth2TokenType tokenType) { + // 增加空指针保护 + if (tokenType == null) { + return "oauth2:token:" + token; + } + + if (OAuth2TokenType.ACCESS_TOKEN.equals(tokenType)) { + return "oauth2:token:" + token; + } else if (OAuth2TokenType.REFRESH_TOKEN.equals(tokenType)) { + return "oauth2:refresh:" + token; + } else if ("code".equals(tokenType.getValue())) { + return "oauth2:code:" + token; + } else if ("state".equals(tokenType.getValue())) { + return "oauth2:state:" + token; + } + return "oauth2:token:" + token; + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationProvider.java b/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationProvider.java new file mode 100644 index 0000000..51f984e --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationProvider.java @@ -0,0 +1,47 @@ +package com.example.springboot4.service; + +import org.jspecify.annotations.NonNull; +import org.springframework.security.authentication.AuthenticationProvider; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.AuthenticationException; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.stereotype.Component; + +@Component +public class SmsAuthenticationProvider implements AuthenticationProvider { + + private final SmsCodeService smsCodeService; + private final SmsUserDetailsService userDetailsService; + + public SmsAuthenticationProvider(SmsCodeService smsCodeService, SmsUserDetailsService userDetailsService) { + this.smsCodeService = smsCodeService; + this.userDetailsService = userDetailsService; + } + + @Override + public Authentication authenticate(@NonNull Authentication authentication) throws AuthenticationException { + SmsAuthenticationToken auth = (SmsAuthenticationToken) authentication; + String phone = (String) auth.getPrincipal(); + String code = (String) auth.getCredentials(); + + // 验证短信验证码 + if (!smsCodeService.verifyCode(phone, code)) { + throw new BadCredentialsException("短信验证码错误或已过期"); + } + + // 加载用户 + UserDetails userDetails = userDetailsService.loadUserByPhone(phone); + + // 创建已认证的Token + SmsAuthenticationToken authenticatedToken = new SmsAuthenticationToken( + phone, userDetails.getAuthorities()); + authenticatedToken.setDetails(userDetails); + return authenticatedToken; + } + + @Override + public boolean supports(@NonNull Class authentication) { + return SmsAuthenticationToken.class.isAssignableFrom(authentication); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationToken.java b/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationToken.java new file mode 100644 index 0000000..b19acca --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/SmsAuthenticationToken.java @@ -0,0 +1,43 @@ +package com.example.springboot4.service; + +import org.springframework.security.authentication.AbstractAuthenticationToken; +import org.springframework.security.core.GrantedAuthority; + +import java.util.Collection; + +public class SmsAuthenticationToken extends AbstractAuthenticationToken { + + private final Object principal; // 手机号 + private final Object credentials; // 验证码 + + /** + * 用于认证前的构造函数(未认证) + */ + public SmsAuthenticationToken(String phone, String code) { + super((Collection) null); + this.principal = phone; + this.credentials = code; + setAuthenticated(false); + } + + /** + * 用于认证后的构造函数(已认证) + */ + public SmsAuthenticationToken(String phone, Collection authorities) { + super(authorities); + this.principal = phone; + this.credentials = null; + super.setAuthenticated(true); + } + + @Override + public Object getCredentials() { + return credentials; + } + + @Override + public Object getPrincipal() { + return principal; + } + +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/SmsCodeService.java b/auth/src/main/java/com/example/springboot4/service/SmsCodeService.java new file mode 100644 index 0000000..11f4912 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/SmsCodeService.java @@ -0,0 +1,67 @@ +package com.example.springboot4.service; + +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.stereotype.Service; + +import java.util.Random; +import java.util.concurrent.TimeUnit; + +@Service +public class SmsCodeService { + + private final StringRedisTemplate redisTemplate; + private static final String SMS_CODE_PREFIX = "sms:code:"; + private static final long EXPIRE_MINUTES = 5; + private static final int CODE_LENGTH = 6; + + public SmsCodeService(StringRedisTemplate redisTemplate) { + this.redisTemplate = redisTemplate; + } + + /** + * 生成随机6位数字验证码 + */ + public String generateCode() { + Random random = new Random(); + StringBuilder sb = new StringBuilder(); + for (int i = 0; i < CODE_LENGTH; i++) { + sb.append(random.nextInt(10)); + } + return sb.toString(); + } + + /** + * 保存验证码到Redis,5分钟有效 + */ + public void saveCode(String phone, String code) { + String key = SMS_CODE_PREFIX + phone; + redisTemplate.opsForValue().set(key, code, EXPIRE_MINUTES, TimeUnit.MINUTES); + } + + /** + * 验证手机号和验证码 + */ + public boolean verifyCode(String phone, String code) { + String key = SMS_CODE_PREFIX + phone; + String storedCode = redisTemplate.opsForValue().get(key); + if (storedCode == null) { + return false; + } + // 验证通过后删除验证码,防止重复使用 + if (storedCode.equals(code)) { + redisTemplate.delete(key); + return true; + } + return false; + } + + /** + * 发送短信验证码(模拟) + */ + public void sendCode(String phone, String code) { + // 这里模拟发送短信,实际应调用短信服务商API + System.out.println("发送短信验证码到 " + phone + ": " + code); + // 保存到Redis + saveCode(phone, code); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/service/SmsUserDetailsService.java b/auth/src/main/java/com/example/springboot4/service/SmsUserDetailsService.java new file mode 100644 index 0000000..aa49284 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/service/SmsUserDetailsService.java @@ -0,0 +1,88 @@ +package com.example.springboot4.service; + +import com.example.springboot4.config.Users; +import org.jspecify.annotations.NonNull; +import org.jspecify.annotations.Nullable; +import org.springframework.context.annotation.Primary; +import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.security.core.userdetails.UserDetailsService; +import org.springframework.security.core.userdetails.UsernameNotFoundException; +import org.springframework.security.crypto.factory.PasswordEncoderFactories; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.provisioning.JdbcUserDetailsManager; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.util.Collections; + +@Service +@Primary +public class SmsUserDetailsService implements UserDetailsService { + + private final JdbcTemplate jdbcTemplate; + private final JdbcUserDetailsManager jdbcUserDetailsManager; + private final PasswordEncoder passwordEncoder = PasswordEncoderFactories.createDelegatingPasswordEncoder(); + + public SmsUserDetailsService(JdbcTemplate jdbcTemplate, DataSource dataSource) { + this.jdbcTemplate = jdbcTemplate; + this.jdbcUserDetailsManager = new JdbcUserDetailsManager(dataSource); + } + + @Override + public @Nullable UserDetails loadUserByUsername(@NonNull String username) throws UsernameNotFoundException { + + // 模拟数据库 + if ("admin".equals(username)) { + return new Users("admin", passwordEncoder.encode("123123"), + Users.withUsername("admin").password(passwordEncoder.encode("123123")).roles("USER").build().getAuthorities(), + "凌萧", "https://img.makesong.cn/10.png", "13777777777"); + } + return null; + } + /** + * 通过手机号加载用户 + */ + public UserDetails loadUserByPhone(String phone) throws UsernameNotFoundException { + // 查询users表,假设有phone列 + String sql = "SELECT username, password, enabled FROM users WHERE phone = ?"; + try { + return jdbcTemplate.queryForObject(sql, (rs, rowNum) -> { + String username = rs.getString("username"); + String password = rs.getString("password"); + boolean enabled = rs.getBoolean("enabled"); + // 暂时忽略权限,使用默认权限 + return new Users(username, password, Users.withUsername(username).roles("USER").build().getAuthorities(), "", "", phone); + }, phone); + } catch (org.springframework.dao.EmptyResultDataAccessException e) { + // 用户不存在,自动创建用户 + return createUserByPhone(phone); + } + } + + /** + * 根据手机号创建新用户 + */ + private UserDetails createUserByPhone(String phone) { + // 生成用户名:手机号 + String username = phone; + // 生成随机密码(用户无法用密码登录,只能短信登录) + String password = passwordEncoder.encode(generateRandomPassword()); + // 使用JdbcUserDetailsManager创建用户 + jdbcUserDetailsManager.createUser(org.springframework.security.core.userdetails.User.builder() + .username(username) + .password(password) + .roles("USER") + .build()); + // 更新phone列(需要自定义SQL,因为JdbcUserDetailsManager不处理phone) + String updateSql = "UPDATE users SET phone = ? WHERE username = ?"; + jdbcTemplate.update(updateSql, phone, username); + // 返回用户详情 + return new Users(username, password, Collections.emptyList(), "", "", phone); + } + + private String generateRandomPassword() { + // 生成随机字符串作为密码,用户不会用到 + return java.util.UUID.randomUUID().toString().substring(0, 16); + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/util/CaptchaUtil.java b/auth/src/main/java/com/example/springboot4/util/CaptchaUtil.java new file mode 100644 index 0000000..9d16ef5 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/util/CaptchaUtil.java @@ -0,0 +1,112 @@ +package com.example.springboot4.util; + +import javax.imageio.ImageIO; +import java.awt.*; +import java.awt.image.BufferedImage; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.Base64; +import java.util.Random; + +public class CaptchaUtil { + + private static final char[] CHARS = {'2', '3', '4', '5', '6', '7', '8', '9', + 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'J', 'K', 'L', 'M', + 'N', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z'}; + + private static final int SIZE = 4; + private static final int LINES = 5; + private static final int WIDTH = 120; + private static final int HEIGHT = 40; + + /** + * 生成验证码图片和文本 + */ + public static Captcha generateCaptcha() { + BufferedImage image = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB); + Graphics g = image.getGraphics(); + + // 设置背景色 + g.setColor(Color.WHITE); + g.fillRect(0, 0, WIDTH, HEIGHT); + + // 生成随机验证码 + Random random = new Random(); + StringBuilder captchaText = new StringBuilder(); + for (int i = 0; i < SIZE; i++) { + char c = CHARS[random.nextInt(CHARS.length)]; + captchaText.append(c); + } + + // 绘制验证码文本 + // 使用系统默认字体替代指定字体 + // g.setFont(new Font("Arial", Font.BOLD, 24)); + Font font = new Font(null, Font.BOLD, 24); + g.setFont(font); + + for (int i = 0; i < captchaText.length(); i++) { + g.setColor(getRandomColor()); + g.drawString(String.valueOf(captchaText.charAt(i)), 20 + i * 20, 30); + } + + // 绘制干扰线 + for (int i = 0; i < LINES; i++) { + g.setColor(getRandomColor()); + int x1 = random.nextInt(WIDTH); + int y1 = random.nextInt(HEIGHT); + int x2 = random.nextInt(WIDTH); + int y2 = random.nextInt(HEIGHT); + g.drawLine(x1, y1, x2, y2); + } + + g.dispose(); + + // 将图片转换为Base64编码 + String base64Image = imageToBase64(image); + + return new Captcha(captchaText.toString(), base64Image); + } + + /** + * 获取随机颜色 + */ + private static Color getRandomColor() { + Random random = new Random(); + return new Color(random.nextInt(256), random.nextInt(256), random.nextInt(256)); + } + + /** + * 将BufferedImage转换为Base64编码 + */ + private static String imageToBase64(BufferedImage image) { + try { + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + ImageIO.write(image, "png", outputStream); + byte[] imageBytes = outputStream.toByteArray(); + return Base64.getEncoder().encodeToString(imageBytes); + } catch (IOException e) { + throw new RuntimeException("Failed to convert image to Base64", e); + } + } + + /** + * 验证码数据类 + */ + public static class Captcha { + private final String text; + private final String imageBase64; + + public Captcha(String text, String imageBase64) { + this.text = text; + this.imageBase64 = imageBase64; + } + + public String getText() { + return text; + } + + public String getImageBase64() { + return imageBase64; + } + } +} \ No newline at end of file diff --git a/auth/src/main/java/com/example/springboot4/util/RsaKeyLoader.java b/auth/src/main/java/com/example/springboot4/util/RsaKeyLoader.java new file mode 100644 index 0000000..8d75f38 --- /dev/null +++ b/auth/src/main/java/com/example/springboot4/util/RsaKeyLoader.java @@ -0,0 +1,48 @@ +package com.example.springboot4.util; + + +import org.springframework.util.ResourceUtils; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.ObjectInputStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.security.PrivateKey; +import java.security.PublicKey; + +/** + * RSA密钥加载器 + * 用于从本地文件加载RSA密钥对 + */ +public class RsaKeyLoader { + + /** + * 从指定路径加载私钥 + * + * @param privateKeyPath 私钥文件路径 + * @return PrivateKey 私钥对象 + * @throws IOException IO异常 + * @throws ClassNotFoundException 类未找到异常 + */ + public static PrivateKey loadPrivateKey(String privateKeyPath) throws IOException, ClassNotFoundException { + try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Path.of(privateKeyPath)))) { + return (PrivateKey) ois.readObject(); + } + } + + /** + * 从指定路径加载公钥 + * + * @param publicKeyPath 公钥文件路径 + * @return PublicKey 公钥对象 + * @throws IOException IO异常 + * @throws ClassNotFoundException 类未找到异常 + */ + public static PublicKey loadPublicKey(String publicKeyPath) throws IOException, ClassNotFoundException { + try (ObjectInputStream ois = new ObjectInputStream(Files.newInputStream(Path.of(publicKeyPath)))) { + return (PublicKey) ois.readObject(); + } + } +} \ No newline at end of file diff --git a/auth/src/main/resources/application.yml b/auth/src/main/resources/application.yml new file mode 100644 index 0000000..a8ca0c8 --- /dev/null +++ b/auth/src/main/resources/application.yml @@ -0,0 +1,41 @@ +#logging: +# level: +# org.springframework.security: trace +# org.springframework.web: debug + +spring: + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: jdbc:mysql://192.168.1.14:3306/test3?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&useSSL=true + username: root + password: lingxiao + hikari: + minimum-idle: 3 + maximum-pool-size: 10000 + max-lifetime: 300000 + connection-test-query: SELECT 1 + connection-timeout: 300000 + main: + allow-bean-definition-overriding: true + data: + redis: + port: 6379 + database: 1 + host: 192.168.1.14 + password: lingxiao + lettuce: + pool: + max-active: 2000 + max-idle: 2000 + min-idle: 2 + max-wait: 2000ms + timeout: 2000ms + connect-timeout: 2000ms + threads: + virtual: + enabled: true +server: + port: 9000 + jetty: + threads: + max: 1000 \ No newline at end of file diff --git a/auth/src/main/resources/static/favicon.ico b/auth/src/main/resources/static/favicon.ico new file mode 100644 index 0000000..bdb0634 Binary files /dev/null and b/auth/src/main/resources/static/favicon.ico differ diff --git a/auth/src/main/resources/templates/consent.html b/auth/src/main/resources/templates/consent.html new file mode 100644 index 0000000..8dd2de8 --- /dev/null +++ b/auth/src/main/resources/templates/consent.html @@ -0,0 +1,312 @@ + + + + + + 应用授权 - 中央授权系统 + + + + + + + + + + diff --git a/auth/src/main/resources/templates/home.html b/auth/src/main/resources/templates/home.html new file mode 100644 index 0000000..948c66a --- /dev/null +++ b/auth/src/main/resources/templates/home.html @@ -0,0 +1,80 @@ + + + + + + 主页 + + + +
+
+

欢迎来到系统管理平台

+
+ +
+
+ +
+

主页

+

恭喜!您已成功登录系统。

+

这是系统的主页内容。

+
+
+ + \ No newline at end of file diff --git a/auth/src/main/resources/templates/login.html b/auth/src/main/resources/templates/login.html new file mode 100644 index 0000000..6a3d56e --- /dev/null +++ b/auth/src/main/resources/templates/login.html @@ -0,0 +1,547 @@ + + + + + + 中央授权系统 + + + + + + + + + + \ No newline at end of file diff --git a/auth/src/test/java/com/example/springboot4/Springboot4ApplicationTests.java b/auth/src/test/java/com/example/springboot4/Springboot4ApplicationTests.java new file mode 100644 index 0000000..dbe7615 --- /dev/null +++ b/auth/src/test/java/com/example/springboot4/Springboot4ApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.springboot4; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class Springboot4ApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/geteway/.gitattributes b/geteway/.gitattributes new file mode 100644 index 0000000..3b41682 --- /dev/null +++ b/geteway/.gitattributes @@ -0,0 +1,2 @@ +/mvnw text eol=lf +*.cmd text eol=crlf diff --git a/geteway/.gitignore b/geteway/.gitignore new file mode 100644 index 0000000..667aaef --- /dev/null +++ b/geteway/.gitignore @@ -0,0 +1,33 @@ +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/ diff --git a/geteway/.mvn/wrapper/maven-wrapper.properties b/geteway/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 0000000..c0bcafe --- /dev/null +++ b/geteway/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1,3 @@ +wrapperVersion=3.3.4 +distributionType=only-script +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.11/apache-maven-3.9.11-bin.zip diff --git a/geteway/Dockerfile b/geteway/Dockerfile new file mode 100644 index 0000000..d2d86e6 --- /dev/null +++ b/geteway/Dockerfile @@ -0,0 +1,9 @@ +FROM openjdk:25-jdk-slim + +WORKDIR /app + +COPY target/*.jar app.jar + +EXPOSE 8083 + +ENTRYPOINT ["java", "-jar", "app.jar"] \ No newline at end of file diff --git a/geteway/mvnw b/geteway/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/geteway/mvnw @@ -0,0 +1,295 @@ +#!/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.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/geteway/mvnw.cmd b/geteway/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/geteway/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@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.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/geteway/pom.xml b/geteway/pom.xml new file mode 100644 index 0000000..8cf607b --- /dev/null +++ b/geteway/pom.xml @@ -0,0 +1,51 @@ + + + 4.0.0 + + com + lingxiao + 0.0.1-SNAPSHOT + ../pom.xml + + + geteway + + + + org.springframework.cloud + spring-cloud-starter-gateway-server-webflux + + + + org.springframework.boot + spring-boot-starter-security-oauth2-resource-server + + + + org.springframework.boot + spring-boot-starter-oauth2-client + + + + org.springframework.boot + spring-boot-starter-test + test + + + io.projectreactor + reactor-test + test + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + + diff --git a/geteway/src/main/java/com/example/geteway/GatewayApplication.java b/geteway/src/main/java/com/example/geteway/GatewayApplication.java new file mode 100644 index 0000000..80ac119 --- /dev/null +++ b/geteway/src/main/java/com/example/geteway/GatewayApplication.java @@ -0,0 +1,13 @@ +package com.example.geteway; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; + +@SpringBootApplication +public class GatewayApplication { + + public static void main(String[] args) { + SpringApplication.run(GatewayApplication.class, args); + } + +} diff --git a/geteway/src/main/java/com/example/geteway/config/CookieBearerTokenResolver.java b/geteway/src/main/java/com/example/geteway/config/CookieBearerTokenResolver.java new file mode 100644 index 0000000..0f7d484 --- /dev/null +++ b/geteway/src/main/java/com/example/geteway/config/CookieBearerTokenResolver.java @@ -0,0 +1,37 @@ +package com.example.geteway.config; + +import org.jspecify.annotations.NonNull; +import org.springframework.http.HttpCookie; +import org.springframework.security.core.Authentication; +import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; +import org.springframework.util.StringUtils; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +public class CookieBearerTokenResolver extends ServerBearerTokenAuthenticationConverter { + + + private final String cookieName; + + public CookieBearerTokenResolver(String cookieName) { + this.cookieName = cookieName; + } + + @Override + public @NonNull Mono convert(ServerWebExchange exchange) { + HttpCookie cookie = exchange.getRequest().getCookies().getFirst(cookieName); + if (cookie == null || !StringUtils.hasText(cookie.getValue())) { + // No token → return empty → triggers 401 Unauthorized + return Mono.empty(); + } + + String token = cookie.getValue().trim(); + if (token.isEmpty()) { + return Mono.empty(); + } + + // Create authentication token with the extracted JWT + return Mono.just(new BearerTokenAuthenticationToken(token)); + } +} \ No newline at end of file diff --git a/geteway/src/main/java/com/example/geteway/config/SecurityConfig.java b/geteway/src/main/java/com/example/geteway/config/SecurityConfig.java new file mode 100644 index 0000000..53047ec --- /dev/null +++ b/geteway/src/main/java/com/example/geteway/config/SecurityConfig.java @@ -0,0 +1,73 @@ +package com.example.geteway.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.Customizer; +import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; +import org.springframework.security.config.web.server.ServerHttpSecurity; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProvider; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientProviderBuilder; +import org.springframework.security.oauth2.client.OAuth2AuthorizedClientService; +import org.springframework.security.oauth2.client.registration.ClientRegistrationRepository; +import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizedClientManager; +import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository; +import org.springframework.security.web.server.SecurityWebFilterChain; +import org.springframework.web.cors.CorsConfiguration; +import org.springframework.web.cors.reactive.CorsConfigurationSource; +import org.springframework.web.cors.reactive.CorsWebFilter; +import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; +import org.springframework.web.reactive.config.CorsRegistry; +import org.springframework.web.reactive.config.WebFluxConfigurer; + +import java.util.List; + +@Configuration +@EnableWebFluxSecurity +public class SecurityConfig implements WebFluxConfigurer { + + @Bean + public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http) { + http + .csrf(ServerHttpSecurity.CsrfSpec::disable) + .authorizeExchange(exchanges -> + exchanges + .pathMatchers("/api/public/**", "/api/auth/**").permitAll() // 公共API路径 + .pathMatchers("/api/**").authenticated() // 保护你的 API 路由 + .anyExchange().permitAll() + ) + .oauth2ResourceServer(oauth2 -> + oauth2.bearerTokenConverter(new CookieBearerTokenResolver("access_token")) + .jwt(Customizer.withDefaults()) + ) + .cors(cors -> cors.configurationSource(corsConfigurationSource())); // 使用自定义CORS配置 + + return http.build(); + } + + @Bean + CorsConfigurationSource corsConfigurationSource() { + CorsConfiguration configuration = new CorsConfiguration(); + configuration.setAllowedOriginPatterns(List.of("*")); + configuration.setAllowedHeaders(List.of("*")); + configuration.setAllowedMethods(List.of("*")); + configuration.setAllowCredentials(true); + UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource(); + source.registerCorsConfiguration("/**", configuration); + return source; + } + + @Bean + CorsWebFilter corsWebFilter() { + return new CorsWebFilter(corsConfigurationSource()); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/**") + .allowedOriginPatterns("*") + .allowedHeaders("*") + .allowedMethods("*") + .allowCredentials(true); + } +} \ No newline at end of file diff --git a/geteway/src/main/java/com/example/geteway/config/UserIdMappingFilter.java b/geteway/src/main/java/com/example/geteway/config/UserIdMappingFilter.java new file mode 100644 index 0000000..4ddd414 --- /dev/null +++ b/geteway/src/main/java/com/example/geteway/config/UserIdMappingFilter.java @@ -0,0 +1,41 @@ +package com.example.geteway.config; + +import org.springframework.cloud.gateway.filter.GatewayFilterChain; +import org.springframework.cloud.gateway.filter.GlobalFilter; +import org.springframework.core.annotation.Order; +import org.springframework.http.server.reactive.ServerHttpRequest; +import org.springframework.security.core.context.ReactiveSecurityContextHolder; +import org.springframework.security.core.context.SecurityContext; +import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; +import org.springframework.stereotype.Component; +import org.springframework.web.server.ServerWebExchange; +import reactor.core.publisher.Mono; + +@Component +@Order(1000) +public class UserIdMappingFilter implements GlobalFilter { + + @Override + public Mono filter(ServerWebExchange exchange, GatewayFilterChain chain) { + System.out.println(exchange.getRequest().getHeaders()); + + + return ReactiveSecurityContextHolder.getContext() + .mapNotNull(SecurityContext::getAuthentication) + .cast(JwtAuthenticationToken.class) // ✅ 先转为 JwtAuthenticationToken + .map(AbstractOAuth2TokenAuthenticationToken::getToken) // ✅ 再获取 Jwt + .map(jwt -> { + String userId = jwt.getSubject(); // ✅ 现在可以安全调用 + if (userId == null || userId.isBlank()) { + throw new IllegalArgumentException("JWT missing 'sub' claim"); + } + ServerHttpRequest modifiedRequest = exchange.getRequest().mutate() + .header("X-User-Id", userId) + .build(); + return exchange.mutate().request(modifiedRequest).build(); + }) + .switchIfEmpty(Mono.just(exchange)) + .flatMap(chain::filter); + } +} \ No newline at end of file diff --git a/geteway/src/main/resources/application.yml b/geteway/src/main/resources/application.yml new file mode 100644 index 0000000..55b4979 --- /dev/null +++ b/geteway/src/main/resources/application.yml @@ -0,0 +1,50 @@ +server: + port: 8083 +#spring: +# application: +# name: api-gateway +# cloud: +# gateway: +# server: +# webflux: +# default-filters: +# - RewritePath=/api/(?.*)/(?.*), /$\{path} +# enabled: true +# routes: +# - id: user-service +# uri: http://a-service:8091 +# predicates: +# - Path=/api/auth/** +# security: +# oauth2: +# resourceserver: +# jwt: +# jwk-set-uri: http://auth-service:9000/oauth2/jwks +# client: +# provider: +# spring: +# issuer-uri: http://auth-service:9000 + +spring: + cloud: + gateway: + server: + webflux: + default-filters: + - RewritePath=/api/(?.*)/(?.*), /$\{path} + enabled: true + routes: + - id: user-service + uri: http://localhost:8091 + predicates: + - Path=/api/auth/** + + security: + oauth2: + resourceserver: + jwt: + jwk-set-uri: http://localhost:9000/oauth2/jwks + client: + provider: + spring: + issuer-uri: http://localhost:9000 \ No newline at end of file diff --git a/geteway/src/test/java/com/example/geteway/GatewayApplicationTests.java b/geteway/src/test/java/com/example/geteway/GatewayApplicationTests.java new file mode 100644 index 0000000..7ab4993 --- /dev/null +++ b/geteway/src/test/java/com/example/geteway/GatewayApplicationTests.java @@ -0,0 +1,13 @@ +package com.example.geteway; + +import org.junit.jupiter.api.Test; +import org.springframework.boot.test.context.SpringBootTest; + +@SpringBootTest +class GatewayApplicationTests { + + @Test + void contextLoads() { + } + +} diff --git a/k8s/a-service-deployment.yaml b/k8s/a-service-deployment.yaml new file mode 100644 index 0000000..7d93efa --- /dev/null +++ b/k8s/a-service-deployment.yaml @@ -0,0 +1,38 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: a-service-deployment + labels: + app: a-service +spec: + replicas: 1 + selector: + matchLabels: + app: a-service + template: + metadata: + labels: + app: a-service + spec: + containers: + - name: a-service + image: 192.168.1.14:5000/a-service:v10 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8091 + env: + - name: SPRING_APPLICATION_NAME + value: "re-config" # ← 可选,但推荐;也可在 ConfigMap 中设置 +--- +apiVersion: v1 +kind: Service +metadata: + name: a-service +spec: + selector: + app: a-service + ports: + - protocol: TCP + port: 8091 + targetPort: 8091 + type: LoadBalancer \ No newline at end of file diff --git a/k8s/auth-deployment.yaml b/k8s/auth-deployment.yaml new file mode 100644 index 0000000..bf0c91f --- /dev/null +++ b/k8s/auth-deployment.yaml @@ -0,0 +1,44 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: auth-deployment + labels: + app: auth +spec: + replicas: 1 + selector: + matchLabels: + app: auth + template: + metadata: + labels: + app: auth + spec: + containers: + - name: auth + image: 192.168.1.14:5000/auth:v33 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 9000 + volumeMounts: + - name: keys-secret + mountPath: keys + readOnly: true + volumes: + - name: keys-secret + secret: + secretName: app-keys + defaultMode: 0600 +--- +apiVersion: v1 +kind: Service +metadata: + name: auth-service +spec: + selector: + app: auth + ports: + - protocol: TCP + port: 9000 + targetPort: 9000 + type: LoadBalancer \ No newline at end of file diff --git a/k8s/gateway-deployment.yaml b/k8s/gateway-deployment.yaml new file mode 100644 index 0000000..ee22de6 --- /dev/null +++ b/k8s/gateway-deployment.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: gateway-deployment + labels: + app: gateway +spec: + replicas: 1 + selector: + matchLabels: + app: gateway + template: + metadata: + labels: + app: gateway + spec: + containers: + - name: gateway + image: 192.168.1.14:5000/gateway:v19 + imagePullPolicy: IfNotPresent + ports: + - containerPort: 8083 +--- +apiVersion: v1 +kind: Service +metadata: + name: gateway-service +spec: + selector: + app: gateway + ports: + - protocol: TCP + port: 8083 + targetPort: 8083 + type: LoadBalancer \ No newline at end of file diff --git a/k8s/secrets.yaml b/k8s/secrets.yaml new file mode 100644 index 0000000..0408fe2 --- /dev/null +++ b/k8s/secrets.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: Secret +metadata: + name: auth-secret +type: Opaque +data: + # 注意:这些是base64编码的值 + # root (用户名) + db-username: cm9vdA== + # lingxiao (密码) + db-password: bGluZ3hpYW8= \ No newline at end of file diff --git a/keys/oauth2-private.key b/keys/oauth2-private.key new file mode 100644 index 0000000..d604b99 Binary files /dev/null and b/keys/oauth2-private.key differ diff --git a/keys/oauth2-public.key b/keys/oauth2-public.key new file mode 100644 index 0000000..defbafc Binary files /dev/null and b/keys/oauth2-public.key differ diff --git a/mvnw b/mvnw new file mode 100644 index 0000000..bd8896b --- /dev/null +++ b/mvnw @@ -0,0 +1,295 @@ +#!/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.3.4 +# +# Optional ENV vars +# ----------------- +# JAVA_HOME - location of a JDK home dir, required when download maven via java source +# MVNW_REPOURL - repo url base for downloading maven distribution +# MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +# MVNW_VERBOSE - true: enable verbose log; debug: trace the mvnw script; others: silence the output +# ---------------------------------------------------------------------------- + +set -euf +[ "${MVNW_VERBOSE-}" != debug ] || set -x + +# OS specific support. +native_path() { printf %s\\n "$1"; } +case "$(uname)" in +CYGWIN* | MINGW*) + [ -z "${JAVA_HOME-}" ] || JAVA_HOME="$(cygpath --unix "$JAVA_HOME")" + native_path() { cygpath --path --windows "$1"; } + ;; +esac + +# set JAVACMD and JAVACCMD +set_java_home() { + # For Cygwin and MinGW, ensure paths are in Unix format before anything is touched + 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" + JAVACCMD="$JAVA_HOME/jre/sh/javac" + else + JAVACMD="$JAVA_HOME/bin/java" + JAVACCMD="$JAVA_HOME/bin/javac" + + if [ ! -x "$JAVACMD" ] || [ ! -x "$JAVACCMD" ]; then + echo "The JAVA_HOME environment variable is not defined correctly, so mvnw cannot run." >&2 + echo "JAVA_HOME is set to \"$JAVA_HOME\", but \"\$JAVA_HOME/bin/java\" or \"\$JAVA_HOME/bin/javac\" does not exist." >&2 + return 1 + fi + fi + else + JAVACMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v java + )" || : + JAVACCMD="$( + 'set' +e + 'unset' -f command 2>/dev/null + 'command' -v javac + )" || : + + if [ ! -x "${JAVACMD-}" ] || [ ! -x "${JAVACCMD-}" ]; then + echo "The java/javac command does not exist in PATH nor is JAVA_HOME set, so mvnw cannot run." >&2 + return 1 + fi + fi +} + +# hash string like Java String::hashCode +hash_string() { + str="${1:-}" h=0 + while [ -n "$str" ]; do + char="${str%"${str#?}"}" + h=$(((h * 31 + $(LC_CTYPE=C printf %d "'$char")) % 4294967296)) + str="${str#?}" + done + printf %x\\n $h +} + +verbose() { :; } +[ "${MVNW_VERBOSE-}" != true ] || verbose() { printf %s\\n "${1-}"; } + +die() { + printf %s\\n "$1" >&2 + exit 1 +} + +trim() { + # MWRAPPER-139: + # Trims trailing and leading whitespace, carriage returns, tabs, and linefeeds. + # Needed for removing poorly interpreted newline sequences when running in more + # exotic environments such as mingw bash on Windows. + printf "%s" "${1}" | tr -d '[:space:]' +} + +scriptDir="$(dirname "$0")" +scriptName="$(basename "$0")" + +# parse distributionUrl and optional distributionSha256Sum, requires .mvn/wrapper/maven-wrapper.properties +while IFS="=" read -r key value; do + case "${key-}" in + distributionUrl) distributionUrl=$(trim "${value-}") ;; + distributionSha256Sum) distributionSha256Sum=$(trim "${value-}") ;; + esac +done <"$scriptDir/.mvn/wrapper/maven-wrapper.properties" +[ -n "${distributionUrl-}" ] || die "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" + +case "${distributionUrl##*/}" in +maven-mvnd-*bin.*) + MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ + case "${PROCESSOR_ARCHITECTURE-}${PROCESSOR_ARCHITEW6432-}:$(uname -a)" in + *AMD64:CYGWIN* | *AMD64:MINGW*) distributionPlatform=windows-amd64 ;; + :Darwin*x86_64) distributionPlatform=darwin-amd64 ;; + :Darwin*arm64) distributionPlatform=darwin-aarch64 ;; + :Linux*x86_64*) distributionPlatform=linux-amd64 ;; + *) + echo "Cannot detect native platform for mvnd on $(uname)-$(uname -m), use pure java version" >&2 + distributionPlatform=linux-amd64 + ;; + esac + distributionUrl="${distributionUrl%-bin.*}-$distributionPlatform.zip" + ;; +maven-mvnd-*) MVN_CMD=mvnd.sh _MVNW_REPO_PATTERN=/maven/mvnd/ ;; +*) MVN_CMD="mvn${scriptName#mvnw}" _MVNW_REPO_PATTERN=/org/apache/maven/ ;; +esac + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +[ -z "${MVNW_REPOURL-}" ] || distributionUrl="$MVNW_REPOURL$_MVNW_REPO_PATTERN${distributionUrl#*"$_MVNW_REPO_PATTERN"}" +distributionUrlName="${distributionUrl##*/}" +distributionUrlNameMain="${distributionUrlName%.*}" +distributionUrlNameMain="${distributionUrlNameMain%-bin}" +MAVEN_USER_HOME="${MAVEN_USER_HOME:-${HOME}/.m2}" +MAVEN_HOME="${MAVEN_USER_HOME}/wrapper/dists/${distributionUrlNameMain-}/$(hash_string "$distributionUrl")" + +exec_maven() { + unset MVNW_VERBOSE MVNW_USERNAME MVNW_PASSWORD MVNW_REPOURL || : + exec "$MAVEN_HOME/bin/$MVN_CMD" "$@" || die "cannot exec $MAVEN_HOME/bin/$MVN_CMD" +} + +if [ -d "$MAVEN_HOME" ]; then + verbose "found existing MAVEN_HOME at $MAVEN_HOME" + exec_maven "$@" +fi + +case "${distributionUrl-}" in +*?-bin.zip | *?maven-mvnd-?*-?*.zip) ;; +*) die "distributionUrl is not valid, must match *-bin.zip or maven-mvnd-*.zip, but found '${distributionUrl-}'" ;; +esac + +# prepare tmp dir +if TMP_DOWNLOAD_DIR="$(mktemp -d)" && [ -d "$TMP_DOWNLOAD_DIR" ]; then + clean() { rm -rf -- "$TMP_DOWNLOAD_DIR"; } + trap clean HUP INT TERM EXIT +else + die "cannot create temp dir" +fi + +mkdir -p -- "${MAVEN_HOME%/*}" + +# Download and Install Apache Maven +verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +verbose "Downloading from: $distributionUrl" +verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +# select .zip or .tar.gz +if ! command -v unzip >/dev/null; then + distributionUrl="${distributionUrl%.zip}.tar.gz" + distributionUrlName="${distributionUrl##*/}" +fi + +# verbose opt +__MVNW_QUIET_WGET=--quiet __MVNW_QUIET_CURL=--silent __MVNW_QUIET_UNZIP=-q __MVNW_QUIET_TAR='' +[ "${MVNW_VERBOSE-}" != true ] || __MVNW_QUIET_WGET='' __MVNW_QUIET_CURL='' __MVNW_QUIET_UNZIP='' __MVNW_QUIET_TAR=v + +# normalize http auth +case "${MVNW_PASSWORD:+has-password}" in +'') MVNW_USERNAME='' MVNW_PASSWORD='' ;; +has-password) [ -n "${MVNW_USERNAME-}" ] || MVNW_USERNAME='' MVNW_PASSWORD='' ;; +esac + +if [ -z "${MVNW_USERNAME-}" ] && command -v wget >/dev/null; then + verbose "Found wget ... using wget" + wget ${__MVNW_QUIET_WGET:+"$__MVNW_QUIET_WGET"} "$distributionUrl" -O "$TMP_DOWNLOAD_DIR/$distributionUrlName" || die "wget: Failed to fetch $distributionUrl" +elif [ -z "${MVNW_USERNAME-}" ] && command -v curl >/dev/null; then + verbose "Found curl ... using curl" + curl ${__MVNW_QUIET_CURL:+"$__MVNW_QUIET_CURL"} -f -L -o "$TMP_DOWNLOAD_DIR/$distributionUrlName" "$distributionUrl" || die "curl: Failed to fetch $distributionUrl" +elif set_java_home; then + verbose "Falling back to use Java to download" + javaSource="$TMP_DOWNLOAD_DIR/Downloader.java" + targetZip="$TMP_DOWNLOAD_DIR/$distributionUrlName" + cat >"$javaSource" <<-END + public class Downloader extends java.net.Authenticator + { + protected java.net.PasswordAuthentication getPasswordAuthentication() + { + return new java.net.PasswordAuthentication( System.getenv( "MVNW_USERNAME" ), System.getenv( "MVNW_PASSWORD" ).toCharArray() ); + } + public static void main( String[] args ) throws Exception + { + setDefault( new Downloader() ); + java.nio.file.Files.copy( java.net.URI.create( args[0] ).toURL().openStream(), java.nio.file.Paths.get( args[1] ).toAbsolutePath().normalize() ); + } + } + END + # For Cygwin/MinGW, switch paths to Windows format before running javac and java + verbose " - Compiling Downloader.java ..." + "$(native_path "$JAVACCMD")" "$(native_path "$javaSource")" || die "Failed to compile Downloader.java" + verbose " - Running Downloader.java ..." + "$(native_path "$JAVACMD")" -cp "$(native_path "$TMP_DOWNLOAD_DIR")" Downloader "$distributionUrl" "$(native_path "$targetZip")" +fi + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +if [ -n "${distributionSha256Sum-}" ]; then + distributionSha256Result=false + if [ "$MVN_CMD" = mvnd.sh ]; then + echo "Checksum validation is not supported for maven-mvnd." >&2 + echo "Please disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + elif command -v sha256sum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | sha256sum -c - >/dev/null 2>&1; then + distributionSha256Result=true + fi + elif command -v shasum >/dev/null; then + if echo "$distributionSha256Sum $TMP_DOWNLOAD_DIR/$distributionUrlName" | shasum -a 256 -c >/dev/null 2>&1; then + distributionSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." >&2 + echo "Please install either command, or disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." >&2 + exit 1 + fi + if [ $distributionSha256Result = false ]; then + echo "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised." >&2 + echo "If you updated your Maven version, you need to update the specified distributionSha256Sum property." >&2 + exit 1 + fi +fi + +# unzip and move +if command -v unzip >/dev/null; then + unzip ${__MVNW_QUIET_UNZIP:+"$__MVNW_QUIET_UNZIP"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -d "$TMP_DOWNLOAD_DIR" || die "failed to unzip" +else + tar xzf${__MVNW_QUIET_TAR:+"$__MVNW_QUIET_TAR"} "$TMP_DOWNLOAD_DIR/$distributionUrlName" -C "$TMP_DOWNLOAD_DIR" || die "failed to untar" +fi + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +actualDistributionDir="" + +# First try the expected directory name (for regular distributions) +if [ -d "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain" ]; then + if [ -f "$TMP_DOWNLOAD_DIR/$distributionUrlNameMain/bin/$MVN_CMD" ]; then + actualDistributionDir="$distributionUrlNameMain" + fi +fi + +# If not found, search for any directory with the Maven executable (for snapshots) +if [ -z "$actualDistributionDir" ]; then + # enable globbing to iterate over items + set +f + for dir in "$TMP_DOWNLOAD_DIR"/*; do + if [ -d "$dir" ]; then + if [ -f "$dir/bin/$MVN_CMD" ]; then + actualDistributionDir="$(basename "$dir")" + break + fi + fi + done + set -f +fi + +if [ -z "$actualDistributionDir" ]; then + verbose "Contents of $TMP_DOWNLOAD_DIR:" + verbose "$(ls -la "$TMP_DOWNLOAD_DIR")" + die "Could not find Maven distribution directory in extracted archive" +fi + +verbose "Found extracted Maven distribution directory: $actualDistributionDir" +printf %s\\n "$distributionUrl" >"$TMP_DOWNLOAD_DIR/$actualDistributionDir/mvnw.url" +mv -- "$TMP_DOWNLOAD_DIR/$actualDistributionDir" "$MAVEN_HOME" || [ -d "$MAVEN_HOME" ] || die "fail to move MAVEN_HOME" + +clean || : +exec_maven "$@" diff --git a/mvnw.cmd b/mvnw.cmd new file mode 100644 index 0000000..92450f9 --- /dev/null +++ b/mvnw.cmd @@ -0,0 +1,189 @@ +<# : batch portion +@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.3.4 +@REM +@REM Optional ENV vars +@REM MVNW_REPOURL - repo url base for downloading maven distribution +@REM MVNW_USERNAME/MVNW_PASSWORD - user and password for downloading maven +@REM MVNW_VERBOSE - true: enable verbose log; others: silence the output +@REM ---------------------------------------------------------------------------- + +@IF "%__MVNW_ARG0_NAME__%"=="" (SET __MVNW_ARG0_NAME__=%~nx0) +@SET __MVNW_CMD__= +@SET __MVNW_ERROR__= +@SET __MVNW_PSMODULEP_SAVE=%PSModulePath% +@SET PSModulePath= +@FOR /F "usebackq tokens=1* delims==" %%A IN (`powershell -noprofile "& {$scriptDir='%~dp0'; $script='%__MVNW_ARG0_NAME__%'; icm -ScriptBlock ([Scriptblock]::Create((Get-Content -Raw '%~f0'))) -NoNewScope}"`) DO @( + IF "%%A"=="MVN_CMD" (set __MVNW_CMD__=%%B) ELSE IF "%%B"=="" (echo %%A) ELSE (echo %%A=%%B) +) +@SET PSModulePath=%__MVNW_PSMODULEP_SAVE% +@SET __MVNW_PSMODULEP_SAVE= +@SET __MVNW_ARG0_NAME__= +@SET MVNW_USERNAME= +@SET MVNW_PASSWORD= +@IF NOT "%__MVNW_CMD__%"=="" ("%__MVNW_CMD__%" %*) +@echo Cannot start maven from wrapper >&2 && exit /b 1 +@GOTO :EOF +: end batch / begin powershell #> + +$ErrorActionPreference = "Stop" +if ($env:MVNW_VERBOSE -eq "true") { + $VerbosePreference = "Continue" +} + +# calculate distributionUrl, requires .mvn/wrapper/maven-wrapper.properties +$distributionUrl = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionUrl +if (!$distributionUrl) { + Write-Error "cannot read distributionUrl property in $scriptDir/.mvn/wrapper/maven-wrapper.properties" +} + +switch -wildcard -casesensitive ( $($distributionUrl -replace '^.*/','') ) { + "maven-mvnd-*" { + $USE_MVND = $true + $distributionUrl = $distributionUrl -replace '-bin\.[^.]*$',"-windows-amd64.zip" + $MVN_CMD = "mvnd.cmd" + break + } + default { + $USE_MVND = $false + $MVN_CMD = $script -replace '^mvnw','mvn' + break + } +} + +# apply MVNW_REPOURL and calculate MAVEN_HOME +# maven home pattern: ~/.m2/wrapper/dists/{apache-maven-,maven-mvnd--}/ +if ($env:MVNW_REPOURL) { + $MVNW_REPO_PATTERN = if ($USE_MVND -eq $False) { "/org/apache/maven/" } else { "/maven/mvnd/" } + $distributionUrl = "$env:MVNW_REPOURL$MVNW_REPO_PATTERN$($distributionUrl -replace "^.*$MVNW_REPO_PATTERN",'')" +} +$distributionUrlName = $distributionUrl -replace '^.*/','' +$distributionUrlNameMain = $distributionUrlName -replace '\.[^.]*$','' -replace '-bin$','' + +$MAVEN_M2_PATH = "$HOME/.m2" +if ($env:MAVEN_USER_HOME) { + $MAVEN_M2_PATH = "$env:MAVEN_USER_HOME" +} + +if (-not (Test-Path -Path $MAVEN_M2_PATH)) { + New-Item -Path $MAVEN_M2_PATH -ItemType Directory | Out-Null +} + +$MAVEN_WRAPPER_DISTS = $null +if ((Get-Item $MAVEN_M2_PATH).Target[0] -eq $null) { + $MAVEN_WRAPPER_DISTS = "$MAVEN_M2_PATH/wrapper/dists" +} else { + $MAVEN_WRAPPER_DISTS = (Get-Item $MAVEN_M2_PATH).Target[0] + "/wrapper/dists" +} + +$MAVEN_HOME_PARENT = "$MAVEN_WRAPPER_DISTS/$distributionUrlNameMain" +$MAVEN_HOME_NAME = ([System.Security.Cryptography.SHA256]::Create().ComputeHash([byte[]][char[]]$distributionUrl) | ForEach-Object {$_.ToString("x2")}) -join '' +$MAVEN_HOME = "$MAVEN_HOME_PARENT/$MAVEN_HOME_NAME" + +if (Test-Path -Path "$MAVEN_HOME" -PathType Container) { + Write-Verbose "found existing MAVEN_HOME at $MAVEN_HOME" + Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" + exit $? +} + +if (! $distributionUrlNameMain -or ($distributionUrlName -eq $distributionUrlNameMain)) { + Write-Error "distributionUrl is not valid, must end with *-bin.zip, but found $distributionUrl" +} + +# prepare tmp dir +$TMP_DOWNLOAD_DIR_HOLDER = New-TemporaryFile +$TMP_DOWNLOAD_DIR = New-Item -Itemtype Directory -Path "$TMP_DOWNLOAD_DIR_HOLDER.dir" +$TMP_DOWNLOAD_DIR_HOLDER.Delete() | Out-Null +trap { + if ($TMP_DOWNLOAD_DIR.Exists) { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } + } +} + +New-Item -Itemtype Directory -Path "$MAVEN_HOME_PARENT" -Force | Out-Null + +# Download and Install Apache Maven +Write-Verbose "Couldn't find MAVEN_HOME, downloading and installing it ..." +Write-Verbose "Downloading from: $distributionUrl" +Write-Verbose "Downloading to: $TMP_DOWNLOAD_DIR/$distributionUrlName" + +$webclient = New-Object System.Net.WebClient +if ($env:MVNW_USERNAME -and $env:MVNW_PASSWORD) { + $webclient.Credentials = New-Object System.Net.NetworkCredential($env:MVNW_USERNAME, $env:MVNW_PASSWORD) +} +[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12 +$webclient.DownloadFile($distributionUrl, "$TMP_DOWNLOAD_DIR/$distributionUrlName") | Out-Null + +# If specified, validate the SHA-256 sum of the Maven distribution zip file +$distributionSha256Sum = (Get-Content -Raw "$scriptDir/.mvn/wrapper/maven-wrapper.properties" | ConvertFrom-StringData).distributionSha256Sum +if ($distributionSha256Sum) { + if ($USE_MVND) { + Write-Error "Checksum validation is not supported for maven-mvnd. `nPlease disable validation by removing 'distributionSha256Sum' from your maven-wrapper.properties." + } + Import-Module $PSHOME\Modules\Microsoft.PowerShell.Utility -Function Get-FileHash + if ((Get-FileHash "$TMP_DOWNLOAD_DIR/$distributionUrlName" -Algorithm SHA256).Hash.ToLower() -ne $distributionSha256Sum) { + Write-Error "Error: Failed to validate Maven distribution SHA-256, your Maven distribution might be compromised. If you updated your Maven version, you need to update the specified distributionSha256Sum property." + } +} + +# unzip and move +Expand-Archive "$TMP_DOWNLOAD_DIR/$distributionUrlName" -DestinationPath "$TMP_DOWNLOAD_DIR" | Out-Null + +# Find the actual extracted directory name (handles snapshots where filename != directory name) +$actualDistributionDir = "" + +# First try the expected directory name (for regular distributions) +$expectedPath = Join-Path "$TMP_DOWNLOAD_DIR" "$distributionUrlNameMain" +$expectedMvnPath = Join-Path "$expectedPath" "bin/$MVN_CMD" +if ((Test-Path -Path $expectedPath -PathType Container) -and (Test-Path -Path $expectedMvnPath -PathType Leaf)) { + $actualDistributionDir = $distributionUrlNameMain +} + +# If not found, search for any directory with the Maven executable (for snapshots) +if (!$actualDistributionDir) { + Get-ChildItem -Path "$TMP_DOWNLOAD_DIR" -Directory | ForEach-Object { + $testPath = Join-Path $_.FullName "bin/$MVN_CMD" + if (Test-Path -Path $testPath -PathType Leaf) { + $actualDistributionDir = $_.Name + } + } +} + +if (!$actualDistributionDir) { + Write-Error "Could not find Maven distribution directory in extracted archive" +} + +Write-Verbose "Found extracted Maven distribution directory: $actualDistributionDir" +Rename-Item -Path "$TMP_DOWNLOAD_DIR/$actualDistributionDir" -NewName $MAVEN_HOME_NAME | Out-Null +try { + Move-Item -Path "$TMP_DOWNLOAD_DIR/$MAVEN_HOME_NAME" -Destination $MAVEN_HOME_PARENT | Out-Null +} catch { + if (! (Test-Path -Path "$MAVEN_HOME" -PathType Container)) { + Write-Error "fail to move MAVEN_HOME" + } +} finally { + try { Remove-Item $TMP_DOWNLOAD_DIR -Recurse -Force | Out-Null } + catch { Write-Warning "Cannot remove $TMP_DOWNLOAD_DIR" } +} + +Write-Output "MVN_CMD=$MAVEN_HOME/bin/$MVN_CMD" diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..c8b2ee2 --- /dev/null +++ b/pom.xml @@ -0,0 +1,92 @@ + + + 4.0.0 + pom + + + auth + geteway + a-service + + + org.springframework.boot + spring-boot-starter-parent + 4.0.2 + + + com + lingxiao + 0.0.1-SNAPSHOT + lingxiao + lingxiao + + + 25 + true + UTF-8 + UTF-8 + 25 + 25 + UTF-8 + 2025.1.1 + + + + org.springframework.boot + spring-boot-starter + + + + com.github.ben-manes.caffeine + caffeine + + + + org.springframework.cloud + spring-cloud-starter-kubernetes-fabric8-all + + + + + + + org.springframework.cloud + spring-cloud-dependencies + ${spring.cloud-version} + pom + import + + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + + org.projectlombok + lombok + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + + org.projectlombok + lombok + + + + + + + +