├── .gitignore ├── .mvn └── wrapper │ ├── maven-wrapper.jar │ └── maven-wrapper.properties ├── README.md ├── mvnw ├── mvnw.cmd ├── pom.xml ├── src ├── main │ ├── java │ │ └── com │ │ │ └── wheejuni │ │ │ └── jwtdemo │ │ │ ├── JwtdemoApplication.java │ │ │ ├── controllers │ │ │ └── ApiController.java │ │ │ ├── domain │ │ │ ├── Account.kt │ │ │ ├── AccountRepository.java │ │ │ ├── SocialProviders.java │ │ │ └── UserRole.java │ │ │ ├── dtos │ │ │ ├── FormLoginDto.kt │ │ │ ├── SocialLoginDto.kt │ │ │ └── TokenDto.kt │ │ │ └── security │ │ │ ├── AccountContext.java │ │ │ ├── AccountContextService.java │ │ │ ├── InvalidJwtException.java │ │ │ ├── SecurityConfig.java │ │ │ ├── filters │ │ │ ├── FilterSkipMatcher.java │ │ │ ├── FormLoginFilter.java │ │ │ ├── JwtAuthenticationFilter.java │ │ │ └── SocialLoginFilter.java │ │ │ ├── handlers │ │ │ ├── FormLoginAuthenticationSuccessHandler.java │ │ │ └── JwtAuthenticationFailureHandler.java │ │ │ ├── jwt │ │ │ ├── HeaderTokenExtractor.java │ │ │ ├── JwtDecoder.java │ │ │ └── JwtFactory.java │ │ │ ├── providers │ │ │ ├── FormLoginAuthenticationProvider.java │ │ │ ├── JwtAuthenticationProvider.java │ │ │ └── SocialLoginAuthenticationProvider.java │ │ │ ├── services │ │ │ ├── impl │ │ │ │ ├── SocialFetchServiceProd.java │ │ │ │ └── SocialFetchServiceTest.java │ │ │ └── specification │ │ │ │ └── SocialFetchService.java │ │ │ ├── social │ │ │ ├── KakaoUserProperty.kt │ │ │ ├── NaverUserProperty.kt │ │ │ └── SocialUserProperty.java │ │ │ └── tokens │ │ │ ├── JwtPostProcessingToken.java │ │ │ ├── JwtPreProcessingToken.java │ │ │ ├── PostAuthorizationToken.java │ │ │ ├── PreAuthorizationToken.java │ │ │ └── SocialPreAuthorizationToken.java │ └── resources │ │ └── application.yml └── test │ └── java │ └── com │ └── wheejuni │ └── jwtdemo │ ├── JwtdemoApplicationTests.java │ └── security │ ├── HeaderTokenExtractorTest.java │ ├── JwtFactoryTest.java │ └── services │ └── specification │ └── SocialFetchServiceTest.java └── test-http ├── hello.http ├── loggedin-request.http ├── login-naver.http ├── login-request.http └── login-social.http /.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 | .sts4-cache 12 | 13 | ### IntelliJ IDEA ### 14 | .idea 15 | *.iws 16 | *.iml 17 | *.ipr 18 | 19 | ### NetBeans ### 20 | /nbproject/private/ 21 | /build/ 22 | /nbbuild/ 23 | /dist/ 24 | /nbdist/ 25 | /.nb-gradle/ -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/wheejuni/spring-jwt/92f567eab41f830ecad5124bb59414aaeaec5b9f/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.3/apache-maven-3.5.3-bin.zip 2 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 봄이네집 스프링 - (1) Spring Security + Auth0 JWT Library 2 | --- 3 | 4 | #### 안녕하세요. 5 | 6 | 안녕하세요. 7 | 8 | 봄이네집 스프링의 첫 세션입니다. 9 | 10 | 이번 세션에서는 Spring Security의 기본 개념을 알아보고, 레퍼런스 문서를 함께 읽습니다. 또 내장된 기본 구현체들을 까 보는(?) 시간도 가질 예정입니다. 11 | 12 | 또한 Auth0 Java-JWT( https://github.com/auth0/java-jwt )를 사용해 JWT를 발급하고, 인증하는 방법을 알아봅니다. 소셜 공급자 연동도 간단히 맛봅니다. 13 | 14 | 감사합니다! 15 | 16 | 17 | **방송 일지** 18 | 19 | [제 1회차 방송](https://www.youtube.com/watch?v=SMZm2aqI_dQ&index=1&list=PLcsqrv8NxApXzHViDU2fB1ew7KoLoaB02) 20 | 21 | [제 2회차 방송](https://www.youtube.com/watch?v=x2i96t1aA3s&index=2&list=PLcsqrv8NxApXzHViDU2fB1ew7KoLoaB02&t=0s) 22 | 23 | [제 3회차 방송](https://www.youtube.com/watch?v=qCA3JB4W_cw) 24 | 25 | [제 4회차 방송](https://www.youtube.com/watch?v=jNNJnGiLMl8&list=PLcsqrv8NxApXzHViDU2fB1ew7KoLoaB02&index=4&t=0s) 26 | 27 | [제 5회차 방송](https://www.youtube.com/watch?v=qhCVfz1t68I&t=0s&index=6&list=PLcsqrv8NxApXzHViDU2fB1ew7KoLoaB02) 28 | 29 | [방송 바로가기](https://www.youtube.com/watch?v=SMZm2aqI_dQ) 30 | 31 | --- 32 | #### Account 객체 요구사항 33 | 34 | * 기본적인 유저 정보 35 | 36 | * 아이디, 비밀번호, 이름, 프사 링크(profileHref) 37 | * 서비스상에서 유저에게 부여하고싶은 권한 38 | * 소셜 로그인한 사용자의 경우, 소셜 서비스가 부여한 ID 코드 **(로그인 ID 아님)** 39 | 40 | * 유저 정보를 인증과정에서 처리하는 방식 41 | 42 | * 유저 모델을 그대로 사용 43 | * 유저디테일즈 구현체를 사용 44 | 45 | --- 46 | ###오늘의 할일 47 | 48 | * 카카오 로그인을 구현한다. 49 | 50 | * 유저의 카카오 토큰으로 유저 정보를 얻어오는 로직 (서비스) 작성. 51 | * 추후 다른 소셜 공급자와도 연동할 수 있게 확장성있게 구현. 52 | * 최대한 객체지향적으로 설계. 53 | * DTO 작성. 54 | 55 | * 인증 후 JWT를 발급해야 한다. 56 | * 데이터베이스에서 검색되지 않는 사용자는 회원가입 처리 후 발급. 57 | * 데이트베이스에 있는 사용자는 바로 발급. 58 | 59 | #### 소셜 공급자에서 빼올 데이터. 60 | - email 61 | - 고유번호('id') 62 | - 프사 href 63 | - 닉네임 64 | 65 | ### 인터페이스부터 구현합시다. 66 | * SocialUserProperty 67 | 68 | --- 69 | ###오늘의 할 일 70 | 71 | * 네이버 로그인을 구현한다. 72 | 73 | -------------------------------------------------------------------------------- /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 | 4 | 4.0.0 5 | 6 | com.wheejuni 7 | jwtdemo 8 | 0.0.1-SNAPSHOT 9 | jar 10 | 11 | jwtdemo 12 | Demo project for Spring Boot 13 | 14 | 15 | org.springframework.boot 16 | spring-boot-starter-parent 17 | 2.0.1.RELEASE 18 | 19 | 20 | 21 | 22 | UTF-8 23 | UTF-8 24 | 1.8 25 | 1.2.40 26 | 27 | 28 | 29 | 30 | org.springframework.boot 31 | spring-boot-starter-data-jpa 32 | 33 | 34 | org.springframework.boot 35 | spring-boot-starter-security 36 | 37 | 38 | org.springframework.boot 39 | spring-boot-starter-web 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-devtools 45 | runtime 46 | 47 | 48 | org.projectlombok 49 | lombok 50 | true 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-test 55 | test 56 | 57 | 58 | org.springframework.security 59 | spring-security-test 60 | test 61 | 62 | 63 | com.auth0 64 | java-jwt 65 | 3.3.0 66 | 67 | 68 | org.jetbrains.kotlin 69 | kotlin-stdlib-jdk8 70 | ${kotlin.version} 71 | 72 | 73 | org.jetbrains.kotlin 74 | kotlin-test 75 | ${kotlin.version} 76 | test 77 | 78 | 79 | org.jetbrains.kotlin 80 | kotlin-reflect 81 | 1.1.2 82 | 83 | 84 | com.fasterxml.jackson.module 85 | jackson-module-kotlin 86 | 87 | 88 | com.h2database 89 | h2 90 | 1.4.196 91 | runtime 92 | 93 | 94 | 95 | 96 | 97 | 98 | org.springframework.boot 99 | spring-boot-maven-plugin 100 | 101 | 102 | org.jetbrains.kotlin 103 | kotlin-maven-plugin 104 | ${kotlin.version} 105 | 106 | 107 | compile 108 | process-sources 109 | 110 | compile 111 | 112 | 113 | 114 | test-compile 115 | test-compile 116 | 117 | test-compile 118 | 119 | 120 | 121 | 122 | 1.8 123 | 124 | 125 | 126 | org.apache.maven.plugins 127 | maven-compiler-plugin 128 | 129 | 130 | compile 131 | compile 132 | 133 | compile 134 | 135 | 136 | 137 | testCompile 138 | test-compile 139 | 140 | testCompile 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/JwtdemoApplication.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.domain.AccountRepository; 5 | import org.springframework.boot.CommandLineRunner; 6 | import org.springframework.boot.SpringApplication; 7 | import org.springframework.boot.autoconfigure.SpringBootApplication; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.security.crypto.password.PasswordEncoder; 10 | 11 | @SpringBootApplication 12 | public class JwtdemoApplication { 13 | 14 | public static void main(String[] args) { 15 | SpringApplication.run(JwtdemoApplication.class, args); 16 | } 17 | 18 | 19 | @Bean 20 | CommandLineRunner bootstrapTestAccount(AccountRepository accountRepository, PasswordEncoder passwordEncoder) { 21 | return args -> { 22 | Account account = new Account(); 23 | account.setPassword(passwordEncoder.encode("1234")); 24 | 25 | accountRepository.save(account); 26 | }; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/controllers/ApiController.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.controllers; 2 | 3 | import com.wheejuni.jwtdemo.security.tokens.PostAuthorizationToken; 4 | import org.springframework.security.access.prepost.PreAuthorize; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestMapping; 8 | import org.springframework.web.bind.annotation.RestController; 9 | 10 | @RestController 11 | @RequestMapping("/api") 12 | public class ApiController { 13 | 14 | 15 | @GetMapping("/hello") 16 | @PreAuthorize("hasRole('ROLE_USER')") 17 | public String getUsername(Authentication authentication) { 18 | 19 | PostAuthorizationToken token = (PostAuthorizationToken)authentication; 20 | 21 | return token.getAccountContext().getUsername(); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/domain/Account.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.domain 2 | 3 | import javax.persistence.* 4 | 5 | @Entity 6 | @Table(name = "ACCOUNT") 7 | data class Account( 8 | 9 | @Id 10 | @GeneratedValue(strategy = GenerationType.AUTO) 11 | val id: Long? = null, 12 | 13 | @Column(name = "ACCOUNT_USERNAME") 14 | val username: String? = null, 15 | 16 | @Column(name = "ACCOUNT_LOGINID") 17 | val userId: String? = "emalyun@naver.com", 18 | 19 | @Column(name = "ACCOUNT_PASSWORD") 20 | var password: String? = "1234", 21 | 22 | @Column(name = "ACCOUNT_ROLE") 23 | @Enumerated(value = EnumType.STRING) 24 | var userRole: UserRole? = UserRole.USER, 25 | 26 | @Column(name = "ACCOUNT_SOCIAL_ID") 27 | var socialId: Long? = null, 28 | 29 | @Column(name = "ACCOUNT_SOCIAL_PROVIDER") 30 | @Enumerated(value = EnumType.STRING) 31 | var socialProvider: SocialProviders? = null, 32 | 33 | @Column(name = "ACCOUNT_SOCIAL_PROFILEPIC") 34 | var profileHref: String? = null) { 35 | 36 | 37 | } -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/domain/AccountRepository.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.domain; 2 | 3 | import org.springframework.data.repository.CrudRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface AccountRepository extends CrudRepository { 8 | 9 | Optional findByUserId(String userId); 10 | 11 | Optional findBySocialId(long socialId); 12 | 13 | Optional findBySocialIdAndSocialProvider(long socialId, SocialProviders socialProvider); 14 | 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/domain/SocialProviders.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonValue; 4 | import com.wheejuni.jwtdemo.security.social.KakaoUserProperty; 5 | import com.wheejuni.jwtdemo.security.social.NaverUserProperty; 6 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 7 | import lombok.Getter; 8 | 9 | @Getter 10 | public enum SocialProviders { 11 | 12 | KAKAO("https://kapi.kakao.com/v1/user/me", KakaoUserProperty.class), 13 | NAVER("https://openapi.naver.com/v1/nid/me", NaverUserProperty.class); 14 | 15 | private String userinfoEndpoint; 16 | private Class propertyMetaclass; 17 | 18 | SocialProviders(String userinfoEndpoint, Class propertyMetaclass) { 19 | this.userinfoEndpoint = userinfoEndpoint; 20 | this.propertyMetaclass = propertyMetaclass; 21 | } 22 | 23 | @JsonValue 24 | public String getProviderName() { 25 | return this.name(); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/domain/UserRole.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.domain; 2 | 3 | import lombok.Getter; 4 | import org.springframework.security.core.GrantedAuthority; 5 | 6 | import java.util.Arrays; 7 | import java.util.NoSuchElementException; 8 | 9 | @Getter 10 | public enum UserRole { 11 | 12 | USER("ROLE_USER"), ADMIN("ROLE_ADMIN") ; 13 | 14 | private String roleName; 15 | 16 | UserRole(String roleName) { 17 | this.roleName = roleName; 18 | } 19 | 20 | public boolean isCorrectName(String name) { 21 | return name.equalsIgnoreCase(this.roleName); 22 | } 23 | 24 | public static UserRole getRoleByName(String roleName) { 25 | return Arrays.stream(UserRole.values()).filter(r -> r.isCorrectName(roleName)).findFirst().orElseThrow(() -> new NoSuchElementException("검색된 권한이 없습니다.")); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/dtos/FormLoginDto.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.dtos 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | 5 | data class FormLoginDto( 6 | 7 | @field:JsonProperty("userid") 8 | val id: String? = null, 9 | 10 | @field:JsonProperty("password") 11 | val password: String? = null 12 | ) -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/dtos/SocialLoginDto.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.dtos 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | import com.wheejuni.jwtdemo.domain.SocialProviders 5 | 6 | data class SocialLoginDto( 7 | @field:JsonProperty("provider") 8 | val provider: SocialProviders? = null, 9 | 10 | @field:JsonProperty("token") 11 | val token: String? = null 12 | ) -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/dtos/TokenDto.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.dtos 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | 5 | data class TokenDto( 6 | 7 | @field:JsonProperty("token") 8 | val token: String? = null) -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/AccountContext.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.domain.UserRole; 5 | import com.wheejuni.jwtdemo.security.tokens.JwtPostProcessingToken; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import org.springframework.security.core.userdetails.User; 9 | 10 | import java.util.Arrays; 11 | import java.util.Collection; 12 | import java.util.List; 13 | import java.util.stream.Collectors; 14 | 15 | public class AccountContext extends User { 16 | 17 | private Account account; 18 | 19 | private AccountContext(Account account, String username, String password, Collection authorities) { 20 | super(username, password, authorities); 21 | this.account = account; 22 | } 23 | 24 | public AccountContext(String username, String password, String role) { 25 | super(username, password, parseAuthorities(role)); 26 | } 27 | 28 | public static AccountContext fromAccountModel(Account account) { 29 | return new AccountContext(account, account.getUserId(), account.getPassword(), parseAuthorities(account.getUserRole())); 30 | } 31 | 32 | public static AccountContext fromJwtPostToken(JwtPostProcessingToken token) { 33 | return new AccountContext(null, token.getUserid(), token.getPassword(), token.getAuthorities()); 34 | } 35 | 36 | private static List parseAuthorities(UserRole role) { 37 | return Arrays.asList(role).stream().map(r -> new SimpleGrantedAuthority(r.getRoleName())).collect(Collectors.toList()); 38 | } 39 | 40 | private static List parseAuthorities(String role) { 41 | return parseAuthorities(UserRole.getRoleByName(role)); 42 | } 43 | 44 | public Account getAccount() { 45 | return account; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/AccountContextService.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.domain.AccountRepository; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.security.core.userdetails.UserDetails; 7 | import org.springframework.security.core.userdetails.UserDetailsService; 8 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 9 | import org.springframework.stereotype.Component; 10 | 11 | import java.util.NoSuchElementException; 12 | 13 | @Component 14 | public class AccountContextService implements UserDetailsService { 15 | 16 | @Autowired 17 | private AccountRepository accountRepository; 18 | 19 | 20 | @Override 21 | public UserDetails loadUserByUsername(String userId) throws UsernameNotFoundException { 22 | Account account = accountRepository.findByUserId(userId).orElseThrow(() -> new NoSuchElementException("아이디에 맞는 계정이 없습니다.")); 23 | 24 | return getAccountContext(account); 25 | } 26 | 27 | private AccountContext getAccountContext(Account account) { 28 | return AccountContext.fromAccountModel(account); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/InvalidJwtException.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | public class InvalidJwtException extends RuntimeException { 4 | 5 | public InvalidJwtException(String msg) { 6 | super(msg); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.wheejuni.jwtdemo.security.filters.FilterSkipMatcher; 5 | import com.wheejuni.jwtdemo.security.filters.FormLoginFilter; 6 | import com.wheejuni.jwtdemo.security.filters.JwtAuthenticationFilter; 7 | import com.wheejuni.jwtdemo.security.filters.SocialLoginFilter; 8 | import com.wheejuni.jwtdemo.security.handlers.FormLoginAuthenticationSuccessHandler; 9 | import com.wheejuni.jwtdemo.security.handlers.JwtAuthenticationFailureHandler; 10 | import com.wheejuni.jwtdemo.security.jwt.HeaderTokenExtractor; 11 | import com.wheejuni.jwtdemo.security.providers.FormLoginAuthenticationProvider; 12 | import com.wheejuni.jwtdemo.security.providers.JwtAuthenticationProvider; 13 | import com.wheejuni.jwtdemo.security.providers.SocialLoginAuthenticationProvider; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.context.annotation.Bean; 16 | import org.springframework.context.annotation.Configuration; 17 | import org.springframework.security.authentication.AuthenticationManager; 18 | import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; 19 | import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity; 20 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 21 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 22 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 23 | import org.springframework.security.config.http.SessionCreationPolicy; 24 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 25 | import org.springframework.security.crypto.password.PasswordEncoder; 26 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 27 | 28 | import java.util.Arrays; 29 | 30 | @Configuration 31 | @EnableWebSecurity 32 | @EnableGlobalMethodSecurity(prePostEnabled = true) 33 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 34 | 35 | @Autowired 36 | private FormLoginAuthenticationSuccessHandler formLoginAuthenticationSuccessHandler; 37 | 38 | @Autowired 39 | private FormLoginAuthenticationProvider provider; 40 | 41 | @Autowired 42 | private JwtAuthenticationProvider jwtProvider; 43 | 44 | @Autowired 45 | private SocialLoginAuthenticationProvider socialProvider; 46 | 47 | @Autowired 48 | private JwtAuthenticationFailureHandler jwtFailureHandler; 49 | 50 | @Autowired 51 | private HeaderTokenExtractor headerTokenExtractor; 52 | 53 | @Bean 54 | public PasswordEncoder getPasswordEncoder() { 55 | return new BCryptPasswordEncoder(); 56 | } 57 | 58 | @Bean 59 | public ObjectMapper getObjectMapper() { 60 | return new ObjectMapper(); 61 | } 62 | 63 | @Bean 64 | public AuthenticationManager getAuthenticationManager() throws Exception { 65 | return super.authenticationManagerBean(); 66 | } 67 | 68 | protected FormLoginFilter formLoginFilter() throws Exception { 69 | FormLoginFilter filter = new FormLoginFilter("/formlogin", formLoginAuthenticationSuccessHandler, null); 70 | filter.setAuthenticationManager(super.authenticationManagerBean()); 71 | 72 | return filter; 73 | } 74 | 75 | protected JwtAuthenticationFilter jwtFilter() throws Exception { 76 | FilterSkipMatcher matcher = new FilterSkipMatcher(Arrays.asList("/formlogin", "/social"), "/api/**"); 77 | JwtAuthenticationFilter filter = new JwtAuthenticationFilter(matcher, jwtFailureHandler, headerTokenExtractor); 78 | filter.setAuthenticationManager(super.authenticationManagerBean()); 79 | 80 | return filter; 81 | } 82 | 83 | protected SocialLoginFilter socialFilter() throws Exception { 84 | SocialLoginFilter filter = new SocialLoginFilter("/social", formLoginAuthenticationSuccessHandler); 85 | filter.setAuthenticationManager(super.authenticationManagerBean()); 86 | 87 | return filter; 88 | } 89 | 90 | 91 | 92 | @Override 93 | protected void configure(AuthenticationManagerBuilder auth) throws Exception { 94 | auth 95 | .authenticationProvider(this.provider) 96 | .authenticationProvider(this.socialProvider) 97 | .authenticationProvider(this.jwtProvider); 98 | } 99 | 100 | @Override 101 | protected void configure(HttpSecurity http) throws Exception { 102 | 103 | http 104 | .sessionManagement() 105 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS); 106 | 107 | http 108 | .csrf().disable(); 109 | 110 | http 111 | .headers().frameOptions().disable(); 112 | 113 | http 114 | .authorizeRequests() 115 | .antMatchers("/h2-console**").permitAll(); 116 | 117 | http 118 | .addFilterBefore(formLoginFilter(), UsernamePasswordAuthenticationFilter.class) 119 | .addFilterBefore(socialFilter(), UsernamePasswordAuthenticationFilter.class) 120 | .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/filters/FilterSkipMatcher.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.filters; 2 | 3 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 4 | import org.springframework.security.web.util.matcher.OrRequestMatcher; 5 | import org.springframework.security.web.util.matcher.RequestMatcher; 6 | 7 | import javax.servlet.http.HttpServletRequest; 8 | import java.util.List; 9 | import java.util.stream.Collectors; 10 | 11 | public class FilterSkipMatcher implements RequestMatcher { 12 | 13 | private OrRequestMatcher orRequestMatcher; 14 | private RequestMatcher processingMatcher; 15 | 16 | public FilterSkipMatcher(List pathToSkip, String processingPath) { 17 | this.orRequestMatcher = new OrRequestMatcher(pathToSkip.stream().map(p -> new AntPathRequestMatcher(p)).collect(Collectors.toList())); 18 | this.processingMatcher = new AntPathRequestMatcher(processingPath); 19 | } 20 | 21 | @Override 22 | public boolean matches(HttpServletRequest req) { 23 | return !orRequestMatcher.matches(req) && processingMatcher.matches(req); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/filters/FormLoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.filters; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.wheejuni.jwtdemo.dtos.FormLoginDto; 5 | import com.wheejuni.jwtdemo.security.handlers.FormLoginAuthenticationSuccessHandler; 6 | import com.wheejuni.jwtdemo.security.tokens.PreAuthorizationToken; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 12 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 13 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 14 | 15 | import javax.servlet.FilterChain; 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | public class FormLoginFilter extends AbstractAuthenticationProcessingFilter { 22 | 23 | private AuthenticationSuccessHandler authenticationSuccessHandler; 24 | private AuthenticationFailureHandler authenticationFailureHandler; 25 | 26 | public FormLoginFilter(String defaultUrl, AuthenticationSuccessHandler successHandler, AuthenticationFailureHandler failureHandler) { 27 | super(defaultUrl); 28 | 29 | this.authenticationSuccessHandler = successHandler; 30 | this.authenticationFailureHandler = failureHandler; 31 | } 32 | 33 | 34 | protected FormLoginFilter(String defaultFilterProcessesUrl) { 35 | super(defaultFilterProcessesUrl); 36 | } 37 | 38 | @Override 39 | public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException { 40 | 41 | FormLoginDto dto = new ObjectMapper().readValue(req.getReader(), FormLoginDto.class); 42 | PreAuthorizationToken token = new PreAuthorizationToken(dto); 43 | 44 | return super.getAuthenticationManager().authenticate(token); 45 | } 46 | 47 | @Override 48 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 49 | this.authenticationSuccessHandler.onAuthenticationSuccess(request, response, authResult); 50 | } 51 | 52 | @Override 53 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { 54 | AuthenticationFailureHandler handler = (req, res, exception) -> { 55 | Logger log = LoggerFactory.getLogger("authentication_failure"); 56 | 57 | log.error(exception.getMessage()); 58 | }; 59 | 60 | handler.onAuthenticationFailure(request, response, failed); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/filters/JwtAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.filters; 2 | 3 | import com.wheejuni.jwtdemo.security.jwt.HeaderTokenExtractor; 4 | import com.wheejuni.jwtdemo.security.handlers.JwtAuthenticationFailureHandler; 5 | import com.wheejuni.jwtdemo.security.tokens.JwtPreProcessingToken; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.core.context.SecurityContext; 11 | import org.springframework.security.core.context.SecurityContextHolder; 12 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 13 | import org.springframework.security.web.util.matcher.RequestMatcher; 14 | 15 | import javax.servlet.FilterChain; 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | public class JwtAuthenticationFilter extends AbstractAuthenticationProcessingFilter { 22 | 23 | private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFilter.class); 24 | 25 | private JwtAuthenticationFailureHandler failureHandler; 26 | private HeaderTokenExtractor extractor; 27 | 28 | protected JwtAuthenticationFilter(RequestMatcher matcher) { 29 | super(matcher); 30 | } 31 | 32 | public JwtAuthenticationFilter(RequestMatcher matcher, JwtAuthenticationFailureHandler failureHandler, HeaderTokenExtractor extractor) { 33 | super(matcher); 34 | 35 | this.failureHandler = failureHandler; 36 | this.extractor = extractor; 37 | } 38 | 39 | 40 | @Override 41 | public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException { 42 | 43 | String tokenPayload = req.getHeader("Authorization"); 44 | 45 | JwtPreProcessingToken token = new JwtPreProcessingToken(this.extractor.extract(tokenPayload)); 46 | return super.getAuthenticationManager().authenticate(token); 47 | } 48 | 49 | @Override 50 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 51 | 52 | SecurityContext context = SecurityContextHolder.createEmptyContext(); 53 | 54 | context.setAuthentication(authResult); 55 | SecurityContextHolder.setContext(context); 56 | 57 | chain.doFilter(request, response); 58 | } 59 | 60 | 61 | @Override 62 | protected void unsuccessfulAuthentication(HttpServletRequest request, HttpServletResponse response, AuthenticationException failed) throws IOException, ServletException { 63 | SecurityContextHolder.clearContext(); 64 | 65 | this.unsuccessfulAuthentication(request, response, failed); 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/filters/SocialLoginFilter.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.filters; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 5 | import com.wheejuni.jwtdemo.security.tokens.PreAuthorizationToken; 6 | import com.wheejuni.jwtdemo.security.tokens.SocialPreAuthorizationToken; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.springframework.security.core.Authentication; 9 | import org.springframework.security.core.AuthenticationException; 10 | import org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter; 11 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 12 | 13 | import javax.servlet.FilterChain; 14 | import javax.servlet.ServletException; 15 | import javax.servlet.http.HttpServletRequest; 16 | import javax.servlet.http.HttpServletResponse; 17 | import java.io.IOException; 18 | 19 | @Slf4j 20 | public class SocialLoginFilter extends AbstractAuthenticationProcessingFilter { 21 | 22 | private AuthenticationSuccessHandler successHandler; 23 | 24 | protected SocialLoginFilter(String defaultFilterProcessesUrl) { 25 | super(defaultFilterProcessesUrl); 26 | } 27 | 28 | public SocialLoginFilter(String url, AuthenticationSuccessHandler handler) { 29 | super(url); 30 | this.successHandler = handler; 31 | } 32 | 33 | @Override 34 | public Authentication attemptAuthentication(HttpServletRequest req, HttpServletResponse res) throws AuthenticationException, IOException, ServletException { 35 | 36 | ObjectMapper objectMapper = new ObjectMapper(); 37 | 38 | SocialLoginDto dto = objectMapper.readValue(req.getReader(), SocialLoginDto.class); 39 | return super.getAuthenticationManager().authenticate(new SocialPreAuthorizationToken(dto)); 40 | } 41 | 42 | @Override 43 | protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain, Authentication authResult) throws IOException, ServletException { 44 | this.successHandler.onAuthenticationSuccess(request, response, authResult); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/handlers/FormLoginAuthenticationSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.handlers; 2 | 3 | import com.fasterxml.jackson.core.JsonProcessingException; 4 | import com.fasterxml.jackson.databind.ObjectMapper; 5 | import com.wheejuni.jwtdemo.dtos.TokenDto; 6 | import com.wheejuni.jwtdemo.security.AccountContext; 7 | import com.wheejuni.jwtdemo.security.jwt.JwtFactory; 8 | import com.wheejuni.jwtdemo.security.tokens.PostAuthorizationToken; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.security.core.Authentication; 13 | import org.springframework.security.web.authentication.AuthenticationSuccessHandler; 14 | import org.springframework.stereotype.Component; 15 | 16 | import javax.servlet.ServletException; 17 | import javax.servlet.http.HttpServletRequest; 18 | import javax.servlet.http.HttpServletResponse; 19 | import java.io.IOException; 20 | 21 | @Component 22 | public class FormLoginAuthenticationSuccessHandler implements AuthenticationSuccessHandler { 23 | 24 | @Autowired 25 | private JwtFactory factory; 26 | 27 | @Autowired 28 | private ObjectMapper objectMapper; 29 | 30 | @Override 31 | public void onAuthenticationSuccess(HttpServletRequest req, HttpServletResponse res, Authentication auth) throws IOException, ServletException { 32 | AccountContext context = ((PostAuthorizationToken) auth).getAccountContext(); 33 | 34 | String tokenString = factory.generateToken(context); 35 | 36 | processResponse(res, writeDto(tokenString)); 37 | } 38 | 39 | private TokenDto writeDto(String token) { 40 | return new TokenDto(token); 41 | } 42 | 43 | private void processResponse(HttpServletResponse res, TokenDto dto) throws JsonProcessingException, IOException { 44 | res.setContentType(MediaType.APPLICATION_JSON_UTF8_VALUE); 45 | res.setStatus(HttpStatus.OK.value()); 46 | res.getWriter().write(objectMapper.writeValueAsString(dto)); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/handlers/JwtAuthenticationFailureHandler.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.handlers; 2 | 3 | import com.wheejuni.jwtdemo.security.filters.JwtAuthenticationFilter; 4 | import org.slf4j.Logger; 5 | import org.slf4j.LoggerFactory; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.authentication.AuthenticationFailureHandler; 8 | import org.springframework.stereotype.Component; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | @Component 16 | public class JwtAuthenticationFailureHandler implements AuthenticationFailureHandler { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(JwtAuthenticationFailureHandler.class); 19 | 20 | @Override 21 | public void onAuthenticationFailure(HttpServletRequest req, HttpServletResponse res, AuthenticationException e) throws IOException, ServletException { 22 | log.error(e.getMessage()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/jwt/HeaderTokenExtractor.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.jwt; 2 | 3 | import com.wheejuni.jwtdemo.security.InvalidJwtException; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.util.StringUtils; 6 | 7 | @Component 8 | public class HeaderTokenExtractor { 9 | 10 | public static final String HEADER_PREFIX = "Bearer "; 11 | 12 | public String extract(String header) { 13 | 14 | if(StringUtils.isEmpty(header) | header.length() < HEADER_PREFIX.length()) { 15 | throw new InvalidJwtException("올바른 토큰 정보가 아닙니다."); 16 | } 17 | 18 | return header.substring(HEADER_PREFIX.length(), header.length()); 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/jwt/JwtDecoder.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.jwt; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.JWTVerifier; 5 | import com.auth0.jwt.algorithms.Algorithm; 6 | import com.auth0.jwt.interfaces.DecodedJWT; 7 | import com.wheejuni.jwtdemo.security.AccountContext; 8 | import com.wheejuni.jwtdemo.security.InvalidJwtException; 9 | import org.slf4j.Logger; 10 | import org.slf4j.LoggerFactory; 11 | import org.springframework.stereotype.Component; 12 | 13 | import java.util.Optional; 14 | 15 | @Component 16 | public class JwtDecoder { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(JwtDecoder.class); 19 | 20 | public AccountContext decodeJwt(String token) { 21 | DecodedJWT decodedJWT = isValidToken(token).orElseThrow(() -> new InvalidJwtException("유효한 토큰아 아닙니다.")); 22 | 23 | String username = decodedJWT.getClaim("USERNAME").asString(); 24 | String role = decodedJWT.getClaim("USER_ROLE").asString(); 25 | 26 | return new AccountContext(username, "1234", role); 27 | } 28 | 29 | private Optional isValidToken(String token) { 30 | 31 | DecodedJWT jwt = null; 32 | 33 | try { 34 | Algorithm algorithm = Algorithm.HMAC256("jwttest"); 35 | JWTVerifier verifier = JWT.require(algorithm).build(); 36 | 37 | jwt = verifier.verify(token); 38 | } catch (Exception e) { 39 | log.error(e.getMessage()); 40 | } 41 | 42 | return Optional.ofNullable(jwt); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/jwt/JwtFactory.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.jwt; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import com.wheejuni.jwtdemo.security.AccountContext; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.stereotype.Component; 9 | 10 | import java.io.UnsupportedEncodingException; 11 | 12 | @Component 13 | public class JwtFactory { 14 | 15 | private static final Logger log = LoggerFactory.getLogger(JwtFactory.class); 16 | 17 | private static String signingKey = "jwttest"; 18 | 19 | public String generateToken(AccountContext context) { 20 | 21 | String token = null; 22 | 23 | 24 | 25 | try { 26 | token = JWT.create() 27 | .withIssuer("bomeehouse") 28 | .withClaim("USERNAME", context.getAccount().getUserId()) 29 | .withClaim("USER_ROLE", context.getAccount().getUserRole().getRoleName()) 30 | .sign(generateAlgorithm()); 31 | 32 | } catch (Exception e) { 33 | log.error(e.getMessage()); 34 | } 35 | 36 | return token; 37 | } 38 | 39 | private Algorithm generateAlgorithm() throws UnsupportedEncodingException { 40 | return Algorithm.HMAC256(signingKey); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/providers/FormLoginAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.providers; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.domain.AccountRepository; 5 | import com.wheejuni.jwtdemo.security.AccountContext; 6 | import com.wheejuni.jwtdemo.security.AccountContextService; 7 | import com.wheejuni.jwtdemo.security.tokens.PostAuthorizationToken; 8 | import com.wheejuni.jwtdemo.security.tokens.PreAuthorizationToken; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.security.authentication.AuthenticationProvider; 11 | import org.springframework.security.core.Authentication; 12 | import org.springframework.security.core.AuthenticationException; 13 | import org.springframework.security.crypto.password.PasswordEncoder; 14 | import org.springframework.stereotype.Component; 15 | 16 | import java.util.NoSuchElementException; 17 | 18 | @Component 19 | public class FormLoginAuthenticationProvider implements AuthenticationProvider { 20 | 21 | @Autowired 22 | private PasswordEncoder passwordEncoder; 23 | 24 | @Autowired 25 | private AccountRepository accountRepository; 26 | 27 | @Override 28 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 29 | 30 | PreAuthorizationToken token = (PreAuthorizationToken)authentication; 31 | 32 | String username = token.getUsername(); 33 | String password = token.getUserPassword(); 34 | 35 | Account account = accountRepository.findByUserId(username).orElseThrow(() -> new NoSuchElementException("정보에 맞는 계정이 없습니다.")); 36 | 37 | if(isCorrectPassword(password, account)) { 38 | return PostAuthorizationToken.getTokenFromAccountContext(AccountContext.fromAccountModel(account)); 39 | } 40 | 41 | throw new NoSuchElementException("인증 정보가 정확하지 않습니다."); 42 | } 43 | 44 | @Override 45 | public boolean supports(Class aClass) { 46 | return PreAuthorizationToken.class.isAssignableFrom(aClass); 47 | } 48 | 49 | private boolean isCorrectPassword(String password, Account account) { 50 | return passwordEncoder.matches(password, account.getPassword()); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/providers/JwtAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.providers; 2 | 3 | import com.wheejuni.jwtdemo.security.AccountContext; 4 | import com.wheejuni.jwtdemo.security.jwt.JwtDecoder; 5 | import com.wheejuni.jwtdemo.security.tokens.JwtPreProcessingToken; 6 | import com.wheejuni.jwtdemo.security.tokens.PostAuthorizationToken; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.security.authentication.AuthenticationProvider; 9 | import org.springframework.security.core.Authentication; 10 | import org.springframework.security.core.AuthenticationException; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Component 14 | public class JwtAuthenticationProvider implements AuthenticationProvider { 15 | 16 | @Autowired 17 | private JwtDecoder jwtDecoder; 18 | 19 | @Override 20 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 21 | 22 | String token = (String)authentication.getPrincipal(); 23 | AccountContext context = jwtDecoder.decodeJwt(token); 24 | 25 | return PostAuthorizationToken.getTokenFromAccountContext(context); 26 | } 27 | 28 | @Override 29 | public boolean supports(Class aClass) { 30 | return JwtPreProcessingToken.class.isAssignableFrom(aClass); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/providers/SocialLoginAuthenticationProvider.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.providers; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.domain.AccountRepository; 5 | import com.wheejuni.jwtdemo.domain.SocialProviders; 6 | import com.wheejuni.jwtdemo.domain.UserRole; 7 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 8 | import com.wheejuni.jwtdemo.security.AccountContext; 9 | import com.wheejuni.jwtdemo.security.services.specification.SocialFetchService; 10 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 11 | import com.wheejuni.jwtdemo.security.tokens.PostAuthorizationToken; 12 | import com.wheejuni.jwtdemo.security.tokens.SocialPreAuthorizationToken; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.beans.factory.annotation.Qualifier; 15 | import org.springframework.security.authentication.AuthenticationProvider; 16 | import org.springframework.security.core.Authentication; 17 | import org.springframework.security.core.AuthenticationException; 18 | import org.springframework.stereotype.Component; 19 | 20 | import java.util.UUID; 21 | 22 | @Component 23 | public class SocialLoginAuthenticationProvider implements AuthenticationProvider { 24 | 25 | @Autowired 26 | private AccountRepository accountRepository; 27 | 28 | @Qualifier("socialFetchServiceProd") 29 | @Autowired 30 | private SocialFetchService service; 31 | 32 | @Override 33 | public Authentication authenticate(Authentication authentication) throws AuthenticationException { 34 | SocialPreAuthorizationToken token = (SocialPreAuthorizationToken)authentication; 35 | SocialLoginDto dto = token.getDto(); 36 | 37 | return PostAuthorizationToken.getTokenFromAccountContext(AccountContext.fromAccountModel(getAccount(dto))); 38 | } 39 | 40 | @Override 41 | public boolean supports(Class aClass) { 42 | return SocialPreAuthorizationToken.class.isAssignableFrom(aClass); 43 | } 44 | 45 | private Account getAccount(SocialLoginDto dto) { 46 | SocialUserProperty property = service.getSocialUserInfo(dto); 47 | 48 | String userId = property.getUserId(); 49 | SocialProviders provider = dto.getProvider(); 50 | 51 | return accountRepository.findBySocialIdAndSocialProvider(Long.valueOf(userId), provider) 52 | .orElseGet(() -> accountRepository.save( 53 | new Account(null, property.getUserNickname(), "SOCIAL_USER", String.valueOf(UUID.randomUUID().getMostSignificantBits()), UserRole.USER, Long.valueOf(property.getUserId()), provider, property.getProfileHref()))); 54 | 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/services/impl/SocialFetchServiceProd.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.services.impl; 2 | 3 | import com.wheejuni.jwtdemo.domain.SocialProviders; 4 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 5 | import com.wheejuni.jwtdemo.security.services.specification.SocialFetchService; 6 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.http.HttpEntity; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpMethod; 11 | import org.springframework.stereotype.Service; 12 | import org.springframework.web.client.RestTemplate; 13 | 14 | @Service 15 | public class SocialFetchServiceProd implements SocialFetchService { 16 | 17 | private static final String HEADER_PREFIX = "Bearer "; 18 | 19 | @Override 20 | public SocialUserProperty getSocialUserInfo(SocialLoginDto dto) { 21 | SocialProviders provider = dto.getProvider(); 22 | RestTemplate restTemplate = new RestTemplate(); 23 | 24 | HttpEntity entity = new HttpEntity<>("parameter", generateHeader(dto.getToken())); 25 | 26 | return restTemplate.exchange(provider.getUserinfoEndpoint(), HttpMethod.GET, entity, provider.getPropertyMetaclass()).getBody(); 27 | } 28 | 29 | private HttpHeaders generateHeader(String token) { 30 | HttpHeaders header = new HttpHeaders(); 31 | 32 | header.add("Authorization", generateHeaderContent(token)); 33 | return header; 34 | } 35 | 36 | private String generateHeaderContent(String token) { 37 | StringBuilder sb = new StringBuilder(); 38 | 39 | sb.append(HEADER_PREFIX); 40 | sb.append(token); 41 | 42 | return sb.toString(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/services/impl/SocialFetchServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.services.impl; 2 | 3 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 4 | import com.wheejuni.jwtdemo.security.services.specification.SocialFetchService; 5 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SocialFetchServiceTest implements SocialFetchService { 10 | 11 | @Override 12 | public SocialUserProperty getSocialUserInfo(SocialLoginDto dto) { 13 | return null; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/services/specification/SocialFetchService.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.services.specification; 2 | 3 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 4 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 5 | 6 | public interface SocialFetchService { 7 | 8 | SocialUserProperty getSocialUserInfo(SocialLoginDto dto); 9 | 10 | } 11 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/social/KakaoUserProperty.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.social 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | 5 | data class KakaoUserProperty( 6 | 7 | @field:JsonProperty("kaccount_email") 8 | private val userEmail: String? = null, 9 | 10 | @field:JsonProperty("kaccount_email_verified") 11 | val verified: Boolean = false, 12 | 13 | @field:JsonProperty("id") 14 | private val userUniqueId: Long? = null, 15 | 16 | @field:JsonProperty("properties") 17 | private val userProperties: MutableMap? = null): SocialUserProperty { 18 | 19 | override fun getUserId(): String { 20 | return userUniqueId.toString() 21 | } 22 | 23 | override fun getUserNickname(): String { 24 | return userProperties!!["nickname"]!! 25 | } 26 | 27 | override fun getProfileHref(): String { 28 | return userProperties!!["profile_image"]!! 29 | } 30 | 31 | override fun getEmail(): String { 32 | return userEmail!! 33 | } 34 | 35 | 36 | } -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/social/NaverUserProperty.kt: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.social 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty 4 | 5 | data class NaverUserProperty( 6 | 7 | @field:JsonProperty("resultcode") 8 | val resultcode: String? = null, 9 | 10 | @field:JsonProperty("message") 11 | val message: String? = null, 12 | 13 | @field:JsonProperty("response") 14 | private val properties: MutableMap? = null): SocialUserProperty { 15 | 16 | override fun getUserId(): String { 17 | return properties!!["id"]!! 18 | } 19 | 20 | override fun getUserNickname(): String { 21 | return properties!!["nickname"]!! 22 | } 23 | 24 | override fun getProfileHref(): String { 25 | return properties!!["profile_image"]!! 26 | } 27 | 28 | override fun getEmail(): String { 29 | return properties!!["email"]!! 30 | } 31 | 32 | } -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/social/SocialUserProperty.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.social; 2 | 3 | public interface SocialUserProperty { 4 | 5 | String getUserId(); 6 | 7 | String getUserNickname(); 8 | 9 | String getProfileHref(); 10 | 11 | String getEmail(); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/tokens/JwtPostProcessingToken.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.tokens; 2 | 3 | import com.wheejuni.jwtdemo.domain.UserRole; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.GrantedAuthority; 6 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 7 | 8 | import java.util.Arrays; 9 | import java.util.Collection; 10 | import java.util.stream.Collectors; 11 | 12 | public class JwtPostProcessingToken extends UsernamePasswordAuthenticationToken { 13 | 14 | private JwtPostProcessingToken(Object principal, Object credentials, Collection authorities) { 15 | super(principal, credentials, authorities); 16 | } 17 | 18 | public JwtPostProcessingToken(String username, UserRole role) { 19 | super(username, "1234", parseAuthorities(role)); 20 | } 21 | 22 | private static Collection parseAuthorities(UserRole role) { 23 | return Arrays.asList(role).stream().map(r -> new SimpleGrantedAuthority(r.getRoleName())).collect(Collectors.toList()); 24 | } 25 | 26 | public String getUserid() { 27 | return (String)super.getPrincipal(); 28 | } 29 | 30 | public String getPassword() { 31 | return (String)super.getCredentials(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/tokens/JwtPreProcessingToken.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.tokens; 2 | 3 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 4 | 5 | public class JwtPreProcessingToken extends UsernamePasswordAuthenticationToken { 6 | 7 | private JwtPreProcessingToken(Object principal, Object credentials) { 8 | super(principal, credentials); 9 | } 10 | 11 | public JwtPreProcessingToken(String token) { 12 | this(token, token.length()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/tokens/PostAuthorizationToken.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.tokens; 2 | 3 | import com.wheejuni.jwtdemo.security.AccountContext; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | import org.springframework.security.core.GrantedAuthority; 6 | 7 | import java.util.Collection; 8 | 9 | public class PostAuthorizationToken extends UsernamePasswordAuthenticationToken { 10 | 11 | private PostAuthorizationToken(Object principal, Object credentials, Collection authorities) { 12 | super(principal, credentials, authorities); 13 | } 14 | 15 | public AccountContext getAccountContext() { 16 | return (AccountContext)super.getPrincipal(); 17 | } 18 | 19 | public static PostAuthorizationToken getTokenFromAccountContext(AccountContext context) { 20 | return new PostAuthorizationToken(context, context.getPassword(), context.getAuthorities()); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/tokens/PreAuthorizationToken.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.tokens; 2 | 3 | import com.wheejuni.jwtdemo.domain.SocialProviders; 4 | import com.wheejuni.jwtdemo.dtos.FormLoginDto; 5 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | 8 | public class PreAuthorizationToken extends UsernamePasswordAuthenticationToken { 9 | 10 | private PreAuthorizationToken(String username, String password) { 11 | super(username, password); 12 | } 13 | 14 | private PreAuthorizationToken(SocialProviders providers, SocialLoginDto dto) { 15 | super(providers, dto); 16 | } 17 | 18 | public PreAuthorizationToken(FormLoginDto dto) { 19 | this(dto.getId(), dto.getPassword()); 20 | } 21 | 22 | public PreAuthorizationToken(SocialLoginDto dto) { 23 | this(dto.getProvider(), dto); 24 | } 25 | 26 | public String getUsername() { 27 | return (String)super.getPrincipal(); 28 | } 29 | 30 | public String getUserPassword() { 31 | return (String)super.getCredentials(); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/wheejuni/jwtdemo/security/tokens/SocialPreAuthorizationToken.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.tokens; 2 | 3 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 4 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 5 | 6 | public class SocialPreAuthorizationToken extends UsernamePasswordAuthenticationToken { 7 | 8 | public SocialPreAuthorizationToken(SocialLoginDto dto) { 9 | super(dto.getProvider(), dto); 10 | } 11 | 12 | public SocialLoginDto getDto() { 13 | return (SocialLoginDto)super.getCredentials(); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | profiles: 3 | active: local-dev 4 | 5 | --- 6 | spring: 7 | profiles: local-dev 8 | datasource: 9 | url: jdbc:h2:~/jwt-demo;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE;AUTO_SERVER=true 10 | driver-class-name: org.h2.Driver 11 | username: sa 12 | password: 13 | platform: org.hibernate.dialect.H2Dialect 14 | 15 | h2: 16 | console: 17 | enabled: true 18 | jpa: 19 | hibernate: 20 | ddl-auto: create 21 | show-sql: true 22 | 23 | jwt: 24 | signingKey: test 25 | issuer: bomee 26 | expiry: 14400 -------------------------------------------------------------------------------- /src/test/java/com/wheejuni/jwtdemo/JwtdemoApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.junit4.SpringRunner; 7 | 8 | @RunWith(SpringRunner.class) 9 | @SpringBootTest 10 | public class JwtdemoApplicationTests { 11 | 12 | @Test 13 | public void contextLoads() { 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/test/java/com/wheejuni/jwtdemo/security/HeaderTokenExtractorTest.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | import com.wheejuni.jwtdemo.security.jwt.HeaderTokenExtractor; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | 7 | import static org.hamcrest.CoreMatchers.is; 8 | import static org.junit.Assert.*; 9 | 10 | public class HeaderTokenExtractorTest { 11 | 12 | private HeaderTokenExtractor extractor = new HeaderTokenExtractor(); 13 | private String header; 14 | 15 | @Before 16 | public void setUp() { 17 | this.header = "Bearer asdfhakjsdh.asdfdhfkhdkfj"; 18 | } 19 | 20 | @Test 21 | public void TEST_JWT_EXTRACT() { 22 | assertThat(extractor.extract(this.header), is("asdfhakjsdh.asdfdhfkhdkfj")); 23 | } 24 | 25 | } -------------------------------------------------------------------------------- /src/test/java/com/wheejuni/jwtdemo/security/JwtFactoryTest.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security; 2 | 3 | import com.wheejuni.jwtdemo.domain.Account; 4 | import com.wheejuni.jwtdemo.security.jwt.JwtFactory; 5 | import org.junit.Before; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.slf4j.Logger; 9 | import org.slf4j.LoggerFactory; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.SpringBootTest; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | 14 | @SpringBootTest 15 | @RunWith(SpringRunner.class) 16 | public class JwtFactoryTest { 17 | 18 | private static final Logger log = LoggerFactory.getLogger(JwtFactoryTest.class); 19 | 20 | private AccountContext context; 21 | 22 | @Autowired 23 | private JwtFactory factory; 24 | 25 | @Before 26 | public void setUp() { 27 | Account account = new Account(); 28 | log.error("userid: {}, password: {}, role: {}", account.getUserId(), account.getPassword(), account.getUserRole()); 29 | this.context = AccountContext.fromAccountModel(account); 30 | } 31 | 32 | @Test 33 | public void TEST_JWT_GENERATE() { 34 | log.error(factory.generateToken(this.context)); 35 | } 36 | } -------------------------------------------------------------------------------- /src/test/java/com/wheejuni/jwtdemo/security/services/specification/SocialFetchServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.wheejuni.jwtdemo.security.services.specification; 2 | 3 | import com.wheejuni.jwtdemo.domain.SocialProviders; 4 | import com.wheejuni.jwtdemo.dtos.SocialLoginDto; 5 | import com.wheejuni.jwtdemo.security.services.impl.SocialFetchServiceProd; 6 | import com.wheejuni.jwtdemo.security.social.SocialUserProperty; 7 | import lombok.extern.slf4j.Slf4j; 8 | import org.junit.Before; 9 | import org.junit.Test; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.web.client.RestTemplate; 12 | 13 | import static org.hamcrest.CoreMatchers.is; 14 | import static org.junit.Assert.*; 15 | 16 | @Slf4j 17 | public class SocialFetchServiceTest { 18 | 19 | private SocialFetchServiceProd prod = new SocialFetchServiceProd(); 20 | private SocialLoginDto dto; 21 | 22 | @Before 23 | public void setUp() { 24 | this.dto = new SocialLoginDto(SocialProviders.KAKAO, "lxLHHcv9X8Qt2xxpgdgME7n7oxpX9eINQ_3i4wopdtYAAAFjRJ6Fhw"); 25 | } 26 | 27 | 28 | @Test 29 | public void restTemplate_Practice1() { 30 | log.debug(new RestTemplate().getForObject("http://www.naver.com", String.class)); 31 | } 32 | 33 | @Test 34 | public void service_fetchSocialInfo() { 35 | SocialUserProperty property = prod.getSocialUserInfo(this.dto); 36 | 37 | assertThat(property.getEmail(), is("wheejuni@gmail.com")); 38 | } 39 | 40 | } -------------------------------------------------------------------------------- /test-http/hello.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8080/api/hello 2 | Authorization: Bearer token_value 3 | 4 | ### -------------------------------------------------------------------------------- /test-http/loggedin-request.http: -------------------------------------------------------------------------------- 1 | GET http://localhost:8080/api/hello 2 | Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJib21lZWhvdXNlIiwiVVNFUk5BTUUiOiJlbWFseXVuQG5hdmVyLmNvbSIsIlVTRVJfUk9MRSI6IlJPTEVfVVNFUiJ9.xE3sdMteckD1zO01cvqCWYijxy5GwBPYz7y7vC6I5hU 3 | ### 4 | -------------------------------------------------------------------------------- /test-http/login-naver.http: -------------------------------------------------------------------------------- 1 | GET https://nid.naver.com/oauth2.0/token 2 | -------------------------------------------------------------------------------- /test-http/login-request.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:8080/formlogin 2 | Content-Type: application/json 3 | 4 | {"userid" : "emalyun@naver.com", "password" : "1234"} 5 | 6 | ### 7 | -------------------------------------------------------------------------------- /test-http/login-social.http: -------------------------------------------------------------------------------- 1 | POST http://localhost:8080/social 2 | Content-Type: application/json 3 | 4 | {"provider" : "NAVER", "token" : "AAAAQnNiK8JK2IpwABX/7P8A5dPfoiqkl9cbfe6f1HiqVnHBhk4Gsdw0AEvIVtKQSHqV2d6kpE00uXtSPp8r8HAFqtN1TlbWQCoEt89XTvYzrxD5"} 5 | 6 | ### --------------------------------------------------------------------------------