├── .gitignore ├── README.md ├── build.gradle ├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle └── src ├── main ├── java │ └── me │ │ └── benny │ │ └── practice │ │ └── spring │ │ └── security │ │ ├── SpringSecurityPracticeApplication.java │ │ ├── admin │ │ └── AdminController.java │ │ ├── config │ │ ├── InitializeDefaultConfig.java │ │ ├── JpaAuditorConfig.java │ │ ├── MvcConfig.java │ │ ├── PasswordEncoderConfig.java │ │ └── SpringSecurityConfig.java │ │ ├── note │ │ ├── Note.java │ │ ├── NoteController.java │ │ ├── NoteRegisterDto.java │ │ ├── NoteRepository.java │ │ └── NoteService.java │ │ ├── notice │ │ ├── Notice.java │ │ ├── NoticeController.java │ │ ├── NoticeRepository.java │ │ └── NoticeService.java │ │ └── user │ │ ├── AlreadyRegisteredUserException.java │ │ ├── SignUpController.java │ │ ├── User.java │ │ ├── UserNotFoundException.java │ │ ├── UserRegisterDto.java │ │ ├── UserRepository.java │ │ └── UserService.java └── resources │ ├── application.yml │ ├── static │ ├── css │ │ └── signin.css │ └── images │ │ └── spring-security.png │ └── templates │ ├── admin │ └── index.html │ ├── fragments.html │ ├── index.html │ ├── login.html │ ├── note │ └── index.html │ ├── notice │ └── index.html │ └── signup.html └── test └── java └── me └── benny └── practice └── spring └── security ├── admin └── AdminControllerTest.java ├── note ├── NoteControllerTest.java └── NoteServiceTest.java ├── notice └── NoticeControllerTest.java └── user ├── SignUpControllerTest.java └── UserServiceTest.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### idea ### 2 | .idea/ 3 | 4 | ### Gradle ### 5 | .gradle 6 | build/ 7 | out/ 8 | src/main/resources/static/.DS_Store 9 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### 개인 보안 노트 서비스 만들기 2 | #### main branch 3 | * Spring Security 단독 Project 4 | #### jwt branch 5 | * Spring Security + JWT Project 6 | -------------------------------------------------------------------------------- /build.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id 'java' 3 | // spring boot version 2.5.2 4 | id 'org.springframework.boot' version '2.5.2' 5 | id "io.spring.dependency-management" version "1.0.11.RELEASE" 6 | } 7 | 8 | group 'me.benny' 9 | version '1.0-SNAPSHOT' 10 | 11 | repositories { 12 | mavenCentral() 13 | } 14 | 15 | dependencies { 16 | // web mvc 추가 17 | implementation 'org.springframework.boot:spring-boot-starter-web' 18 | // 이 강의의 주제인 Security 추가 19 | implementation 'org.springframework.boot:spring-boot-starter-security' 20 | // 웹 페이지를 쉽게 생성하기 위해 thymeleaf 추가 21 | implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' 22 | // Thymeleaf에서 SpringSecurity를 Integration 23 | implementation 'org.thymeleaf.extras:thymeleaf-extras-springsecurity5' 24 | // lombok 25 | compileOnly 'org.projectlombok:lombok:1.18.20' 26 | annotationProcessor 'org.projectlombok:lombok:1.18.20' 27 | // spring data jpa 28 | implementation 'org.springframework.boot:spring-boot-starter-data-jpa' 29 | // h2 30 | runtimeOnly 'com.h2database:h2' 31 | // starter test 32 | testImplementation 'org.springframework.boot:spring-boot-starter-test' 33 | // security test 34 | testImplementation 'org.springframework.security:spring-security-test' 35 | // junit test 36 | testImplementation 'org.junit.jupiter:junit-jupiter-api:5.7.0' 37 | testRuntimeOnly 'org.junit.jupiter:junit-jupiter-engine:5.7.0' 38 | } 39 | 40 | test { 41 | useJUnitPlatform() 42 | } -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kker5/spring-security-practice/45f532d9404c28df22e59586b23402d63e1fd981/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.8-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # 4 | # Copyright 2015 the original author or authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | 19 | ############################################################################## 20 | ## 21 | ## Gradle start up script for UN*X 22 | ## 23 | ############################################################################## 24 | 25 | # Attempt to set APP_HOME 26 | # Resolve links: $0 may be a link 27 | PRG="$0" 28 | # Need this for relative symlinks. 29 | while [ -h "$PRG" ] ; do 30 | ls=`ls -ld "$PRG"` 31 | link=`expr "$ls" : '.*-> \(.*\)$'` 32 | if expr "$link" : '/.*' > /dev/null; then 33 | PRG="$link" 34 | else 35 | PRG=`dirname "$PRG"`"/$link" 36 | fi 37 | done 38 | SAVED="`pwd`" 39 | cd "`dirname \"$PRG\"`/" >/dev/null 40 | APP_HOME="`pwd -P`" 41 | cd "$SAVED" >/dev/null 42 | 43 | APP_NAME="Gradle" 44 | APP_BASE_NAME=`basename "$0"` 45 | 46 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 47 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 48 | 49 | # Use the maximum available, or set MAX_FD != -1 to use that value. 50 | MAX_FD="maximum" 51 | 52 | warn () { 53 | echo "$*" 54 | } 55 | 56 | die () { 57 | echo 58 | echo "$*" 59 | echo 60 | exit 1 61 | } 62 | 63 | # OS specific support (must be 'true' or 'false'). 64 | cygwin=false 65 | msys=false 66 | darwin=false 67 | nonstop=false 68 | case "`uname`" in 69 | CYGWIN* ) 70 | cygwin=true 71 | ;; 72 | Darwin* ) 73 | darwin=true 74 | ;; 75 | MINGW* ) 76 | msys=true 77 | ;; 78 | NONSTOP* ) 79 | nonstop=true 80 | ;; 81 | esac 82 | 83 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 84 | 85 | 86 | # Determine the Java command to use to start the JVM. 87 | if [ -n "$JAVA_HOME" ] ; then 88 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 89 | # IBM's JDK on AIX uses strange locations for the executables 90 | JAVACMD="$JAVA_HOME/jre/sh/java" 91 | else 92 | JAVACMD="$JAVA_HOME/bin/java" 93 | fi 94 | if [ ! -x "$JAVACMD" ] ; then 95 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 96 | 97 | Please set the JAVA_HOME variable in your environment to match the 98 | location of your Java installation." 99 | fi 100 | else 101 | JAVACMD="java" 102 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 103 | 104 | Please set the JAVA_HOME variable in your environment to match the 105 | location of your Java installation." 106 | fi 107 | 108 | # Increase the maximum file descriptors if we can. 109 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then 110 | MAX_FD_LIMIT=`ulimit -H -n` 111 | if [ $? -eq 0 ] ; then 112 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then 113 | MAX_FD="$MAX_FD_LIMIT" 114 | fi 115 | ulimit -n $MAX_FD 116 | if [ $? -ne 0 ] ; then 117 | warn "Could not set maximum file descriptor limit: $MAX_FD" 118 | fi 119 | else 120 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" 121 | fi 122 | fi 123 | 124 | # For Darwin, add options to specify how the application appears in the dock 125 | if $darwin; then 126 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" 127 | fi 128 | 129 | # For Cygwin or MSYS, switch paths to Windows format before running java 130 | if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then 131 | APP_HOME=`cygpath --path --mixed "$APP_HOME"` 132 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` 133 | 134 | JAVACMD=`cygpath --unix "$JAVACMD"` 135 | 136 | # We build the pattern for arguments to be converted via cygpath 137 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` 138 | SEP="" 139 | for dir in $ROOTDIRSRAW ; do 140 | ROOTDIRS="$ROOTDIRS$SEP$dir" 141 | SEP="|" 142 | done 143 | OURCYGPATTERN="(^($ROOTDIRS))" 144 | # Add a user-defined pattern to the cygpath arguments 145 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then 146 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" 147 | fi 148 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 149 | i=0 150 | for arg in "$@" ; do 151 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` 152 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option 153 | 154 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition 155 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` 156 | else 157 | eval `echo args$i`="\"$arg\"" 158 | fi 159 | i=`expr $i + 1` 160 | done 161 | case $i in 162 | 0) set -- ;; 163 | 1) set -- "$args0" ;; 164 | 2) set -- "$args0" "$args1" ;; 165 | 3) set -- "$args0" "$args1" "$args2" ;; 166 | 4) set -- "$args0" "$args1" "$args2" "$args3" ;; 167 | 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; 168 | 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; 169 | 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; 170 | 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; 171 | 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; 172 | esac 173 | fi 174 | 175 | # Escape application args 176 | save () { 177 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done 178 | echo " " 179 | } 180 | APP_ARGS=`save "$@"` 181 | 182 | # Collect all arguments for the java command, following the shell quoting and substitution rules 183 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" 184 | 185 | exec "$JAVACMD" "$@" 186 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | 17 | @if "%DEBUG%" == "" @echo off 18 | @rem ########################################################################## 19 | @rem 20 | @rem Gradle startup script for Windows 21 | @rem 22 | @rem ########################################################################## 23 | 24 | @rem Set local scope for the variables with windows NT shell 25 | if "%OS%"=="Windows_NT" setlocal 26 | 27 | set DIRNAME=%~dp0 28 | if "%DIRNAME%" == "" set DIRNAME=. 29 | set APP_BASE_NAME=%~n0 30 | set APP_HOME=%DIRNAME% 31 | 32 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 33 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 34 | 35 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 36 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 37 | 38 | @rem Find java.exe 39 | if defined JAVA_HOME goto findJavaFromJavaHome 40 | 41 | set JAVA_EXE=java.exe 42 | %JAVA_EXE% -version >NUL 2>&1 43 | if "%ERRORLEVEL%" == "0" goto execute 44 | 45 | echo. 46 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 47 | echo. 48 | echo Please set the JAVA_HOME variable in your environment to match the 49 | echo location of your Java installation. 50 | 51 | goto fail 52 | 53 | :findJavaFromJavaHome 54 | set JAVA_HOME=%JAVA_HOME:"=% 55 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 56 | 57 | if exist "%JAVA_EXE%" goto execute 58 | 59 | echo. 60 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 61 | echo. 62 | echo Please set the JAVA_HOME variable in your environment to match the 63 | echo location of your Java installation. 64 | 65 | goto fail 66 | 67 | :execute 68 | @rem Setup the command line 69 | 70 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 71 | 72 | 73 | @rem Execute Gradle 74 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 75 | 76 | :end 77 | @rem End local scope for the variables with windows NT shell 78 | if "%ERRORLEVEL%"=="0" goto mainEnd 79 | 80 | :fail 81 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 82 | rem the _cmd.exe /c_ return code! 83 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 84 | exit /b 1 85 | 86 | :mainEnd 87 | if "%OS%"=="Windows_NT" endlocal 88 | 89 | :omega 90 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | rootProject.name = 'spring-security-practice' -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/SpringSecurityPracticeApplication.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | 6 | /** 7 | * SpringSecurity 연습 Application 8 | */ 9 | @SpringBootApplication 10 | public class SpringSecurityPracticeApplication { 11 | public static void main(String[] args) { 12 | SpringApplication.run(SpringSecurityPracticeApplication.class, args); 13 | } 14 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/admin/AdminController.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.admin; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.note.Note; 5 | import me.benny.practice.spring.security.note.NoteService; 6 | import me.benny.practice.spring.security.user.User; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.stereotype.Controller; 9 | import org.springframework.ui.Model; 10 | import org.springframework.web.bind.annotation.GetMapping; 11 | import org.springframework.web.bind.annotation.RequestMapping; 12 | 13 | import java.util.List; 14 | 15 | @Controller 16 | @RequiredArgsConstructor 17 | @RequestMapping("/admin") 18 | public class AdminController { 19 | 20 | private final NoteService noteService; 21 | 22 | /** 23 | * 어드민인 경우 노트 조회 24 | * 25 | * @return admin/index.html 26 | */ 27 | @GetMapping 28 | public String getNoteForAdmin(Authentication authentication, Model model) { 29 | User user = (User) authentication.getPrincipal(); 30 | List notes = noteService.findByUser(user); 31 | model.addAttribute("notes", notes); 32 | return "admin/index"; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/config/InitializeDefaultConfig.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.note.NoteService; 5 | import me.benny.practice.spring.security.notice.NoticeService; 6 | import me.benny.practice.spring.security.user.User; 7 | import me.benny.practice.spring.security.user.UserService; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.context.annotation.Profile; 11 | 12 | /** 13 | * 초기 상태 등록 Config 14 | */ 15 | @Configuration 16 | @RequiredArgsConstructor 17 | @Profile(value = "!test") // test 에서는 제외 18 | public class InitializeDefaultConfig { 19 | 20 | private final UserService userService; 21 | private final NoteService noteService; 22 | private final NoticeService noticeService; 23 | 24 | /** 25 | * 유저등록, note 4개 등록 26 | */ 27 | @Bean 28 | public void initializeDefaultUser() { 29 | User user = userService.signup("user", "user"); 30 | noteService.saveNote(user, "테스트", "테스트입니다."); 31 | noteService.saveNote(user, "테스트2", "테스트2입니다."); 32 | noteService.saveNote(user, "테스트3", "테스트3입니다."); 33 | noteService.saveNote(user, "여름 여행계획", "여름 여행계획 작성중..."); 34 | } 35 | 36 | /** 37 | * 어드민등록, 공지사항 2개 등록 38 | */ 39 | @Bean 40 | public void initializeDefaultAdmin() { 41 | userService.signupAdmin("admin", "admin"); 42 | noticeService.saveNotice("환영합니다.", "환영합니다 여러분"); 43 | noticeService.saveNotice("노트 작성 방법 공지", "1. 회원가입\n2. 로그인\n3. 노트 작성\n4. 저장\n* 본인 외에는 게시글을 볼 수 없습니다."); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/config/JpaAuditorConfig.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.data.jpa.repository.config.EnableJpaAuditing; 5 | 6 | /** 7 | * JPA auditor enable 8 | */ 9 | @Configuration 10 | @EnableJpaAuditing // JpaAuditing을 Enable 11 | public class JpaAuditorConfig { 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/config/MvcConfig.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.config; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.web.servlet.config.annotation.ViewControllerRegistry; 5 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 6 | 7 | /** 8 | * WebMVC Config 9 | */ 10 | @Configuration 11 | public class MvcConfig implements WebMvcConfigurer { 12 | 13 | public void addViewControllers(ViewControllerRegistry registry) { 14 | registry.addViewController("/home").setViewName("index"); 15 | registry.addViewController("/").setViewName("index"); 16 | registry.addViewController("/login").setViewName("login"); 17 | } 18 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/config/PasswordEncoderConfig.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.context.annotation.Configuration; 5 | import org.springframework.security.crypto.factory.PasswordEncoderFactories; 6 | import org.springframework.security.crypto.password.PasswordEncoder; 7 | 8 | /** 9 | * PasswordEncoder Config 10 | */ 11 | @Configuration 12 | public class PasswordEncoderConfig { 13 | 14 | @Bean 15 | public PasswordEncoder passwordEncoder() { 16 | return PasswordEncoderFactories.createDelegatingPasswordEncoder(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/config/SpringSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.config; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.user.User; 5 | import me.benny.practice.spring.security.user.UserService; 6 | import org.springframework.boot.autoconfigure.security.servlet.PathRequest; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | import org.springframework.http.HttpMethod; 10 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 11 | import org.springframework.security.config.annotation.web.builders.WebSecurity; 12 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 13 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 14 | import org.springframework.security.core.userdetails.UserDetailsService; 15 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 16 | import org.springframework.security.web.util.matcher.AntPathRequestMatcher; 17 | 18 | /** 19 | * Security 설정 Config 20 | */ 21 | @Configuration 22 | @EnableWebSecurity 23 | @RequiredArgsConstructor 24 | public class SpringSecurityConfig extends WebSecurityConfigurerAdapter { 25 | 26 | private final UserService userService; 27 | 28 | @Override 29 | protected void configure(HttpSecurity http) throws Exception { 30 | // basic authentication 31 | http.httpBasic().disable(); // basic authentication filter 비활성화 32 | // csrf 33 | http.csrf(); 34 | // remember-me 35 | http.rememberMe(); 36 | // authorization 37 | http.authorizeRequests() 38 | // /와 /home은 모두에게 허용 39 | .antMatchers("/", "/home", "/signup").permitAll() 40 | // hello 페이지는 USER 롤을 가진 유저에게만 허용 41 | .antMatchers("/note").hasRole("USER") 42 | .antMatchers("/admin").hasRole("ADMIN") 43 | .antMatchers(HttpMethod.POST, "/notice").hasRole("ADMIN") 44 | .antMatchers(HttpMethod.DELETE, "/notice").hasRole("ADMIN") 45 | .anyRequest().authenticated(); 46 | // login 47 | http.formLogin() 48 | .loginPage("/login") 49 | .defaultSuccessUrl("/") 50 | .permitAll(); // 모두 허용 51 | // logout 52 | http.logout() 53 | .logoutRequestMatcher(new AntPathRequestMatcher("/logout")) 54 | .logoutSuccessUrl("/"); 55 | } 56 | 57 | @Override 58 | public void configure(WebSecurity web) { 59 | // 정적 리소스 spring security 대상에서 제외 60 | // web.ignoring().antMatchers("/images/**", "/css/**"); // 아래 코드와 같은 코드입니다. 61 | web.ignoring().requestMatchers(PathRequest.toStaticResources().atCommonLocations()); 62 | } 63 | 64 | /** 65 | * UserDetailsService 구현 66 | * 67 | * @return UserDetailsService 68 | */ 69 | @Bean 70 | @Override 71 | public UserDetailsService userDetailsService() { 72 | return username -> { 73 | User user = userService.findByUsername(username); 74 | if (user == null) { 75 | throw new UsernameNotFoundException(username); 76 | } 77 | return user; 78 | }; 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/note/Note.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import me.benny.practice.spring.security.user.User; 7 | import org.springframework.data.annotation.CreatedDate; 8 | import org.springframework.data.annotation.LastModifiedDate; 9 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 10 | 11 | import javax.persistence.*; 12 | import java.time.LocalDateTime; 13 | 14 | @Entity 15 | @Table 16 | @Getter 17 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 18 | @EntityListeners(AuditingEntityListener.class) 19 | public class Note { 20 | 21 | @Id 22 | @GeneratedValue 23 | private Long id; 24 | 25 | /** 26 | * 제목 27 | */ 28 | private String title; 29 | 30 | /** 31 | * 내용 32 | */ 33 | @Lob 34 | private String content; 35 | 36 | /** 37 | * User 참조 38 | */ 39 | @ManyToOne(fetch = FetchType.LAZY) 40 | @JoinColumn(name = "USER_ID") 41 | private User user; 42 | 43 | @CreatedDate 44 | private LocalDateTime createdAt; 45 | @LastModifiedDate 46 | private LocalDateTime updatedAt; 47 | 48 | public Note( 49 | String title, 50 | String content, 51 | User user 52 | ) { 53 | this.title = title; 54 | this.content = content; 55 | this.user = user; 56 | } 57 | 58 | public void updateContent( 59 | String title, 60 | String content 61 | ) { 62 | this.title = title; 63 | this.content = content; 64 | } 65 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/note/NoteController.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.user.User; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.stereotype.Controller; 7 | import org.springframework.ui.Model; 8 | import org.springframework.web.bind.annotation.*; 9 | 10 | import java.util.List; 11 | 12 | @Controller 13 | @RequiredArgsConstructor 14 | @RequestMapping("/note") 15 | public class NoteController { 16 | 17 | private final NoteService noteService; 18 | 19 | /** 20 | * 노트(게시글) 조회 21 | * 22 | * @return 노트 view (note/index.html) 23 | */ 24 | @GetMapping 25 | public String getNote(Authentication authentication, Model model) { 26 | User user = (User) authentication.getPrincipal(); 27 | List notes = noteService.findByUser(user); 28 | // note/index.html 에서 notes 사용가능 29 | model.addAttribute("notes", notes); 30 | // note/index.html 제공 31 | return "note/index"; 32 | } 33 | 34 | /** 35 | * 노트 저장 36 | */ 37 | @PostMapping 38 | public String saveNote(Authentication authentication, @ModelAttribute NoteRegisterDto noteDto) { 39 | User user = (User) authentication.getPrincipal(); 40 | noteService.saveNote(user, noteDto.getTitle(), noteDto.getContent()); 41 | return "redirect:note"; 42 | } 43 | 44 | /** 45 | * 노트 삭제 46 | */ 47 | @DeleteMapping 48 | public String deleteNote(Authentication authentication, @RequestParam Long id) { 49 | User user = (User) authentication.getPrincipal(); 50 | noteService.deleteNote(user, id); 51 | return "redirect:note"; 52 | } 53 | } 54 | 55 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/note/NoteRegisterDto.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | /** 7 | * 노트 등록 Dto 8 | */ 9 | @Getter 10 | @Setter 11 | public class NoteRegisterDto { 12 | 13 | private String title; 14 | private String content; 15 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/note/NoteRepository.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import me.benny.practice.spring.security.user.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | 6 | import java.util.List; 7 | 8 | public interface NoteRepository extends JpaRepository { 9 | 10 | List findByUserOrderByIdDesc(User user); 11 | 12 | Note findByIdAndUser(Long id, User user); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/note/NoteService.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.user.User; 5 | import me.benny.practice.spring.security.user.UserNotFoundException; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.data.domain.Sort.Direction; 8 | import org.springframework.stereotype.Service; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | @Service 14 | @Transactional 15 | @RequiredArgsConstructor 16 | public class NoteService { 17 | 18 | private final NoteRepository noteRepository; 19 | 20 | /** 21 | * 노트 조회 22 | * 유저는 본인의 노트만 조회할 수 있다. 23 | * 어드민은 모든 노트를 조회할 수 있다. 24 | * 25 | * @param user 노트를 찾을 유저 26 | * @return 유저가 조회할 수 있는 모든 노트 List 27 | */ 28 | @Transactional(readOnly = true) 29 | public List findByUser(User user) { 30 | if (user == null) { 31 | throw new UserNotFoundException(); 32 | } 33 | if (user.isAdmin()) { 34 | return noteRepository.findAll(Sort.by(Direction.DESC, "id")); 35 | } 36 | return noteRepository.findByUserOrderByIdDesc(user); 37 | } 38 | 39 | /** 40 | * 노트 저장 41 | * 42 | * @param user 노트 저장하는 유저 43 | * @param title 제목 44 | * @param content 내용 45 | * @return 저장된 노트 46 | */ 47 | public Note saveNote(User user, String title, String content) { 48 | if (user == null) { 49 | throw new UserNotFoundException(); 50 | } 51 | return noteRepository.save(new Note(title, content, user)); 52 | } 53 | 54 | /** 55 | * 노트 삭제 56 | * 57 | * @param user 삭제하려는 노트의 유저 58 | * @param noteId 노트 ID 59 | */ 60 | public void deleteNote(User user, Long noteId) { 61 | if (user == null) { 62 | throw new UserNotFoundException(); 63 | } 64 | Note note = noteRepository.findByIdAndUser(noteId, user); 65 | if (note != null) { 66 | noteRepository.delete(note); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/notice/Notice.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.notice; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.data.annotation.CreatedDate; 7 | import org.springframework.data.annotation.LastModifiedDate; 8 | import org.springframework.data.jpa.domain.support.AuditingEntityListener; 9 | 10 | import javax.persistence.*; 11 | import java.time.LocalDateTime; 12 | 13 | @Entity 14 | @Table 15 | @Getter 16 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 17 | @EntityListeners(AuditingEntityListener.class) 18 | public class Notice { 19 | 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | /** 25 | * 공지사항 제목 26 | */ 27 | private String title; 28 | 29 | /** 30 | * 공지사항 내용 31 | */ 32 | @Lob 33 | private String content; 34 | 35 | /** 36 | * 등록일시 37 | */ 38 | @CreatedDate 39 | private LocalDateTime createdAt; 40 | 41 | /** 42 | * 수정일시 43 | */ 44 | @LastModifiedDate 45 | private LocalDateTime updatedAt; 46 | 47 | public Notice( 48 | String title, 49 | String content 50 | ) { 51 | this.title = title; 52 | this.content = content; 53 | } 54 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/notice/NoticeController.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.notice; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import me.benny.practice.spring.security.note.NoteRegisterDto; 5 | import org.springframework.stereotype.Controller; 6 | import org.springframework.ui.Model; 7 | import org.springframework.web.bind.annotation.*; 8 | 9 | import java.util.List; 10 | 11 | /** 12 | * 공지사항 서비스 Controller 13 | */ 14 | @Controller 15 | @RequiredArgsConstructor 16 | @RequestMapping("/notice") 17 | public class NoticeController { 18 | 19 | private final NoticeService noticeService; 20 | 21 | /** 22 | * 공지사항 조회 23 | * 24 | * @return notice/index.html 25 | */ 26 | @GetMapping 27 | public String getNotice(Model model) { 28 | List notices = noticeService.findAll(); 29 | model.addAttribute("notices", notices); 30 | return "notice/index"; 31 | } 32 | 33 | /** 34 | * 공지사항 등록 35 | * 36 | * @param noteDto 노트 등록 Dto 37 | * @return notice/index.html refresh 38 | */ 39 | @PostMapping 40 | public String postNotice(@ModelAttribute NoteRegisterDto noteDto) { 41 | noticeService.saveNotice(noteDto.getTitle(), noteDto.getContent()); 42 | return "redirect:notice"; 43 | } 44 | 45 | /** 46 | * 공지사항 삭제 47 | * 48 | * @param id 공지사항 ID 49 | * @return notice/index.html refresh 50 | */ 51 | @DeleteMapping 52 | public String deleteNotice(@RequestParam Long id) { 53 | noticeService.deleteNotice(id); 54 | return "redirect:notice"; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/notice/NoticeRepository.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.notice; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface NoticeRepository extends JpaRepository { 6 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/notice/NoticeService.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.notice; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.data.domain.Sort; 5 | import org.springframework.data.domain.Sort.Direction; 6 | import org.springframework.stereotype.Service; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import java.util.List; 10 | 11 | @Service 12 | @Transactional 13 | @RequiredArgsConstructor 14 | public class NoticeService { 15 | 16 | private final NoticeRepository noticeRepository; 17 | 18 | /** 19 | * 모든 공지사항 조회 20 | * 21 | * @return 모든 공지사항 List 22 | */ 23 | @Transactional(readOnly = true) 24 | public List findAll() { 25 | return noticeRepository.findAll(Sort.by(Direction.DESC, "id")); 26 | } 27 | 28 | /** 29 | * 공지사항 저장 30 | * 31 | * @param title 제목 32 | * @param content 내용 33 | * @return 저장된 공지사항 34 | */ 35 | public Notice saveNotice(String title, String content) { 36 | return noticeRepository.save(new Notice(title, content)); 37 | } 38 | 39 | /** 40 | * 공지사항 삭제 41 | * 42 | * @param id ID 43 | */ 44 | public void deleteNotice(Long id) { 45 | noticeRepository.findById(id).ifPresent(noticeRepository::delete); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/AlreadyRegisteredUserException.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | /** 4 | * 이미 등록된 유저를 재등록하려고 할때 발생하는 Exception 5 | */ 6 | public class AlreadyRegisteredUserException extends RuntimeException { 7 | 8 | public AlreadyRegisteredUserException(String message) { 9 | super(message); 10 | } 11 | 12 | public AlreadyRegisteredUserException() { 13 | super("이미 등록된 유저입니다."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/SignUpController.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Controller; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.ModelAttribute; 7 | import org.springframework.web.bind.annotation.PostMapping; 8 | import org.springframework.web.bind.annotation.RequestMapping; 9 | 10 | /** 11 | * 회원가입 Controller 12 | */ 13 | @Controller 14 | @RequiredArgsConstructor 15 | @RequestMapping("/signup") 16 | public class SignUpController { 17 | 18 | private final UserService userService; 19 | 20 | /** 21 | * @return 회원가입 페이지 리소스 22 | */ 23 | @GetMapping 24 | public String signup() { 25 | return "signup"; 26 | } 27 | 28 | @PostMapping 29 | public String signup( 30 | @ModelAttribute UserRegisterDto userDto 31 | ) { 32 | userService.signup(userDto.getUsername(), userDto.getPassword()); 33 | // 회원가입 후 로그인 페이지로 이동 34 | return "redirect:login"; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/User.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import org.springframework.security.core.userdetails.UserDetails; 8 | 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | import javax.persistence.Table; 13 | import java.util.Collection; 14 | import java.util.Collections; 15 | 16 | @Entity 17 | @Table 18 | @Getter 19 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 20 | public class User implements UserDetails { 21 | 22 | @GeneratedValue 23 | @Id 24 | private Long id; 25 | private String username; 26 | private String password; 27 | private String authority; 28 | 29 | public User( 30 | String username, 31 | String password, 32 | String authority 33 | ) { 34 | this.username = username; 35 | this.password = password; 36 | this.authority = authority; 37 | } 38 | 39 | @Override 40 | public Collection getAuthorities() { 41 | return Collections.singleton((GrantedAuthority) () -> authority); 42 | } 43 | 44 | public Boolean isAdmin() { 45 | return authority.equals("ROLE_ADMIN"); 46 | } 47 | 48 | @Override 49 | public boolean isAccountNonExpired() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean isAccountNonLocked() { 55 | return true; 56 | } 57 | 58 | @Override 59 | public boolean isCredentialsNonExpired() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public boolean isEnabled() { 65 | return true; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | /** 4 | * 유저를 찾을 수 없을 때 발생하는 Exception 5 | */ 6 | public class UserNotFoundException extends RuntimeException { 7 | 8 | public UserNotFoundException(String message) { 9 | super(message); 10 | } 11 | 12 | public UserNotFoundException() { 13 | super("유저를 찾을 수 없습니다."); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/UserRegisterDto.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import lombok.*; 4 | 5 | /** 6 | * 유저 회원가입용 Dto 7 | */ 8 | @Getter 9 | @Setter 10 | @NoArgsConstructor(access = AccessLevel.PROTECTED) 11 | @AllArgsConstructor(access = AccessLevel.PUBLIC) 12 | public class UserRegisterDto { 13 | 14 | private String username; 15 | private String password; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface UserRepository extends JpaRepository { 6 | 7 | User findByUsername(String name); 8 | } -------------------------------------------------------------------------------- /src/main/java/me/benny/practice/spring/security/user/UserService.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.security.crypto.password.PasswordEncoder; 5 | import org.springframework.stereotype.Service; 6 | 7 | @Service 8 | @RequiredArgsConstructor 9 | public class UserService { 10 | 11 | private final UserRepository userRepository; 12 | private final PasswordEncoder passwordEncoder; 13 | 14 | /** 15 | * 유저 등록 16 | * 17 | * @param username username 18 | * @param password password 19 | * @return 유저 권한을 가지고 있는 유저 20 | */ 21 | public User signup( 22 | String username, 23 | String password 24 | ) { 25 | if (userRepository.findByUsername(username) != null) { 26 | throw new AlreadyRegisteredUserException(); 27 | } 28 | return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_USER")); 29 | } 30 | 31 | /** 32 | * 관리자 등록 33 | * 34 | * @param username username 35 | * @param password password 36 | * @return 관리자 권한을 가지고 있는 유저 37 | */ 38 | public User signupAdmin( 39 | String username, 40 | String password 41 | ) { 42 | if (userRepository.findByUsername(username) != null) { 43 | throw new AlreadyRegisteredUserException(); 44 | } 45 | return userRepository.save(new User(username, passwordEncoder.encode(password), "ROLE_ADMIN")); 46 | } 47 | 48 | public User findByUsername(String username) { 49 | return userRepository.findByUsername(username); 50 | } 51 | } -------------------------------------------------------------------------------- /src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | # logging 레벨 설정 2 | logging: 3 | level: 4 | root: INFO 5 | sql: ERROR 6 | # Overloaded POST 사용 7 | spring: 8 | mvc: 9 | hiddenmethod: 10 | filter: 11 | enabled: true 12 | # session 13 | server: 14 | servlet: 15 | session: 16 | timeout: 10m # 기본이 30분이고 최소는 1분입니다. -------------------------------------------------------------------------------- /src/main/resources/static/css/signin.css: -------------------------------------------------------------------------------- 1 | .form-signin { 2 | max-width: 330px; 3 | padding: 15px; 4 | margin: 0 auto; 5 | } 6 | .form-signin .form-signin-heading, 7 | .form-signin .checkbox { 8 | margin-bottom: 10px; 9 | } 10 | .form-signin .checkbox { 11 | font-weight: 400; 12 | } 13 | .form-signin .form-control { 14 | position: relative; 15 | box-sizing: border-box; 16 | height: auto; 17 | padding: 10px; 18 | font-size: 16px; 19 | } 20 | .form-signin .form-control:focus { 21 | z-index: 2; 22 | } 23 | .form-signin input[type="email"] { 24 | margin-bottom: -1px; 25 | border-bottom-right-radius: 0; 26 | border-bottom-left-radius: 0; 27 | } 28 | .form-signin input[type="password"] { 29 | margin-bottom: 10px; 30 | border-top-left-radius: 0; 31 | border-top-right-radius: 0; 32 | } -------------------------------------------------------------------------------- /src/main/resources/static/images/spring-security.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kker5/spring-security-practice/45f532d9404c28df22e59586b23402d63e1fd981/src/main/resources/static/images/spring-security.png -------------------------------------------------------------------------------- /src/main/resources/templates/admin/index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |
9 | 10 |
11 |

관리자 페이지

12 |

노트 게시 내역

13 |
14 |
15 |
16 |

17 | 18 | Posted by 19 | on 20 | 21 |

22 |
23 |
24 |
25 | 26 | -------------------------------------------------------------------------------- /src/main/resources/templates/fragments.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 스프링 시큐리티 예제 10 | 11 | 12 | 16 | 20 | 21 | 22 | 23 |
24 | 102 |
103 | 104 | -------------------------------------------------------------------------------- /src/main/resources/templates/index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 |
9 |
10 |

개인 보안 노트 서비스

11 |

Spring Security 예제 프로젝트

12 |
13 | 14 | -------------------------------------------------------------------------------- /src/main/resources/templates/login.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 26 |
27 | 28 | -------------------------------------------------------------------------------- /src/main/resources/templates/note/index.html: -------------------------------------------------------------------------------- 1 | 2 | 6 | 7 | 8 | 10 | 11 | 12 | 13 |
14 | 15 |
16 |

개인노트

17 | 18 | 19 | 27 | 28 | 29 | 69 | 70 | 71 |
72 |
73 | 74 |
75 |

76 |
77 |

78 |
79 | 80 | 81 | 82 | 83 | Posted On 84 | 85 | 86 |
87 |
88 |
89 |
90 |
91 |
92 | 93 | -------------------------------------------------------------------------------- /src/main/resources/templates/notice/index.html: -------------------------------------------------------------------------------- 1 | 2 | 7 | 8 | 9 | 11 | 12 | 13 | 14 |
15 |
16 |

공지사항

17 | 18 | 19 | 28 | 29 | 30 | 70 | 71 | 72 |
73 |
74 | 75 |
76 |

77 |
78 |

79 |
80 | 81 | Posted On 82 | 83 | 84 | 85 | 90 |
91 |
92 |
93 |
94 |
95 |
96 | 97 | -------------------------------------------------------------------------------- /src/main/resources/templates/signup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 |
10 | 22 |
23 | 24 | -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/admin/AdminControllerTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.admin; 2 | 3 | import me.benny.practice.spring.security.user.User; 4 | import me.benny.practice.spring.security.user.UserRepository; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.test.context.ActiveProfiles; 10 | import org.springframework.test.web.servlet.MockMvc; 11 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 12 | import org.springframework.transaction.annotation.Transactional; 13 | import org.springframework.web.context.WebApplicationContext; 14 | 15 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 16 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; 17 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 18 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 19 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 20 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrlPattern; 21 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 22 | 23 | 24 | @SpringBootTest 25 | @ActiveProfiles(profiles = "test") 26 | @Transactional 27 | class AdminControllerTest { 28 | 29 | @Autowired 30 | private UserRepository userRepository; 31 | private MockMvc mockMvc; 32 | private User user; 33 | private User admin; 34 | 35 | @BeforeEach 36 | public void setUp(@Autowired WebApplicationContext applicationContext) { 37 | this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) 38 | .apply(springSecurity()) // spring security 적용 39 | .alwaysDo(print()) 40 | .build(); 41 | // ROLE_USER 권한이 있는 유저 생성 42 | user = userRepository.save(new User("user", "user", "ROLE_USER")); 43 | // ROLE_ADMIN 권한이 있는 관리자 생성 44 | admin = userRepository.save(new User("admin", "admin", "ROLE_ADMIN")); 45 | } 46 | 47 | @Test 48 | void getNoteForAdmin_인증없음() throws Exception { 49 | mockMvc.perform(get("/admin").with(csrf())) // csrf 토큰 추가 50 | .andExpect(redirectedUrlPattern("**/login")) 51 | .andExpect(status().is3xxRedirection()); // login이 안되있으므로 로그인 페이지로 redirect 52 | } 53 | 54 | @Test 55 | void getNoteForAdmin_어드민인증있음() throws Exception { 56 | mockMvc.perform(get("/admin").with(csrf()).with(user(admin))) // 어드민 추가 57 | .andExpect(status().is2xxSuccessful()); 58 | } 59 | 60 | @Test 61 | void getNoteForAdmin_유저인증있음() throws Exception { 62 | mockMvc.perform(get("/admin").with(csrf()).with(user(user))) // 유저 추가 63 | .andExpect(status().isForbidden()); // 접근 거부 64 | } 65 | } -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/note/NoteControllerTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import me.benny.practice.spring.security.user.User; 4 | import me.benny.practice.spring.security.user.UserRepository; 5 | import org.junit.jupiter.api.BeforeEach; 6 | import org.junit.jupiter.api.Test; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.http.MediaType; 10 | import org.springframework.security.test.context.support.TestExecutionEvent; 11 | import org.springframework.security.test.context.support.WithUserDetails; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.web.servlet.MockMvc; 14 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 15 | import org.springframework.transaction.annotation.Transactional; 16 | import org.springframework.web.context.WebApplicationContext; 17 | 18 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 19 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; 20 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 21 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 22 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 23 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 24 | 25 | @SpringBootTest 26 | @ActiveProfiles(profiles = "test") 27 | @Transactional 28 | class NoteControllerTest { 29 | 30 | @Autowired 31 | private UserRepository userRepository; 32 | @Autowired 33 | private NoteRepository noteRepository; 34 | private MockMvc mockMvc; 35 | private User user; 36 | private User admin; 37 | 38 | @BeforeEach 39 | public void setUp(@Autowired WebApplicationContext applicationContext) { 40 | this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) 41 | .apply(springSecurity()) 42 | .alwaysDo(print()) 43 | .build(); 44 | user = userRepository.save(new User("user123", "user", "ROLE_USER")); 45 | admin = userRepository.save(new User("admin123", "admin", "ROLE_ADMIN")); 46 | } 47 | 48 | @Test 49 | void getNote_인증없음() throws Exception { 50 | mockMvc.perform(get("/note")) 51 | .andExpect(redirectedUrlPattern("**/login")) 52 | .andExpect(status().is3xxRedirection()); 53 | } 54 | 55 | @Test 56 | // WithUserDetails 로 테스트 하는 방법 57 | @WithUserDetails( 58 | value = "user123", // userDetailsService를 통해 가져올 수 있는 유저 59 | userDetailsServiceBeanName = "userDetailsService", // UserDetailsService 구현체의 Bean 60 | setupBefore = TestExecutionEvent.TEST_EXECUTION // 테스트 실행 직전에 유저를 가져온다. 61 | ) 62 | void getNote_인증있음() throws Exception { 63 | mockMvc.perform( 64 | get("/note") 65 | ).andExpect(status().isOk()) 66 | .andExpect(view().name("note/index")) 67 | .andDo(print()); 68 | } 69 | 70 | @Test 71 | void postNote_인증없음() throws Exception { 72 | mockMvc.perform( 73 | post("/note").with(csrf()) 74 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 75 | .param("title", "제목") 76 | .param("content", "내용") 77 | ).andExpect(redirectedUrlPattern("**/login")) 78 | .andExpect(status().is3xxRedirection()); 79 | } 80 | 81 | @Test 82 | @WithUserDetails( 83 | value = "admin123", 84 | userDetailsServiceBeanName = "userDetailsService", 85 | setupBefore = TestExecutionEvent.TEST_EXECUTION 86 | ) 87 | void postNote_어드민인증있음() throws Exception { 88 | mockMvc.perform( 89 | post("/note").with(csrf()) 90 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 91 | .param("title", "제목") 92 | .param("content", "내용") 93 | ).andExpect(status().isForbidden()); // 접근 거부 94 | } 95 | 96 | @Test 97 | @WithUserDetails( 98 | value = "user123", 99 | userDetailsServiceBeanName = "userDetailsService", 100 | setupBefore = TestExecutionEvent.TEST_EXECUTION 101 | ) 102 | void postNote_유저인증있음() throws Exception { 103 | mockMvc.perform( 104 | post("/note").with(csrf()) 105 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 106 | .param("title", "제목") 107 | .param("content", "내용") 108 | ).andExpect(redirectedUrl("note")).andExpect(status().is3xxRedirection()); 109 | } 110 | 111 | @Test 112 | void deleteNote_인증없음() throws Exception { 113 | Note note = noteRepository.save(new Note("제목", "내용", user)); 114 | mockMvc.perform( 115 | delete("/note?id=" + note.getId()).with(csrf()) 116 | ).andExpect(redirectedUrlPattern("**/login")) 117 | .andExpect(status().is3xxRedirection()); 118 | } 119 | 120 | @Test 121 | @WithUserDetails( 122 | value = "user123", 123 | userDetailsServiceBeanName = "userDetailsService", 124 | setupBefore = TestExecutionEvent.TEST_EXECUTION 125 | ) 126 | void deleteNote_유저인증있음() throws Exception { 127 | Note note = noteRepository.save(new Note("제목", "내용", user)); 128 | mockMvc.perform( 129 | delete("/note?id=" + note.getId()).with(csrf()) 130 | ).andExpect(redirectedUrl("note")).andExpect(status().is3xxRedirection()); 131 | } 132 | 133 | @Test 134 | @WithUserDetails( 135 | value = "admin123", 136 | userDetailsServiceBeanName = "userDetailsService", 137 | setupBefore = TestExecutionEvent.TEST_EXECUTION 138 | ) 139 | void deleteNote_어드민인증있음() throws Exception { 140 | Note note = noteRepository.save(new Note("제목", "내용", user)); 141 | mockMvc.perform( 142 | delete("/note?id=" + note.getId()).with(csrf()).with(user(admin)) 143 | ).andExpect(status().isForbidden()); // 접근 거부 144 | } 145 | } -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/note/NoteServiceTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.note; 2 | 3 | import me.benny.practice.spring.security.user.User; 4 | import me.benny.practice.spring.security.user.UserRepository; 5 | import org.junit.jupiter.api.Test; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.boot.test.context.SpringBootTest; 8 | import org.springframework.test.context.ActiveProfiles; 9 | import org.springframework.transaction.annotation.Transactional; 10 | 11 | import java.util.List; 12 | 13 | import static org.assertj.core.api.BDDAssertions.then; 14 | 15 | @SpringBootTest 16 | @ActiveProfiles(profiles = "test") 17 | @Transactional 18 | class NoteServiceTest { 19 | 20 | @Autowired 21 | private NoteService noteService; 22 | @Autowired 23 | private UserRepository userRepository; 24 | @Autowired 25 | private NoteRepository noteRepository; 26 | 27 | @Test 28 | void findByUser_유저가_게시물조회() { 29 | // given 30 | User user = userRepository.save(new User("username", "password", "ROLE_USER")); 31 | noteRepository.save(new Note("title1", "content1", user)); 32 | noteRepository.save(new Note("title2", "content2", user)); 33 | // when 34 | List notes = noteService.findByUser(user); 35 | // then 36 | then(notes.size()).isEqualTo(2); 37 | Note note1 = notes.get(0); 38 | Note note2 = notes.get(1); 39 | 40 | // note1 = title2 41 | then(note1.getUser().getUsername()).isEqualTo("username"); 42 | then(note1.getTitle()).isEqualTo("title2"); // 가장 늦게 insert된 데이터가 먼저 나와야합니다. 43 | then(note1.getContent()).isEqualTo("content2"); 44 | // note2 = title1 45 | then(note2.getUser().getUsername()).isEqualTo("username"); 46 | then(note2.getTitle()).isEqualTo("title1"); 47 | then(note2.getContent()).isEqualTo("content1"); 48 | } 49 | 50 | @Test 51 | void findByUser_어드민이_조회() { 52 | // given 53 | User admin = userRepository.save(new User("admin", "password", "ROLE_ADMIN")); 54 | User user1 = userRepository.save(new User("username", "password", "ROLE_USER")); 55 | User user2 = userRepository.save(new User("username2", "password", "ROLE_USER")); 56 | noteRepository.save(new Note("title1", "content1", user1)); 57 | noteRepository.save(new Note("title2", "content2", user1)); 58 | noteRepository.save(new Note("title3", "content3", user2)); 59 | // when 60 | List notes = noteService.findByUser(admin); 61 | // then 62 | then(notes.size()).isEqualTo(3); 63 | Note note1 = notes.get(0); 64 | Note note2 = notes.get(1); 65 | Note note3 = notes.get(2); 66 | 67 | // note1 = title3 68 | then(note1.getUser().getUsername()).isEqualTo("username2"); 69 | then(note1.getTitle()).isEqualTo("title3"); // 가장 늦게 insert된 데이터가 먼저 나와야합니다. 70 | then(note1.getContent()).isEqualTo("content3"); 71 | // note2 = title2 72 | then(note2.getUser().getUsername()).isEqualTo("username"); 73 | then(note2.getTitle()).isEqualTo("title2"); 74 | then(note2.getContent()).isEqualTo("content2"); 75 | // note3 = title1 76 | then(note3.getUser().getUsername()).isEqualTo("username"); 77 | then(note3.getTitle()).isEqualTo("title1"); 78 | then(note3.getContent()).isEqualTo("content1"); 79 | } 80 | 81 | @Test 82 | void saveNote() { 83 | // given 84 | User user = userRepository.save(new User("username", "password", "ROLE_USER")); 85 | // when 86 | noteService.saveNote(user, "title1", "content1"); 87 | // then 88 | then(noteRepository.count()).isOne(); 89 | } 90 | 91 | @Test 92 | void deleteNote() { 93 | User user = userRepository.save(new User("username", "password", "ROLE_USER")); 94 | Note note = noteRepository.save(new Note("title1", "content1", user)); 95 | noteService.deleteNote(user, note.getId()); 96 | // then 97 | then(noteRepository.count()).isZero(); 98 | } 99 | } -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/notice/NoticeControllerTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.notice; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.security.test.context.support.WithMockUser; 9 | import org.springframework.test.web.servlet.MockMvc; 10 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 11 | import org.springframework.transaction.annotation.Transactional; 12 | import org.springframework.web.context.WebApplicationContext; 13 | 14 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 15 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 16 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; 19 | 20 | @SpringBootTest 21 | @Transactional 22 | class NoticeControllerTest { 23 | 24 | @Autowired 25 | private NoticeRepository noticeRepository; 26 | private MockMvc mockMvc; 27 | 28 | @BeforeEach 29 | public void setUp(@Autowired WebApplicationContext applicationContext) { 30 | this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) 31 | .apply(springSecurity()) 32 | .alwaysDo(print()) 33 | .build(); 34 | } 35 | 36 | @Test 37 | void getNotice_인증없음() throws Exception { 38 | mockMvc.perform(get("/notice")) 39 | .andExpect(redirectedUrlPattern("**/login")) 40 | .andExpect(status().is3xxRedirection()); 41 | } 42 | 43 | @Test 44 | @WithMockUser 45 | void getNotice_인증있음() throws Exception { 46 | mockMvc.perform(get("/notice")) 47 | .andExpect(status().isOk()) 48 | .andExpect(view().name("notice/index")); 49 | } 50 | 51 | @Test 52 | void postNotice_인증없음() throws Exception { 53 | mockMvc.perform( 54 | post("/notice") 55 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 56 | .param("title", "제목") 57 | .param("content", "내용") 58 | ).andExpect(status().isForbidden()); // 접근 거부 59 | } 60 | 61 | @Test 62 | @WithMockUser(roles = {"USER"}, username = "admin", password = "admin") 63 | void postNotice_유저인증있음() throws Exception { 64 | mockMvc.perform( 65 | post("/notice").with(csrf()) 66 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 67 | .param("title", "제목") 68 | .param("content", "내용") 69 | ).andExpect(status().isForbidden()); // 접근 거부 70 | } 71 | 72 | @Test 73 | @WithMockUser(roles = {"ADMIN"}, username = "admin", password = "admin") 74 | void postNotice_어드민인증있음() throws Exception { 75 | mockMvc.perform( 76 | post("/notice").with(csrf()) 77 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 78 | .param("title", "제목") 79 | .param("content", "내용") 80 | ).andExpect(redirectedUrl("notice")).andExpect(status().is3xxRedirection()); 81 | } 82 | 83 | @Test 84 | void deleteNotice_인증없음() throws Exception { 85 | Notice notice = noticeRepository.save(new Notice("제목", "내용")); 86 | mockMvc.perform( 87 | delete("/notice?id=" + notice.getId()) 88 | ).andExpect(status().isForbidden()); // 접근 거부 89 | } 90 | 91 | @Test 92 | @WithMockUser(roles = {"USER"}, username = "admin", password = "admin") 93 | void deleteNotice_유저인증있음() throws Exception { 94 | Notice notice = noticeRepository.save(new Notice("제목", "내용")); 95 | mockMvc.perform( 96 | delete("/notice?id=" + notice.getId()).with(csrf()) 97 | ).andExpect(status().isForbidden()); // 접근 거부 98 | } 99 | 100 | @Test 101 | @WithMockUser(roles = {"ADMIN"}, username = "admin", password = "admin") 102 | void deleteNotice_어드민인증있음() throws Exception { 103 | Notice notice = noticeRepository.save(new Notice("제목", "내용")); 104 | mockMvc.perform( 105 | delete("/notice?id=" + notice.getId()).with(csrf()) 106 | ).andExpect(redirectedUrl("notice")).andExpect(status().is3xxRedirection()); 107 | } 108 | } -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/user/SignUpControllerTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.SpringBootTest; 7 | import org.springframework.http.MediaType; 8 | import org.springframework.test.web.servlet.MockMvc; 9 | import org.springframework.test.web.servlet.setup.MockMvcBuilders; 10 | import org.springframework.transaction.annotation.Transactional; 11 | import org.springframework.web.context.WebApplicationContext; 12 | 13 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 14 | import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; 15 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; 16 | import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; 17 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.redirectedUrl; 18 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 19 | 20 | @SpringBootTest 21 | @Transactional 22 | class SignUpControllerTest { 23 | 24 | private MockMvc mockMvc; 25 | 26 | @BeforeEach 27 | public void setUp(@Autowired WebApplicationContext applicationContext) { 28 | this.mockMvc = MockMvcBuilders.webAppContextSetup(applicationContext) 29 | .apply(springSecurity()) 30 | .alwaysDo(print()) 31 | .build(); 32 | } 33 | 34 | @Test 35 | void signup() throws Exception { 36 | mockMvc.perform( 37 | post("/signup").with(csrf()) 38 | .contentType(MediaType.APPLICATION_FORM_URLENCODED) 39 | .param("username", "user123") 40 | .param("password", "password") 41 | ).andExpect(redirectedUrl("login")).andExpect(status().is3xxRedirection()); 42 | } 43 | } -------------------------------------------------------------------------------- /src/test/java/me/benny/practice/spring/security/user/UserServiceTest.java: -------------------------------------------------------------------------------- 1 | package me.benny.practice.spring.security.user; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.beans.factory.annotation.Autowired; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.transaction.annotation.Transactional; 8 | 9 | import static org.assertj.core.api.BDDAssertions.then; 10 | 11 | @SpringBootTest 12 | @ActiveProfiles(profiles = "test") 13 | @Transactional 14 | class UserServiceTest { 15 | 16 | @Autowired 17 | private UserService userService; 18 | @Autowired 19 | private UserRepository userRepository; 20 | 21 | @Test 22 | void signup() { 23 | //given 24 | String username = "user123"; 25 | String password = "password"; 26 | //when 27 | User user = userService.signup(username, password); 28 | //then 29 | then(user.getId()).isNotNull(); // id가 NotNull인지 검증 30 | then(user.getUsername()).isEqualTo("user123"); // 유저명이 user123인지 검증 31 | then(user.getPassword()).startsWith("{bcrypt}"); // 패스워드가 {bcrypt}로 시작하는지 검증 32 | then(user.getAuthorities()).hasSize(1); // Authorities가 1개인지 검증 33 | then(user.getAuthorities().stream().findFirst().get().getAuthority()).isEqualTo("ROLE_USER"); 34 | then(user.isAdmin()).isFalse(); // 어드민 여부가 False인지 검증 35 | then(user.isAccountNonExpired()).isTrue(); 36 | then(user.isAccountNonLocked()).isTrue(); 37 | then(user.isEnabled()).isTrue(); 38 | then(user.isCredentialsNonExpired()).isTrue(); 39 | } 40 | 41 | @Test 42 | void signupAdmin() { 43 | //given 44 | String username = "admin123"; 45 | String password = "password"; 46 | //when 47 | User user = userService.signupAdmin(username, password); 48 | //then 49 | then(user.getId()).isNotNull(); 50 | then(user.getUsername()).isEqualTo("admin123"); 51 | then(user.getPassword()).startsWith("{bcrypt}"); 52 | then(user.getAuthorities()).hasSize(1); 53 | then(user.getAuthorities().stream().findFirst().get().getAuthority()).isEqualTo("ROLE_ADMIN"); 54 | then(user.isAdmin()).isTrue(); 55 | then(user.isAccountNonExpired()).isTrue(); 56 | then(user.isAccountNonLocked()).isTrue(); 57 | then(user.isEnabled()).isTrue(); 58 | then(user.isCredentialsNonExpired()).isTrue(); 59 | } 60 | 61 | @Test 62 | void findByUsername() { 63 | //given 64 | userRepository.save(new User("user123", "password", "ROLE_USER")); 65 | //when 66 | User user = userService.findByUsername("user123"); 67 | //then 68 | then(user.getId()).isNotNull(); 69 | } 70 | } --------------------------------------------------------------------------------