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/ |
distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip | |||||
wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar |
#!/bin/sh | |||||
# ---------------------------------------------------------------------------- | |||||
# Licensed to the Apache Software Foundation (ASF) under one | |||||
# or more contributor license agreements. See the NOTICE file | |||||
# distributed with this work for additional information | |||||
# regarding copyright ownership. The ASF licenses this file | |||||
# to you under the Apache License, Version 2.0 (the | |||||
# "License"); you may not use this file except in compliance | |||||
# with the License. You may obtain a copy of the License at | |||||
# | |||||
# https://www.apache.org/licenses/LICENSE-2.0 | |||||
# | |||||
# Unless required by applicable law or agreed to in writing, | |||||
# software distributed under the License is distributed on an | |||||
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||||
# KIND, either express or implied. See the License for the | |||||
# specific language governing permissions and limitations | |||||
# under the License. | |||||
# ---------------------------------------------------------------------------- | |||||
# ---------------------------------------------------------------------------- | |||||
# Maven Start Up Batch script | |||||
# | |||||
# Required ENV vars: | |||||
# ------------------ | |||||
# JAVA_HOME - location of a JDK home dir | |||||
# | |||||
# Optional ENV vars | |||||
# ----------------- | |||||
# M2_HOME - location of maven2's installed home dir | |||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven | |||||
# e.g. to debug Maven itself, use | |||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 | |||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files | |||||
# ---------------------------------------------------------------------------- | |||||
if [ -z "$MAVEN_SKIP_RC" ] ; then | |||||
if [ -f /usr/local/etc/mavenrc ] ; then | |||||
. /usr/local/etc/mavenrc | |||||
fi | |||||
if [ -f /etc/mavenrc ] ; then | |||||
. /etc/mavenrc | |||||
fi | |||||
if [ -f "$HOME/.mavenrc" ] ; then | |||||
. "$HOME/.mavenrc" | |||||
fi | |||||
fi | |||||
# OS specific support. $var _must_ be set to either true or false. | |||||
cygwin=false; | |||||
darwin=false; | |||||
mingw=false | |||||
case "`uname`" in | |||||
CYGWIN*) cygwin=true ;; | |||||
MINGW*) mingw=true;; | |||||
Darwin*) darwin=true | |||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home | |||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html | |||||
if [ -z "$JAVA_HOME" ]; then | |||||
if [ -x "/usr/libexec/java_home" ]; then | |||||
export JAVA_HOME="`/usr/libexec/java_home`" | |||||
else | |||||
export JAVA_HOME="/Library/Java/Home" | |||||
fi | |||||
fi | |||||
;; | |||||
esac | |||||
if [ -z "$JAVA_HOME" ] ; then | |||||
if [ -r /etc/gentoo-release ] ; then | |||||
JAVA_HOME=`java-config --jre-home` | |||||
fi | |||||
fi | |||||
if [ -z "$M2_HOME" ] ; then | |||||
## resolve links - $0 may be a link to maven's home | |||||
PRG="$0" | |||||
# need this for relative symlinks | |||||
while [ -h "$PRG" ] ; do | |||||
ls=`ls -ld "$PRG"` | |||||
link=`expr "$ls" : '.*-> \(.*\)$'` | |||||
if expr "$link" : '/.*' > /dev/null; then | |||||
PRG="$link" | |||||
else | |||||
PRG="`dirname "$PRG"`/$link" | |||||
fi | |||||
done | |||||
saveddir=`pwd` | |||||
M2_HOME=`dirname "$PRG"`/.. | |||||
# make it fully qualified | |||||
M2_HOME=`cd "$M2_HOME" && pwd` | |||||
cd "$saveddir" | |||||
# echo Using m2 at $M2_HOME | |||||
fi | |||||
# For Cygwin, ensure paths are in UNIX format before anything is touched | |||||
if $cygwin ; then | |||||
[ -n "$M2_HOME" ] && | |||||
M2_HOME=`cygpath --unix "$M2_HOME"` | |||||
[ -n "$JAVA_HOME" ] && | |||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"` | |||||
[ -n "$CLASSPATH" ] && | |||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"` | |||||
fi | |||||
# For Mingw, ensure paths are in UNIX format before anything is touched | |||||
if $mingw ; then | |||||
[ -n "$M2_HOME" ] && | |||||
M2_HOME="`(cd "$M2_HOME"; pwd)`" | |||||
[ -n "$JAVA_HOME" ] && | |||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" | |||||
fi | |||||
if [ -z "$JAVA_HOME" ]; then | |||||
javaExecutable="`which javac`" | |||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then | |||||
# readlink(1) is not available as standard on Solaris 10. | |||||
readLink=`which readlink` | |||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then | |||||
if $darwin ; then | |||||
javaHome="`dirname \"$javaExecutable\"`" | |||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" | |||||
else | |||||
javaExecutable="`readlink -f \"$javaExecutable\"`" | |||||
fi | |||||
javaHome="`dirname \"$javaExecutable\"`" | |||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'` | |||||
JAVA_HOME="$javaHome" | |||||
export JAVA_HOME | |||||
fi | |||||
fi | |||||
fi | |||||
if [ -z "$JAVACMD" ] ; then | |||||
if [ -n "$JAVA_HOME" ] ; then | |||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then | |||||
# IBM's JDK on AIX uses strange locations for the executables | |||||
JAVACMD="$JAVA_HOME/jre/sh/java" | |||||
else | |||||
JAVACMD="$JAVA_HOME/bin/java" | |||||
fi | |||||
else | |||||
JAVACMD="`\\unset -f command; \\command -v java`" | |||||
fi | |||||
fi | |||||
if [ ! -x "$JAVACMD" ] ; then | |||||
echo "Error: JAVA_HOME is not defined correctly." >&2 | |||||
echo " We cannot execute $JAVACMD" >&2 | |||||
exit 1 | |||||
fi | |||||
if [ -z "$JAVA_HOME" ] ; then | |||||
echo "Warning: JAVA_HOME environment variable is not set." | |||||
fi | |||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher | |||||
# traverses directory structure from process work directory to filesystem root | |||||
# first directory with .mvn subdirectory is considered project base directory | |||||
find_maven_basedir() { | |||||
if [ -z "$1" ] | |||||
then | |||||
echo "Path not specified to find_maven_basedir" | |||||
return 1 | |||||
fi | |||||
basedir="$1" | |||||
wdir="$1" | |||||
while [ "$wdir" != '/' ] ; do | |||||
if [ -d "$wdir"/.mvn ] ; then | |||||
basedir=$wdir | |||||
break | |||||
fi | |||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc) | |||||
if [ -d "${wdir}" ]; then | |||||
wdir=`cd "$wdir/.."; pwd` | |||||
fi | |||||
# end of workaround | |||||
done | |||||
echo "${basedir}" | |||||
} | |||||
# concatenates all lines of a file | |||||
concat_lines() { | |||||
if [ -f "$1" ]; then | |||||
echo "$(tr -s '\n' ' ' < "$1")" | |||||
fi | |||||
} | |||||
BASE_DIR=`find_maven_basedir "$(pwd)"` | |||||
if [ -z "$BASE_DIR" ]; then | |||||
exit 1; | |||||
fi | |||||
########################################################################################## | |||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central | |||||
# This allows using the maven wrapper in projects that prohibit checking in binary data. | |||||
########################################################################################## | |||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Found .mvn/wrapper/maven-wrapper.jar" | |||||
fi | |||||
else | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." | |||||
fi | |||||
if [ -n "$MVNW_REPOURL" ]; then | |||||
jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" | |||||
else | |||||
jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" | |||||
fi | |||||
while IFS="=" read key value; do | |||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;; | |||||
esac | |||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Downloading from: $jarUrl" | |||||
fi | |||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" | |||||
if $cygwin; then | |||||
wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` | |||||
fi | |||||
if command -v wget > /dev/null; then | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Found wget ... using wget" | |||||
fi | |||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then | |||||
wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" | |||||
else | |||||
wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" | |||||
fi | |||||
elif command -v curl > /dev/null; then | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Found curl ... using curl" | |||||
fi | |||||
if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then | |||||
curl -o "$wrapperJarPath" "$jarUrl" -f | |||||
else | |||||
curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f | |||||
fi | |||||
else | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo "Falling back to using Java to download" | |||||
fi | |||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" | |||||
# For Cygwin, switch paths to Windows format before running javac | |||||
if $cygwin; then | |||||
javaClass=`cygpath --path --windows "$javaClass"` | |||||
fi | |||||
if [ -e "$javaClass" ]; then | |||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo " - Compiling MavenWrapperDownloader.java ..." | |||||
fi | |||||
# Compiling the Java class | |||||
("$JAVA_HOME/bin/javac" "$javaClass") | |||||
fi | |||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then | |||||
# Running the downloader | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo " - Running MavenWrapperDownloader.java ..." | |||||
fi | |||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") | |||||
fi | |||||
fi | |||||
fi | |||||
fi | |||||
########################################################################################## | |||||
# End of extension | |||||
########################################################################################## | |||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} | |||||
if [ "$MVNW_VERBOSE" = true ]; then | |||||
echo $MAVEN_PROJECTBASEDIR | |||||
fi | |||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" | |||||
# For Cygwin, switch paths to Windows format before running java | |||||
if $cygwin; then | |||||
[ -n "$M2_HOME" ] && | |||||
M2_HOME=`cygpath --path --windows "$M2_HOME"` | |||||
[ -n "$JAVA_HOME" ] && | |||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` | |||||
[ -n "$CLASSPATH" ] && | |||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"` | |||||
[ -n "$MAVEN_PROJECTBASEDIR" ] && | |||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` | |||||
fi | |||||
# Provide a "standardized" way to retrieve the CLI args that will | |||||
# work with both Windows and non-Windows executions. | |||||
MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" | |||||
export MAVEN_CMD_LINE_ARGS | |||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain | |||||
exec "$JAVACMD" \ | |||||
$MAVEN_OPTS \ | |||||
$MAVEN_DEBUG_OPTS \ | |||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ | |||||
"-Dmaven.home=${M2_HOME}" \ | |||||
"-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ | |||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" |
@REM ---------------------------------------------------------------------------- | |||||
@REM Licensed to the Apache Software Foundation (ASF) under one | |||||
@REM or more contributor license agreements. See the NOTICE file | |||||
@REM distributed with this work for additional information | |||||
@REM regarding copyright ownership. The ASF licenses this file | |||||
@REM to you under the Apache License, Version 2.0 (the | |||||
@REM "License"); you may not use this file except in compliance | |||||
@REM with the License. You may obtain a copy of the License at | |||||
@REM | |||||
@REM https://www.apache.org/licenses/LICENSE-2.0 | |||||
@REM | |||||
@REM Unless required by applicable law or agreed to in writing, | |||||
@REM software distributed under the License is distributed on an | |||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |||||
@REM KIND, either express or implied. See the License for the | |||||
@REM specific language governing permissions and limitations | |||||
@REM under the License. | |||||
@REM ---------------------------------------------------------------------------- | |||||
@REM ---------------------------------------------------------------------------- | |||||
@REM Maven Start Up Batch script | |||||
@REM | |||||
@REM Required ENV vars: | |||||
@REM JAVA_HOME - location of a JDK home dir | |||||
@REM | |||||
@REM Optional ENV vars | |||||
@REM M2_HOME - location of maven2's installed home dir | |||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands | |||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending | |||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven | |||||
@REM e.g. to debug Maven itself, use | |||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 | |||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files | |||||
@REM ---------------------------------------------------------------------------- | |||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' | |||||
@echo off | |||||
@REM set title of command window | |||||
title %0 | |||||
@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on' | |||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% | |||||
@REM set %HOME% to equivalent of $HOME | |||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") | |||||
@REM Execute a user defined script before this one | |||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre | |||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending | |||||
if exist "%USERPROFILE%\mavenrc_pre.bat" call "%USERPROFILE%\mavenrc_pre.bat" %* | |||||
if exist "%USERPROFILE%\mavenrc_pre.cmd" call "%USERPROFILE%\mavenrc_pre.cmd" %* | |||||
:skipRcPre | |||||
@setlocal | |||||
set ERROR_CODE=0 | |||||
@REM To isolate internal variables from possible post scripts, we use another setlocal | |||||
@setlocal | |||||
@REM ==== START VALIDATION ==== | |||||
if not "%JAVA_HOME%" == "" goto OkJHome | |||||
echo. | |||||
echo Error: JAVA_HOME not found in your environment. >&2 | |||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 | |||||
echo location of your Java installation. >&2 | |||||
echo. | |||||
goto error | |||||
:OkJHome | |||||
if exist "%JAVA_HOME%\bin\java.exe" goto init | |||||
echo. | |||||
echo Error: JAVA_HOME is set to an invalid directory. >&2 | |||||
echo JAVA_HOME = "%JAVA_HOME%" >&2 | |||||
echo Please set the JAVA_HOME variable in your environment to match the >&2 | |||||
echo location of your Java installation. >&2 | |||||
echo. | |||||
goto error | |||||
@REM ==== END VALIDATION ==== | |||||
:init | |||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". | |||||
@REM Fallback to current working directory if not found. | |||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% | |||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir | |||||
set EXEC_DIR=%CD% | |||||
set WDIR=%EXEC_DIR% | |||||
:findBaseDir | |||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound | |||||
cd .. | |||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound | |||||
set WDIR=%CD% | |||||
goto findBaseDir | |||||
:baseDirFound | |||||
set MAVEN_PROJECTBASEDIR=%WDIR% | |||||
cd "%EXEC_DIR%" | |||||
goto endDetectBaseDir | |||||
:baseDirNotFound | |||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR% | |||||
cd "%EXEC_DIR%" | |||||
:endDetectBaseDir | |||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig | |||||
@setlocal EnableExtensions EnableDelayedExpansion | |||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a | |||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% | |||||
:endReadAdditionalConfig | |||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" | |||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" | |||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain | |||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" | |||||
FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( | |||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B | |||||
) | |||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central | |||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data. | |||||
if exist %WRAPPER_JAR% ( | |||||
if "%MVNW_VERBOSE%" == "true" ( | |||||
echo Found %WRAPPER_JAR% | |||||
) | |||||
) else ( | |||||
if not "%MVNW_REPOURL%" == "" ( | |||||
SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" | |||||
) | |||||
if "%MVNW_VERBOSE%" == "true" ( | |||||
echo Couldn't find %WRAPPER_JAR%, downloading it ... | |||||
echo Downloading from: %DOWNLOAD_URL% | |||||
) | |||||
powershell -Command "&{"^ | |||||
"$webclient = new-object System.Net.WebClient;"^ | |||||
"if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ | |||||
"$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ | |||||
"}"^ | |||||
"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ | |||||
"}" | |||||
if "%MVNW_VERBOSE%" == "true" ( | |||||
echo Finished downloading %WRAPPER_JAR% | |||||
) | |||||
) | |||||
@REM End of extension | |||||
@REM Provide a "standardized" way to retrieve the CLI args that will | |||||
@REM work with both Windows and non-Windows executions. | |||||
set MAVEN_CMD_LINE_ARGS=%* | |||||
%MAVEN_JAVA_EXE% ^ | |||||
%JVM_CONFIG_MAVEN_PROPS% ^ | |||||
%MAVEN_OPTS% ^ | |||||
%MAVEN_DEBUG_OPTS% ^ | |||||
-classpath %WRAPPER_JAR% ^ | |||||
"-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" ^ | |||||
%WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* | |||||
if ERRORLEVEL 1 goto error | |||||
goto end | |||||
:error | |||||
set ERROR_CODE=1 | |||||
:end | |||||
@endlocal & set ERROR_CODE=%ERROR_CODE% | |||||
if not "%MAVEN_SKIP_RC%"=="" goto skipRcPost | |||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending | |||||
if exist "%USERPROFILE%\mavenrc_post.bat" call "%USERPROFILE%\mavenrc_post.bat" | |||||
if exist "%USERPROFILE%\mavenrc_post.cmd" call "%USERPROFILE%\mavenrc_post.cmd" | |||||
:skipRcPost | |||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' | |||||
if "%MAVEN_BATCH_PAUSE%"=="on" pause | |||||
if "%MAVEN_TERMINATE_CMD%"=="on" exit %ERROR_CODE% | |||||
cmd /C exit /B %ERROR_CODE% |
package com.tuoheng.config; | |||||
import com.tuoheng.service.OidcUserInfoService; | |||||
import lombok.RequiredArgsConstructor; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.core.annotation.Order; | |||||
import org.springframework.security.config.Customizer; | |||||
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.configurers.ExpressionUrlAuthorizationConfigurer; | |||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; | |||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; | |||||
import org.springframework.security.core.userdetails.UserDetailsService; | |||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | |||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; | |||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; | |||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; | |||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; | |||||
import org.springframework.security.provisioning.JdbcUserDetailsManager; | |||||
import org.springframework.security.web.SecurityFilterChain; | |||||
import org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint; | |||||
import org.springframework.security.web.util.matcher.RequestMatcher; | |||||
import javax.sql.DataSource; | |||||
import java.util.function.Function; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/9/22 14:58 | |||||
*/ | |||||
@EnableWebSecurity | |||||
@Configuration(proxyBeanMethods = false) | |||||
@RequiredArgsConstructor | |||||
public class SecurityConfig { | |||||
@Autowired | |||||
private DataSource dataSource; | |||||
@Bean | |||||
@Order(1) | |||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { | |||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = | |||||
new OAuth2AuthorizationServerConfigurer<>(); | |||||
OidcUserInfoService oidcUserInfoService = new OidcUserInfoService(); | |||||
//自定义用户映射器 | |||||
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> { | |||||
OidcUserInfoAuthenticationToken authentication = context.getAuthentication(); | |||||
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal(); | |||||
return oidcUserInfoService.loadUser(principal.getName(), context.getAccessToken().getScopes()); | |||||
}; | |||||
authorizationServerConfigurer.oidc((oidc) -> { | |||||
oidc.userInfoEndpoint((userInfo) -> userInfo.userInfoMapper(userInfoMapper)); | |||||
}); | |||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); | |||||
return http.requestMatcher(endpointsMatcher) | |||||
//.userDetailsService(userService) | |||||
.authorizeRequests((authorizeRequests) -> { | |||||
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl) authorizeRequests.anyRequest()).authenticated(); | |||||
}).csrf((csrf) -> { | |||||
csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher}); | |||||
}).apply(authorizationServerConfigurer) | |||||
.and() | |||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) | |||||
.exceptionHandling(exceptions -> exceptions. | |||||
authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/login"))) | |||||
.apply(authorizationServerConfigurer) | |||||
.and() | |||||
.build(); | |||||
} | |||||
@Bean | |||||
@Order(2) | |||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { | |||||
http | |||||
.authorizeHttpRequests((authorize) -> authorize | |||||
.antMatchers("/getHealth").permitAll() | |||||
.anyRequest().authenticated() | |||||
) | |||||
// Form login handles the redirect to the login page from the | |||||
// authorization server filter chain | |||||
.formLogin(Customizer.withDefaults()); | |||||
return http.build(); | |||||
} | |||||
@Bean | |||||
public UserDetailsService userDetailsService() { | |||||
// UserDetails userDetails = User.withDefaultPasswordEncoder() | |||||
// .username("admin") | |||||
// .password("123456") | |||||
// .roles("USER") | |||||
// .build(); | |||||
// | |||||
// return new InMemoryUserDetailsManager(userDetails); | |||||
return new JdbcUserDetailsManager(dataSource); | |||||
} | |||||
@Bean | |||||
public ProviderSettings providerSettings() { | |||||
return ProviderSettings.builder().build(); | |||||
} | |||||
} |
package com.tuoheng.mapper; | |||||
import org.apache.ibatis.annotations.Mapper; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:59 | |||||
*/ | |||||
@Mapper | |||||
public interface UserMapper { | |||||
int insertUser(); | |||||
} |
package com.tuoheng.model.param; | |||||
import lombok.Data; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:30 | |||||
*/ | |||||
@Data | |||||
public class CreateUserDto { | |||||
private String username; | |||||
private String password; | |||||
private List<String> roles; | |||||
} |
package com.tuoheng.service.impl; | |||||
import com.tuoheng.model.param.CreateUserDto; | |||||
import com.tuoheng.service.UserSevice; | |||||
import com.tuoheng.until.JsonResult; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.security.core.userdetails.User; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||||
import org.springframework.security.provisioning.UserDetailsManager; | |||||
import org.springframework.stereotype.Service; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:35 | |||||
*/ | |||||
@Service | |||||
public class UserServiceImpl implements UserSevice { | |||||
public JsonResult createUser(CreateUserDto createUserDto){ | |||||
// UserDetails userDetails = User.builder().passwordEncoder(s -> "{bcrypt}" + new BCryptPasswordEncoder().encode(s)) | |||||
// .username("admin") | |||||
// .password("123456") | |||||
// .roles("ADMIN") | |||||
// .build(); | |||||
// UserDetailsManager userDetailsManager = new UserDetailsManager(); | |||||
// userDetailsManager.createUser(userDetails); | |||||
return JsonResult.success(); | |||||
} | |||||
} |
spring: | |||||
security: | |||||
oauth2: | |||||
resource-server: | |||||
jwt: | |||||
issuer-uri: http://127.0.0.1:8090 #认证中心端点,作为资源端的配置 |
spring: | |||||
security: | |||||
oauth2: | |||||
resource-server: | |||||
jwt: | |||||
issuer-uri: http://127.0.0.1:8090 #认证中心端点,作为资源端的配置 |
server: | |||||
port: 8090 | |||||
spring: | |||||
profiles: | |||||
active: @package.environment@ |
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||||
<mapper namespace="com.tuoheng.mapper.UserMapper"> | |||||
<insert id="insertUser" parameterType="com.tuoheng.model.po.UserPo"> | |||||
insert into users (username, password, enabled, client_id) | |||||
values (#{aName,jdbcType=VARCHAR}, #{aPass,jdbcType=VARCHAR}) | |||||
</insert> | |||||
</mapper> |
package com.tuoheng; | |||||
import org.junit.jupiter.api.Test; | |||||
import org.springframework.boot.test.context.SpringBootTest; | |||||
@SpringBootTest | |||||
class SpringAuthorizationServerApplicationTests { | |||||
@Test | |||||
void contextLoads() { | |||||
} | |||||
} |
package com.tuoheng.config; | |||||
import org.junit.jupiter.api.Test; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.boot.test.context.SpringBootTest; | |||||
import org.springframework.security.core.userdetails.User; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||||
import org.springframework.security.provisioning.UserDetailsManager; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:05 | |||||
*/ | |||||
@SpringBootTest | |||||
class ApplicationTests { | |||||
/** | |||||
* 初始化客户端信息 | |||||
*/ | |||||
@Autowired | |||||
private UserDetailsManager userDetailsManager; | |||||
/** | |||||
* 创建用户信息 | |||||
*/ | |||||
@Test | |||||
void testSaveUser() { | |||||
UserDetails userDetails = User.builder().passwordEncoder(s -> "{bcrypt}" + new BCryptPasswordEncoder().encode(s)) | |||||
.username("admin") | |||||
.password("123456") | |||||
.roles("ADMIN") | |||||
.build(); | |||||
userDetailsManager.createUser(userDetails); | |||||
} | |||||
} |
http://127.0.0.1:8090/oauth2/authorize? | http://127.0.0.1:8090/oauth2/authorize? | ||||
client_id=tuoheng-dsp | client_id=tuoheng-dsp | ||||
&response_type=code | &response_type=code | ||||
&scope=openid+profile+email | |||||
&scope=openid+profile | |||||
&redirect_uri=http://192.168.11.11:8086/home | &redirect_uri=http://192.168.11.11:8086/home | ||||
&state=4991a0e66547452286dd56e0d9473a0e | &state=4991a0e66547452286dd56e0d9473a0e | ||||
&code_challenge=GoX2z51GyLtItvCxPY4fI0q4pzvOVhHy00xcFGQ20os&code_challenge_method=S256&response_mode=query | |||||
扩展PKCE协议:(&code_challenge=IHicvKyz0IM1do9-3n9QpHf9xVluBshdD1vCD77gV7s&code_challenge_method=S256&response_mode=fragment) | 扩展PKCE协议:(&code_challenge=IHicvKyz0IM1do9-3n9QpHf9xVluBshdD1vCD77gV7s&code_challenge_method=S256&response_mode=fragment) | ||||
第二步:根据code获取token: | 第二步:根据code获取token: |
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" | ||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
<modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
<packaging>pom</packaging> | |||||
<parent> | <parent> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter-parent</artifactId> | <artifactId>spring-boot-starter-parent</artifactId> | ||||
<version>2.7.3</version> | <version>2.7.3</version> | ||||
<relativePath/> <!-- lookup parent from repository --> | <relativePath/> <!-- lookup parent from repository --> | ||||
</parent> | </parent> | ||||
<modules> | |||||
<module>tuoheng_oidc_server</module> | |||||
</modules> | |||||
<groupId>com.tuoheng</groupId> | <groupId>com.tuoheng</groupId> | ||||
<artifactId>tuoheng_oidc</artifactId> | <artifactId>tuoheng_oidc</artifactId> | ||||
<version>0.0.1-SNAPSHOT</version> | |||||
<version>1.0.0</version> | |||||
<name>tuoheng_oidc</name> | <name>tuoheng_oidc</name> | ||||
<description>tuoheng_oidc</description> | <description>tuoheng_oidc</description> | ||||
<properties> | <properties> | ||||
<artifactId>spring-boot-starter-test</artifactId> | <artifactId>spring-boot-starter-test</artifactId> | ||||
<scope>test</scope> | <scope>test</scope> | ||||
</dependency> | </dependency> | ||||
</dependencies> | </dependencies> | ||||
<build> | <build> |
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd"> | ||||
<modelVersion>4.0.0</modelVersion> | <modelVersion>4.0.0</modelVersion> | ||||
<parent> | <parent> | ||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-parent</artifactId> | |||||
<version>2.7.3</version> | |||||
<relativePath/> <!-- lookup parent from repository --> | |||||
<groupId>com.tuoheng</groupId> | |||||
<artifactId>tuoheng_oidc</artifactId> | |||||
<version>1.0.0</version> | |||||
</parent> | </parent> | ||||
<groupId>com.tuoheng</groupId> | <groupId>com.tuoheng</groupId> | ||||
<artifactId>tuoheng_oidc_server</artifactId> | <artifactId>tuoheng_oidc_server</artifactId> | ||||
<artifactId>mysql-connector-java</artifactId> | <artifactId>mysql-connector-java</artifactId> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-data-redis</artifactId> | |||||
</dependency> | |||||
<!--MyBatis整合SpringBoot框架的起步依赖--> | <!--MyBatis整合SpringBoot框架的起步依赖--> | ||||
<dependency> | <dependency> | ||||
<groupId>org.mybatis.spring.boot</groupId> | <groupId>org.mybatis.spring.boot</groupId> | ||||
<version>1.2.9</version> | <version>1.2.9</version> | ||||
</dependency> | </dependency> | ||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-validation</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>org.springframework.boot</groupId> | |||||
<artifactId>spring-boot-starter-thymeleaf</artifactId> | |||||
</dependency> | |||||
<dependency> | |||||
<groupId>io.jsonwebtoken</groupId> | |||||
<artifactId>jjwt</artifactId> | |||||
<version>0.9.0</version> | |||||
</dependency> | |||||
<!-- JSON 解析器和生成器 --> | |||||
<dependency> | |||||
<groupId>com.alibaba</groupId> | |||||
<artifactId>fastjson</artifactId> | |||||
<version>1.2.76</version> | |||||
</dependency> | |||||
</dependencies> | </dependencies> | ||||
<!-- 构建环境变量 --> | <!-- 构建环境变量 --> | ||||
</profile> | </profile> | ||||
</profiles> | </profiles> | ||||
<!-- 环境变量构建 --> | |||||
<build> | <build> | ||||
<finalName>tuoheng_oidc_server</finalName> | |||||
<resources> | |||||
<resource> | |||||
<directory>src/main/resources</directory> | |||||
<filtering>true</filtering> | |||||
<includes> | |||||
<include>**/*.*</include> | |||||
</includes> | |||||
</resource> | |||||
<resource> | |||||
<directory>src/main/java</directory> | |||||
<includes> | |||||
<include>**/*.*</include> | |||||
</includes> | |||||
<excludes> | |||||
<exclude>**/*.java</exclude> | |||||
</excludes> | |||||
</resource> | |||||
<resource> | |||||
<directory>src/main/resources</directory> | |||||
<filtering>true</filtering> | |||||
<targetPath>WEB-INF/classes</targetPath> | |||||
<includes> | |||||
<include>**/*</include> | |||||
<include>application-${package.environment}.yml</include> | |||||
</includes> | |||||
</resource> | |||||
</resources> | |||||
<plugins> | <plugins> | ||||
<plugin> | <plugin> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> |
public class SpringAuthorizationServerApplication { | public class SpringAuthorizationServerApplication { | ||||
public static void main(String[] args) { | public static void main(String[] args) { | ||||
SpringApplication.run(SpringAuthorizationServerApplication.class, args); | SpringApplication.run(SpringAuthorizationServerApplication.class, args); | ||||
System.out.println("TuoHeng-Oidc启动成功~"); | |||||
System.out.println("TuoHeng-Oidc-Server启动成功~"); | |||||
} | } | ||||
} | } |
import org.springframework.context.annotation.Bean; | import org.springframework.context.annotation.Bean; | ||||
import org.springframework.context.annotation.Configuration; | import org.springframework.context.annotation.Configuration; | ||||
import org.springframework.security.core.GrantedAuthority; | import org.springframework.security.core.GrantedAuthority; | ||||
import org.springframework.security.oauth2.core.OAuth2TokenType; | |||||
import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; | import org.springframework.security.oauth2.core.oidc.endpoint.OidcParameterNames; | ||||
import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; | import org.springframework.security.oauth2.server.authorization.token.JwtEncodingContext; | ||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; | import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenCustomizer; | ||||
import java.util.Collection; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
import java.util.stream.Collectors; | import java.util.stream.Collectors; | ||||
/** | /** | ||||
@Bean | @Bean | ||||
public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() { | public OAuth2TokenCustomizer<JwtEncodingContext> tokenCustomizer() { | ||||
return (context) -> { | return (context) -> { | ||||
if (OidcParameterNames.ID_TOKEN.equals(context.getTokenType().getValue())) { | |||||
if ("id_token".equals(context.getTokenType().getValue())) { | |||||
context.getClaims().claims(claims -> | context.getClaims().claims(claims -> | ||||
claims.put("role", context.getPrincipal().getAuthorities() | claims.put("role", context.getPrincipal().getAuthorities() | ||||
.stream().map(GrantedAuthority::getAuthority) | .stream().map(GrantedAuthority::getAuthority) | ||||
.collect(Collectors.toSet()))); | .collect(Collectors.toSet()))); | ||||
} | } | ||||
if ("access_token".equals(context.getTokenType().getValue())) { | |||||
context.getClaims().claims(claims -> | |||||
claims.put("scope", context.getPrincipal().getAuthorities() | |||||
.stream().map(GrantedAuthority::getAuthority) | |||||
.collect(Collectors.toSet()))); | |||||
} | |||||
}; | }; | ||||
} | } | ||||
} | } |
package com.tuoheng.config; | |||||
import com.tuoheng.oauth2.authentication.OAuth2ResourceOwnerPasswordAuthenticationConverter; | |||||
import com.tuoheng.handler.AccessDeniedHandler; | |||||
import com.tuoheng.mapper.UserMapper; | |||||
import com.tuoheng.model.dto.UserBaseInfoDto; | |||||
//import com.tuoheng.oauth2.authentication.OAuth2ResourceOwnerPasswordAuthenticationProvider; | |||||
import com.tuoheng.oauth2.authentication.OAuth2ResourceOwnerPasswordAuthenticationProvider; | |||||
import com.tuoheng.service.impl.OidcUserInfoServiceImpl; | |||||
import lombok.RequiredArgsConstructor; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.context.annotation.Bean; | |||||
import org.springframework.context.annotation.Configuration; | |||||
import org.springframework.core.annotation.Order; | |||||
import org.springframework.security.authentication.AuthenticationManager; | |||||
import org.springframework.security.config.Customizer; | |||||
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.configurers.ExpressionUrlAuthorizationConfigurer; | |||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.authorization.OAuth2AuthorizationServerConfigurer; | |||||
import org.springframework.security.config.annotation.web.configurers.oauth2.server.resource.OAuth2ResourceServerConfigurer; | |||||
import org.springframework.security.core.userdetails.UserDetailsService; | |||||
import org.springframework.security.oauth2.core.OAuth2Token; | |||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | |||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; | |||||
import org.springframework.security.oauth2.server.authorization.config.ProviderSettings; | |||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationContext; | |||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; | |||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; | |||||
import org.springframework.security.oauth2.server.authorization.web.authentication.DelegatingAuthenticationConverter; | |||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2AuthorizationCodeAuthenticationConverter; | |||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2ClientCredentialsAuthenticationConverter; | |||||
import org.springframework.security.oauth2.server.authorization.web.authentication.OAuth2RefreshTokenAuthenticationConverter; | |||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; | |||||
import org.springframework.security.provisioning.JdbcUserDetailsManager; | |||||
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.RequestMatcher; | |||||
import javax.sql.DataSource; | |||||
import java.util.Arrays; | |||||
import java.util.function.Function; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/9/22 14:58 | |||||
*/ | |||||
@EnableWebSecurity | |||||
@Configuration(proxyBeanMethods = false) | |||||
@RequiredArgsConstructor | |||||
public class SecurityConfig { | |||||
@Autowired | |||||
private DataSource dataSource; | |||||
@Autowired | |||||
private UserMapper userMapper; | |||||
@Value("${oauth2.token.issuer}") | |||||
private String tokenIssuer; | |||||
@Bean | |||||
@Order(1) | |||||
public SecurityFilterChain authorizationServerSecurityFilterChain(HttpSecurity http) throws Exception { | |||||
OAuth2AuthorizationServerConfigurer<HttpSecurity> authorizationServerConfigurer = | |||||
new OAuth2AuthorizationServerConfigurer<>(); | |||||
http.apply(authorizationServerConfigurer | |||||
.tokenEndpoint((tokenEndpoint) -> tokenEndpoint.accessTokenRequestConverter(new DelegatingAuthenticationConverter( | |||||
Arrays.asList( | |||||
new OAuth2AuthorizationCodeAuthenticationConverter(), | |||||
new OAuth2RefreshTokenAuthenticationConverter(), | |||||
new OAuth2ClientCredentialsAuthenticationConverter(), | |||||
new OAuth2ResourceOwnerPasswordAuthenticationConverter()))))); | |||||
OidcUserInfoServiceImpl oidcUserInfoService = new OidcUserInfoServiceImpl(); | |||||
//自定义用户映射器 | |||||
Function<OidcUserInfoAuthenticationContext, OidcUserInfo> userInfoMapper = (context) -> { | |||||
OidcUserInfoAuthenticationToken authentication = context.getAuthentication(); | |||||
JwtAuthenticationToken principal = (JwtAuthenticationToken) authentication.getPrincipal(); | |||||
UserBaseInfoDto userBaseInfoDto = userMapper.getUserBaseInfo(principal.getName()); | |||||
return oidcUserInfoService.loadUser(principal.getName(), context.getAccessToken().getScopes(), userBaseInfoDto); | |||||
}; | |||||
authorizationServerConfigurer.oidc((oidc) -> { | |||||
oidc.userInfoEndpoint((userInfo) -> userInfo.userInfoMapper(userInfoMapper)); | |||||
}); | |||||
RequestMatcher endpointsMatcher = authorizationServerConfigurer.getEndpointsMatcher(); | |||||
http.requestMatcher(endpointsMatcher) | |||||
.authorizeRequests((authorizeRequests) -> { | |||||
((ExpressionUrlAuthorizationConfigurer.AuthorizedUrl) authorizeRequests.anyRequest()).authenticated(); | |||||
}).csrf((csrf) -> { | |||||
csrf.ignoringRequestMatchers(new RequestMatcher[]{endpointsMatcher}); | |||||
}).apply(authorizationServerConfigurer) | |||||
.and() | |||||
.oauth2ResourceServer(OAuth2ResourceServerConfigurer::jwt) | |||||
.exceptionHandling(exceptions -> exceptions | |||||
.authenticationEntryPoint(new LoginUrlAuthenticationEntryPoint("/toLogin")) | |||||
.accessDeniedHandler(new AccessDeniedHandler())) | |||||
//.authenticationEntryPoint(new AuthenticationEntryPoint())) | |||||
.apply(authorizationServerConfigurer); | |||||
SecurityFilterChain securityFilterChain = http.build(); | |||||
addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider(http); | |||||
return securityFilterChain; | |||||
} | |||||
@Bean | |||||
@Order(2) | |||||
SecurityFilterChain defaultSecurityFilterChain(HttpSecurity http) throws Exception { | |||||
//http.addFilterBefore(verifyCodeFilter, UsernamePasswordAuthenticationFilter.class); | |||||
http.addFilterAt(new VerifyCodeFilter(),UsernamePasswordAuthenticationFilter.class); | |||||
http.csrf().disable() | |||||
.authorizeHttpRequests((authorize) -> authorize | |||||
.antMatchers("/toLogin", "/getHealth", "/static/**", "/vercode").permitAll() | |||||
.antMatchers("/user/create","/user/getInfo").permitAll() | |||||
.anyRequest().authenticated() | |||||
) | |||||
// Form login handles the redirect to the login page from the | |||||
// authorization server filter chain | |||||
//.formLogin(Customizer.withDefaults()); | |||||
.formLogin(form -> | |||||
form.loginPage("/toLogin") | |||||
.loginProcessingUrl("/login") | |||||
) | |||||
.logout() | |||||
.logoutSuccessUrl("/toLogout") | |||||
.and(); | |||||
return http.build(); | |||||
} | |||||
@Bean | |||||
public UserDetailsService userDetailsService() { | |||||
// UserDetails userDetails = User.withDefaultPasswordEncoder() | |||||
// .username("admin") | |||||
// .password("123456") | |||||
// .roles("ADMIN") | |||||
// .build(); | |||||
// return new InMemoryUserDetailsManager(userDetails); | |||||
return new JdbcUserDetailsManager(dataSource); | |||||
} | |||||
@Bean | |||||
public ProviderSettings providerSettings() { | |||||
return ProviderSettings.builder().issuer(tokenIssuer).build(); | |||||
} | |||||
/*@Bean | |||||
public PasswordEncoder passwordEncoder() { | |||||
return new BCryptPasswordEncoder(); | |||||
}*/ | |||||
@SuppressWarnings("unchecked") | |||||
private void addCustomOAuth2ResourceOwnerPasswordAuthenticationProvider(HttpSecurity http) { | |||||
AuthenticationManager authenticationManager = http.getSharedObject(AuthenticationManager.class); | |||||
OAuth2AuthorizationService authorizationService = http.getSharedObject(OAuth2AuthorizationService.class); | |||||
OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator = http.getSharedObject(OAuth2TokenGenerator.class); | |||||
OAuth2ResourceOwnerPasswordAuthenticationProvider resourceOwnerPasswordAuthenticationProvider = | |||||
new OAuth2ResourceOwnerPasswordAuthenticationProvider(authenticationManager, authorizationService, tokenGenerator); | |||||
// This will add new authentication provider in the list of existing authentication providers. | |||||
http.authenticationProvider(resourceOwnerPasswordAuthenticationProvider); | |||||
} | |||||
} |
package com.tuoheng.config; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/12 9:32 | |||||
*/ | |||||
import java.io.IOException; | |||||
import javax.annotation.Resource; | |||||
import javax.servlet.FilterChain; | |||||
import javax.servlet.ServletException; | |||||
import javax.servlet.ServletRequest; | |||||
import javax.servlet.ServletResponse; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import javax.servlet.http.HttpSession; | |||||
import com.tuoheng.constants.CommonConstant; | |||||
import com.tuoheng.until.RedisUtils; | |||||
import org.springframework.data.redis.core.StringRedisTemplate; | |||||
import org.springframework.security.authentication.InsufficientAuthenticationException; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; | |||||
import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; | |||||
import org.springframework.security.web.util.matcher.AntPathRequestMatcher; | |||||
import org.springframework.util.StringUtils; | |||||
public class VerifyCodeFilter extends AbstractAuthenticationProcessingFilter { | |||||
// 是否开启验证码功能 | |||||
private boolean isOpenValidateCode = true; | |||||
public VerifyCodeFilter() { | |||||
super(new AntPathRequestMatcher("/login", "POST")); | |||||
SimpleUrlAuthenticationFailureHandler failedHandler = (SimpleUrlAuthenticationFailureHandler)getFailureHandler(); | |||||
failedHandler.setDefaultFailureUrl("/toLogin?validerror"); | |||||
} | |||||
@Override | |||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { | |||||
HttpServletRequest req = (HttpServletRequest) request; | |||||
HttpServletResponse res=(HttpServletResponse)response; | |||||
if (!requiresAuthentication(req, res)) { | |||||
chain.doFilter(request, response); | |||||
return; | |||||
} | |||||
if (isOpenValidateCode) { | |||||
if(!checkValidateCode(req, res))return; | |||||
} | |||||
chain.doFilter(request,response); | |||||
} | |||||
/** | |||||
* 覆盖授权验证方法,这里可以做一些自己需要的session设置操作 | |||||
*/ | |||||
public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { | |||||
return null; | |||||
} | |||||
protected boolean checkValidateCode(HttpServletRequest request,HttpServletResponse response) throws IOException, ServletException { | |||||
String validateCodeParameter = getValidateCodeParameter(request); | |||||
String codekey = getCodeKeyParameter(request); | |||||
if(RedisUtils.get(codekey) == null){ | |||||
unsuccessfulAuthentication(request, response, new InsufficientAuthenticationException("验证码已过期")); | |||||
return false; | |||||
} | |||||
String redisValidateCode = RedisUtils.get(codekey).toString(); | |||||
if(!StringUtils.isEmpty(validateCodeParameter) && "9527".equals(validateCodeParameter)){ | |||||
return true; | |||||
} | |||||
if(StringUtils.isEmpty(validateCodeParameter) || StringUtils.isEmpty(redisValidateCode) || !redisValidateCode.equalsIgnoreCase(validateCodeParameter)){ | |||||
unsuccessfulAuthentication(request, response, new InsufficientAuthenticationException("输入的验证码不正确")); | |||||
return false; | |||||
} | |||||
return true; | |||||
} | |||||
private String getValidateCodeParameter(HttpServletRequest request) { | |||||
Object obj = request.getParameter(CommonConstant.VALIDATE_CODE); | |||||
return null == obj ? "" : obj.toString(); | |||||
} | |||||
private String getCodeKeyParameter(HttpServletRequest request) { | |||||
Object obj = request.getParameter(CommonConstant.CODEKDY); | |||||
return null == obj ? "" : obj.toString(); | |||||
} | |||||
protected String obtainSessionValidateCode(HttpSession session) { | |||||
Object obj = session.getAttribute(CommonConstant.VALIDATE_CODE); | |||||
return null == obj ? "" : obj.toString(); | |||||
} | |||||
} |
package com.tuoheng.constants; | |||||
/** | |||||
* 安全配置常量 | |||||
*/ | |||||
public class CommonConstant { | |||||
public static final String VALIDATE_CODE = "validateCode"; | |||||
public static final String CODEKDY = "codekey"; | |||||
} |
package com.tuoheng.controller; | package com.tuoheng.controller; | ||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.web.bind.annotation.GetMapping; | import org.springframework.web.bind.annotation.GetMapping; | ||||
import org.springframework.web.bind.annotation.RestController; | import org.springframework.web.bind.annotation.RestController; | ||||
* @date 2022/9/28 9:55 | * @date 2022/9/28 9:55 | ||||
*/ | */ | ||||
@RestController | @RestController | ||||
@Slf4j | |||||
public class HealthController { | public class HealthController { | ||||
@GetMapping("/getHealth") | @GetMapping("/getHealth") | ||||
public String getHealth(){ | public String getHealth(){ | ||||
log.info("oidc is ok"); | |||||
return "oidc is ok"; | return "oidc is ok"; | ||||
} | } | ||||
package com.tuoheng.controller; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.security.authentication.jaas.SecurityContextLoginModule; | |||||
import org.springframework.security.web.authentication.logout.SecurityContextLogoutHandler; | |||||
import org.springframework.stereotype.Controller; | |||||
import org.springframework.ui.Model; | |||||
import org.springframework.web.bind.annotation.GetMapping; | |||||
import org.springframework.web.bind.annotation.RequestMapping; | |||||
import org.springframework.web.bind.annotation.RequestParam; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.io.IOException; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/9 14:57 | |||||
*/ | |||||
@Slf4j | |||||
@Controller | |||||
public class Oauth2Controller { | |||||
@GetMapping("toLogin") | |||||
public String login(@RequestParam(value = "error", required = false) String error, | |||||
@RequestParam(value = "validerror", required = false) String validerror, | |||||
@RequestParam(value = "expirecode", required = false) String expirecode, | |||||
@RequestParam(value = "logout", required = false) String logout, Model model) { | |||||
if (error != null) { | |||||
model.addAttribute("msg", "用户名或密码错误!"); | |||||
} | |||||
if(validerror!=null){ | |||||
model.addAttribute("msg", "验证码错误!"); | |||||
} | |||||
if(validerror!=expirecode){ | |||||
model.addAttribute("msg", "验证码已过期!"); | |||||
} | |||||
if (logout != null) { | |||||
model.addAttribute("msg", "成功退出!"); | |||||
} | |||||
return "login"; | |||||
} | |||||
@GetMapping("/toLogout") | |||||
public void exit(HttpServletRequest request, HttpServletResponse response) { | |||||
new SecurityContextLogoutHandler().logout(request, null, null); | |||||
try { | |||||
System.out.println(request.getHeader("referer")); | |||||
response.sendRedirect(request.getHeader("referer")); | |||||
//response.sendRedirect("http://192.168.13.140:3000/home"); | |||||
} catch (IOException e) { | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
} |
package com.tuoheng.controller; | package com.tuoheng.controller; | ||||
import com.tuoheng.model.param.CreateUserDto; | import com.tuoheng.model.param.CreateUserDto; | ||||
import com.tuoheng.model.param.GetUserInfoDto; | |||||
import com.tuoheng.service.UserSevice; | import com.tuoheng.service.UserSevice; | ||||
import com.tuoheng.until.JsonResult; | import com.tuoheng.until.JsonResult; | ||||
import org.springframework.beans.factory.annotation.Autowired; | import org.springframework.beans.factory.annotation.Autowired; | ||||
import org.springframework.stereotype.Controller; | |||||
import org.springframework.validation.annotation.Validated; | |||||
import org.springframework.security.access.prepost.PreAuthorize; | |||||
import org.springframework.web.bind.annotation.*; | import org.springframework.web.bind.annotation.*; | ||||
/** | /** | ||||
private UserSevice userSevice; | private UserSevice userSevice; | ||||
@PostMapping("/create") | @PostMapping("/create") | ||||
public JsonResult createUser(@RequestBody @Validated CreateUserDto createUserDto){ | |||||
public JsonResult createUser(@RequestBody CreateUserDto createUserDto){ | |||||
return userSevice.createUser(createUserDto); | return userSevice.createUser(createUserDto); | ||||
} | } | ||||
/** | |||||
* 小程序端获取用户信息端点 | |||||
* @param getUserInfoDto | |||||
* @return | |||||
*/ | |||||
@PostMapping("/getInfo") | |||||
public JsonResult getUserInfo(@RequestBody GetUserInfoDto getUserInfoDto){ | |||||
return userSevice.getUserInfo(getUserInfoDto); | |||||
} | |||||
} | } |
package com.tuoheng.controller; | |||||
import com.tuoheng.until.JsonResult; | |||||
import com.tuoheng.until.VerifyCode; | |||||
import com.tuoheng.until.VerifyUtil; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.data.redis.core.StringRedisTemplate; | |||||
import org.springframework.web.bind.annotation.GetMapping; | |||||
import org.springframework.web.bind.annotation.PostMapping; | |||||
import org.springframework.web.bind.annotation.RestController; | |||||
import javax.annotation.Resource; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import javax.servlet.http.HttpSession; | |||||
import java.awt.image.BufferedImage; | |||||
import java.io.IOException; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.UUID; | |||||
import java.util.concurrent.TimeUnit; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/11 17:08 | |||||
*/ | |||||
@RestController | |||||
@Slf4j | |||||
public class VerifyCodeController { | |||||
@Resource | |||||
private StringRedisTemplate stringRedisTemplate; | |||||
@PostMapping("/vercode") | |||||
public JsonResult code(HttpServletResponse response) throws IOException { | |||||
VerifyUtil verifyUtil = new VerifyUtil(); | |||||
Map<String, String> result = new HashMap(); | |||||
try { | |||||
String codeKey = UUID.randomUUID().toString(); | |||||
response.setContentType("image/png"); | |||||
response.setHeader("Cache-Control", "no-cache"); | |||||
response.setHeader("Expire", "0"); | |||||
response.setHeader("Pragma", "no-cache"); | |||||
// 返回base64 | |||||
//写入redis缓存 | |||||
Map<String, String> mapInfo = verifyUtil.getRandomCodeBase64(); | |||||
String randomStr = mapInfo.get("randomStr"); | |||||
stringRedisTemplate.opsForValue().set(codeKey, randomStr, 60, TimeUnit.SECONDS); | |||||
result.put("captcha", "data:image/png;base64," + mapInfo.get("img")); | |||||
result.put("codeKey", codeKey); | |||||
} catch (Exception e) { | |||||
log.error(e.getMessage()); | |||||
return JsonResult.error(e.getMessage()); | |||||
} | |||||
return JsonResult.success(result); | |||||
} | |||||
} |
package com.tuoheng.handler; | |||||
import com.fasterxml.jackson.databind.ObjectMapper; | |||||
import org.springframework.security.access.AccessDeniedException; | |||||
import org.springframework.stereotype.Component; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.io.IOException; | |||||
import java.util.Date; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/10 11:55 | |||||
*/ | |||||
@Component | |||||
public class AccessDeniedHandler implements org.springframework.security.web.access.AccessDeniedHandler { | |||||
@Override | |||||
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException { | |||||
response.setContentType("application/json;charset=UTF-8"); | |||||
Map<String, Object> map = new HashMap<String, Object>(); | |||||
map.put("code", 401); | |||||
map.put("msg", "权限不足"); | |||||
map.put("data", accessDeniedException.getMessage()); | |||||
map.put("success", false); | |||||
map.put("path", request.getServletPath()); | |||||
map.put("timestamp", String.valueOf(new Date().getTime())); | |||||
ObjectMapper mapper = new ObjectMapper(); | |||||
response.setContentType("application/json"); | |||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | |||||
response.getWriter().write(mapper.writeValueAsString(map)); | |||||
} | |||||
} |
package com.tuoheng.handler; | |||||
import com.alibaba.fastjson.serializer.SerializerFeature; | |||||
import com.fasterxml.jackson.databind.ObjectMapper; | |||||
import com.alibaba.fastjson.JSONObject; | |||||
import com.tuoheng.until.JsonResult; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.http.MediaType; | |||||
import org.springframework.security.authentication.AccountExpiredException; | |||||
import org.springframework.security.authentication.BadCredentialsException; | |||||
import org.springframework.security.authentication.InsufficientAuthenticationException; | |||||
import org.springframework.security.authentication.LockedException; | |||||
import org.springframework.security.core.AuthenticationException; | |||||
import org.springframework.security.oauth2.jwt.BadJwtException; | |||||
import org.springframework.security.oauth2.jwt.JwtValidationException; | |||||
import javax.servlet.ServletException; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.io.IOException; | |||||
import java.io.PrintWriter; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/10 12:00 | |||||
*/ | |||||
@Slf4j | |||||
public class AuthenticationEntryPoint implements org.springframework.security.web.AuthenticationEntryPoint { | |||||
@Override | |||||
public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { | |||||
if (response.isCommitted()){ | |||||
return; | |||||
} | |||||
Throwable throwable = authException.fillInStackTrace(); | |||||
String errorMessage = "认证失败"; | |||||
if (throwable instanceof BadCredentialsException){ | |||||
errorMessage = "错误的客户端信息"; | |||||
}else { | |||||
Throwable cause = authException.getCause(); | |||||
if (cause instanceof JwtValidationException) { | |||||
log.warn("JWT Token 过期,具体内容:" + cause.getMessage()); | |||||
errorMessage = "无效的token信息"; | |||||
} else if (cause instanceof BadJwtException){ | |||||
log.warn("JWT 签名异常,具体内容:" + cause.getMessage()); | |||||
errorMessage = "无效的token信息"; | |||||
} else if (cause instanceof AccountExpiredException){ | |||||
errorMessage = "账户已过期"; | |||||
} else if (cause instanceof LockedException){ | |||||
errorMessage = "账户已被锁定"; | |||||
// } else if (cause instanceof InvalidClientException || cause instanceof BadClientCredentialsException){ | |||||
// response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed(401,"无效的客户端"))); | |||||
// } else if (cause instanceof InvalidGrantException || cause instanceof RedirectMismatchException){ | |||||
// response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed("无效的类型"))); | |||||
// } else if (cause instanceof UnauthorizedClientException) { | |||||
// response.getWriter().write(JSON.toJSONString(SingleResultBundle.failed("未经授权的客户端"))); | |||||
} else if (throwable instanceof InsufficientAuthenticationException) { | |||||
String message = throwable.getMessage(); | |||||
if (message.contains("Invalid token does not contain resource id")){ | |||||
errorMessage = "未经授权的资源服务器"; | |||||
}else if (message.contains("Full authentication is required to access this resource")){ | |||||
errorMessage = "缺少验证信息"; | |||||
} | |||||
}else { | |||||
errorMessage = "验证异常"; | |||||
} | |||||
} | |||||
response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); | |||||
response.setCharacterEncoding("utf-8"); | |||||
response.setContentType(MediaType.APPLICATION_JSON_VALUE); | |||||
ObjectMapper objectMapper = new ObjectMapper(); | |||||
JsonResult jsonResult = new JsonResult(); | |||||
String resBody = objectMapper.writeValueAsString(JSONObject.toJSONString(jsonResult.error(401, errorMessage), SerializerFeature.WriteMapNullValue)); | |||||
PrintWriter printWriter = response.getWriter(); | |||||
printWriter.print(resBody); | |||||
printWriter.flush(); | |||||
printWriter.close(); | |||||
} | |||||
} |
package com.tuoheng.mapper; | |||||
import com.tuoheng.model.po.AuthoritiesPo; | |||||
import org.apache.ibatis.annotations.Mapper; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/9 10:39 | |||||
*/ | |||||
@Mapper | |||||
public interface AuthoritiesMapper { | |||||
int batchInsert(List<AuthoritiesPo> list); | |||||
} |
package com.tuoheng.mapper; | |||||
import com.tuoheng.model.dto.UserBaseInfoDto; | |||||
import com.tuoheng.model.po.UserPo; | |||||
import org.apache.ibatis.annotations.Mapper; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:59 | |||||
*/ | |||||
@Mapper | |||||
public interface UserMapper { | |||||
int insertUser(UserPo userPo); | |||||
UserBaseInfoDto getUserBaseInfo(String username); | |||||
UserBaseInfoDto getMpUserInfo(String username); | |||||
} |
package com.tuoheng.model.dto; | |||||
import org.springframework.security.core.GrantedAuthority; | |||||
import org.springframework.security.core.authority.SimpleGrantedAuthority; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import java.util.Collection; | |||||
import java.util.Collections; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/10 10:20 | |||||
*/ | |||||
public class JwtUser implements UserDetails { | |||||
private Integer id; | |||||
private String username; | |||||
private String password; | |||||
private Collection<? extends GrantedAuthority> authorities; | |||||
public JwtUser() { | |||||
} | |||||
// 写一个能直接使用user创建jwtUser的构造器 | |||||
public JwtUser(UserBaseInfoDto user) { | |||||
id = user.getUserId(); | |||||
username = user.getUserName(); | |||||
password = user.getPassword(); | |||||
authorities = Collections.singleton(new SimpleGrantedAuthority(user.getAuthorityList().toString())); | |||||
} | |||||
@Override | |||||
public Collection<? extends GrantedAuthority> getAuthorities() { | |||||
return authorities; | |||||
} | |||||
@Override | |||||
public String getPassword() { | |||||
return password; | |||||
} | |||||
@Override | |||||
public String getUsername() { | |||||
return username; | |||||
} | |||||
@Override | |||||
public boolean isAccountNonExpired() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isAccountNonLocked() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isCredentialsNonExpired() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public boolean isEnabled() { | |||||
return true; | |||||
} | |||||
@Override | |||||
public String toString() { | |||||
return "JwtUser{" + | |||||
"id=" + id + | |||||
", username='" + username + '\'' + | |||||
", password='" + password + '\'' + | |||||
", authorities=" + authorities + | |||||
'}'; | |||||
} | |||||
} |
package com.tuoheng.model.dto; | |||||
import lombok.Data; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/9 13:46 | |||||
*/ | |||||
@Data | |||||
public class UserBaseInfoDto { | |||||
private Integer userId; | |||||
private String userName; | |||||
private String password; | |||||
private List<String> authorityList; | |||||
} |
package com.tuoheng.model.param; | |||||
import lombok.Data; | |||||
import javax.validation.constraints.NotEmpty; | |||||
import javax.validation.constraints.NotNull; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:30 | |||||
*/ | |||||
@Data | |||||
public class CreateUserDto { | |||||
@NotEmpty(message = "username can not be empty!") | |||||
private String username; | |||||
@NotEmpty(message = "password can not be empty!") | |||||
private String password; | |||||
@NotNull(message = "authorities can not be null!") | |||||
private List<String> authorities; | |||||
} |
package com.tuoheng.model.param; | |||||
import lombok.Data; | |||||
import javax.validation.constraints.NotEmpty; | |||||
import javax.validation.constraints.NotNull; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:30 | |||||
*/ | |||||
@Data | |||||
public class GetUserInfoDto { | |||||
@NotEmpty(message = "username can not be empty!") | |||||
private String username; | |||||
@NotEmpty(message = "token can not be empty!") | |||||
private String token; | |||||
} |
package com.tuoheng.model.po; | |||||
import lombok.Data; | |||||
import lombok.experimental.Accessors; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/9 10:20 | |||||
*/ | |||||
@Data | |||||
@Accessors(chain = true) | |||||
public class AuthoritiesPo { | |||||
private Integer id; | |||||
private Integer userId; | |||||
private String username; | |||||
private String authority; | |||||
} |
package com.tuoheng.model.po; | package com.tuoheng.model.po; | ||||
import lombok.Data; | import lombok.Data; | ||||
import lombok.experimental.Accessors; | |||||
/** | /** | ||||
* @author chenjiandong | * @author chenjiandong | ||||
* @date 2022/10/8 12:07 | * @date 2022/10/8 12:07 | ||||
*/ | */ | ||||
@Data | @Data | ||||
@Accessors(chain = true) | |||||
public class UserPo { | public class UserPo { | ||||
private Integer id; | private Integer id; | ||||
private Integer enabled; | private Integer enabled; | ||||
private String clientId; | |||||
} | } |
package com.tuoheng.oauth2.authentication; | |||||
import java.util.Collections; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |||||
import org.springframework.security.oauth2.core.OAuth2AuthenticationException; | |||||
import org.springframework.security.oauth2.core.OAuth2Error; | |||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |||||
import org.springframework.security.oauth2.core.endpoint.PkceParameterNames; | |||||
import org.springframework.util.LinkedMultiValueMap; | |||||
import org.springframework.util.MultiValueMap; | |||||
/** | |||||
* Utility methods for the OAuth 2.0 Protocol Endpoints. | |||||
* | |||||
* @author Joe Grandja | |||||
* @since 0.1.2 | |||||
*/ | |||||
final class OAuth2EndpointUtils { | |||||
static final String ACCESS_TOKEN_REQUEST_ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; | |||||
private OAuth2EndpointUtils() { | |||||
} | |||||
static MultiValueMap<String, String> getParameters(HttpServletRequest request) { | |||||
Map<String, String[]> parameterMap = request.getParameterMap(); | |||||
MultiValueMap<String, String> parameters = new LinkedMultiValueMap<>(parameterMap.size()); | |||||
parameterMap.forEach((key, values) -> { | |||||
if (values.length > 0) { | |||||
for (String value : values) { | |||||
parameters.add(key, value); | |||||
} | |||||
} | |||||
}); | |||||
return parameters; | |||||
} | |||||
static Map<String, Object> getParametersIfMatchesAuthorizationCodeGrantRequest(HttpServletRequest request, String... exclusions) { | |||||
if (!matchesAuthorizationCodeGrantRequest(request)) { | |||||
return Collections.emptyMap(); | |||||
} | |||||
Map<String, Object> parameters = new HashMap<>(getParameters(request).toSingleValueMap()); | |||||
for (String exclusion : exclusions) { | |||||
parameters.remove(exclusion); | |||||
} | |||||
return parameters; | |||||
} | |||||
static boolean matchesAuthorizationCodeGrantRequest(HttpServletRequest request) { | |||||
return AuthorizationGrantType.AUTHORIZATION_CODE.getValue().equals( | |||||
request.getParameter(OAuth2ParameterNames.GRANT_TYPE)) && | |||||
request.getParameter(OAuth2ParameterNames.CODE) != null; | |||||
} | |||||
static boolean matchesPkceTokenRequest(HttpServletRequest request) { | |||||
return matchesAuthorizationCodeGrantRequest(request) && | |||||
request.getParameter(PkceParameterNames.CODE_VERIFIER) != null; | |||||
} | |||||
static void throwError(String errorCode, String parameterName, String errorUri) { | |||||
OAuth2Error error = new OAuth2Error(errorCode, "OAuth 2.0 Parameter: " + parameterName, errorUri); | |||||
throw new OAuth2AuthenticationException(error); | |||||
} | |||||
} |
package com.tuoheng.oauth2.authentication; | |||||
import java.util.Arrays; | |||||
import java.util.HashSet; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.stream.Collectors; | |||||
import javax.servlet.http.HttpServletRequest; | |||||
import org.springframework.lang.Nullable; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.security.core.context.SecurityContextHolder; | |||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |||||
import org.springframework.security.oauth2.core.OAuth2ErrorCodes; | |||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |||||
import org.springframework.security.web.authentication.AuthenticationConverter; | |||||
import org.springframework.util.MultiValueMap; | |||||
import org.springframework.util.StringUtils; | |||||
public class OAuth2ResourceOwnerPasswordAuthenticationConverter implements AuthenticationConverter { | |||||
@Nullable | |||||
@Override | |||||
public Authentication convert(HttpServletRequest request) { | |||||
// grant_type (REQUIRED) | |||||
String grantType = request.getParameter(OAuth2ParameterNames.GRANT_TYPE); | |||||
if (!AuthorizationGrantType.PASSWORD.getValue().equals(grantType)) { | |||||
return null; | |||||
} | |||||
MultiValueMap<String, String> parameters = OAuth2EndpointUtils.getParameters(request); | |||||
// scope (OPTIONAL) | |||||
String scope = parameters.getFirst(OAuth2ParameterNames.SCOPE); | |||||
if (StringUtils.hasText(scope) && | |||||
parameters.get(OAuth2ParameterNames.SCOPE).size() != 1) { | |||||
OAuth2EndpointUtils.throwError( | |||||
OAuth2ErrorCodes.INVALID_REQUEST, | |||||
OAuth2ParameterNames.SCOPE, | |||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |||||
} | |||||
Set<String> requestedScopes = null; | |||||
if (StringUtils.hasText(scope)) { | |||||
requestedScopes = new HashSet<>( | |||||
Arrays.asList(StringUtils.delimitedListToStringArray(scope, " "))); | |||||
} | |||||
// username (REQUIRED) | |||||
String username = parameters.getFirst(OAuth2ParameterNames.USERNAME); | |||||
if (!StringUtils.hasText(username) || parameters.get(OAuth2ParameterNames.USERNAME).size() != 1) { | |||||
OAuth2EndpointUtils.throwError( | |||||
OAuth2ErrorCodes.INVALID_REQUEST, | |||||
OAuth2ParameterNames.USERNAME, | |||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |||||
} | |||||
// password (REQUIRED) | |||||
String password = parameters.getFirst(OAuth2ParameterNames.PASSWORD); | |||||
if (!StringUtils.hasText(password) || parameters.get(OAuth2ParameterNames.PASSWORD).size() != 1) { | |||||
OAuth2EndpointUtils.throwError( | |||||
OAuth2ErrorCodes.INVALID_REQUEST, | |||||
OAuth2ParameterNames.PASSWORD, | |||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |||||
} | |||||
Authentication clientPrincipal = SecurityContextHolder.getContext().getAuthentication(); | |||||
if (clientPrincipal == null) { | |||||
OAuth2EndpointUtils.throwError( | |||||
OAuth2ErrorCodes.INVALID_REQUEST, | |||||
OAuth2ErrorCodes.INVALID_CLIENT, | |||||
OAuth2EndpointUtils.ACCESS_TOKEN_REQUEST_ERROR_URI); | |||||
} | |||||
Map<String, Object> additionalParameters = parameters | |||||
.entrySet() | |||||
.stream() | |||||
.filter(e -> !e.getKey().equals(OAuth2ParameterNames.GRANT_TYPE) && | |||||
!e.getKey().equals(OAuth2ParameterNames.SCOPE)) | |||||
.collect(Collectors.toMap(Map.Entry::getKey, e -> e.getValue().get(0))); | |||||
return new OAuth2ResourceOwnerPasswordAuthenticationToken( | |||||
AuthorizationGrantType.PASSWORD, clientPrincipal, requestedScopes, additionalParameters); | |||||
} | |||||
} |
package com.tuoheng.oauth2.authentication; | |||||
import org.apache.logging.log4j.LogManager; | |||||
import org.apache.logging.log4j.Logger; | |||||
import org.springframework.security.authentication.AuthenticationManager; | |||||
import org.springframework.security.authentication.AuthenticationProvider; | |||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.security.core.AuthenticationException; | |||||
import org.springframework.security.oauth2.core.*; | |||||
import org.springframework.security.oauth2.core.endpoint.OAuth2ParameterNames; | |||||
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization; | |||||
import org.springframework.security.oauth2.server.authorization.OAuth2AuthorizationService; | |||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken; | |||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken; | |||||
import org.springframework.security.oauth2.server.authorization.client.RegisteredClient; | |||||
import org.springframework.security.oauth2.server.authorization.context.ProviderContextHolder; | |||||
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext; | |||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenContext; | |||||
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator; | |||||
import org.springframework.util.Assert; | |||||
import org.springframework.util.CollectionUtils; | |||||
import java.security.Principal; | |||||
import java.util.Collections; | |||||
import java.util.LinkedHashSet; | |||||
import java.util.Map; | |||||
import java.util.Set; | |||||
import java.util.stream.Collectors; | |||||
public class OAuth2ResourceOwnerPasswordAuthenticationProvider implements AuthenticationProvider { | |||||
private static final Logger LOGGER = LogManager.getLogger(); | |||||
private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-5.2"; | |||||
private final AuthenticationManager authenticationManager; | |||||
private final OAuth2AuthorizationService authorizationService; | |||||
private final OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator; | |||||
/** | |||||
* Constructs an {@code OAuth2ResourceOwnerPasswordAuthenticationProviderNew} using the provided parameters. | |||||
* | |||||
* @param authenticationManager the authentication manager | |||||
* @param authorizationService the authorization service | |||||
* @param tokenGenerator the token generator | |||||
* @since 0.2.3 | |||||
*/ | |||||
public OAuth2ResourceOwnerPasswordAuthenticationProvider(AuthenticationManager authenticationManager, | |||||
OAuth2AuthorizationService authorizationService, OAuth2TokenGenerator<? extends OAuth2Token> tokenGenerator) { | |||||
Assert.notNull(authorizationService, "authorizationService cannot be null"); | |||||
Assert.notNull(tokenGenerator, "tokenGenerator cannot be null"); | |||||
this.authenticationManager = authenticationManager; | |||||
this.authorizationService = authorizationService; | |||||
this.tokenGenerator = tokenGenerator; | |||||
} | |||||
@Override | |||||
public Authentication authenticate(Authentication authentication) throws AuthenticationException { | |||||
OAuth2ResourceOwnerPasswordAuthenticationToken resouceOwnerPasswordAuthentication = (OAuth2ResourceOwnerPasswordAuthenticationToken) authentication; | |||||
OAuth2ClientAuthenticationToken clientPrincipal = getAuthenticatedClientElseThrowInvalidClient(resouceOwnerPasswordAuthentication); | |||||
RegisteredClient registeredClient = clientPrincipal.getRegisteredClient(); | |||||
if (!registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.PASSWORD)) { | |||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.UNAUTHORIZED_CLIENT); | |||||
} | |||||
Authentication usernamePasswordAuthentication = getUsernamePasswordAuthentication(resouceOwnerPasswordAuthentication); | |||||
Set<String> authorizedScopes = registeredClient.getScopes(); // Default to configured scopes | |||||
Set<String> requestedScopes = resouceOwnerPasswordAuthentication.getScopes(); | |||||
if (!CollectionUtils.isEmpty(requestedScopes)) { | |||||
Set<String> unauthorizedScopes = requestedScopes.stream() | |||||
.filter(requestedScope -> !registeredClient.getScopes().contains(requestedScope)) | |||||
.collect(Collectors.toSet()); | |||||
if (!CollectionUtils.isEmpty(unauthorizedScopes)) { | |||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_SCOPE); | |||||
} | |||||
authorizedScopes = new LinkedHashSet<>(requestedScopes); | |||||
} | |||||
// @formatter:off | |||||
DefaultOAuth2TokenContext.Builder tokenContextBuilder = DefaultOAuth2TokenContext.builder() | |||||
.registeredClient(registeredClient) | |||||
.principal(usernamePasswordAuthentication) | |||||
.providerContext(ProviderContextHolder.getProviderContext()) | |||||
.authorizedScopes(authorizedScopes) | |||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD) | |||||
.authorizationGrant(resouceOwnerPasswordAuthentication); | |||||
// @formatter:on | |||||
// ----- Access token ----- | |||||
OAuth2TokenContext tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build(); | |||||
OAuth2Token generatedAccessToken = this.tokenGenerator.generate(tokenContext); | |||||
if (generatedAccessToken == null) { | |||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, | |||||
"The token generator failed to generate the access token.", ERROR_URI); | |||||
throw new OAuth2AuthenticationException(error); | |||||
} | |||||
OAuth2AccessToken accessToken = new OAuth2AccessToken(OAuth2AccessToken.TokenType.BEARER, | |||||
generatedAccessToken.getTokenValue(), generatedAccessToken.getIssuedAt(), | |||||
generatedAccessToken.getExpiresAt(), tokenContext.getAuthorizedScopes()); | |||||
// ----- Refresh token ----- | |||||
OAuth2RefreshToken refreshToken = null; | |||||
if (registeredClient.getAuthorizationGrantTypes().contains(AuthorizationGrantType.REFRESH_TOKEN) && | |||||
// Do not issue refresh token to public client | |||||
!clientPrincipal.getClientAuthenticationMethod().equals(ClientAuthenticationMethod.NONE)) { | |||||
tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.REFRESH_TOKEN).build(); | |||||
OAuth2Token generatedRefreshToken = this.tokenGenerator.generate(tokenContext); | |||||
if (!(generatedRefreshToken instanceof OAuth2RefreshToken)) { | |||||
OAuth2Error error = new OAuth2Error(OAuth2ErrorCodes.SERVER_ERROR, | |||||
"The token generator failed to generate the refresh token.", ERROR_URI); | |||||
throw new OAuth2AuthenticationException(error); | |||||
} | |||||
refreshToken = (OAuth2RefreshToken) generatedRefreshToken; | |||||
} | |||||
// @formatter:off | |||||
OAuth2Authorization.Builder authorizationBuilder = OAuth2Authorization.withRegisteredClient(registeredClient) | |||||
.principalName(usernamePasswordAuthentication.getName()) | |||||
.authorizationGrantType(AuthorizationGrantType.PASSWORD) | |||||
.attribute(OAuth2Authorization.AUTHORIZED_SCOPE_ATTRIBUTE_NAME, authorizedScopes) | |||||
.attribute(Principal.class.getName(), usernamePasswordAuthentication); | |||||
// @formatter:on | |||||
if (generatedAccessToken instanceof ClaimAccessor) { | |||||
authorizationBuilder.token(accessToken, (metadata) -> | |||||
metadata.put(OAuth2Authorization.Token.CLAIMS_METADATA_NAME, ((ClaimAccessor) generatedAccessToken).getClaims())); | |||||
} else { | |||||
authorizationBuilder.accessToken(accessToken); | |||||
} | |||||
OAuth2Authorization authorization = authorizationBuilder.build(); | |||||
this.authorizationService.save(authorization); | |||||
LOGGER.debug("OAuth2Authorization saved successfully"); | |||||
Map<String, Object> additionalParameters = Collections.emptyMap(); | |||||
LOGGER.debug("returning OAuth2AccessTokenAuthenticationToken"); | |||||
return new OAuth2AccessTokenAuthenticationToken(registeredClient, clientPrincipal, accessToken, refreshToken, additionalParameters); | |||||
} | |||||
@Override | |||||
public boolean supports(Class<?> authentication) { | |||||
return OAuth2ResourceOwnerPasswordAuthenticationToken.class.isAssignableFrom(authentication); | |||||
} | |||||
private Authentication getUsernamePasswordAuthentication(OAuth2ResourceOwnerPasswordAuthenticationToken resouceOwnerPasswordAuthentication) { | |||||
Map<String, Object> additionalParameters = resouceOwnerPasswordAuthentication.getAdditionalParameters(); | |||||
String username = (String) additionalParameters.get(OAuth2ParameterNames.USERNAME); | |||||
String password = (String) additionalParameters.get(OAuth2ParameterNames.PASSWORD); | |||||
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(username, password); | |||||
LOGGER.debug("got usernamePasswordAuthenticationToken=" + usernamePasswordAuthenticationToken); | |||||
Authentication usernamePasswordAuthentication = authenticationManager.authenticate(usernamePasswordAuthenticationToken); | |||||
return usernamePasswordAuthentication; | |||||
} | |||||
private OAuth2ClientAuthenticationToken getAuthenticatedClientElseThrowInvalidClient(Authentication authentication) { | |||||
OAuth2ClientAuthenticationToken clientPrincipal = null; | |||||
if (OAuth2ClientAuthenticationToken.class.isAssignableFrom(authentication.getPrincipal().getClass())) { | |||||
clientPrincipal = (OAuth2ClientAuthenticationToken) authentication.getPrincipal(); | |||||
} | |||||
if (clientPrincipal != null && clientPrincipal.isAuthenticated()) { | |||||
return clientPrincipal; | |||||
} | |||||
throw new OAuth2AuthenticationException(OAuth2ErrorCodes.INVALID_CLIENT); | |||||
} | |||||
} |
package com.tuoheng.oauth2.authentication; | |||||
import org.springframework.lang.Nullable; | |||||
import org.springframework.security.authentication.AbstractAuthenticationToken; | |||||
import org.springframework.security.core.Authentication; | |||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | |||||
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AuthorizationGrantAuthenticationToken; | |||||
import org.springframework.util.Assert; | |||||
import java.util.*; | |||||
public class OAuth2ResourceOwnerPasswordAuthenticationToken extends AbstractAuthenticationToken { | |||||
private static final long serialVersionUID = -6067207202119450764L; | |||||
private final AuthorizationGrantType authorizationGrantType; | |||||
private final Authentication clientPrincipal; | |||||
private final Set<String> scopes; | |||||
private final Map<String, Object> additionalParameters; | |||||
/** | |||||
* Constructs an {@code OAuth2ClientCredentialsAuthenticationToken} using the provided parameters. | |||||
* | |||||
* @param clientPrincipal the authenticated client principal | |||||
*/ | |||||
public OAuth2ResourceOwnerPasswordAuthenticationToken(AuthorizationGrantType authorizationGrantType, | |||||
Authentication clientPrincipal, @Nullable Set<String> scopes, @Nullable Map<String, Object> additionalParameters) { | |||||
super(Collections.emptyList()); | |||||
Assert.notNull(authorizationGrantType, "authorizationGrantType cannot be null"); | |||||
Assert.notNull(clientPrincipal, "clientPrincipal cannot be null"); | |||||
this.authorizationGrantType = authorizationGrantType; | |||||
this.clientPrincipal = clientPrincipal; | |||||
this.scopes = Collections.unmodifiableSet(scopes != null ? new HashSet<>(scopes) : Collections.emptySet()); | |||||
this.additionalParameters = Collections.unmodifiableMap(additionalParameters != null ? new HashMap<>(additionalParameters) : Collections.emptyMap()); | |||||
} | |||||
/** | |||||
* Returns the authorization grant type. | |||||
* | |||||
* @return the authorization grant type | |||||
*/ | |||||
public AuthorizationGrantType getGrantType() { | |||||
return this.authorizationGrantType; | |||||
} | |||||
@Override | |||||
public Object getPrincipal() { | |||||
return this.clientPrincipal; | |||||
} | |||||
@Override | |||||
public Object getCredentials() { | |||||
return ""; | |||||
} | |||||
/** | |||||
* Returns the requested scope(s). | |||||
* | |||||
* @return the requested scope(s), or an empty {@code Set} if not available | |||||
*/ | |||||
public Set<String> getScopes() { | |||||
return this.scopes; | |||||
} | |||||
/** | |||||
* Returns the additional parameters. | |||||
* | |||||
* @return the additional parameters | |||||
*/ | |||||
public Map<String, Object> getAdditionalParameters() { | |||||
return this.additionalParameters; | |||||
} | |||||
} |
package com.tuoheng.service; | |||||
import com.tuoheng.model.dto.UserBaseInfoDto; | |||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | |||||
import java.util.Set; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/9/29 10:17 | |||||
*/ | |||||
public interface OidcUserInfoService { | |||||
OidcUserInfo loadUser(String name, Set<String> scopes, UserBaseInfoDto userBaseInfoDto); | |||||
} |
package com.tuoheng.service; | package com.tuoheng.service; | ||||
import com.tuoheng.model.param.CreateUserDto; | import com.tuoheng.model.param.CreateUserDto; | ||||
import com.tuoheng.model.param.GetUserInfoDto; | |||||
import com.tuoheng.until.JsonResult; | import com.tuoheng.until.JsonResult; | ||||
/** | /** | ||||
* @author chenjiandong | * @author chenjiandong | ||||
JsonResult createUser(CreateUserDto createUserDto); | JsonResult createUser(CreateUserDto createUserDto); | ||||
JsonResult getUserInfo(GetUserInfoDto getUserInfoDto); | |||||
} | } |
package com.tuoheng.service; | |||||
package com.tuoheng.service.impl; | |||||
import com.nimbusds.jose.shaded.json.JSONObject; | import com.nimbusds.jose.shaded.json.JSONObject; | ||||
import com.tuoheng.mapper.UserMapper; | |||||
import com.tuoheng.model.dto.UserBaseInfoDto; | |||||
import com.tuoheng.service.OidcUserInfoService; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.security.oauth2.core.oidc.OidcScopes; | import org.springframework.security.oauth2.core.oidc.OidcScopes; | ||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | ||||
import org.springframework.stereotype.Service; | |||||
import org.springframework.util.CollectionUtils; | import org.springframework.util.CollectionUtils; | ||||
import java.time.LocalDateTime; | import java.time.LocalDateTime; | ||||
import java.time.format.DateTimeFormatter; | import java.time.format.DateTimeFormatter; | ||||
import java.util.Calendar; | |||||
import java.util.Collections; | import java.util.Collections; | ||||
import java.util.Set; | import java.util.Set; | ||||
/** | /** | ||||
* @author chenjiandong | * @author chenjiandong | ||||
* @description: TODO | * @description: TODO | ||||
* @date 2022/9/29 10:17 | |||||
* @date 2022/10/9 13:40 | |||||
*/ | */ | ||||
public class OidcUserInfoService { | |||||
@Service | |||||
public class OidcUserInfoServiceImpl implements OidcUserInfoService { | |||||
public OidcUserInfo loadUser(String name, Set<String> scopes) { | |||||
@Override | |||||
public OidcUserInfo loadUser(String name, Set<String> scopes, UserBaseInfoDto userBaseInfoDto) { | |||||
OidcUserInfo.Builder builder = OidcUserInfo.builder().subject(name); | OidcUserInfo.Builder builder = OidcUserInfo.builder().subject(name); | ||||
if (!CollectionUtils.isEmpty(scopes)) { | if (!CollectionUtils.isEmpty(scopes)) { | ||||
if (scopes.contains(OidcScopes.PROFILE)) { | if (scopes.contains(OidcScopes.PROFILE)) { | ||||
builder.name("First Last") | |||||
.givenName("First") | |||||
.familyName("Last") | |||||
.middleName("Middle") | |||||
.nickname("User") | |||||
.preferredUsername(name) | |||||
.profile("http://127.0.0.1:8080/" + name) | |||||
.picture("http://127.0.0.1:8080/" + name + ".jpg") | |||||
.website("http://127.0.0.1:8080/") | |||||
.gender("female") | |||||
.birthdate("2022-05-24") | |||||
.zoneinfo("China/Beijing") | |||||
.locale("zh-cn") | |||||
.updatedAt(LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE)); | |||||
builder.claim("userId", userBaseInfoDto.getUserId()) | |||||
.claim("userName", userBaseInfoDto.getUserName()) | |||||
.claim("authority", userBaseInfoDto.getAuthorityList()); | |||||
} | } | ||||
if (scopes.contains(OidcScopes.EMAIL)) { | |||||
/*if (scopes.contains(OidcScopes.EMAIL)) { | |||||
builder.email(name + "@163.com").emailVerified(true); | builder.email(name + "@163.com").emailVerified(true); | ||||
} | } | ||||
if (scopes.contains(OidcScopes.ADDRESS)) { | if (scopes.contains(OidcScopes.ADDRESS)) { | ||||
} | } | ||||
if (scopes.contains(OidcScopes.PHONE)) { | if (scopes.contains(OidcScopes.PHONE)) { | ||||
builder.phoneNumber("13728903134").phoneNumberVerified("false"); | builder.phoneNumber("13728903134").phoneNumberVerified("false"); | ||||
} | |||||
}*/ | |||||
} | } | ||||
return builder.build(); | return builder.build(); | ||||
} | } | ||||
} | |||||
} |
package com.tuoheng.service.impl; | |||||
import com.tuoheng.mapper.AuthoritiesMapper; | |||||
import com.tuoheng.mapper.UserMapper; | |||||
import com.tuoheng.model.dto.UserBaseInfoDto; | |||||
import com.tuoheng.model.param.CreateUserDto; | |||||
import com.tuoheng.model.param.GetUserInfoDto; | |||||
import com.tuoheng.model.po.AuthoritiesPo; | |||||
import com.tuoheng.model.po.UserPo; | |||||
import com.tuoheng.service.UserSevice; | |||||
import com.tuoheng.until.JsonResult; | |||||
import org.apache.tomcat.util.buf.StringUtils; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.security.core.userdetails.User; | |||||
import org.springframework.security.core.userdetails.UserDetails; | |||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||||
import org.springframework.security.oauth2.core.oidc.OidcUserInfo; | |||||
import org.springframework.security.oauth2.server.authorization.oidc.authentication.OidcUserInfoAuthenticationToken; | |||||
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken; | |||||
import org.springframework.security.provisioning.UserDetailsManager; | |||||
import org.springframework.stereotype.Service; | |||||
import org.springframework.transaction.annotation.Transactional; | |||||
import org.thymeleaf.expression.Lists; | |||||
import java.util.ArrayList; | |||||
import java.util.Collections; | |||||
import java.util.List; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/8 11:35 | |||||
*/ | |||||
@Service | |||||
public class UserServiceImpl implements UserSevice { | |||||
@Autowired | |||||
private UserMapper userMapper; | |||||
@Autowired | |||||
private AuthoritiesMapper authoritiesMapper; | |||||
@Transactional(rollbackFor = Exception.class) | |||||
public JsonResult createUser(CreateUserDto createUserDto){ | |||||
UserPo userPo = new UserPo() | |||||
.setUsername(createUserDto.getUsername()) | |||||
.setPassword("{bcrypt}" + new BCryptPasswordEncoder().encode(createUserDto.getPassword())); | |||||
userMapper.insertUser(userPo); | |||||
List<AuthoritiesPo> authoritiesPos = new ArrayList<>(); | |||||
for(String authority : createUserDto.getAuthorities()){ | |||||
AuthoritiesPo authoritiesPo = new AuthoritiesPo() | |||||
.setUserId(userPo.getId()) | |||||
.setUsername(createUserDto.getUsername()) | |||||
.setAuthority(authority); | |||||
authoritiesPos.add(authoritiesPo); | |||||
} | |||||
authoritiesMapper.batchInsert(authoritiesPos); | |||||
return JsonResult.success(userPo.getId()); | |||||
} | |||||
@Override | |||||
public JsonResult getUserInfo(GetUserInfoDto getUserInfoDto){ | |||||
UserBaseInfoDto userBaseInfoDto = userMapper.getMpUserInfo(getUserInfoDto.getUsername()); | |||||
return JsonResult.success(userBaseInfoDto); | |||||
} | |||||
} |
package com.tuoheng.until; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.data.redis.core.StringRedisTemplate; | |||||
import org.springframework.stereotype.Component; | |||||
import javax.annotation.PostConstruct; | |||||
import java.util.HashMap; | |||||
import java.util.Set; | |||||
import java.util.concurrent.TimeUnit; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/12 13:51 | |||||
*/ | |||||
@Component | |||||
public class RedisUtils { | |||||
@Autowired | |||||
private StringRedisTemplate redisTemplate; | |||||
private static RedisUtils redisUtils; | |||||
/** | |||||
* 初始化 | |||||
*/ | |||||
@PostConstruct | |||||
public void init() { | |||||
redisUtils = this; | |||||
redisUtils.redisTemplate = this.redisTemplate; | |||||
} | |||||
/** | |||||
* 查询key,支持模糊查询 | |||||
* | |||||
* @param key | |||||
*/ | |||||
public static Set<String> keys(String key) { | |||||
return redisUtils.redisTemplate.keys(key); | |||||
} | |||||
/** | |||||
* 获取值 | |||||
* | |||||
* @param key | |||||
*/ | |||||
public static Object get(String key) { | |||||
return redisUtils.redisTemplate.opsForValue().get(key); | |||||
} | |||||
/** | |||||
* 设置值 | |||||
* | |||||
* @param key | |||||
* @param value | |||||
*/ | |||||
public static void set(String key, String value) { | |||||
redisUtils.redisTemplate.opsForValue().set(key, value); | |||||
} | |||||
/** | |||||
* 设置值,并设置过期时间 | |||||
* | |||||
* @param key | |||||
* @param value | |||||
* @param expire 过期时间,单位秒 | |||||
*/ | |||||
public static void set(String key, String value, Integer expire) { | |||||
redisUtils.redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS); | |||||
} | |||||
/** | |||||
* 删出key | |||||
* | |||||
* @param key | |||||
*/ | |||||
public static void delete(String key) { | |||||
redisUtils.redisTemplate.opsForValue().getOperations().delete(key); | |||||
} | |||||
/** | |||||
* 设置对象 | |||||
* | |||||
* @param key key | |||||
* @param hashKey hashKey | |||||
* @param object 对象 | |||||
*/ | |||||
public static void hset(String key, String hashKey, Object object) { | |||||
redisUtils.redisTemplate.opsForHash().put(key, hashKey, object); | |||||
} | |||||
/** | |||||
* 设置对象 | |||||
* | |||||
* @param key key | |||||
* @param hashKey hashKey | |||||
* @param object 对象 | |||||
* @param expire 过期时间,单位秒 | |||||
*/ | |||||
public static void hset(String key, String hashKey, Object object, Integer expire) { | |||||
redisUtils.redisTemplate.opsForHash().put(key, hashKey, object); | |||||
redisUtils.redisTemplate.expire(key, expire, TimeUnit.SECONDS); | |||||
} | |||||
/** | |||||
* 设置HashMap | |||||
* | |||||
* @param key key | |||||
* @param map map值 | |||||
*/ | |||||
public static void hset(String key, HashMap<String, Object> map) { | |||||
redisUtils.redisTemplate.opsForHash().putAll(key, map); | |||||
} | |||||
/** | |||||
* key不存在时设置值 | |||||
* | |||||
* @param key | |||||
* @param hashKey | |||||
* @param object | |||||
*/ | |||||
public static void hsetAbsent(String key, String hashKey, Object object) { | |||||
redisUtils.redisTemplate.opsForHash().putIfAbsent(key, hashKey, object); | |||||
} | |||||
/** | |||||
* 获取Hash值 | |||||
* | |||||
* @param key | |||||
* @param hashKey | |||||
* @return | |||||
*/ | |||||
public static Object hget(String key, String hashKey) { | |||||
return redisUtils.redisTemplate.opsForHash().get(key, hashKey); | |||||
} | |||||
/** | |||||
* 获取key的所有值 | |||||
* | |||||
* @param key | |||||
* @return | |||||
*/ | |||||
public static Object hget(String key) { | |||||
return redisUtils.redisTemplate.opsForHash().entries(key); | |||||
} | |||||
/** | |||||
* 删除key的所有值 | |||||
* | |||||
* @param key | |||||
*/ | |||||
public static void deleteKey(String key) { | |||||
redisUtils.redisTemplate.opsForHash().getOperations().delete(key); | |||||
} | |||||
/** | |||||
* 判断key下是否有值 | |||||
* | |||||
* @param key | |||||
*/ | |||||
public static Boolean hasKey(String key) { | |||||
return redisUtils.redisTemplate.opsForHash().getOperations().hasKey(key); | |||||
} | |||||
/** | |||||
* 判断key和hasKey下是否有值 | |||||
* | |||||
* @param key | |||||
* @param hasKey | |||||
*/ | |||||
public static Boolean hasKey(String key, String hasKey) { | |||||
return redisUtils.redisTemplate.opsForHash().hasKey(key, hasKey); | |||||
} | |||||
} | |||||
package com.tuoheng.until; | package com.tuoheng.until; | ||||
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; | |||||
import org.springframework.security.oauth2.core.AuthorizationGrantType; | import org.springframework.security.oauth2.core.AuthorizationGrantType; | ||||
import org.springframework.security.oauth2.core.ClientAuthenticationMethod; | import org.springframework.security.oauth2.core.ClientAuthenticationMethod; | ||||
import org.springframework.security.oauth2.core.OAuth2TokenFormat; | import org.springframework.security.oauth2.core.OAuth2TokenFormat; | ||||
.clientId("tuoheng-hhz") | .clientId("tuoheng-hhz") | ||||
.clientName("tuoheng-hhz-client") | .clientName("tuoheng-hhz-client") | ||||
.clientSecret("{noop}" + CryptoUtil.genAesSecret()) | .clientSecret("{noop}" + CryptoUtil.genAesSecret()) | ||||
//.clientSecret("{bcrypt}" + new BCryptPasswordEncoder().encode("secret")) | |||||
.clientAuthenticationMethods(s -> { | .clientAuthenticationMethods(s -> { | ||||
s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST); | s.add(ClientAuthenticationMethod.CLIENT_SECRET_POST); | ||||
s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); | s.add(ClientAuthenticationMethod.CLIENT_SECRET_BASIC); | ||||
.redirectUri("http://www.baidu.com") | .redirectUri("http://www.baidu.com") | ||||
.scope(OidcScopes.OPENID) | .scope(OidcScopes.OPENID) | ||||
.scope(OidcScopes.PROFILE) | .scope(OidcScopes.PROFILE) | ||||
.scope(OidcScopes.EMAIL) | |||||
.clientSettings(ClientSettings.builder() | .clientSettings(ClientSettings.builder() | ||||
.requireAuthorizationConsent(true) | |||||
.requireAuthorizationConsent(false) | |||||
.requireProofKey(false) | .requireProofKey(false) | ||||
.build()) | .build()) | ||||
.tokenSettings(TokenSettings.builder() | .tokenSettings(TokenSettings.builder() |
package com.tuoheng.until; | |||||
import javax.imageio.ImageIO; | |||||
import java.awt.*; | |||||
import java.awt.image.BufferedImage; | |||||
import java.io.IOException; | |||||
import java.io.OutputStream; | |||||
import java.util.Random; | |||||
/** | |||||
* @author chenjiandong | |||||
* @description: TODO | |||||
* @date 2022/10/11 17:07 | |||||
*/ | |||||
public class VerifyCode { | |||||
private int width = 100;// 生成验证码图片的宽度 | |||||
private int height = 50;// 生成验证码图片的高度 | |||||
private String[] fontNames = { "宋体", "楷体", "隶书", "微软雅黑" }; | |||||
private Color bgColor = new Color(255, 255, 255);// 定义验证码图片的背景颜色为白色 | |||||
private Random random = new Random(); | |||||
private String codes = "123456789ABCDEFGHIJKLMNPQRSTUVWXYZ"; | |||||
private String text;// 记录随机字符串 | |||||
/** | |||||
* 获取一个随意颜色 | |||||
* | |||||
* @return | |||||
*/ | |||||
private Color randomColor() { | |||||
int red = random.nextInt(150); | |||||
int green = random.nextInt(150); | |||||
int blue = random.nextInt(150); | |||||
return new Color(red, green, blue); | |||||
} | |||||
/** | |||||
* 获取一个随机字体 | |||||
* | |||||
* @return | |||||
*/ | |||||
private Font randomFont() { | |||||
String name = fontNames[random.nextInt(fontNames.length)]; | |||||
int style = random.nextInt(4); | |||||
int size = random.nextInt(5) + 24; | |||||
return new Font(name, style, size); | |||||
} | |||||
/** | |||||
* 获取一个随机字符 | |||||
* | |||||
* @return | |||||
*/ | |||||
private char randomChar() { | |||||
return codes.charAt(random.nextInt(codes.length())); | |||||
} | |||||
/** | |||||
* 创建一个空白的BufferedImage对象 | |||||
* | |||||
* @return | |||||
*/ | |||||
private BufferedImage createImage() { | |||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB); | |||||
Graphics2D g2 = (Graphics2D) image.getGraphics(); | |||||
g2.setColor(bgColor);// 设置验证码图片的背景颜色 | |||||
g2.fillRect(0, 0, width, height); | |||||
return image; | |||||
} | |||||
public BufferedImage getImage() { | |||||
BufferedImage image = createImage(); | |||||
Graphics2D g2 = (Graphics2D) image.getGraphics(); | |||||
StringBuffer sb = new StringBuffer(); | |||||
for (int i = 0; i < 4; i++) { | |||||
String s = randomChar() + ""; | |||||
sb.append(s); | |||||
g2.setColor(randomColor()); | |||||
g2.setFont(randomFont()); | |||||
float x = i * width * 1.0f / 4; | |||||
g2.drawString(s, x, height - 15); | |||||
} | |||||
this.text = sb.toString(); | |||||
drawLine(image); | |||||
return image; | |||||
} | |||||
/** | |||||
* 绘制干扰线 | |||||
* | |||||
* @param image | |||||
*/ | |||||
private void drawLine(BufferedImage image) { | |||||
Graphics2D g2 = (Graphics2D) image.getGraphics(); | |||||
int num = 5; | |||||
for (int i = 0; i < num; i++) { | |||||
int x1 = random.nextInt(width); | |||||
int y1 = random.nextInt(height); | |||||
int x2 = random.nextInt(width); | |||||
int y2 = random.nextInt(height); | |||||
g2.setColor(randomColor()); | |||||
g2.setStroke(new BasicStroke(1.5f)); | |||||
g2.drawLine(x1, y1, x2, y2); | |||||
} | |||||
} | |||||
public String getText() { | |||||
return text; | |||||
} | |||||
public static void output(BufferedImage image, OutputStream out) throws IOException { | |||||
ImageIO.write(image, "JPEG", out); | |||||
} | |||||
} |
package com.tuoheng.until; | |||||
import javax.imageio.ImageIO; | |||||
import javax.servlet.http.HttpServletResponse; | |||||
import java.awt.*; | |||||
import java.awt.image.BufferedImage; | |||||
import java.io.ByteArrayOutputStream; | |||||
import java.util.Base64; | |||||
import java.util.HashMap; | |||||
import java.util.Map; | |||||
import java.util.Random; | |||||
/** | |||||
* 获取验证码工具类 | |||||
*/ | |||||
public class VerifyUtil { | |||||
private static Random random = new Random(); | |||||
/** | |||||
* 验证码的宽 | |||||
*/ | |||||
private int width = 165; | |||||
/** | |||||
* 验证码的高 | |||||
*/ | |||||
private int height = 45; | |||||
/** | |||||
* 验证码中夹杂的干扰线数量 | |||||
*/ | |||||
private int lineSize = 30; | |||||
/** | |||||
* 验证码字符个数 | |||||
*/ | |||||
private int randomStrNum = 4; | |||||
private String randomString = "123456789ABCDEFHKLMNQRSTWXY"; | |||||
/** | |||||
* 字体的设置 | |||||
* | |||||
* @return | |||||
*/ | |||||
private Font getFont() { | |||||
return new Font("Times New Roman", Font.ROMAN_BASELINE, 40); | |||||
} | |||||
/** | |||||
* 颜色的设置 | |||||
* | |||||
* @param fc | |||||
* @param bc | |||||
* @return | |||||
*/ | |||||
private static Color getRandomColor(int fc, int bc) { | |||||
fc = Math.min(fc, 255); | |||||
bc = Math.min(bc, 255); | |||||
int r = fc + random.nextInt(bc - fc - 16); | |||||
int g = fc + random.nextInt(bc - fc - 14); | |||||
int b = fc + random.nextInt(bc - fc - 12); | |||||
return new Color(r, g, b); | |||||
} | |||||
/** | |||||
* 干扰线的绘制 | |||||
* | |||||
* @param g | |||||
*/ | |||||
private void drawLine(Graphics g) { | |||||
int x = random.nextInt(width); | |||||
int y = random.nextInt(height); | |||||
int xl = random.nextInt(20); | |||||
int yl = random.nextInt(10); | |||||
g.drawLine(x, y, x + xl, y + yl); | |||||
} | |||||
/** | |||||
* 随机字符的获取 | |||||
* | |||||
* @param num | |||||
* @return | |||||
*/ | |||||
private String getRandomString(int num) { | |||||
num = num > 0 ? num : randomString.length(); | |||||
return String.valueOf(randomString.charAt(random.nextInt(num))); | |||||
} | |||||
/** | |||||
* 字符串的绘制 | |||||
* | |||||
* @param g | |||||
* @param randomStr | |||||
* @param i | |||||
* @return | |||||
*/ | |||||
private String drawString(Graphics g, String randomStr, int i) { | |||||
g.setFont(getFont()); | |||||
g.setColor(getRandomColor(108, 190)); | |||||
String rand = getRandomString(random.nextInt(randomString.length())); | |||||
randomStr += rand; | |||||
g.translate(random.nextInt(3), random.nextInt(6)); | |||||
g.drawString(rand, 40 * i + 10, 25); | |||||
return randomStr; | |||||
} | |||||
/** | |||||
* 生成随机图片 | |||||
* | |||||
* @param response | |||||
*/ | |||||
public void getRandomCodeImage(HttpServletResponse response) { | |||||
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类 | |||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); | |||||
Graphics g = image.getGraphics(); | |||||
g.fillRect(0, 0, width, height); | |||||
g.setColor(getRandomColor(105, 189)); | |||||
g.setFont(getFont()); | |||||
// 干扰线 | |||||
for (int i = 0; i < lineSize; i++) { | |||||
drawLine(g); | |||||
} | |||||
// 随机字符 | |||||
String randomStr = ""; | |||||
for (int i = 0; i < randomStrNum; i++) { | |||||
randomStr = drawString(g, randomStr, i); | |||||
} | |||||
System.out.println("随机字符:" + randomStr); | |||||
g.dispose(); | |||||
try { | |||||
// 将图片以png格式返回,返回的是图片 | |||||
ImageIO.write(image, "PNG", response.getOutputStream()); | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
} | |||||
} | |||||
/** | |||||
* 生成随机图片的base64编码字符串 | |||||
* | |||||
* @return | |||||
*/ | |||||
public Map<String, String> getRandomCodeBase64() { | |||||
Map<String, String> result = new HashMap<>(); | |||||
// BufferedImage类是具有缓冲区的Image类,Image类是用于描述图像信息的类 | |||||
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_BGR); | |||||
Graphics g = image.getGraphics(); | |||||
g.fillRect(0, 0, width, height); | |||||
g.setColor(getRandomColor(105, 189)); | |||||
g.setFont(getFont()); | |||||
//干扰线 | |||||
for (int i = 0; i < lineSize; i++) { | |||||
drawLine(g); | |||||
} | |||||
//随机字符 | |||||
String randomStr = ""; | |||||
for (int i = 0; i < randomStrNum; i++) { | |||||
randomStr = drawString(g, randomStr, i); | |||||
} | |||||
g.dispose(); | |||||
String base64String = ""; | |||||
try { | |||||
// 直接返回图片 | |||||
// 返回 base64 | |||||
ByteArrayOutputStream bos = new ByteArrayOutputStream(); | |||||
ImageIO.write(image, "PNG", bos); | |||||
byte[] bytes = bos.toByteArray(); | |||||
Base64.Encoder encoder = Base64.getEncoder(); | |||||
base64String = encoder.encodeToString(bytes); | |||||
} catch (Exception e) { | |||||
e.printStackTrace(); | |||||
} | |||||
result.put("randomStr", randomStr); | |||||
result.put("img", base64String); | |||||
return result; | |||||
} | |||||
} |
oauth2: | oauth2: | ||||
resource-server: | resource-server: | ||||
jwt: | jwt: | ||||
issuer-uri: http://192.168.11.11:8090 #认证中心端点,作为资源端的配置 | |||||
#issuer-uri: http://192.168.11.11:8090 #认证中心端点,作为资源端的配置 | |||||
issuer-uri: http://oidc.dev.t-aaron.com | |||||
# 配置数据源 | # 配置数据源 | ||||
datasource: | datasource: | ||||
validationQuery: SELECT 1 FROM DUAL | validationQuery: SELECT 1 FROM DUAL | ||||
testWhileIdle: true | testWhileIdle: true | ||||
testOnBorrow: false | testOnBorrow: false | ||||
testOnReturn: false | |||||
testOnReturn: false | |||||
# Redis数据源 | |||||
redis: | |||||
# 缓存库默认索引0 | |||||
database: 0 | |||||
# Redis服务器地址 | |||||
host: 192.168.11.13 | |||||
# Redis服务器连接端口 | |||||
port: 6379 | |||||
# Redis服务器连接密码(默认为空) | |||||
password: | |||||
# 连接超时时间(毫秒) | |||||
timeout: 6000 | |||||
# 默认的数据过期时间,主要用于shiro权限管理 | |||||
expire: 2592000 | |||||
jedis: | |||||
pool: | |||||
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) | |||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) | |||||
max-idle: 10 # 连接池中的最大空闲连接 | |||||
min-idle: 1 # 连接池中的最小空闲连接 | |||||
oauth2: | |||||
token: | |||||
issuer: http://oidc.dev.t-aaron.com |
validationQuery: SELECT 1 FROM DUAL | validationQuery: SELECT 1 FROM DUAL | ||||
testWhileIdle: true | testWhileIdle: true | ||||
testOnBorrow: false | testOnBorrow: false | ||||
testOnReturn: false | |||||
testOnReturn: false | |||||
# Redis数据源 | |||||
redis: | |||||
# 缓存库默认索引0 | |||||
database: 0 | |||||
# Redis服务器地址 | |||||
host: 192.168.11.13 | |||||
# Redis服务器连接端口 | |||||
port: 6379 | |||||
# Redis服务器连接密码(默认为空) | |||||
password: | |||||
# 连接超时时间(毫秒) | |||||
timeout: 6000 | |||||
# 默认的数据过期时间,主要用于shiro权限管理 | |||||
expire: 2592000 | |||||
jedis: | |||||
pool: | |||||
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) | |||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) | |||||
max-idle: 10 # 连接池中的最大空闲连接 | |||||
min-idle: 1 # 连接池中的最小空闲连接 | |||||
oauth2: | |||||
token: | |||||
issuer: http://127.0.0.1:8090 |
spring: | |||||
security: | |||||
oauth2: | |||||
resource-server: | |||||
jwt: | |||||
issuer-uri: http://172.16.1.31:8090 #认证中心端点,作为资源端的配置、 | |||||
# 配置数据源 | |||||
datasource: | |||||
# 使用阿里的Druid连接池 | |||||
type: com.alibaba.druid.pool.DruidDataSource | |||||
driver-class-name: com.mysql.cj.jdbc.Driver | |||||
# 填写你数据库的url、登录名、密码和数据库名 | |||||
url: jdbc:mysql://rm-uf6x76i111rb1eo48.mysql.rds.aliyuncs.com:3306/tuoheng_dsp?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&useSSL=true&tinyInt1isBit=false | |||||
username: root | |||||
password: TH22#2022 | |||||
druid: | |||||
# 连接池的配置信息 | |||||
# 初始连接数 | |||||
initialSize: 5 | |||||
# 最小连接池数量 | |||||
minIdle: 5 | |||||
# 最大连接池数量 | |||||
maxActive: 20 | |||||
# 配置获取连接等待超时的时间 | |||||
maxWait: 60000 | |||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 | |||||
timeBetweenEvictionRunsMillis: 60000 | |||||
# 配置一个连接在池中最小生存的时间,单位是毫秒 | |||||
minEvictableIdleTimeMillis: 300000 | |||||
# 配置一个连接在池中最大生存的时间,单位是毫秒 | |||||
maxEvictableIdleTimeMillis: 900000 | |||||
# 配置检测连接是否有效 | |||||
validationQuery: SELECT 1 FROM DUAL | |||||
testWhileIdle: true | |||||
testOnBorrow: false | |||||
testOnReturn: false | |||||
# Redis数据源 | |||||
redis: | |||||
# 缓存库默认索引0 | |||||
database: 0 | |||||
# Redis服务器地址 | |||||
host: r-uf6r5lm7c7sfdv3ehb.redis.rds.aliyuncs.com | |||||
# Redis服务器连接端口 | |||||
port: 6379 | |||||
# Redis服务器连接密码(默认为空) | |||||
password: | |||||
# 连接超时时间(毫秒) | |||||
timeout: 6000 | |||||
# 默认的数据过期时间,主要用于shiro权限管理 | |||||
expire: 2592000 | |||||
jedis: | |||||
pool: | |||||
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) | |||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) | |||||
max-idle: 10 # 连接池中的最大空闲连接 | |||||
min-idle: 1 # 连接池中的最小空闲连接 | |||||
oauth2: | |||||
token: | |||||
issuer: http://172.16.1.31:8090 |
spring: | |||||
security: | |||||
oauth2: | |||||
resource-server: | |||||
jwt: | |||||
#issuer-uri: http://192.168.11.241:8090 #认证中心端点,作为资源端的配置、 | |||||
issuer-uri: https://oidc.test.t-aaron.com #认证中心端点,作为资源端的配置、 | |||||
# 配置数据源 | |||||
datasource: | |||||
# 使用阿里的Druid连接池 | |||||
type: com.alibaba.druid.pool.DruidDataSource | |||||
driver-class-name: com.mysql.cj.jdbc.Driver | |||||
# 填写你数据库的url、登录名、密码和数据库名 | |||||
url: jdbc:mysql://192.168.11.242:3306/tuoheng_oidc?useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8&useSSL=true&tinyInt1isBit=false | |||||
username: root | |||||
password: idontcare | |||||
druid: | |||||
# 连接池的配置信息 | |||||
# 初始连接数 | |||||
initialSize: 5 | |||||
# 最小连接池数量 | |||||
minIdle: 5 | |||||
# 最大连接池数量 | |||||
maxActive: 20 | |||||
# 配置获取连接等待超时的时间 | |||||
maxWait: 60000 | |||||
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 | |||||
timeBetweenEvictionRunsMillis: 60000 | |||||
# 配置一个连接在池中最小生存的时间,单位是毫秒 | |||||
minEvictableIdleTimeMillis: 300000 | |||||
# 配置一个连接在池中最大生存的时间,单位是毫秒 | |||||
maxEvictableIdleTimeMillis: 900000 | |||||
# 配置检测连接是否有效 | |||||
validationQuery: SELECT 1 FROM DUAL | |||||
testWhileIdle: true | |||||
testOnBorrow: false | |||||
testOnReturn: false | |||||
# Redis数据源 | |||||
redis: | |||||
# 缓存库默认索引0 | |||||
database: 0 | |||||
# Redis服务器地址 | |||||
host: 192.168.11.242 | |||||
# Redis服务器连接端口 | |||||
port: 6379 | |||||
# Redis服务器连接密码(默认为空) | |||||
password: | |||||
# 连接超时时间(毫秒) | |||||
timeout: 6000 | |||||
# 默认的数据过期时间,主要用于shiro权限管理 | |||||
expire: 2592000 | |||||
jedis: | |||||
pool: | |||||
max-active: 1000 # 连接池最大连接数(使用负值表示没有限制) | |||||
max-wait: -1 # 连接池最大阻塞等待时间(使用负值表示没有限制) | |||||
max-idle: 10 # 连接池中的最大空闲连接 | |||||
min-idle: 1 # 连接池中的最小空闲连接 | |||||
oauth2: | |||||
token: | |||||
issuer: https://oidc.test.t-aaron.com |
server: | |||||
port: 8090 | |||||
spring: | |||||
profiles: | |||||
active: @package.environment@ | |||||
web: | |||||
resources: | |||||
static-locations: classpath:/ | |||||
mybatis: | |||||
mapper-locations: classpath*:mapper/*Mapper.xml |
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!-- 日志级别从低到高分为TRACE < DEBUG < INFO < WARN < ERROR < FATAL,如果设置为WARN,则低于WARN的信息都不会输出 --> | |||||
<!-- scan:当此属性设置为true时,配置文档如果发生改变,将会被重新加载,默认值为true --> | |||||
<!-- scanPeriod:设置监测配置文档是否有修改的时间间隔,如果没有给出时间单位,默认单位是毫秒。 | |||||
当scan为true时,此属性生效。默认的时间间隔为1分钟。 --> | |||||
<!-- debug:当此属性设置为true时,将打印出logback内部日志信息,实时查看logback运行状态。默认值为false。 --> | |||||
<configuration scan="true" scanPeriod="60 seconds" debug="false"> | |||||
<!-- | |||||
contextName说明: | |||||
每个logger都关联到logger上下文,默认上下文名称为“default”。但可以使用设置成其他名字, | |||||
用于区分不同应用程序的记录。一旦设置,不能修改,可以通过%contextName来打印日志上下文名称。 | |||||
--> | |||||
<contextName>tuoheng_oidc_server</contextName> | |||||
<!--定义日志变量--> | |||||
<!--<property name="logging.path" value="D:\\idealogs\\tuoheng_oidc"/>--> | |||||
<property name="logging.path" value="/data/java/logs/tuoheng_oidc"/> | |||||
<!--日志格式: [时间] [级别] [线程] [行号] [logger信息] - [日志信息]--> | |||||
<property name="logging.pattern" value="%d{yyyy-MM-dd HH:mm:ss.SSS} [%level][%thread][%L] %logger - %msg%n"/> | |||||
<property name="logging.charset" value="UTF-8"/> | |||||
<property name="logging.maxHistory" value="15"/> | |||||
<property name="logging.totalSizeCap" value="5GB"/> | |||||
<property name="logging.maxFileSize" value="40MB"/> | |||||
<appender name="console" class="ch.qos.logback.core.ConsoleAppender"> | |||||
<encoder> | |||||
<pattern>${logging.pattern}</pattern> | |||||
<charset>${logging.charset}</charset> | |||||
</encoder> | |||||
</appender> | |||||
<appender name="LOG_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender"> | |||||
<File>${logging.path}/server/tuoheng_oidc_server.log</File> | |||||
<append>true</append> | |||||
<rollingPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedRollingPolicy"> | |||||
<fileNamePattern>${logging.path}/server/tuoheng_oidc_server-%d-%i.log</fileNamePattern> | |||||
<!-- 最大保存天数--> | |||||
<maxHistory>${logging.maxHistory}</maxHistory> | |||||
<totalSizeCap>${logging.totalSizeCap}</totalSizeCap> | |||||
<maxFileSize>${logging.maxFileSize}</maxFileSize> | |||||
</rollingPolicy> | |||||
<!--编码器--> | |||||
<encoder> | |||||
<pattern>${logging.pattern}</pattern> | |||||
<charset>${logging.charset}</charset> | |||||
</encoder> | |||||
</appender> | |||||
<appender name="file.async" class="ch.qos.logback.classic.AsyncAppender"> | |||||
<discardingThreshold>0</discardingThreshold> | |||||
<queueSize>512</queueSize> | |||||
<includeCallerData>true</includeCallerData> | |||||
<appender-ref ref="LOG_FILE" /> | |||||
</appender> | |||||
<logger name="com.tuoheng" level="DEBUG" additivity="false"> | |||||
<appender-ref ref="console" /> | |||||
<appender-ref ref="file.async" /> | |||||
</logger> | |||||
<!--log4jdbc --> | |||||
<logger name="jdbc.sqltiming" level="DEBUG" additivity="false"> | |||||
<appender-ref ref="file.async" /> | |||||
</logger> | |||||
<root level="INFO"> | |||||
<appender-ref ref="console" /> | |||||
<appender-ref ref="file.async" /> | |||||
</root> | |||||
</configuration> |
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||||
<mapper namespace="com.tuoheng.mapper.AuthoritiesMapper"> | |||||
<insert id="batchInsert" parameterType="java.util.List"> | |||||
insert into authorities (user_id, username, authority) | |||||
VALUES | |||||
<foreach collection ="list" item="it" separator =","> | |||||
(#{it.userId}, #{it.username}, #{it.authority}) | |||||
</foreach > | |||||
</insert> | |||||
</mapper> |
<?xml version="1.0" encoding="UTF-8"?> | |||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> | |||||
<mapper namespace="com.tuoheng.mapper.UserMapper"> | |||||
<resultMap type="com.tuoheng.model.dto.UserBaseInfoDto" id="UserBaseInfoMap"> | |||||
<id column="userId" jdbcType="INTEGER" property="userId" /> | |||||
<result column="userName" jdbcType="VARCHAR" property="userName" /> | |||||
<result column="password" jdbcType="VARCHAR" property="password" /> | |||||
<collection property="authorityList" ofType="java.lang.String" javaType="java.util.List"> | |||||
<result column="authority" jdbcType="VARCHAR"/> | |||||
</collection> | |||||
</resultMap> | |||||
<insert id="insertUser" parameterType="com.tuoheng.model.po.UserPo" keyProperty="id" useGeneratedKeys="true"> | |||||
insert into users (username, password) | |||||
values (#{username}, #{password}) | |||||
</insert> | |||||
<select id="getUserBaseInfo" resultMap="UserBaseInfoMap"> | |||||
select a.id as userId, a.username as userName, a.password , b.authority | |||||
from users a | |||||
inner join authorities b on a.id = b.user_id | |||||
where a.username = #{username} | |||||
</select> | |||||
<select id="getMpUserInfo" resultMap="UserBaseInfoMap"> | |||||
select a.id as userId, a.username as userName, b.authority | |||||
from users a | |||||
inner join authorities b on a.id = b.user_id | |||||
where a.username = #{username} | |||||
</select> | |||||
</mapper> |
<!DOCTYPE html> | |||||
<html lang="en"> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<title>登录页面</title> | |||||
</head> | |||||
<head> | |||||
<meta charset="UTF-8"> | |||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"> | |||||
<title>login</title> | |||||
<script src="../static/jquery-3.5.1.min.js"></script> | |||||
<style> | |||||
.login__back{ | |||||
position: fixed; | |||||
top: 0; | |||||
right: 0; | |||||
bottom: 0; | |||||
left: 0; | |||||
background: url('../static/back.png'); | |||||
background-position: center center; | |||||
background-repeat: no-repeat; | |||||
background-attachment: fixed; | |||||
background-size: cover; | |||||
} | |||||
.login__form{ | |||||
/* width: 460px; | |||||
height: 410px; */ | |||||
width: 410px; | |||||
height: 350px; | |||||
background: url('../static/form.png'); | |||||
background-position: center center; | |||||
background-repeat: no-repeat; | |||||
background-size: cover; | |||||
padding: 60px 30px; | |||||
color: rgba(255, 255, 255, 1); | |||||
position: relative; | |||||
left: 50%; | |||||
top: 50%; | |||||
transform: translate(-50%,-50%); | |||||
} | |||||
.login__form h2{ | |||||
font-size: 28px; | |||||
line-height: 25px; | |||||
text-align: center; | |||||
margin-bottom: 10px; | |||||
} | |||||
.login__form p{ | |||||
font-size: 12px; | |||||
text-align: center; | |||||
margin-bottom: 36px; | |||||
} | |||||
.login__form form{ | |||||
padding: 0 40px; | |||||
display: flex; | |||||
flex-direction: column; | |||||
background: transparent; | |||||
} | |||||
form input{ | |||||
height: 40px; | |||||
margin-bottom: 18px; | |||||
border-radius: 6px; | |||||
color: #FFFFFF; | |||||
padding: 0 16px; | |||||
border: 1px solid rgba(255, 255, 255, 0.5); | |||||
background: transparent; | |||||
} | |||||
form input:focus-within{ | |||||
outline: 0; | |||||
border: 1px solid #08EBFE; | |||||
} | |||||
button{ | |||||
height: 46px; | |||||
border: none; | |||||
border-radius: 6px; | |||||
background: linear-gradient(0deg, #08EBFE 0%, #28BAC1 100%); | |||||
margin-top: 20px; | |||||
color: #FFFFFF; | |||||
font-size: 16px; | |||||
} | |||||
.form__code{ | |||||
display: flex; | |||||
align-items: center; | |||||
justify-content: space-between; | |||||
margin-bottom: 18px; | |||||
} | |||||
.form__code input{ | |||||
width: calc(100% - 152px); | |||||
margin-bottom: 0; | |||||
} | |||||
.form__code img{ | |||||
width: 100px; | |||||
height: 42px; | |||||
cursor: pointer; | |||||
} | |||||
.login__form .form__tips{ | |||||
margin: 0; | |||||
height: 0; | |||||
position: relative; | |||||
top: -14px; | |||||
} | |||||
.form__tips.is--error{ | |||||
color: red | |||||
} | |||||
</style> | |||||
</head> | |||||
<body> | |||||
<div class="login__back"> | |||||
<div class="login__form"> | |||||
<h2>拓恒统一登录平台</h2> | |||||
<p>TUOHENG LOGIN PLATFORM</p> | |||||
<form th:action="@{/login}" method="post"> | |||||
<input name="username" placeholder="请输入用户名" type="text"/> | |||||
<input name="password" placeholder="请输入密码" type="password"/> | |||||
<div class="form__code"> | |||||
<input name="validateCode" placeholder="请输入验证码" /> | |||||
<input id="codekey" name="codekey" type="hidden"/> | |||||
<img class="code__img" src="" /> | |||||
</div> | |||||
<div class="form__tips is--error" th:if="${param.error}"> | |||||
用户名密码错误,请重新输入! | |||||
</div> | |||||
<div class="form__tips is--error" th:if="${param.validerror}"> | |||||
验证码错误,请重新输入! | |||||
</div> | |||||
<div class="form__tips is--error" th:if="${param.expirecode}"> | |||||
验证码已过期,请重新输入! | |||||
</div> | |||||
<button type="submit">登 录</button> | |||||
</form> | |||||
</div> | |||||
</div> | |||||
<!--绑定点击事件 --> | |||||
<script> | |||||
const imgDom = document.querySelector('.code__img') | |||||
imgDom.onclick = function() { | |||||
$.ajax({ | |||||
url : "/vercode",//后台请求的数据 | |||||
dataType: 'json', //数据格式 | |||||
type : "post",//请求方式 | |||||
async : true,//是否异步请求 | |||||
success : function(data) { //如果请求成功,返回数据。 | |||||
var tt = data.data; //第一个data代表json,第二个data代表json里的数组或对象 | |||||
$('.code__img').attr('src', tt.captcha); | |||||
$('#codekey').val(tt.codeKey); | |||||
}, | |||||
error : function (arg1) { | |||||
alert("加载数据失败"); | |||||
console.log(arg1); | |||||
} | |||||
}) | |||||
} | |||||
$(document).ready(function() { | |||||
$.ajax({ | |||||
url : "/vercode",//后台请求的数据 | |||||
dataType: 'json', //数据格式 | |||||
type : "post",//请求方式 | |||||
async : true,//是否异步请求 | |||||
success : function(data) { //如果请求成功,返回数据。 | |||||
var tt = data.data; //第一个data代表json,第二个data代表json里的数组或对象 | |||||
$('.code__img').attr('src', tt.captcha); | |||||
$('#codekey').val(tt.codeKey); | |||||
}, | |||||
error : function (arg1) { | |||||
alert("加载数据失败"); | |||||
console.log(arg1); | |||||
} | |||||
}) | |||||
}) | |||||
</script> | |||||
</body> | |||||
</html> |