├── .gitignore
├── .mvn
└── wrapper
│ ├── maven-wrapper.jar
│ └── maven-wrapper.properties
├── mvnw
├── mvnw.cmd
├── pom.xml
├── readme.md
└── src
├── main
├── java
│ └── com
│ │ └── gnu
│ │ └── AuthServer
│ │ ├── AuthServerApplication.java
│ │ ├── config
│ │ ├── AuthServerConfig.java
│ │ ├── AuthServerMethodSecurityConfig.java
│ │ └── AuthServerWebSecurityConfig.java
│ │ ├── controller
│ │ └── TestController.java
│ │ ├── entity
│ │ ├── AuthoritiesEntity.java
│ │ └── UserEntity.java
│ │ ├── filter
│ │ └── AuthInnerFilter.java
│ │ ├── method
│ │ ├── AuthServerMethodSecurityExpression.java
│ │ ├── AuthServerMethodSecurityExpressionHandler.java
│ │ └── AuthServerSecurityExpressionRoot.java
│ │ ├── repository
│ │ └── UserRepository.java
│ │ ├── service
│ │ ├── AuthClientDetailsService.java
│ │ ├── AuthTokenService.java
│ │ ├── AuthUserDetails.java
│ │ ├── AuthUserDetailsService.java
│ │ ├── TestService.java
│ │ └── TestServiceImpl.java
│ │ └── utils
│ │ ├── CustomChecker.java
│ │ └── GrantTypes.java
└── resources
│ ├── application.properties
│ └── security.properties
└── test
└── java
└── com
└── gnu
└── AuthServer
├── AuthServerApplicationTests.java
└── dto
└── AccessToken.java
/.gitignore:
--------------------------------------------------------------------------------
1 | target/
2 | !.mvn/wrapper/maven-wrapper.jar
3 |
4 | ### STS ###
5 | .apt_generated
6 | .classpath
7 | .factorypath
8 | .project
9 | .settings
10 | .springBeans
11 |
12 | ### IntelliJ IDEA ###
13 | .idea
14 | *.iws
15 | *.iml
16 | *.ipr
17 |
18 | ### NetBeans ###
19 | nbproject/private/
20 | build/
21 | nbbuild/
22 | dist/
23 | nbdist/
24 | .nb-gradle/
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/gnu-gnu/spring-boot-oauth-authserver/1203c58896a4692448ae3dd066db92294662519c/.mvn/wrapper/maven-wrapper.jar
--------------------------------------------------------------------------------
/.mvn/wrapper/maven-wrapper.properties:
--------------------------------------------------------------------------------
1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.2/apache-maven-3.5.2-bin.zip
2 |
--------------------------------------------------------------------------------
/mvnw:
--------------------------------------------------------------------------------
1 | #!/bin/sh
2 | # ----------------------------------------------------------------------------
3 | # Licensed to the Apache Software Foundation (ASF) under one
4 | # or more contributor license agreements. See the NOTICE file
5 | # distributed with this work for additional information
6 | # regarding copyright ownership. The ASF licenses this file
7 | # to you under the Apache License, Version 2.0 (the
8 | # "License"); you may not use this file except in compliance
9 | # with the License. You may obtain a copy of the License at
10 | #
11 | # http://www.apache.org/licenses/LICENSE-2.0
12 | #
13 | # Unless required by applicable law or agreed to in writing,
14 | # software distributed under the License is distributed on an
15 | # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16 | # KIND, either express or implied. See the License for the
17 | # specific language governing permissions and limitations
18 | # under the License.
19 | # ----------------------------------------------------------------------------
20 |
21 | # ----------------------------------------------------------------------------
22 | # Maven2 Start Up Batch script
23 | #
24 | # Required ENV vars:
25 | # ------------------
26 | # JAVA_HOME - location of a JDK home dir
27 | #
28 | # Optional ENV vars
29 | # -----------------
30 | # M2_HOME - location of maven2's installed home dir
31 | # MAVEN_OPTS - parameters passed to the Java VM when running Maven
32 | # e.g. to debug Maven itself, use
33 | # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
34 | # MAVEN_SKIP_RC - flag to disable loading of mavenrc files
35 | # ----------------------------------------------------------------------------
36 |
37 | if [ -z "$MAVEN_SKIP_RC" ] ; then
38 |
39 | if [ -f /etc/mavenrc ] ; then
40 | . /etc/mavenrc
41 | fi
42 |
43 | if [ -f "$HOME/.mavenrc" ] ; then
44 | . "$HOME/.mavenrc"
45 | fi
46 |
47 | fi
48 |
49 | # OS specific support. $var _must_ be set to either true or false.
50 | cygwin=false;
51 | darwin=false;
52 | mingw=false
53 | case "`uname`" in
54 | CYGWIN*) cygwin=true ;;
55 | MINGW*) mingw=true;;
56 | Darwin*) darwin=true
57 | # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
58 | # See https://developer.apple.com/library/mac/qa/qa1170/_index.html
59 | if [ -z "$JAVA_HOME" ]; then
60 | if [ -x "/usr/libexec/java_home" ]; then
61 | export JAVA_HOME="`/usr/libexec/java_home`"
62 | else
63 | export JAVA_HOME="/Library/Java/Home"
64 | fi
65 | fi
66 | ;;
67 | esac
68 |
69 | if [ -z "$JAVA_HOME" ] ; then
70 | if [ -r /etc/gentoo-release ] ; then
71 | JAVA_HOME=`java-config --jre-home`
72 | fi
73 | fi
74 |
75 | if [ -z "$M2_HOME" ] ; then
76 | ## resolve links - $0 may be a link to maven's home
77 | PRG="$0"
78 |
79 | # need this for relative symlinks
80 | while [ -h "$PRG" ] ; do
81 | ls=`ls -ld "$PRG"`
82 | link=`expr "$ls" : '.*-> \(.*\)$'`
83 | if expr "$link" : '/.*' > /dev/null; then
84 | PRG="$link"
85 | else
86 | PRG="`dirname "$PRG"`/$link"
87 | fi
88 | done
89 |
90 | saveddir=`pwd`
91 |
92 | M2_HOME=`dirname "$PRG"`/..
93 |
94 | # make it fully qualified
95 | M2_HOME=`cd "$M2_HOME" && pwd`
96 |
97 | cd "$saveddir"
98 | # echo Using m2 at $M2_HOME
99 | fi
100 |
101 | # For Cygwin, ensure paths are in UNIX format before anything is touched
102 | if $cygwin ; then
103 | [ -n "$M2_HOME" ] &&
104 | M2_HOME=`cygpath --unix "$M2_HOME"`
105 | [ -n "$JAVA_HOME" ] &&
106 | JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
107 | [ -n "$CLASSPATH" ] &&
108 | CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
109 | fi
110 |
111 | # For Migwn, ensure paths are in UNIX format before anything is touched
112 | if $mingw ; then
113 | [ -n "$M2_HOME" ] &&
114 | M2_HOME="`(cd "$M2_HOME"; pwd)`"
115 | [ -n "$JAVA_HOME" ] &&
116 | JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
117 | # TODO classpath?
118 | fi
119 |
120 | if [ -z "$JAVA_HOME" ]; then
121 | javaExecutable="`which javac`"
122 | if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
123 | # readlink(1) is not available as standard on Solaris 10.
124 | readLink=`which readlink`
125 | if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
126 | if $darwin ; then
127 | javaHome="`dirname \"$javaExecutable\"`"
128 | javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
129 | else
130 | javaExecutable="`readlink -f \"$javaExecutable\"`"
131 | fi
132 | javaHome="`dirname \"$javaExecutable\"`"
133 | javaHome=`expr "$javaHome" : '\(.*\)/bin'`
134 | JAVA_HOME="$javaHome"
135 | export JAVA_HOME
136 | fi
137 | fi
138 | fi
139 |
140 | if [ -z "$JAVACMD" ] ; then
141 | if [ -n "$JAVA_HOME" ] ; then
142 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
143 | # IBM's JDK on AIX uses strange locations for the executables
144 | JAVACMD="$JAVA_HOME/jre/sh/java"
145 | else
146 | JAVACMD="$JAVA_HOME/bin/java"
147 | fi
148 | else
149 | JAVACMD="`which java`"
150 | fi
151 | fi
152 |
153 | if [ ! -x "$JAVACMD" ] ; then
154 | echo "Error: JAVA_HOME is not defined correctly." >&2
155 | echo " We cannot execute $JAVACMD" >&2
156 | exit 1
157 | fi
158 |
159 | if [ -z "$JAVA_HOME" ] ; then
160 | echo "Warning: JAVA_HOME environment variable is not set."
161 | fi
162 |
163 | CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
164 |
165 | # traverses directory structure from process work directory to filesystem root
166 | # first directory with .mvn subdirectory is considered project base directory
167 | find_maven_basedir() {
168 |
169 | if [ -z "$1" ]
170 | then
171 | echo "Path not specified to find_maven_basedir"
172 | return 1
173 | fi
174 |
175 | basedir="$1"
176 | wdir="$1"
177 | while [ "$wdir" != '/' ] ; do
178 | if [ -d "$wdir"/.mvn ] ; then
179 | basedir=$wdir
180 | break
181 | fi
182 | # workaround for JBEAP-8937 (on Solaris 10/Sparc)
183 | if [ -d "${wdir}" ]; then
184 | wdir=`cd "$wdir/.."; pwd`
185 | fi
186 | # end of workaround
187 | done
188 | echo "${basedir}"
189 | }
190 |
191 | # concatenates all lines of a file
192 | concat_lines() {
193 | if [ -f "$1" ]; then
194 | echo "$(tr -s '\n' ' ' < "$1")"
195 | fi
196 | }
197 |
198 | BASE_DIR=`find_maven_basedir "$(pwd)"`
199 | if [ -z "$BASE_DIR" ]; then
200 | exit 1;
201 | fi
202 |
203 | export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
204 | echo $MAVEN_PROJECTBASEDIR
205 | MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
206 |
207 | # For Cygwin, switch paths to Windows format before running java
208 | if $cygwin; then
209 | [ -n "$M2_HOME" ] &&
210 | M2_HOME=`cygpath --path --windows "$M2_HOME"`
211 | [ -n "$JAVA_HOME" ] &&
212 | JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
213 | [ -n "$CLASSPATH" ] &&
214 | CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
215 | [ -n "$MAVEN_PROJECTBASEDIR" ] &&
216 | MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
217 | fi
218 |
219 | WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
220 |
221 | exec "$JAVACMD" \
222 | $MAVEN_OPTS \
223 | -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
224 | "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
225 | ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
226 |
--------------------------------------------------------------------------------
/mvnw.cmd:
--------------------------------------------------------------------------------
1 | @REM ----------------------------------------------------------------------------
2 | @REM Licensed to the Apache Software Foundation (ASF) under one
3 | @REM or more contributor license agreements. See the NOTICE file
4 | @REM distributed with this work for additional information
5 | @REM regarding copyright ownership. The ASF licenses this file
6 | @REM to you under the Apache License, Version 2.0 (the
7 | @REM "License"); you may not use this file except in compliance
8 | @REM with the License. You may obtain a copy of the License at
9 | @REM
10 | @REM http://www.apache.org/licenses/LICENSE-2.0
11 | @REM
12 | @REM Unless required by applicable law or agreed to in writing,
13 | @REM software distributed under the License is distributed on an
14 | @REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 | @REM KIND, either express or implied. See the License for the
16 | @REM specific language governing permissions and limitations
17 | @REM under the License.
18 | @REM ----------------------------------------------------------------------------
19 |
20 | @REM ----------------------------------------------------------------------------
21 | @REM Maven2 Start Up Batch script
22 | @REM
23 | @REM Required ENV vars:
24 | @REM JAVA_HOME - location of a JDK home dir
25 | @REM
26 | @REM Optional ENV vars
27 | @REM M2_HOME - location of maven2's installed home dir
28 | @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
29 | @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
30 | @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
31 | @REM e.g. to debug Maven itself, use
32 | @REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
33 | @REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
34 | @REM ----------------------------------------------------------------------------
35 |
36 | @REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
37 | @echo off
38 | @REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
39 | @if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
40 |
41 | @REM set %HOME% to equivalent of $HOME
42 | if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
43 |
44 | @REM Execute a user defined script before this one
45 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
46 | @REM check for pre script, once with legacy .bat ending and once with .cmd ending
47 | if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
48 | if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
49 | :skipRcPre
50 |
51 | @setlocal
52 |
53 | set ERROR_CODE=0
54 |
55 | @REM To isolate internal variables from possible post scripts, we use another setlocal
56 | @setlocal
57 |
58 | @REM ==== START VALIDATION ====
59 | if not "%JAVA_HOME%" == "" goto OkJHome
60 |
61 | echo.
62 | echo Error: JAVA_HOME not found in your environment. >&2
63 | echo Please set the JAVA_HOME variable in your environment to match the >&2
64 | echo location of your Java installation. >&2
65 | echo.
66 | goto error
67 |
68 | :OkJHome
69 | if exist "%JAVA_HOME%\bin\java.exe" goto init
70 |
71 | echo.
72 | echo Error: JAVA_HOME is set to an invalid directory. >&2
73 | echo JAVA_HOME = "%JAVA_HOME%" >&2
74 | echo Please set the JAVA_HOME variable in your environment to match the >&2
75 | echo location of your Java installation. >&2
76 | echo.
77 | goto error
78 |
79 | @REM ==== END VALIDATION ====
80 |
81 | :init
82 |
83 | @REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
84 | @REM Fallback to current working directory if not found.
85 |
86 | set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
87 | IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
88 |
89 | set EXEC_DIR=%CD%
90 | set WDIR=%EXEC_DIR%
91 | :findBaseDir
92 | IF EXIST "%WDIR%"\.mvn goto baseDirFound
93 | cd ..
94 | IF "%WDIR%"=="%CD%" goto baseDirNotFound
95 | set WDIR=%CD%
96 | goto findBaseDir
97 |
98 | :baseDirFound
99 | set MAVEN_PROJECTBASEDIR=%WDIR%
100 | cd "%EXEC_DIR%"
101 | goto endDetectBaseDir
102 |
103 | :baseDirNotFound
104 | set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
105 | cd "%EXEC_DIR%"
106 |
107 | :endDetectBaseDir
108 |
109 | IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
110 |
111 | @setlocal EnableExtensions EnableDelayedExpansion
112 | for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
113 | @endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
114 |
115 | :endReadAdditionalConfig
116 |
117 | SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
118 |
119 | set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
120 | set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
121 |
122 | %MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
123 | if ERRORLEVEL 1 goto error
124 | goto end
125 |
126 | :error
127 | set ERROR_CODE=1
128 |
129 | :end
130 | @endlocal & set ERROR_CODE=%ERROR_CODE%
131 |
132 | if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
133 | @REM check for post script, once with legacy .bat ending and once with .cmd ending
134 | if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
135 | if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
136 | :skipRcPost
137 |
138 | @REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
139 | if "%MAVEN_BATCH_PAUSE%" == "on" pause
140 |
141 | if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
142 |
143 | exit /B %ERROR_CODE%
144 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 4.0.0
6 |
7 | com.gnu
8 | AuthServer
9 | 0.0.1-SNAPSHOT
10 | jar
11 |
12 | AuthServer
13 | Demo project for Spring Boot
14 |
15 |
16 | org.springframework.boot
17 | spring-boot-starter-parent
18 | 1.5.10.RELEASE
19 |
20 |
21 |
22 |
23 | UTF-8
24 | UTF-8
25 | 1.8
26 |
27 |
28 |
29 |
30 | org.springframework.boot
31 | spring-boot-starter-actuator
32 |
33 |
34 | org.springframework.boot
35 | spring-boot-starter-aop
36 |
37 |
38 | org.springframework.boot
39 | spring-boot-starter-security
40 |
41 |
42 | org.springframework.security.oauth
43 | spring-security-oauth2
44 |
45 |
46 | org.springframework.boot
47 | spring-boot-starter-thymeleaf
48 |
49 |
50 | org.springframework.boot
51 | spring-boot-starter-web
52 |
53 |
54 | org.springframework.boot
55 | spring-boot-devtools
56 | runtime
57 |
58 |
59 | org.springframework.boot
60 | spring-boot-starter-data-jpa
61 |
62 |
63 | org.hsqldb
64 | hsqldb
65 | runtime
66 |
67 |
68 | org.springframework.boot
69 | spring-boot-starter-data-redis
70 |
71 |
72 | org.springframework.boot
73 | spring-boot-starter-test
74 | test
75 |
76 |
77 | org.springframework.security
78 | spring-security-test
79 | test
80 |
81 |
82 |
83 |
84 |
85 |
86 | org.springframework.boot
87 | spring-boot-maven-plugin
88 |
89 |
90 | org.apache.maven.plugins
91 | maven-surefire-plugin
92 |
93 | ${argLine}
94 |
95 |
96 |
97 | org.jacoco
98 | jacoco-maven-plugin
99 | 0.8.1
100 |
101 | ${project.build.directory}/jacoco.exec
102 | ${project.build.directory}/jacoco-output
103 | **/*entity.*
104 |
105 |
106 |
107 |
108 | prepare-agent
109 |
110 |
111 |
112 | report
113 | prepare-package
114 |
115 | report
116 |
117 |
118 |
119 | post-unit-test
120 | test
121 |
122 | report
123 |
124 |
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
--------------------------------------------------------------------------------
/readme.md:
--------------------------------------------------------------------------------
1 | # Spring boot OAuth Authorization server example
2 |
3 | 1. authorization code, refresh token, password grant에 대해서 token 발급 기능이 구현되어 있습니다.
4 | 1. request token 요청 주소는 http://localhost:9099/apps/oauth/authorize 이며 access token 요청 주소는 http://localhost:9099/apps/oauth/token 입니다.
5 | 1. redirect_uri는 AuthClientDetailsService.java 를 참조하여 수정 가능합니다. POSTMAN을 통한 OAUTH 테스트를 권장합니다.
6 | 1. 각 사용자별로 추가 정보가 다르게 설정되어 있습니다. 엔드포인트의 인증을 위해 사용할 username과 password는 gnu/pass, noh/pass, jee/pass 입니다.
7 | 1. 전체 flow에 대한 단위테스트는 /test/java/AuthServerApplicationTests.java 에 구현되어 있습니다.
8 | 1. 기본적으로 in-memory store를 사용하도록 되어 있으나, VM args에 -Dspring.profiles.active=redis 를 넣어서 redis store를 사용할 수 있습니다. 접속 정보는 application.properties에 작성되어 있습니다.
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/AuthServerApplication.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer;
2 |
3 | import javax.annotation.Resource;
4 |
5 | import org.slf4j.Logger;
6 | import org.slf4j.LoggerFactory;
7 | import org.springframework.beans.factory.annotation.Autowired;
8 | import org.springframework.boot.CommandLineRunner;
9 | import org.springframework.boot.SpringApplication;
10 | import org.springframework.boot.autoconfigure.SpringBootApplication;
11 | import org.springframework.context.annotation.Bean;
12 | import org.springframework.context.annotation.Profile;
13 | import org.springframework.data.redis.connection.RedisConnectionFactory;
14 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
15 | import org.springframework.security.oauth2.provider.token.TokenStore;
16 | import org.springframework.security.oauth2.provider.token.store.InMemoryTokenStore;
17 | import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;
18 | import org.springframework.security.web.FilterChainProxy;
19 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
20 |
21 | import com.gnu.AuthServer.entity.UserEntity;
22 | import com.gnu.AuthServer.repository.UserRepository;
23 |
24 | @SpringBootApplication
25 | public class AuthServerApplication extends WebMvcConfigurerAdapter implements CommandLineRunner{
26 | Logger logger = LoggerFactory.getLogger(AuthServerApplication.class);
27 | @Autowired
28 | UserRepository userRepository;
29 | @Resource(name="springSecurityFilterChain")
30 | FilterChainProxy springSecurityFilterChain;
31 |
32 | public static void main(String[] args) {
33 | SpringApplication.run(AuthServerApplication.class, args);
34 | }
35 |
36 | @Bean
37 | @Profile("local") // 기본 active profile 은 application.properties에 local로 설정되어 있다.
38 | public TokenStore InMemoryTokenStore(){
39 | logger.info("in-memory token store");
40 | return new InMemoryTokenStore();
41 | }
42 |
43 | @Bean
44 | @Profile("redis") // redis token store 설정을 위해서는 -Dspring.profiles.active=redis 가 필요
45 | public TokenStore tokenStore(RedisConnectionFactory factory){
46 | logger.info("redis token store");
47 | return new RedisTokenStore(factory);
48 | }
49 |
50 |
51 | /**
52 | * 초기 ID/PW 데이터 설정J
53 | */
54 | @Override
55 | public void run(String... arg0) throws Exception {
56 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
57 | UserEntity userShim = new UserEntity("gnu", encoder.encode("pass"));
58 | userShim.createAuthority("read");
59 | userShim.createAuthority("write");
60 | userShim.createAuthority("delete");
61 | userShim.createAuthority("update");
62 | userShim.setLocale("ko");
63 | userRepository.save(userShim);
64 | UserEntity userNoh = new UserEntity("noh", encoder.encode("pass"));
65 | userNoh.createAuthority("read");
66 | userShim.setLocale("jp");
67 | userRepository.save(userNoh);
68 | UserEntity userJee = new UserEntity("jee", encoder.encode("pass"));
69 | userNoh.createAuthority("read");
70 | userNoh.createAuthority("write");
71 | userNoh.createAuthority("update");
72 | userShim.setLocale("en");
73 | userRepository.save(userJee);
74 | springSecurityFilterChain.getFilterChains().forEach(value->{
75 | value.getFilters().forEach(filter->logger.info(filter.toString()));
76 | });
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/config/AuthServerConfig.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.config;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.http.HttpMethod;
8 | import org.springframework.security.authentication.AuthenticationManager;
9 | import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
10 | import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
11 | import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
12 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
13 | import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
14 | import org.springframework.security.oauth2.provider.token.TokenStore;
15 |
16 | import com.gnu.AuthServer.filter.AuthInnerFilter;
17 | import com.gnu.AuthServer.service.AuthClientDetailsService;
18 | import com.gnu.AuthServer.service.AuthTokenService;
19 |
20 | /**
21 | *
22 | * OAuth 인가 서버
23 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
24 | *
25 | */
26 | @Configuration
27 | @EnableAuthorizationServer
28 | public class AuthServerConfig extends AuthorizationServerConfigurerAdapter {
29 | Logger logger = LoggerFactory.getLogger(AuthServerConfig.class);
30 | @Autowired
31 | private AuthenticationManager authenticationManager;
32 | @Autowired
33 | AuthClientDetailsService clientDetailsService;
34 | @Autowired
35 | AuthTokenService tokenService;
36 | @Autowired
37 | TokenStore tokenStore;
38 |
39 |
40 | /**
41 | * endpoint에 대한 설정을 담당하는 메소드
42 | * 기본 endpoint
43 | * 1) ~~/authorize -> request token을 받는다. 나중에 access token 발급에 쓰일 수 있다. 이 단계에서는 httpBasic의 인증에 설정 해 놓은 id, pw를 basic auth로 사용한다
44 | * 2) ~~/token_access -> protected resources에 엑세스하기 위한 access token을 발급한다. 이 단계에서는 client id, secret을 basic auth에 사용한다 (secret 생략 가능)
45 | */
46 | @Override
47 | public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
48 | endpoints.allowedTokenEndpointRequestMethods(HttpMethod.POST, HttpMethod.OPTIONS);
49 | endpoints.authenticationManager(authenticationManager);
50 | endpoints.tokenServices(tokenService);
51 | /*
52 | * Approvalstore 객체를 선언하여 설정하거나, 직접 구현할 경우 허용에 관한 사항을 관리할 수 있다.
53 | * 아래의 주석처리된 내용이 없으며, auto approval 에 관한 사항이 없을 경우 로그인 후 매번 scope에 대한 approval을 요청한다
54 | * 기본 TokenApprovalStore를 사용하여도 Form 로그인 이후 매번 scope approval해야 하는 수고를 줄일 수 있다.
55 | * 현재 프로젝트는 application.properties에 auto approval 할 scopes를 관리하고 있음
56 | *
57 | TokenApprovalStore approvalStore = new TokenApprovalStore();
58 | approvalStore.setTokenStore(tokenStore);
59 | endpoints.approvalStore(approvalStore);
60 | */
61 | }
62 | /**
63 | * 보안에 관련된 설정
64 | * 권한, 접근제어등은 여기서 설정한다.
65 | *
66 | * 보안이 요구되는 endpoints (기본은 denyAll() 이므로 적절히 고쳐서 사용한다)
67 | * 1) ~~/check_token (resource server가 rest로 token의 검증을 요청할 때 사용하는 endpoint, checkTokenAcess 로 조절)
68 | * 2) ~~/token_key (JWT 사용시, 토큰 검증을 위한 공개키를 노출하는 endpoint, tokenKeyAccess로 조절)
69 | */
70 | @Override
71 | public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
72 | security.addTokenEndpointAuthenticationFilter(new AuthInnerFilter()); // ~~/authorize 에 대한 필터
73 | security.allowFormAuthenticationForClients(); // 기본적으로 HTTP HEADER AUTH가 적용되므로, FORM 전송으로 AUTH하기 위해서 적용
74 | security.checkTokenAccess("permitAll()"); // ~~/check_token으로 remoteTokenService가 토큰의 해석을 의뢰할 경우, 해당 endpoint의 권한 설정(기본 denyAll())
75 | security.accessDeniedHandler((request, response, exception) -> logger.error(exception.getMessage()));
76 | }
77 | /**
78 | * OAuth서버에 접근을 요청하는 Client에 관한 설정을 관리하기 위한 Configure
79 | * inMemory 나 jdbc 기반 Builder를 지원하므로 그것을 활용해도 되지만 별도의 Service를 구현
80 | */
81 | @Override
82 | public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
83 | clients.withClientDetails(clientDetailsService);
84 | }
85 | }
86 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/config/AuthServerMethodSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.config;
2 |
3 | import org.springframework.context.annotation.AdviceMode;
4 | import org.springframework.context.annotation.Configuration;
5 | import org.springframework.security.access.expression.method.MethodSecurityExpressionHandler;
6 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
7 | import org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration;
8 |
9 | import com.gnu.AuthServer.method.AuthServerMethodSecurityExpressionHandler;
10 |
11 | @Configuration
12 | @EnableGlobalMethodSecurity(prePostEnabled=true,mode=AdviceMode.PROXY,proxyTargetClass=false)
13 | public class AuthServerMethodSecurityConfig extends GlobalMethodSecurityConfiguration {
14 | /**
15 | * 아래 부분을 주석처리하면 기본 핸들러를 사용하고, Bean 클래스의 메소드 표현식을 사용할 수 있다
16 | */
17 | @Override
18 | protected MethodSecurityExpressionHandler createExpressionHandler() {
19 | return new AuthServerMethodSecurityExpressionHandler();
20 | }
21 |
22 | }
23 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/config/AuthServerWebSecurityConfig.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.config;
2 |
3 | import java.util.ArrayList;
4 | import java.util.Arrays;
5 | import java.util.HashMap;
6 | import java.util.List;
7 | import java.util.Map;
8 | import java.util.Set;
9 |
10 | import org.slf4j.Logger;
11 | import org.slf4j.LoggerFactory;
12 | import org.slf4j.Marker;
13 | import org.slf4j.MarkerFactory;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.beans.factory.annotation.Value;
16 | import org.springframework.context.annotation.Bean;
17 | import org.springframework.context.annotation.Configuration;
18 | import org.springframework.context.annotation.PropertySource;
19 | import org.springframework.security.authentication.AuthenticationManager;
20 | import org.springframework.security.authentication.AuthenticationProvider;
21 | import org.springframework.security.authentication.BadCredentialsException;
22 | import org.springframework.security.authentication.ProviderManager;
23 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
24 | import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
25 | import org.springframework.security.config.annotation.web.builders.HttpSecurity;
26 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
27 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
28 | import org.springframework.security.config.http.SessionCreationPolicy;
29 | import org.springframework.security.core.Authentication;
30 | import org.springframework.security.core.AuthenticationException;
31 | import org.springframework.security.core.GrantedAuthority;
32 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
33 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
34 | import org.springframework.security.oauth2.provider.authentication.OAuth2AuthenticationManager;
35 | import org.springframework.util.StringUtils;
36 | import org.springframework.web.cors.CorsConfiguration;
37 | import org.springframework.web.cors.CorsConfigurationSource;
38 | import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
39 |
40 | import com.gnu.AuthServer.repository.UserRepository;
41 | import com.gnu.AuthServer.service.AuthUserDetails;
42 | import com.gnu.AuthServer.service.AuthUserDetailsService;
43 | /**
44 | *
45 | * 기본적인 Web Security를 설정하는 Bean
46 | * 웹 프로젝트의 Spring security 설정과 크게 다르지 않다.
47 | * Password Grant 기반의 인증을 수행할 경우 ClientDetailsService를 거치지 않고 이 서비스의 AuthenticationManager만으로 인증 과정이 수행되고 Token이 발급된다
48 | *
49 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
50 | *
51 | */
52 | @Configuration
53 | @EnableWebSecurity
54 | @PropertySource("security.properties")
55 | public class AuthServerWebSecurityConfig extends WebSecurityConfigurerAdapter {
56 | Logger logger = LoggerFactory.getLogger(AuthServerWebSecurityConfig.class);
57 | final Marker REQUEST_MARKER = MarkerFactory.getMarker("HTTP_REQUEST");
58 | @Autowired
59 | UserRepository userRepository;
60 | @Autowired
61 | AuthUserDetailsService userDetailsService;
62 | @Value("${securities.permitall}")
63 | private String permitAll;
64 |
65 |
66 | BCryptPasswordEncoder encoder = new BCryptPasswordEncoder();
67 |
68 | @Override
69 | @Bean
70 | protected AuthenticationManager authenticationManager() throws Exception {
71 |
72 | AuthenticationProvider provider = new DaoAuthenticationProvider() {
73 | @Override
74 | public Authentication authenticate(Authentication authentication) throws AuthenticationException {
75 | logger.info(authentication.getName() + "... auth start");
76 | String username = authentication.getName();
77 | CharSequence password = (CharSequence) authentication.getCredentials();
78 | Map details = new HashMap<>();
79 | AuthUserDetails user = (AuthUserDetails) userDetailsService.loadUserByUsername(username);
80 | @SuppressWarnings("unchecked")
81 | Set autho = (Set) user.getAuthorities();
82 | autho.add(new SimpleGrantedAuthority("ACTUATOR"));
83 | if (!encoder.matches(password, user.getPassword())) {
84 | logger.info(authentication.getName() + "... bad credential");
85 | throw new BadCredentialsException("bad credential");
86 | } else {
87 | UsernamePasswordAuthenticationToken token = new UsernamePasswordAuthenticationToken(user.getUsername(), password.toString(),autho);
88 | details.put("uname", user.getEntity().getUsername());
89 | details.put("uid", user.getEntity().getId());
90 | details.put("locale", user.getEntity().getLocale());
91 | token.setDetails(details);
92 | return token;
93 | }
94 | }
95 |
96 | @Override
97 | public boolean supports(Class> authentication) {
98 | return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
99 | }
100 | };
101 | List providers = new ArrayList();
102 | providers.add(provider);
103 | return new ProviderManager(providers, new OAuth2AuthenticationManager());
104 | }
105 |
106 | @Override
107 | protected void configure(HttpSecurity http) throws Exception {
108 | http.authorizeRequests().antMatchers(StringUtils.commaDelimitedListToStringArray(permitAll)).permitAll().anyRequest().authenticated().and().formLogin().and()
109 | .csrf().disable();
110 | http.headers().frameOptions().disable(); // UI redressing attack을 방지하기 위해 X-Frame-Options를 검증하는 부분, 편의상 disable
111 | http.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.NEVER);
112 | http.cors();
113 | }
114 |
115 | @Bean
116 | public CorsConfigurationSource corsConfigurationSource() {
117 | CorsConfiguration configuration = new CorsConfiguration();
118 | configuration.setAllowedOrigins(Arrays.asList("*"));
119 | configuration.setAllowedMethods(Arrays.asList("GET","POST"));
120 | UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
121 | source.registerCorsConfiguration("/**", configuration);
122 | return source;
123 | }
124 |
125 | }
126 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/controller/TestController.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.controller;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.data.repository.query.Param;
7 | import org.springframework.security.access.method.P;
8 | import org.springframework.security.access.prepost.PreAuthorize;
9 | import org.springframework.security.core.Authentication;
10 | import org.springframework.web.bind.annotation.RequestMapping;
11 | import org.springframework.web.bind.annotation.RequestParam;
12 | import org.springframework.web.bind.annotation.ResponseBody;
13 | import org.springframework.web.bind.annotation.RestController;
14 |
15 | import com.gnu.AuthServer.service.TestService;
16 |
17 | @RestController
18 | public class TestController {
19 | @Autowired
20 | TestService service;
21 | Logger logger = LoggerFactory.getLogger(TestController.class);
22 | /**
23 | * AuthServerMethodSecurityConfig의 createExpressionHandler 메소드 override를 주석처리하고 기본 Handler를 타게 한 후
24 | * /open에 접속 해 보면 bean의 메소드 기반으로 타는 것을 확인 가능
25 | * @return
26 | */
27 | @RequestMapping("/open")
28 | @PreAuthorize("@customChecker.isChecked(#auth)")
29 | public @ResponseBody String open(Authentication auth) {
30 | logger.info("/open is PermitAll");
31 | return "open";
32 | }
33 | /**
34 | * 이 endpoint는 websecurity에서 permitAll로 오픈되어 있지만, method security가 적용된 메소드
35 | * 현재 isOk(boolean bool)는 bool= 값으로 들어온 true/false 에 따라 인증 성공 / 실패를 보여준다.
36 | * @return 인증이 성공할 경우 hello? 라는 문자열 출력
37 | */
38 | @RequestMapping("/isok")
39 | public @ResponseBody String isok(boolean bool) {
40 | return service.hello(bool);
41 | }
42 | /**
43 | * web security에서 인증을 요구하는 메소드
44 | * @param auth
45 | * @return
46 | */
47 | @RequestMapping("/test")
48 | public @ResponseBody String test(Authentication auth) {
49 | return "hello?";
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/entity/AuthoritiesEntity.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.entity;
2 |
3 | import javax.persistence.CascadeType;
4 | import javax.persistence.Column;
5 | import javax.persistence.Entity;
6 | import javax.persistence.FetchType;
7 | import javax.persistence.GeneratedValue;
8 | import javax.persistence.GenerationType;
9 | import javax.persistence.Id;
10 | import javax.persistence.JoinColumn;
11 | import javax.persistence.ManyToOne;
12 |
13 | @Entity
14 | public class AuthoritiesEntity {
15 | @Id
16 | @GeneratedValue(strategy=GenerationType.AUTO)
17 | @Column(name="id")
18 | private Long id;
19 | @Column(name="role_name", nullable=false)
20 | private String roleName;
21 | /**
22 | * 회원(1) -> 권한(N) 및 권한(N) -> 회원(1)의 양방향 관계가 성립함을 보임
23 | * FetchType -> Lazy로딩 여부 결정
24 | * Cascade는 자세한 정보는 인터넷 참고
25 | *
26 | */
27 | @ManyToOne(fetch=FetchType.EAGER,cascade=CascadeType.ALL)
28 | @JoinColumn(name="user_seq")
29 | private UserEntity userEntity;
30 |
31 | public AuthoritiesEntity() {
32 | }
33 |
34 | public AuthoritiesEntity(String roleName, UserEntity userEntity) {
35 | setRoleName(roleName);
36 | setUserEntity(userEntity);
37 | }
38 |
39 | public Long getId() {
40 | return id;
41 | }
42 |
43 | public void setId(Long id) {
44 | this.id = id;
45 | }
46 |
47 | public String getRoleName() {
48 | return roleName;
49 | }
50 |
51 | public void setRoleName(String roleName) {
52 | this.roleName = roleName;
53 | }
54 |
55 | public UserEntity getUserEntity() {
56 | return userEntity;
57 | }
58 |
59 | public void setUserEntity(UserEntity userEntity) {
60 | this.userEntity = userEntity;
61 | }
62 |
63 | @Override
64 | public String toString() {
65 | return "AuthoritiesEntity [id=" + id + ", roleName=" + roleName+"]";
66 | }
67 |
68 |
69 |
70 |
71 |
72 | }
73 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/entity/UserEntity.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.entity;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 |
6 | import javax.persistence.CascadeType;
7 | import javax.persistence.Column;
8 | import javax.persistence.Entity;
9 | import javax.persistence.FetchType;
10 | import javax.persistence.GeneratedValue;
11 | import javax.persistence.GenerationType;
12 | import javax.persistence.Id;
13 | import javax.persistence.OneToMany;
14 |
15 | /**
16 | *
17 | * 회원정보 엔티티
18 | * 하이버네이트는 일반적으로 복합키를 허용하지 않으므로 Join은 tuple의 pk를 sequence로 하고, 외래키로 삼는다
19 | * (개선을 위해 @IdClass 나 @EmbeddedId 등을 참고 중)
20 | *
21 | * @author Geunwoo Shim(gflhsin@gmail.com)
22 | *
23 | */
24 | @Entity
25 | public class UserEntity {
26 | @Id
27 | @GeneratedValue(strategy=GenerationType.AUTO)
28 | private Long id;
29 |
30 | @Column(name="user_name",unique=true)
31 | private String username;
32 | @Column(name="password")
33 | private String password;
34 | @Column(name="locale")
35 | private String locale;
36 | @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="userEntity") // userEntity를 참고한다는 것을 나타냄. mappedBy는 Owner를 결정
37 | private List authorities = new ArrayList();
38 |
39 | /**
40 | *
41 | * Join에 필요한 객체는 외부의 Helper 함수를 작성하는 것이 좋음
42 | *
43 | * @param roles
44 | * @return
45 | */
46 | public boolean createAuthority(String... roles){
47 | boolean bool = true;
48 | if(null == authorities){
49 | authorities = new ArrayList();
50 | }
51 | for(String role : roles){
52 | bool &= authorities.add(new AuthoritiesEntity(role, this));
53 | }
54 | return bool;
55 | }
56 |
57 | public UserEntity() {
58 | }
59 |
60 |
61 |
62 | public UserEntity(String username, String password) {
63 | this.username = username;
64 | this.password = password;
65 | }
66 |
67 | public Long getId() {
68 | return id;
69 | }
70 |
71 | public void setId(Long id) {
72 | this.id = id;
73 | }
74 |
75 | public String getUsername() {
76 | return username;
77 | }
78 |
79 | public void setUsername(String username) {
80 | this.username = username;
81 | }
82 |
83 | public String getPassword() {
84 | return password;
85 | }
86 |
87 | public void setPassword(String password) {
88 | this.password = password;
89 | }
90 |
91 | public List getAuthorities() {
92 | return authorities;
93 | }
94 |
95 | public void setAuthorities(List authorities) {
96 | this.authorities = authorities;
97 | }
98 |
99 | public String getLocale() {
100 | return locale;
101 | }
102 |
103 | public void setLocale(String locale) {
104 | this.locale = locale;
105 | }
106 |
107 | @Override
108 | public String toString() {
109 | return "UserEntity [id=" + id + ", username=" + username + ", password=" + password + ", locale=" + locale
110 | + ", authorities=" + authorities + "]";
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/filter/AuthInnerFilter.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.filter;
2 |
3 | import java.io.IOException;
4 | import java.util.Base64;
5 | import java.util.Enumeration;
6 |
7 | import javax.servlet.Filter;
8 | import javax.servlet.FilterChain;
9 | import javax.servlet.FilterConfig;
10 | import javax.servlet.ServletException;
11 | import javax.servlet.ServletRequest;
12 | import javax.servlet.ServletResponse;
13 | import javax.servlet.http.HttpServletRequest;
14 |
15 | import org.slf4j.Logger;
16 | import org.slf4j.LoggerFactory;
17 | import org.slf4j.Marker;
18 | import org.slf4j.MarkerFactory;
19 | import org.springframework.core.Ordered;
20 | import org.springframework.core.annotation.Order;
21 | import org.springframework.stereotype.Component;
22 | /**
23 | *
24 | * Request를 기록하기 위한 Filter class
25 | *
26 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
27 | *
28 | */
29 | @Component
30 | @Order(value=Ordered.HIGHEST_PRECEDENCE)
31 | public class AuthInnerFilter implements Filter {
32 | Logger logger = LoggerFactory.getLogger(AuthInnerFilter.class);
33 | final Marker REQUEST_MARKER = MarkerFactory.getMarker("HTTP_REQUEST");
34 | @Override
35 | public void destroy() {
36 | // TODO Auto-generated method stub
37 |
38 | }
39 |
40 | @Override
41 | public void doFilter(ServletRequest arg0, ServletResponse arg1, FilterChain arg2)
42 | throws IOException, ServletException {
43 | HttpServletRequest req = (HttpServletRequest)arg0;
44 | logger.info("START OF REQUEST -------------------------------");
45 | logger.info("call for {}", req.getRequestURI());
46 | logger.info("userprincipal : {}", req.getUserPrincipal());
47 | Enumeration names = req.getHeaderNames();
48 | logger.info("- headers");
49 | String key = "";
50 | while(names.hasMoreElements()){
51 | key = names.nextElement();
52 | if (key.startsWith("authorization")) {
53 | logger.info(REQUEST_MARKER, "{} : {}", key, new String(Base64.getDecoder().decode(req.getHeader(key).split(" ")[1])));
54 | } else {
55 | logger.info(REQUEST_MARKER, "{} : {}", key, req.getHeader(key));
56 | }
57 | }
58 | String paramKey = "";
59 | logger.info("- params");
60 | Enumeration params = req.getParameterNames();
61 | while(params.hasMoreElements()){
62 | paramKey = params.nextElement();
63 | logger.info(REQUEST_MARKER, "{} : {}", paramKey, req.getParameter(paramKey));
64 | }
65 | logger.info("------------------------------- END OF REQUEST");
66 | arg2.doFilter(arg0, arg1);
67 |
68 | }
69 |
70 | @Override
71 | public void init(FilterConfig arg0) throws ServletException {
72 | // TODO Auto-generated method stub
73 |
74 | }
75 |
76 | }
77 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/method/AuthServerMethodSecurityExpression.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.method;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.security.core.Authentication;
6 |
7 | import com.gnu.AuthServer.config.AuthServerWebSecurityConfig;
8 |
9 | /**
10 | *
11 | * 이 클래스의 public boolean 메소드들이 MethodSecurityExpression 으로 쓰임(eg : PreAuthorize, PostAuthorize)
12 | *
13 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
14 | *
15 | */
16 | public class AuthServerMethodSecurityExpression {
17 |
18 | private Authentication auth;
19 | private static final Logger logger = LoggerFactory.getLogger(AuthServerWebSecurityConfig.class);
20 |
21 | public AuthServerMethodSecurityExpression(Authentication auth) {
22 | this.auth = auth;
23 | }
24 | /**
25 | * #auth.isOk() expression을 호출할 경우 이 메소드를 call하게 된다. 이 메소드의 결과가 true, false 냐에 따라 인가 여부가 결정됨
26 | * @return
27 | */
28 | public boolean isOk(boolean bool) {
29 | logger.info(auth.toString());
30 | return bool;
31 | }
32 | }
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/method/AuthServerMethodSecurityExpressionHandler.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.method;
2 |
3 | import org.aopalliance.intercept.MethodInvocation;
4 | import org.springframework.expression.spel.support.StandardEvaluationContext;
5 | import org.springframework.security.access.expression.method.DefaultMethodSecurityExpressionHandler;
6 | import org.springframework.security.core.Authentication;
7 | /**
8 | *
9 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
10 | *
11 | */
12 | public class AuthServerMethodSecurityExpressionHandler extends DefaultMethodSecurityExpressionHandler{
13 |
14 |
15 |
16 | @Override
17 | public StandardEvaluationContext createEvaluationContextInternal(Authentication auth, MethodInvocation mi) {
18 | System.out.println(mi.getMethod().getName());
19 | AuthServerSecurityExpressionRoot root = new AuthServerSecurityExpressionRoot(auth);
20 | root.setTrustResolver(getTrustResolver());
21 | root.setPermissionEvaluator(getPermissionEvaluator());
22 | root.setRoleHierarchy(getRoleHierarchy());
23 | StandardEvaluationContext sec = super.createEvaluationContextInternal(auth, mi);
24 | sec.setRootObject(root);
25 | sec.setVariable("auth", new AuthServerMethodSecurityExpression(auth));
26 | return sec;
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/method/AuthServerSecurityExpressionRoot.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.method;
2 |
3 | import org.springframework.security.access.expression.SecurityExpressionRoot;
4 | import org.springframework.security.core.Authentication;
5 |
6 | /**
7 | *
8 | * MethodSecurityExpressionRoot는 modifier가 왜 public이 아닌지 알 수가 없음.
9 | * 그냥 바로 써도 될 것 같은데 인스턴스 생성이 불가능하므로 SecurityExpressionRoot를 상속하여 구현
10 | * 아래에 위 클래스의 modifier 관련 이슈가 있음
11 | * @see https://github.com/spring-projects/spring-security/pull/4266
12 | *
13 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
14 | *
15 | */
16 | public class AuthServerSecurityExpressionRoot extends SecurityExpressionRoot {
17 | public AuthServerSecurityExpressionRoot(Authentication authentication) {
18 | super(authentication);
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/repository/UserRepository.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.repository;
2 |
3 | import org.springframework.data.jpa.repository.JpaRepository;
4 | import org.springframework.data.jpa.repository.support.SimpleJpaRepository;
5 | import org.springframework.stereotype.Repository;
6 |
7 | import com.gnu.AuthServer.entity.UserEntity;
8 |
9 | /**
10 | *
11 | * User정보를 읽기 위한 Repository
12 | * Repository란 CRUD 작업 및 기타 필요한 작업에 대한 추상화를 제공해주는 인터페이스이다
13 | *
14 | * @see JpaRepository
15 | * @see SimpleJpaRepository
16 | *
17 | * @author Geunwoo Shim(gflhsin@gmail.com)
18 | *
19 | */
20 | @Repository
21 | public interface UserRepository extends JpaRepository{
22 | UserEntity findByUsername(String userName);
23 | }
24 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/AuthClientDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import java.util.Arrays;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | import org.slf4j.Logger;
8 | import org.slf4j.LoggerFactory;
9 | import org.springframework.beans.factory.annotation.Autowired;
10 | import org.springframework.beans.factory.annotation.Value;
11 | import org.springframework.security.core.authority.AuthorityUtils;
12 | import org.springframework.security.oauth2.provider.ClientDetails;
13 | import org.springframework.security.oauth2.provider.ClientDetailsService;
14 | import org.springframework.security.oauth2.provider.ClientRegistrationException;
15 | import org.springframework.security.oauth2.provider.client.BaseClientDetails;
16 | import org.springframework.security.oauth2.provider.token.TokenStore;
17 | import org.springframework.stereotype.Service;
18 | import org.springframework.util.StringUtils;
19 |
20 | import com.gnu.AuthServer.utils.GrantTypes;
21 | /**
22 | *
23 | * client에 관련된 기능을 처리하는 서비스
24 | * client_id 및 secret을 기반으로 토큰 발급에 권한 인증 처리 및
25 | * 콜백URL 체크, 허용할 scope 및 인층 처리 프로세스간 client에 부여할 authority등이 이 클래스에서 처리 된다.
26 | *
27 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
28 | *
29 | */
30 | @Service
31 | public class AuthClientDetailsService implements ClientDetailsService {
32 | Logger logger = LoggerFactory.getLogger(AuthClientDetailsService.class);
33 |
34 | @Value("${authserver.auto.approval.scopes}")
35 | private String autoScopes;
36 |
37 | @Autowired
38 | TokenStore tokenStore;
39 |
40 | @Override
41 | public ClientDetails loadClientByClientId(String arg0) throws ClientRegistrationException {
42 | /*
43 | * token 요청시 입력하는 Callback url, client id 및 secret, scope 등은 이 메소드에서 작성하는 것과 일치하여야 한다.
44 | * ClientDetailsService 가 JdbcClientDetailsService 구현한다면 해당 클래스에는 Client 를 추가, 수정, 삭제할 수 있는 메소드가 구현되어 있다.
45 | * 해당 클래스를 구현할 경우 Client CRUD 에 에러가 발생하면 ClientRegistrationException을 발생시키도록 한다.
46 | */
47 | logger.info("load client from \"{}\"", arg0);
48 | BaseClientDetails details = new BaseClientDetails();
49 | Set set = new HashSet<>();
50 | set.add("http://localhost:7077/resources/");
51 | details.setClientId("gnu-gnu");
52 | details.setAuthorizedGrantTypes(Arrays.asList(GrantTypes.AUTHORIZATION_CODE, GrantTypes.PASSWORD, GrantTypes.REFRESH_TOKEN));
53 | details.setRegisteredRedirectUri(set);
54 | details.setScope(StringUtils.commaDelimitedListToSet(autoScopes));
55 | details.setAutoApproveScopes(StringUtils.commaDelimitedListToSet(autoScopes));
56 | return details;
57 | }
58 | }
59 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/AuthTokenService.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 | import java.util.stream.Collectors;
6 |
7 | import javax.annotation.PostConstruct;
8 |
9 | import org.slf4j.Logger;
10 | import org.slf4j.LoggerFactory;
11 | import org.springframework.beans.factory.annotation.Autowired;
12 | import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
13 | import org.springframework.security.oauth2.common.OAuth2AccessToken;
14 | import org.springframework.security.oauth2.provider.OAuth2Authentication;
15 | import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
16 | import org.springframework.security.oauth2.provider.token.TokenEnhancer;
17 | import org.springframework.security.oauth2.provider.token.TokenStore;
18 | import org.springframework.stereotype.Service;
19 | /**
20 | *
21 | * Access token 발급에 관한 서비스
22 | * Access token에 global로 기록될 사항들을 이 서비스에서 관리할 수 있다.
23 | *
24 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
25 | *
26 | */
27 | @Service
28 | public class AuthTokenService extends DefaultTokenServices {
29 | Logger logger = LoggerFactory.getLogger(AuthTokenService.class);
30 |
31 | @Autowired
32 | TokenStore tokenStore;
33 |
34 | @PostConstruct
35 | public void init(){
36 | this.setRefreshTokenValiditySeconds(86400);
37 | this.setAccessTokenValiditySeconds(1800);
38 | this.setSupportRefreshToken(true);
39 | this.setTokenStore(tokenStore);
40 | /*
41 | * Token 발급시 부가 정보등을 기록하기 위한 메소드
42 | */
43 | this.setTokenEnhancer(new TokenEnhancer() {
44 | @SuppressWarnings("unchecked")
45 | @Override
46 | public OAuth2AccessToken enhance(OAuth2AccessToken accessToken, OAuth2Authentication authentication) {
47 | logger.info("token enhance");
48 | Map details = new HashMap<>();
49 | logger.info(authentication.getUserAuthentication().toString());
50 | details.putAll((Map extends String, ? extends Object>) authentication.getUserAuthentication().getDetails());
51 | details.put("authorities", authentication.getAuthorities().stream().map(value->value.getAuthority()).distinct().collect(Collectors.toList()));
52 | ((DefaultOAuth2AccessToken)accessToken).setAdditionalInformation(details);
53 | return accessToken;
54 | }
55 | });
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/AuthUserDetails.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import java.util.Collection;
4 | import java.util.HashSet;
5 | import java.util.Set;
6 |
7 | import org.springframework.security.core.GrantedAuthority;
8 | import org.springframework.security.core.authority.SimpleGrantedAuthority;
9 | import org.springframework.security.core.userdetails.UserDetails;
10 |
11 | import com.gnu.AuthServer.entity.UserEntity;
12 | /**
13 | *
14 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
15 | *
16 | */
17 | public class AuthUserDetails implements UserDetails {
18 | private static final long serialVersionUID = 106806877220707136L;
19 | private UserEntity entity;
20 |
21 | public AuthUserDetails(UserEntity entity) {
22 | super();
23 | this.entity = entity;
24 | }
25 |
26 | @Override
27 | public Collection extends GrantedAuthority> getAuthorities() {
28 | Set authorities = new HashSet<>();
29 | entity.getAuthorities().forEach(value -> {
30 | authorities.add(new SimpleGrantedAuthority(value.getRoleName()));
31 | });
32 | return authorities;
33 | }
34 |
35 | public UserEntity getEntity() {
36 | return entity;
37 | }
38 |
39 | public void setEntity(UserEntity entity) {
40 | this.entity = entity;
41 | }
42 |
43 | @Override
44 | public String getPassword() {
45 | return entity.getPassword();
46 | }
47 |
48 | @Override
49 | public String getUsername() {
50 | return entity.getUsername();
51 | }
52 |
53 | @Override
54 | public boolean isAccountNonExpired() {
55 | return true;
56 | }
57 |
58 | @Override
59 | public boolean isAccountNonLocked() {
60 | // TODO Auto-generated method stub
61 | return true;
62 | }
63 |
64 | @Override
65 | public boolean isCredentialsNonExpired() {
66 | // TODO Auto-generated method stub
67 | return true;
68 | }
69 |
70 | @Override
71 | public boolean isEnabled() {
72 | // TODO Auto-generated method stub
73 | return true;
74 | }
75 | }
76 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/AuthUserDetailsService.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import org.slf4j.Logger;
4 | import org.slf4j.LoggerFactory;
5 | import org.springframework.beans.factory.annotation.Autowired;
6 | import org.springframework.context.annotation.Configuration;
7 | import org.springframework.security.core.userdetails.UserDetails;
8 | import org.springframework.security.core.userdetails.UserDetailsService;
9 | import org.springframework.security.core.userdetails.UsernameNotFoundException;
10 |
11 | import com.gnu.AuthServer.entity.UserEntity;
12 | import com.gnu.AuthServer.repository.UserRepository;
13 | /**
14 | *
15 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
16 | *
17 | */
18 | @Configuration
19 | public class AuthUserDetailsService implements UserDetailsService{
20 | Logger logger = LoggerFactory.getLogger(AuthUserDetailsService.class);
21 |
22 | @Autowired
23 | UserRepository userRepository;
24 |
25 | @Override
26 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
27 |
28 | logger.info(username+"... trying to login");
29 | UserEntity findEntity = userRepository.findByUsername(username);
30 | if(null == findEntity){
31 | logger.info(username+"... user not found");
32 | throw new UsernameNotFoundException(username);
33 | }
34 | logger.info(username+"... login success");
35 | AuthUserDetails details = new AuthUserDetails(findEntity);
36 | return details;
37 | }
38 |
39 | }
40 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/TestService.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import org.springframework.security.access.prepost.PreAuthorize;
4 |
5 | public interface TestService {
6 | @PreAuthorize("#auth.isOk(#bool)")
7 | String hello(boolean bool);
8 |
9 | }
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/service/TestServiceImpl.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.service;
2 |
3 | import org.springframework.stereotype.Service;
4 | /**
5 | *
6 | * 서비스 레이어에 메소드 표현식 기반 인가 프로세스 적용
7 | * 인터페이스에 적용 해 두었기 때문에 서비스 레이어는 구현만 하면 됨
8 | *
9 | * @author gnu-gnu(geunwoo.j.shim@gmail.com)
10 | *
11 | */
12 | @Service
13 | public class TestServiceImpl implements TestService {
14 |
15 | @Override
16 | public String hello(boolean bool) {
17 | return "Hello?";
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/utils/CustomChecker.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.utils;
2 |
3 | import org.springframework.security.core.Authentication;
4 | import org.springframework.stereotype.Component;
5 |
6 | @Component
7 | public class CustomChecker {
8 |
9 | public boolean isChecked(Authentication auth) {
10 | System.out.println(auth);
11 | System.out.println("checked");
12 | return true;
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/main/java/com/gnu/AuthServer/utils/GrantTypes.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.utils;
2 | /**
3 | *
4 | * access_token을 발급하는데 필요한 grant type을 정의
5 | * 각 방식의 특징은 다음과 같다
6 | * PASSWORD : 사용자에게 ID와 PASSWORD를 받고 access_token을 발급한다
7 | * AUTHORIZATION_CODE : ~~/authorize에서 발급된 request_token(authorization_code)을 access_token으로 교환한다
8 | * CLIENT_CREDENTIALS : 신뢰하는 client에 부여된 client_credentials를 받아 access_token을 발급한다.
9 | * REFRESH_TOKEN : 만료가 임박한 access_token을 새로 갱신한다
10 | *
11 | * @author Geunwoo.Shim (gflhsin@gmail.com)
12 | */
13 | public interface GrantTypes {
14 | String PASSWORD = "password";
15 | String AUTHORIZATION_CODE = "authorization_code";
16 | String CLIENT_CREDENTIALS = "client_credentials";
17 | String REFRESH_TOKEN = "refresh_token";
18 | }
19 |
--------------------------------------------------------------------------------
/src/main/resources/application.properties:
--------------------------------------------------------------------------------
1 | server.port=9099
2 | server.context-path=/apps
3 |
4 | spring.redis.host=localhost
5 | spring.redis.port=6379
6 |
7 | spring.profiles.active=local
8 |
9 | #OAuth
10 | authserver.auto.approval.scopes=check,read
--------------------------------------------------------------------------------
/src/main/resources/security.properties:
--------------------------------------------------------------------------------
1 | securities.permitall=/open,/login,/isok
--------------------------------------------------------------------------------
/src/test/java/com/gnu/AuthServer/AuthServerApplicationTests.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer;
2 |
3 | import static org.assertj.core.api.Assertions.assertThat;
4 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.formLogin;
5 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
6 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
7 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
8 |
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.slf4j.Logger;
13 | import org.slf4j.LoggerFactory;
14 | import org.springframework.beans.factory.annotation.Autowired;
15 | import org.springframework.boot.test.context.SpringBootTest;
16 | import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;
17 | import org.springframework.http.MediaType;
18 | import org.springframework.security.authentication.AuthenticationManager;
19 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
20 | import org.springframework.security.core.Authentication;
21 | import org.springframework.security.web.FilterChainProxy;
22 | import org.springframework.test.context.junit4.SpringRunner;
23 | import org.springframework.test.web.servlet.MockMvc;
24 | import org.springframework.test.web.servlet.ResultActions;
25 | import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
26 | import org.springframework.test.web.servlet.setup.MockMvcBuilders;
27 | import org.springframework.util.LinkedMultiValueMap;
28 | import org.springframework.util.MultiValueMap;
29 | import org.springframework.web.context.WebApplicationContext;
30 |
31 | import com.fasterxml.jackson.databind.ObjectMapper;
32 | import com.fasterxml.jackson.databind.PropertyNamingStrategy;
33 | import com.gnu.AuthServer.dto.AccessToken;
34 | import com.gnu.AuthServer.utils.GrantTypes;
35 |
36 | @RunWith(SpringRunner.class)
37 | @SpringBootTest(webEnvironment=WebEnvironment.RANDOM_PORT, classes=AuthServerApplication.class)
38 | public class AuthServerApplicationTests {
39 | private static final Logger LOG = LoggerFactory.getLogger(AuthServerApplicationTests.class);
40 | private static final String CLIENT_SECRET = "gnu-secret";
41 | private static final String CLIENT_ID = "gnu-gnu";
42 | private static final String CALLBACK_URL = "http://localhost:7077/resources/open/callback";
43 | private static final String REQUEST_TOKEN_ENDPOINT = "/oauth/authorize";
44 | private static final String ACCESS_TOKEN_ENDPOINT = "/oauth/token";
45 | private static final String LOGIN_ENDPOINT = "/login";
46 |
47 |
48 | @Autowired
49 | private WebApplicationContext ctx;
50 |
51 | @Autowired
52 | AuthenticationManager authenticationManager;
53 | @Autowired
54 | FilterChainProxy filterChainProxy;
55 |
56 | private MockMvc mvc;
57 | private String username;
58 | private String password;
59 | private Authentication auth;
60 |
61 | @Before
62 | public void init() throws Exception {
63 | LOG.info("----- unit test initialize");
64 | username = "gnu";
65 | password = "pass";
66 | this.mvc = MockMvcBuilders.webAppContextSetup(ctx).addFilters(filterChainProxy).build();
67 | auth = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
68 | }
69 |
70 | @Test
71 | public void testGetToken() throws Exception {
72 | testFormLogin();
73 | String requestToken = getRequestTokenForAuthorizationCode();
74 | assertThat(requestToken).isNotEmpty();
75 | AccessToken accessToken = getAccessTokenForAuthorizationCode(requestToken);
76 | assertThat(accessToken).isNotNull();
77 | String refreshToken = accessToken.getRefreshToken();
78 | assertThat(getAccessTokenByRefreshToken(refreshToken)).isNotNull();
79 | assertThat(getAccessTokenForPassword()).isNotNull();
80 | }
81 | /**
82 | * Form 기반 로그인이 성공하는지 테스트
83 | *
84 | * @throws Exception
85 | */
86 | private void testFormLogin() throws Exception {
87 | LOG.info("----- form login test");
88 | ResultActions result = mvc.perform(formLogin(LOGIN_ENDPOINT).user(username).password(password));
89 | assertThat(result.andReturn().getResponse().getStatus()).isNotSameAs(500);
90 | }
91 | /**
92 | *
93 | * Access 토큰 교환을 위해 사용하는 공통 메소드
94 | *
95 | * @return Access_token
96 | * @throws Exception
97 | */
98 | private AccessToken getAccessToken(MultiValueMap requestParam) throws Exception {
99 | /*
100 | * /oauth/token endpoint의 경우 ClientCredentialsTokenEndpointFilter가 등록되어 있음.
101 | * client_id, client_secret을 이용하여 Authentication을 수행하므로 username, password 기반 인증을 엔드포인트 인증을 위해 끼워넣을 경우 오히려 에러가 발생함
102 | * eg) request token 요청시에는 아래와 같이 .with(authentication(auth))를 삽입하여 Endpoint 인증에 Spring security의 AuthenticationManager를 사용하였으나
103 | * post(REQUEST_TOKEN_ENDPOINT).with(authentication(auth)).params(requestParam).contentType(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON_UTF8).characterEncoding("UTF-8");
104 | * access token 교환시 해당 인증을 삽입하면 client_id, secret 기반의 ClientCredentialsTokenEndpointFilter와 충돌을 일으켜 client_id를 AuthenticationManger에 등록된 id,pw로 인식하게 됨
105 | * 그러므로 Access token 교환을 위한 인증시에는 client_id, secret 기반의 인증을 사용하도록 해당 부분을 제거
106 | */
107 | MockHttpServletRequestBuilder reqBuilder = post(ACCESS_TOKEN_ENDPOINT).params(requestParam).contentType(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON_UTF8).characterEncoding("UTF-8");
108 | ResultActions result = mvc.perform(reqBuilder).andExpect(status().isOk());
109 | String responseBody = result.andReturn().getResponse().getContentAsString();
110 | ObjectMapper mapper = new ObjectMapper();
111 | mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
112 | AccessToken accessToken = mapper.readValue(responseBody, AccessToken.class);
113 | LOG.info("raw response : {}", responseBody);
114 | LOG.info("Access token dto : {}", accessToken.toString());
115 | return accessToken;
116 | }
117 | /**
118 | * authorization_code grant 에서 requestToken을 access token으로 교환하기 위한 메소드
119 | *
120 | * @param requestToken
121 | * @return
122 | * @throws Exception
123 | */
124 | private AccessToken getAccessTokenForAuthorizationCode(String requestToken) throws Exception{
125 | LOG.info("----- authorization_code access token test");
126 | MultiValueMap requestParam = new LinkedMultiValueMap<>();
127 | requestParam.add("grant_type", GrantTypes.AUTHORIZATION_CODE);
128 | requestParam.add("code", requestToken);
129 | requestParam.add("redirect_uri", CALLBACK_URL);
130 | requestParam.add("client_id", CLIENT_ID);
131 | // requestParam.add("client_secret", CLIENT_SECRET);
132 | return getAccessToken(requestParam);
133 | }
134 | /**
135 | *
136 | * refresh_token을 이용하여 access_token을 교환하는 메소드
137 | * access token이 유효하여야 한다.
138 | * auth server의 endpoint에 대한 security 인증은 client_id, client_password로 이루어진다. 이 부분이 없다면 401 Unauthorized 에러가 발생한다.
139 | *
140 | * @param refreshToken access_token 발급시 부여받은 refresh_token
141 | * @return 갱신된 access token
142 | * @throws Exception
143 | */
144 | private AccessToken getAccessTokenByRefreshToken(String refreshToken) throws Exception {
145 | LOG.info("----- refresh token test");
146 | MultiValueMap requestParam = new LinkedMultiValueMap<>();
147 | requestParam.add("refresh_token", refreshToken);
148 | requestParam.add("grant_type", GrantTypes.REFRESH_TOKEN);
149 | requestParam.add("client_id", CLIENT_ID);
150 | // requestParam.add("client_secret", CLIENT_SECRET);
151 | return getAccessToken(requestParam);
152 | }
153 | /**
154 | * AuthenticationManager에 등록된 id와 password를 통해 access token을 부여하는 password grant 방식으로 access token을 받는다.
155 | * 이 방식은 third-party에게 제공하기는 대단히 부적합하기 때문에 유의해야 한다.
156 | *
157 | * @return access token
158 | * @throws Exception
159 | */
160 | private AccessToken getAccessTokenForPassword() throws Exception {
161 | LOG.info("----- password grant access token test");
162 | MultiValueMap requestParam = new LinkedMultiValueMap<>();
163 | requestParam.add("grant_type", "password");
164 | requestParam.add("client_id", CLIENT_ID);
165 | // requestParam.add("client_secret", CLIENT_SECRET);
166 | requestParam.add("scope", "read+check");
167 | requestParam.add("username", username);
168 | requestParam.add("password", password);
169 | return getAccessToken(requestParam);
170 | }
171 | /**
172 | *
173 | * authorization_code grant_type에서 access_token 교환에 사용할 request_token을 받는다.
174 | * 이 flow는 일반적으로 client가 redirect_uri로 redirect하면서 해당 주소에 parameter로 code를 전달하는 방식이다.
175 | * 그러므로 단위테스트에서는 redirect 되는 주소의 queryString을 파싱한다.
176 | *
177 | * @return request_token 문자열, 이 request_token을 access_token 교환시 사용한다.
178 | * @throws Exception
179 | */
180 | private String getRequestTokenForAuthorizationCode() throws Exception {
181 | LOG.info("----- request token test");
182 | MultiValueMap requestParam = new LinkedMultiValueMap<>();
183 | requestParam.add("response_type", "code");
184 | requestParam.add("redirect_uri", CALLBACK_URL);
185 | requestParam.add("client_id", CLIENT_ID);
186 | // requestParam.add("client_secret", CLIENT_SECRET);
187 | requestParam.add("scope", "read+check");
188 | MockHttpServletRequestBuilder reqBuilder = post(REQUEST_TOKEN_ENDPOINT).with(authentication(auth)).params(requestParam).contentType(MediaType.APPLICATION_FORM_URLENCODED).accept(MediaType.APPLICATION_JSON_UTF8).characterEncoding("UTF-8");
189 | ResultActions result = mvc.perform(reqBuilder).andExpect(status().is3xxRedirection());
190 | return result.andReturn().getResponse().getRedirectedUrl().split("\\?code=")[1];
191 | }
192 | }
193 |
--------------------------------------------------------------------------------
/src/test/java/com/gnu/AuthServer/dto/AccessToken.java:
--------------------------------------------------------------------------------
1 | package com.gnu.AuthServer.dto;
2 |
3 | import java.util.Arrays;
4 | import java.util.List;
5 | import java.util.Set;
6 |
7 | public class AccessToken {
8 | // {"access_token":"c5352e5e-e451-4047-89dc-bb607827af23","token_type":"bearer","refresh_token":"f19c036b-43aa-4d2f-b774-8a55369a0575","expires_in":1799,"scope":"check read","uid":1,"uname":"gnu","locale":"ko","authorities":["read","ACTUATOR","update","write","delete"]}
9 | private String accessToken;
10 | private String tokenType;
11 | private String refreshToken;
12 | private int expiresIn;
13 | private List scope;
14 | private int uid;
15 | private String uname;
16 | private String locale;
17 | private Set authorities;
18 | public String getAccessToken() {
19 | return accessToken;
20 | }
21 |
22 | public void setAccessToken(String accessToken) {
23 | this.accessToken = accessToken;
24 | }
25 | public String getTokenType() {
26 | return tokenType;
27 | }
28 | public void setTokenType(String tokenType) {
29 | this.tokenType = tokenType;
30 | }
31 | public String getRefreshToken() {
32 | return refreshToken;
33 | }
34 | public void setRefreshToken(String refreshToken) {
35 | this.refreshToken = refreshToken;
36 | }
37 | public int getExpiresIn() {
38 | return expiresIn;
39 | }
40 | public void setExpiresIn(int expiresIn) {
41 | this.expiresIn = expiresIn;
42 | }
43 | public List getScope() {
44 | return scope;
45 | }
46 | public void setScope(String scope) {
47 | this.scope = Arrays.asList(scope.split(" "));
48 | }
49 | public int getUid() {
50 | return uid;
51 | }
52 | public void setUid(int uid) {
53 | this.uid = uid;
54 | }
55 | public String getUname() {
56 | return uname;
57 | }
58 | public void setUname(String uname) {
59 | this.uname = uname;
60 | }
61 | public String getLocale() {
62 | return locale;
63 | }
64 | public void setLocale(String locale) {
65 | this.locale = locale;
66 | }
67 | public Set getAuthorities() {
68 | return authorities;
69 | }
70 | public void setAuthorities(Set authorities) {
71 | this.authorities = authorities;
72 | }
73 |
74 | @Override
75 | public String toString() {
76 | return "AccessToken [accessToken=" + accessToken + ", tokenType=" + tokenType + ", refreshToken=" + refreshToken
77 | + ", expiresIn=" + expiresIn + ", scope=" + scope + ", uid=" + uid + ", uname=" + uname + ", locale="
78 | + locale + ", authorities=" + authorities + "]";
79 | }
80 |
81 |
82 | }
83 |
--------------------------------------------------------------------------------