Repository: SilverNine/spring-boot-jwt-tutorial
Branch: master
Commit: 4606db0bdb56
Files: 38
Total size: 42.8 KB
Directory structure:
gitextract__to4d2u3/
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
├── main/
│ ├── java/
│ │ └── me/
│ │ └── silvernine/
│ │ └── tutorial/
│ │ ├── JwtTutorialApplication.java
│ │ ├── config/
│ │ │ ├── CorsConfig.java
│ │ │ └── SecurityConfig.java
│ │ ├── controller/
│ │ │ ├── AuthController.java
│ │ │ └── UserController.java
│ │ ├── dto/
│ │ │ ├── AuthorityDto.java
│ │ │ ├── ErrorDto.java
│ │ │ ├── LoginDto.java
│ │ │ ├── TokenDto.java
│ │ │ └── UserDto.java
│ │ ├── entity/
│ │ │ ├── Authority.java
│ │ │ └── User.java
│ │ ├── exception/
│ │ │ ├── DuplicateMemberException.java
│ │ │ └── NotFoundMemberException.java
│ │ ├── handler/
│ │ │ ├── MethodArgumentNotValidExceptionHandler.java
│ │ │ └── RestResponseExceptionHandler.java
│ │ ├── jwt/
│ │ │ ├── JwtAccessDeniedHandler.java
│ │ │ ├── JwtAuthenticationEntryPoint.java
│ │ │ ├── JwtFilter.java
│ │ │ ├── JwtSecurityConfig.java
│ │ │ └── TokenProvider.java
│ │ ├── repository/
│ │ │ ├── AuthorityRepository.java
│ │ │ └── UserRepository.java
│ │ ├── service/
│ │ │ ├── CustomUserDetailsService.java
│ │ │ └── UserService.java
│ │ └── util/
│ │ └── SecurityUtil.java
│ └── resources/
│ ├── application.yml
│ └── data.sql
└── test/
└── java/
└── me/
└── silvernine/
└── tutorial/
└── JwtTutorialApplicationTests.java
================================================
FILE CONTENTS
================================================
================================================
FILE: .gitignore
================================================
HELP.md
.gradle
build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**/build/
!**/src/test/**/build/
### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
bin/
!**/src/main/**/bin/
!**/src/test/**/bin/
### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr
out/
!**/src/main/**/out/
!**/src/test/**/out/
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
### VS Code ###
.vscode/
================================================
FILE: LICENSE
================================================
The MIT License (MIT)
Copyright (c) 2021 Eungoo Jung
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
================================================
FILE: README.md
================================================
# Spring Boot JWT Tutorial
## Author
**SilverNine**
* https://silvernine.me
* https://portfolio.silvernine.me
* https://github.com/silvernine
## Copyright and license
The code is released under the [MIT license](LICENSE?raw=true).
================================================
FILE: build.gradle
================================================
plugins {
id 'java'
id 'org.springframework.boot' version '3.4.0-SNAPSHOT'
id 'io.spring.dependency-management' version '1.1.6'
}
group = 'me.silvernine'
version = '0.0.1-SNAPSHOT'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(17)
}
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-validation'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
implementation group: 'io.jsonwebtoken', name: 'jjwt-api', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-impl', version: '0.11.5'
runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5'
runtimeOnly 'com.h2database:h2'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
}
tasks.named('test') {
useJUnitPlatform()
}
================================================
FILE: gradle/wrapper/gradle-wrapper.properties
================================================
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.10.2-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
================================================
FILE: gradlew
================================================
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"
================================================
FILE: gradlew.bat
================================================
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega
================================================
FILE: settings.gradle
================================================
pluginManagement {
repositories {
maven { url 'https://repo.spring.io/milestone' }
maven { url 'https://repo.spring.io/snapshot' }
gradlePluginPortal()
}
}
rootProject.name = 'jwt-tutorial'
================================================
FILE: src/main/java/me/silvernine/tutorial/JwtTutorialApplication.java
================================================
package me.silvernine.tutorial;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class JwtTutorialApplication {
public static void main(String[] args) {
SpringApplication.run(JwtTutorialApplication.class, args);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/config/CorsConfig.java
================================================
package me.silvernine.tutorial.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.filter.CorsFilter;
@Configuration
public class CorsConfig {
@Bean
public CorsFilter corsFilter() {
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
CorsConfiguration config = new CorsConfiguration();
config.setAllowCredentials(true);
config.addAllowedOriginPattern("*");
config.addAllowedHeader("*");
config.addAllowedMethod("*");
source.registerCorsConfiguration("/api/**", config);
return new CorsFilter(source);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/config/SecurityConfig.java
================================================
package me.silvernine.tutorial.config;
import me.silvernine.tutorial.jwt.JwtSecurityConfig;
import me.silvernine.tutorial.jwt.JwtAccessDeniedHandler;
import me.silvernine.tutorial.jwt.JwtAuthenticationEntryPoint;
import me.silvernine.tutorial.jwt.TokenProvider;
import org.springframework.boot.autoconfigure.security.servlet.PathRequest;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.filter.CorsFilter;
@EnableWebSecurity
@EnableMethodSecurity
@Configuration
public class SecurityConfig {
private final TokenProvider tokenProvider;
private final CorsFilter corsFilter;
private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
public SecurityConfig(
TokenProvider tokenProvider,
CorsFilter corsFilter,
JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint,
JwtAccessDeniedHandler jwtAccessDeniedHandler
) {
this.tokenProvider = tokenProvider;
this.corsFilter = corsFilter;
this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
// token을 사용하는 방식이기 때문에 csrf를 disable합니다.
.csrf(AbstractHttpConfigurer::disable)
.addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
.exceptionHandling(exceptionHandling -> exceptionHandling
.accessDeniedHandler(jwtAccessDeniedHandler)
.authenticationEntryPoint(jwtAuthenticationEntryPoint)
)
.authorizeHttpRequests(authorizeHttpRequests -> authorizeHttpRequests
.requestMatchers("/api/hello", "/api/authenticate", "/api/signup").permitAll()
.requestMatchers(PathRequest.toH2Console()).permitAll()
.anyRequest().authenticated()
)
// 세션을 사용하지 않기 때문에 STATELESS로 설정
.sessionManagement(sessionManagement ->
sessionManagement.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// enable h2-console
.headers(headers ->
headers.frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
)
.with(new JwtSecurityConfig(tokenProvider), customizer -> {});
return http.build();
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/controller/AuthController.java
================================================
package me.silvernine.tutorial.controller;
import me.silvernine.tutorial.dto.LoginDto;
import me.silvernine.tutorial.dto.TokenDto;
import me.silvernine.tutorial.jwt.JwtFilter;
import me.silvernine.tutorial.jwt.TokenProvider;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import jakarta.validation.Valid;
@RestController
@RequestMapping("/api")
public class AuthController {
private final TokenProvider tokenProvider;
private final AuthenticationManagerBuilder authenticationManagerBuilder;
public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
this.tokenProvider = tokenProvider;
this.authenticationManagerBuilder = authenticationManagerBuilder;
}
@PostMapping("/authenticate")
public ResponseEntity<TokenDto> authorize(@Valid @RequestBody LoginDto loginDto) {
UsernamePasswordAuthenticationToken authenticationToken =
new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
Authentication authentication = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
SecurityContextHolder.getContext().setAuthentication(authentication);
String jwt = tokenProvider.createToken(authentication);
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/controller/UserController.java
================================================
package me.silvernine.tutorial.controller;
import me.silvernine.tutorial.dto.UserDto;
import me.silvernine.tutorial.service.UserService;
import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import java.io.IOException;
@RestController
@RequestMapping("/api")
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
@GetMapping("/hello")
public ResponseEntity<String> hello() {
return ResponseEntity.ok("hello");
}
@PostMapping("/test-redirect")
public void testRedirect(HttpServletResponse response) throws IOException {
response.sendRedirect("/api/user");
}
@PostMapping("/signup")
public ResponseEntity<UserDto> signup(
@Valid @RequestBody UserDto userDto
) {
return ResponseEntity.ok(userService.signup(userDto));
}
@GetMapping("/user")
@PreAuthorize("hasAnyRole('USER','ADMIN')")
public ResponseEntity<UserDto> getMyUserInfo(HttpServletRequest request) {
return ResponseEntity.ok(userService.getMyUserWithAuthorities());
}
@GetMapping("/user/{username}")
@PreAuthorize("hasAnyRole('ADMIN')")
public ResponseEntity<UserDto> getUserInfo(@PathVariable String username) {
return ResponseEntity.ok(userService.getUserWithAuthorities(username));
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/dto/AuthorityDto.java
================================================
package me.silvernine.tutorial.dto;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class AuthorityDto {
private String authorityName;
}
================================================
FILE: src/main/java/me/silvernine/tutorial/dto/ErrorDto.java
================================================
package me.silvernine.tutorial.dto;
import java.util.ArrayList;
import java.util.List;
import org.springframework.validation.FieldError;
public class ErrorDto {
private final int status;
private final String message;
private List<FieldError> fieldErrors = new ArrayList<>();
public ErrorDto(int status, String message) {
this.status = status;
this.message = message;
}
public int getStatus() {
return status;
}
public String getMessage() {
return message;
}
public void addFieldError(String objectName, String path, String message) {
FieldError error = new FieldError(objectName, path, message);
fieldErrors.add(error);
}
public List<FieldError> getFieldErrors() {
return fieldErrors;
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/dto/LoginDto.java
================================================
package me.silvernine.tutorial.dto;
import lombok.*;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class LoginDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@NotNull
@Size(min = 3, max = 100)
private String password;
}
================================================
FILE: src/main/java/me/silvernine/tutorial/dto/TokenDto.java
================================================
package me.silvernine.tutorial.dto;
import lombok.*;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class TokenDto {
private String token;
}
================================================
FILE: src/main/java/me/silvernine/tutorial/dto/UserDto.java
================================================
package me.silvernine.tutorial.dto;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.*;
import me.silvernine.tutorial.entity.User;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import java.util.Set;
import java.util.stream.Collectors;
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDto {
@NotNull
@Size(min = 3, max = 50)
private String username;
@JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
@NotNull
@Size(min = 3, max = 100)
private String password;
@NotNull
@Size(min = 3, max = 50)
private String nickname;
private Set<AuthorityDto> authorityDtoSet;
public static UserDto from(User user) {
if(user == null) return null;
return UserDto.builder()
.username(user.getUsername())
.nickname(user.getNickname())
.authorityDtoSet(user.getAuthorities().stream()
.map(authority -> AuthorityDto.builder().authorityName(authority.getAuthorityName()).build())
.collect(Collectors.toSet()))
.build();
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/entity/Authority.java
================================================
package me.silvernine.tutorial.entity;
import lombok.*;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
@Entity
@Table(name = "authority")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Authority {
@Id
@Column(name = "authority_name", length = 50)
private String authorityName;
}
================================================
FILE: src/main/java/me/silvernine/tutorial/entity/User.java
================================================
package me.silvernine.tutorial.entity;
import lombok.*;
import jakarta.persistence.*;
import java.util.Set;
@Entity
@Table(name = "`user`")
@Getter
@Setter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class User {
@Id
@Column(name = "user_id")
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long userId;
@Column(name = "username", length = 50, unique = true)
private String username;
@Column(name = "password", length = 100)
private String password;
@Column(name = "nickname", length = 50)
private String nickname;
@Column(name = "activated")
private boolean activated;
@ManyToMany
@JoinTable(
name = "user_authority",
joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")})
private Set<Authority> authorities;
}
================================================
FILE: src/main/java/me/silvernine/tutorial/exception/DuplicateMemberException.java
================================================
package me.silvernine.tutorial.exception;
public class DuplicateMemberException extends RuntimeException {
public DuplicateMemberException() {
super();
}
public DuplicateMemberException(String message, Throwable cause) {
super(message, cause);
}
public DuplicateMemberException(String message) {
super(message);
}
public DuplicateMemberException(Throwable cause) {
super(cause);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/exception/NotFoundMemberException.java
================================================
package me.silvernine.tutorial.exception;
public class NotFoundMemberException extends RuntimeException {
public NotFoundMemberException() {
super();
}
public NotFoundMemberException(String message, Throwable cause) {
super(message, cause);
}
public NotFoundMemberException(String message) {
super(message);
}
public NotFoundMemberException(Throwable cause) {
super(cause);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/handler/MethodArgumentNotValidExceptionHandler.java
================================================
package me.silvernine.tutorial.handler;
import static org.springframework.http.HttpStatus.BAD_REQUEST;
import java.util.List;
import me.silvernine.tutorial.dto.ErrorDto;
import org.springframework.core.Ordered;
import org.springframework.core.annotation.Order;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
@Order(Ordered.HIGHEST_PRECEDENCE)
@ControllerAdvice
public class MethodArgumentNotValidExceptionHandler {
@ResponseStatus(BAD_REQUEST)
@ResponseBody
@ExceptionHandler(MethodArgumentNotValidException.class)
public ErrorDto methodArgumentNotValidException(MethodArgumentNotValidException ex) {
BindingResult result = ex.getBindingResult();
List<org.springframework.validation.FieldError> fieldErrors = result.getFieldErrors();
return processFieldErrors(fieldErrors);
}
private ErrorDto processFieldErrors(List<org.springframework.validation.FieldError> fieldErrors) {
ErrorDto errorDTO = new ErrorDto(BAD_REQUEST.value(), "@Valid Error");
for (org.springframework.validation.FieldError fieldError: fieldErrors) {
errorDTO.addFieldError(fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
}
return errorDTO;
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/handler/RestResponseExceptionHandler.java
================================================
package me.silvernine.tutorial.handler;
import static org.springframework.http.HttpStatus.CONFLICT;
import static org.springframework.http.HttpStatus.FORBIDDEN;
import me.silvernine.tutorial.dto.ErrorDto;
import me.silvernine.tutorial.exception.DuplicateMemberException;
import me.silvernine.tutorial.exception.NotFoundMemberException;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.context.request.WebRequest;
import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
@ControllerAdvice
public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler {
@ResponseStatus(CONFLICT)
@ExceptionHandler(value = { DuplicateMemberException.class })
@ResponseBody
protected ErrorDto conflict(RuntimeException ex, WebRequest request) {
return new ErrorDto(CONFLICT.value(), ex.getMessage());
}
@ResponseStatus(FORBIDDEN)
@ExceptionHandler(value = { NotFoundMemberException.class, AccessDeniedException.class })
@ResponseBody
protected ErrorDto forbidden(RuntimeException ex, WebRequest request) {
return new ErrorDto(FORBIDDEN.value(), ex.getMessage());
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtAccessDeniedHandler.java
================================================
package me.silvernine.tutorial.jwt;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAccessDeniedHandler implements AccessDeniedHandler {
@Override
public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException {
//필요한 권한이 없이 접근하려 할때 403
response.sendError(HttpServletResponse.SC_FORBIDDEN);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtAuthenticationEntryPoint.java
================================================
package me.silvernine.tutorial.jwt;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {
@Override
public void commence(HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException) throws IOException {
// 유효한 자격증명을 제공하지 않고 접근하려 할때 401
response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtFilter.java
================================================
package me.silvernine.tutorial.jwt;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.GenericFilterBean;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletRequest;
import jakarta.servlet.ServletResponse;
import jakarta.servlet.http.HttpServletRequest;
import java.io.IOException;
public class JwtFilter extends GenericFilterBean {
private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
public static final String AUTHORIZATION_HEADER = "Authorization";
private TokenProvider tokenProvider;
public JwtFilter(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
HttpServletRequest httpServletRequest = (HttpServletRequest) servletRequest;
String jwt = resolveToken(httpServletRequest);
String requestURI = httpServletRequest.getRequestURI();
if (StringUtils.hasText(jwt) && tokenProvider.validateToken(jwt)) {
Authentication authentication = tokenProvider.getAuthentication(jwt);
SecurityContextHolder.getContext().setAuthentication(authentication);
logger.debug("Security Context에 '{}' 인증 정보를 저장했습니다, uri: {}", authentication.getName(), requestURI);
} else {
logger.debug("유효한 JWT 토큰이 없습니다, uri: {}", requestURI);
}
filterChain.doFilter(servletRequest, servletResponse);
}
private String resolveToken(HttpServletRequest request) {
String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
if (StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")) {
return bearerToken.substring(7);
}
return null;
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtSecurityConfig.java
================================================
package me.silvernine.tutorial.jwt;
import org.springframework.security.config.annotation.SecurityConfigurerAdapter;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.web.DefaultSecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
public class JwtSecurityConfig extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, HttpSecurity> {
private final TokenProvider tokenProvider;
public JwtSecurityConfig(TokenProvider tokenProvider) {
this.tokenProvider = tokenProvider;
}
@Override
public void configure(HttpSecurity http) {
http.addFilterBefore(
new JwtFilter(tokenProvider),
UsernamePasswordAuthenticationFilter.class
);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/jwt/TokenProvider.java
================================================
package me.silvernine.tutorial.jwt;
import io.jsonwebtoken.*;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.stereotype.Component;
import java.security.Key;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.stream.Collectors;
@Component
public class TokenProvider implements InitializingBean {
private final Logger logger = LoggerFactory.getLogger(TokenProvider.class);
private static final String AUTHORITIES_KEY = "auth";
private final String secret;
private final long tokenValidityInMilliseconds;
private Key key;
public TokenProvider(
@Value("${jwt.secret}") String secret,
@Value("${jwt.token-validity-in-seconds}") long tokenValidityInSeconds) {
this.secret = secret;
this.tokenValidityInMilliseconds = tokenValidityInSeconds * 1000;
}
@Override
public void afterPropertiesSet() {
byte[] keyBytes = Decoders.BASE64.decode(secret);
this.key = Keys.hmacShaKeyFor(keyBytes);
}
public String createToken(Authentication authentication) {
String authorities = authentication.getAuthorities().stream()
.map(GrantedAuthority::getAuthority)
.collect(Collectors.joining(","));
long now = (new Date()).getTime();
Date validity = new Date(now + this.tokenValidityInMilliseconds);
return Jwts.builder()
.setSubject(authentication.getName())
.claim(AUTHORITIES_KEY, authorities)
.signWith(key, SignatureAlgorithm.HS512)
.setExpiration(validity)
.compact();
}
public Authentication getAuthentication(String token) {
Claims claims = Jwts
.parserBuilder()
.setSigningKey(key)
.build()
.parseClaimsJws(token)
.getBody();
Collection<? extends GrantedAuthority> authorities =
Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
User principal = new User(claims.getSubject(), "", authorities);
return new UsernamePasswordAuthenticationToken(principal, token, authorities);
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
return true;
} catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) {
logger.info("잘못된 JWT 서명입니다.");
} catch (ExpiredJwtException e) {
logger.info("만료된 JWT 토큰입니다.");
} catch (UnsupportedJwtException e) {
logger.info("지원되지 않는 JWT 토큰입니다.");
} catch (IllegalArgumentException e) {
logger.info("JWT 토큰이 잘못되었습니다.");
}
return false;
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/repository/AuthorityRepository.java
================================================
package me.silvernine.tutorial.repository;
import me.silvernine.tutorial.entity.Authority;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AuthorityRepository extends JpaRepository<Authority, String> {
}
================================================
FILE: src/main/java/me/silvernine/tutorial/repository/UserRepository.java
================================================
package me.silvernine.tutorial.repository;
import me.silvernine.tutorial.entity.User;
import org.springframework.data.jpa.repository.EntityGraph;
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository<User, Long> {
@EntityGraph(attributePaths = "authorities")
Optional<User> findOneWithAuthoritiesByUsername(String username);
}
================================================
FILE: src/main/java/me/silvernine/tutorial/service/CustomUserDetailsService.java
================================================
package me.silvernine.tutorial.service;
import me.silvernine.tutorial.entity.User;
import me.silvernine.tutorial.repository.UserRepository;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.stream.Collectors;
@Component("userDetailsService")
public class CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
@Transactional
public UserDetails loadUserByUsername(final String username) {
return userRepository.findOneWithAuthoritiesByUsername(username)
.map(user -> createUser(username, user))
.orElseThrow(() -> new UsernameNotFoundException(username + " -> 데이터베이스에서 찾을 수 없습니다."));
}
private org.springframework.security.core.userdetails.User createUser(String username, User user) {
if (!user.isActivated()) {
throw new RuntimeException(username + " -> 활성화되어 있지 않습니다.");
}
List<GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
.map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
.collect(Collectors.toList());
return new org.springframework.security.core.userdetails.User(user.getUsername(),
user.getPassword(),
grantedAuthorities);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/service/UserService.java
================================================
package me.silvernine.tutorial.service;
import java.util.Collections;
import java.util.Optional;
import me.silvernine.tutorial.dto.UserDto;
import me.silvernine.tutorial.entity.Authority;
import me.silvernine.tutorial.entity.User;
import me.silvernine.tutorial.exception.DuplicateMemberException;
import me.silvernine.tutorial.exception.NotFoundMemberException;
import me.silvernine.tutorial.repository.UserRepository;
import me.silvernine.tutorial.util.SecurityUtil;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class UserService {
private final UserRepository userRepository;
private final PasswordEncoder passwordEncoder;
public UserService(UserRepository userRepository, PasswordEncoder passwordEncoder) {
this.userRepository = userRepository;
this.passwordEncoder = passwordEncoder;
}
@Transactional
public UserDto signup(UserDto userDto) {
if (userRepository.findOneWithAuthoritiesByUsername(userDto.getUsername()).orElse(null) != null) {
throw new DuplicateMemberException("이미 가입되어 있는 유저입니다.");
}
Authority authority = Authority.builder()
.authorityName("ROLE_USER")
.build();
User user = User.builder()
.username(userDto.getUsername())
.password(passwordEncoder.encode(userDto.getPassword()))
.nickname(userDto.getNickname())
.authorities(Collections.singleton(authority))
.activated(true)
.build();
return UserDto.from(userRepository.save(user));
}
@Transactional(readOnly = true)
public UserDto getUserWithAuthorities(String username) {
return UserDto.from(userRepository.findOneWithAuthoritiesByUsername(username).orElse(null));
}
@Transactional(readOnly = true)
public UserDto getMyUserWithAuthorities() {
return UserDto.from(
SecurityUtil.getCurrentUsername()
.flatMap(userRepository::findOneWithAuthoritiesByUsername)
.orElseThrow(() -> new NotFoundMemberException("Member not found"))
);
}
}
================================================
FILE: src/main/java/me/silvernine/tutorial/util/SecurityUtil.java
================================================
package me.silvernine.tutorial.util;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import java.util.Optional;
public class SecurityUtil {
private static final Logger logger = LoggerFactory.getLogger(SecurityUtil.class);
private SecurityUtil() {}
public static Optional<String> getCurrentUsername() {
final Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (authentication == null) {
logger.debug("Security Context에 인증 정보가 없습니다.");
return Optional.empty();
}
String username = null;
if (authentication.getPrincipal() instanceof UserDetails) {
UserDetails springSecurityUser = (UserDetails) authentication.getPrincipal();
username = springSecurityUser.getUsername();
} else if (authentication.getPrincipal() instanceof String) {
username = (String) authentication.getPrincipal();
}
return Optional.ofNullable(username);
}
}
================================================
FILE: src/main/resources/application.yml
================================================
spring:
h2:
console:
enabled: true
datasource:
url: jdbc:h2:mem:testdb
driver-class-name: org.h2.Driver
username: sa
password:
jpa:
database-platform: org.hibernate.dialect.H2Dialect
hibernate:
ddl-auto: create-drop
properties:
hibernate:
format_sql: true
show_sql: true
defer-datasource-initialization: true
jwt:
header: Authorization
#HS512 알고리즘을 사용할 것이기 때문에 512bit, 즉 64byte 이상의 secret key를 사용해야 한다.
#echo 'silvernine-tech-spring-boot-jwt-tutorial-secret-silvernine-tech-spring-boot-jwt-tutorial-secret'|base64
secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
token-validity-in-seconds: 86400
logging:
level:
me.silvernine: DEBUG
================================================
FILE: src/main/resources/data.sql
================================================
insert into "user" (username, password, nickname, activated) values ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1);
insert into "user" (username, password, nickname, activated) values ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1);
insert into authority (authority_name) values ('ROLE_USER');
insert into authority (authority_name) values ('ROLE_ADMIN');
insert into user_authority (user_id, authority_name) values (1, 'ROLE_USER');
insert into user_authority (user_id, authority_name) values (1, 'ROLE_ADMIN');
insert into user_authority (user_id, authority_name) values (2, 'ROLE_USER');
================================================
FILE: src/test/java/me/silvernine/tutorial/JwtTutorialApplicationTests.java
================================================
package me.silvernine.tutorial;
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest
class JwtTutorialApplicationTests {
@Test
void contextLoads() {
}
}
gitextract__to4d2u3/
├── .gitignore
├── LICENSE
├── README.md
├── build.gradle
├── gradle/
│ └── wrapper/
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── settings.gradle
└── src/
├── main/
│ ├── java/
│ │ └── me/
│ │ └── silvernine/
│ │ └── tutorial/
│ │ ├── JwtTutorialApplication.java
│ │ ├── config/
│ │ │ ├── CorsConfig.java
│ │ │ └── SecurityConfig.java
│ │ ├── controller/
│ │ │ ├── AuthController.java
│ │ │ └── UserController.java
│ │ ├── dto/
│ │ │ ├── AuthorityDto.java
│ │ │ ├── ErrorDto.java
│ │ │ ├── LoginDto.java
│ │ │ ├── TokenDto.java
│ │ │ └── UserDto.java
│ │ ├── entity/
│ │ │ ├── Authority.java
│ │ │ └── User.java
│ │ ├── exception/
│ │ │ ├── DuplicateMemberException.java
│ │ │ └── NotFoundMemberException.java
│ │ ├── handler/
│ │ │ ├── MethodArgumentNotValidExceptionHandler.java
│ │ │ └── RestResponseExceptionHandler.java
│ │ ├── jwt/
│ │ │ ├── JwtAccessDeniedHandler.java
│ │ │ ├── JwtAuthenticationEntryPoint.java
│ │ │ ├── JwtFilter.java
│ │ │ ├── JwtSecurityConfig.java
│ │ │ └── TokenProvider.java
│ │ ├── repository/
│ │ │ ├── AuthorityRepository.java
│ │ │ └── UserRepository.java
│ │ ├── service/
│ │ │ ├── CustomUserDetailsService.java
│ │ │ └── UserService.java
│ │ └── util/
│ │ └── SecurityUtil.java
│ └── resources/
│ ├── application.yml
│ └── data.sql
└── test/
└── java/
└── me/
└── silvernine/
└── tutorial/
└── JwtTutorialApplicationTests.java
SYMBOL INDEX (81 symbols across 27 files)
FILE: src/main/java/me/silvernine/tutorial/JwtTutorialApplication.java
class JwtTutorialApplication (line 6) | @SpringBootApplication
method main (line 8) | public static void main(String[] args) {
FILE: src/main/java/me/silvernine/tutorial/config/CorsConfig.java
class CorsConfig (line 9) | @Configuration
method corsFilter (line 11) | @Bean
FILE: src/main/java/me/silvernine/tutorial/config/SecurityConfig.java
class SecurityConfig (line 23) | @EnableWebSecurity
method SecurityConfig (line 32) | public SecurityConfig(
method passwordEncoder (line 44) | @Bean
method filterChain (line 49) | @Bean
FILE: src/main/java/me/silvernine/tutorial/controller/AuthController.java
class AuthController (line 21) | @RestController
method AuthController (line 27) | public AuthController(TokenProvider tokenProvider, AuthenticationManag...
method authorize (line 32) | @PostMapping("/authenticate")
FILE: src/main/java/me/silvernine/tutorial/controller/UserController.java
class UserController (line 14) | @RestController
method UserController (line 19) | public UserController(UserService userService) {
method hello (line 23) | @GetMapping("/hello")
method testRedirect (line 28) | @PostMapping("/test-redirect")
method signup (line 33) | @PostMapping("/signup")
method getMyUserInfo (line 40) | @GetMapping("/user")
method getUserInfo (line 46) | @GetMapping("/user/{username}")
FILE: src/main/java/me/silvernine/tutorial/dto/AuthorityDto.java
class AuthorityDto (line 5) | @Getter
FILE: src/main/java/me/silvernine/tutorial/dto/ErrorDto.java
class ErrorDto (line 7) | public class ErrorDto {
method ErrorDto (line 12) | public ErrorDto(int status, String message) {
method getStatus (line 17) | public int getStatus() {
method getMessage (line 21) | public String getMessage() {
method addFieldError (line 25) | public void addFieldError(String objectName, String path, String messa...
method getFieldErrors (line 30) | public List<FieldError> getFieldErrors() {
FILE: src/main/java/me/silvernine/tutorial/dto/LoginDto.java
class LoginDto (line 8) | @Getter
FILE: src/main/java/me/silvernine/tutorial/dto/TokenDto.java
class TokenDto (line 5) | @Getter
FILE: src/main/java/me/silvernine/tutorial/dto/UserDto.java
class UserDto (line 12) | @Getter
method from (line 34) | public static UserDto from(User user) {
FILE: src/main/java/me/silvernine/tutorial/entity/Authority.java
class Authority (line 10) | @Entity
FILE: src/main/java/me/silvernine/tutorial/entity/User.java
class User (line 7) | @Entity
FILE: src/main/java/me/silvernine/tutorial/exception/DuplicateMemberException.java
class DuplicateMemberException (line 3) | public class DuplicateMemberException extends RuntimeException {
method DuplicateMemberException (line 4) | public DuplicateMemberException() {
method DuplicateMemberException (line 7) | public DuplicateMemberException(String message, Throwable cause) {
method DuplicateMemberException (line 10) | public DuplicateMemberException(String message) {
method DuplicateMemberException (line 13) | public DuplicateMemberException(Throwable cause) {
FILE: src/main/java/me/silvernine/tutorial/exception/NotFoundMemberException.java
class NotFoundMemberException (line 3) | public class NotFoundMemberException extends RuntimeException {
method NotFoundMemberException (line 4) | public NotFoundMemberException() {
method NotFoundMemberException (line 7) | public NotFoundMemberException(String message, Throwable cause) {
method NotFoundMemberException (line 10) | public NotFoundMemberException(String message) {
method NotFoundMemberException (line 13) | public NotFoundMemberException(Throwable cause) {
FILE: src/main/java/me/silvernine/tutorial/handler/MethodArgumentNotValidExceptionHandler.java
class MethodArgumentNotValidExceptionHandler (line 16) | @Order(Ordered.HIGHEST_PRECEDENCE)
method methodArgumentNotValidException (line 20) | @ResponseStatus(BAD_REQUEST)
method processFieldErrors (line 29) | private ErrorDto processFieldErrors(List<org.springframework.validatio...
FILE: src/main/java/me/silvernine/tutorial/handler/RestResponseExceptionHandler.java
class RestResponseExceptionHandler (line 17) | @ControllerAdvice
method conflict (line 20) | @ResponseStatus(CONFLICT)
method forbidden (line 27) | @ResponseStatus(FORBIDDEN)
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtAccessDeniedHandler.java
class JwtAccessDeniedHandler (line 11) | @Component
method handle (line 13) | @Override
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtAuthenticationEntryPoint.java
class JwtAuthenticationEntryPoint (line 11) | @Component
method commence (line 13) | @Override
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtFilter.java
class JwtFilter (line 17) | public class JwtFilter extends GenericFilterBean {
method JwtFilter (line 22) | public JwtFilter(TokenProvider tokenProvider) {
method doFilter (line 26) | @Override
method resolveToken (line 43) | private String resolveToken(HttpServletRequest request) {
FILE: src/main/java/me/silvernine/tutorial/jwt/JwtSecurityConfig.java
class JwtSecurityConfig (line 8) | public class JwtSecurityConfig extends SecurityConfigurerAdapter<Default...
method JwtSecurityConfig (line 10) | public JwtSecurityConfig(TokenProvider tokenProvider) {
method configure (line 14) | @Override
FILE: src/main/java/me/silvernine/tutorial/jwt/TokenProvider.java
class TokenProvider (line 23) | @Component
method TokenProvider (line 32) | public TokenProvider(
method afterPropertiesSet (line 39) | @Override
method createToken (line 45) | public String createToken(Authentication authentication) {
method getAuthentication (line 61) | public Authentication getAuthentication(String token) {
method validateToken (line 79) | public boolean validateToken(String token) {
FILE: src/main/java/me/silvernine/tutorial/repository/AuthorityRepository.java
type AuthorityRepository (line 6) | public interface AuthorityRepository extends JpaRepository<Authority, St...
FILE: src/main/java/me/silvernine/tutorial/repository/UserRepository.java
type UserRepository (line 9) | public interface UserRepository extends JpaRepository<User, Long> {
method findOneWithAuthoritiesByUsername (line 10) | @EntityGraph(attributePaths = "authorities")
FILE: src/main/java/me/silvernine/tutorial/service/CustomUserDetailsService.java
class CustomUserDetailsService (line 16) | @Component("userDetailsService")
method CustomUserDetailsService (line 20) | public CustomUserDetailsService(UserRepository userRepository) {
method loadUserByUsername (line 24) | @Override
method createUser (line 32) | private org.springframework.security.core.userdetails.User createUser(...
FILE: src/main/java/me/silvernine/tutorial/service/UserService.java
class UserService (line 16) | @Service
method UserService (line 21) | public UserService(UserRepository userRepository, PasswordEncoder pass...
method signup (line 26) | @Transactional
method getUserWithAuthorities (line 47) | @Transactional(readOnly = true)
method getMyUserWithAuthorities (line 52) | @Transactional(readOnly = true)
FILE: src/main/java/me/silvernine/tutorial/util/SecurityUtil.java
class SecurityUtil (line 11) | public class SecurityUtil {
method SecurityUtil (line 15) | private SecurityUtil() {}
method getCurrentUsername (line 17) | public static Optional<String> getCurrentUsername() {
FILE: src/test/java/me/silvernine/tutorial/JwtTutorialApplicationTests.java
class JwtTutorialApplicationTests (line 6) | @SpringBootTest
method contextLoads (line 9) | @Test
Condensed preview — 38 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (49K chars).
[
{
"path": ".gitignore",
"chars": 444,
"preview": "HELP.md\n.gradle\nbuild/\n!gradle/wrapper/gradle-wrapper.jar\n!**/src/main/**/build/\n!**/src/test/**/build/\n\n### STS ###\n.ap"
},
{
"path": "LICENSE",
"chars": 1078,
"preview": "The MIT License (MIT)\n\nCopyright (c) 2021 Eungoo Jung\n\nPermission is hereby granted, free of charge, to any person obtai"
},
{
"path": "README.md",
"chars": 235,
"preview": "# Spring Boot JWT Tutorial\n\n## Author\n\n**SilverNine**\n\n* https://silvernine.me\n* https://portfolio.silvernine.me\n* https"
},
{
"path": "build.gradle",
"chars": 1441,
"preview": "plugins {\n id 'java'\n id 'org.springframework.boot' version '3.4.0-SNAPSHOT'\n id 'io.spring.dependency-manageme"
},
{
"path": "gradle/wrapper/gradle-wrapper.properties",
"chars": 253,
"preview": "distributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributi"
},
{
"path": "gradlew",
"chars": 5766,
"preview": "#!/usr/bin/env sh\n\n#\n# Copyright 2015 the original author or authors.\n#\n# Licensed under the Apache License, Version 2.0"
},
{
"path": "gradlew.bat",
"chars": 2674,
"preview": "@rem\n@rem Copyright 2015 the original author or authors.\n@rem\n@rem Licensed under the Apache License, Version 2.0 (the \""
},
{
"path": "settings.gradle",
"chars": 197,
"preview": "pluginManagement {\n\trepositories {\n\t\tmaven { url 'https://repo.spring.io/milestone' }\n\t\tmaven { url 'https://repo.spring"
},
{
"path": "src/main/java/me/silvernine/tutorial/JwtTutorialApplication.java",
"chars": 323,
"preview": "package me.silvernine.tutorial;\n\nimport org.springframework.boot.SpringApplication;\nimport org.springframework.boot.auto"
},
{
"path": "src/main/java/me/silvernine/tutorial/config/CorsConfig.java",
"chars": 817,
"preview": "package me.silvernine.tutorial.config;\n\nimport org.springframework.context.annotation.Bean;\nimport org.springframework.c"
},
{
"path": "src/main/java/me/silvernine/tutorial/config/SecurityConfig.java",
"chars": 3492,
"preview": "package me.silvernine.tutorial.config;\n\nimport me.silvernine.tutorial.jwt.JwtSecurityConfig;\nimport me.silvernine.tutori"
},
{
"path": "src/main/java/me/silvernine/tutorial/controller/AuthController.java",
"chars": 2179,
"preview": "package me.silvernine.tutorial.controller;\n\nimport me.silvernine.tutorial.dto.LoginDto;\nimport me.silvernine.tutorial.dt"
},
{
"path": "src/main/java/me/silvernine/tutorial/controller/UserController.java",
"chars": 1636,
"preview": "package me.silvernine.tutorial.controller;\n\nimport me.silvernine.tutorial.dto.UserDto;\nimport me.silvernine.tutorial.ser"
},
{
"path": "src/main/java/me/silvernine/tutorial/dto/AuthorityDto.java",
"chars": 181,
"preview": "package me.silvernine.tutorial.dto;\n\nimport lombok.*;\n\n@Getter\n@Setter\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\np"
},
{
"path": "src/main/java/me/silvernine/tutorial/dto/ErrorDto.java",
"chars": 804,
"preview": "package me.silvernine.tutorial.dto;\n\nimport java.util.ArrayList;\nimport java.util.List;\nimport org.springframework.valid"
},
{
"path": "src/main/java/me/silvernine/tutorial/dto/LoginDto.java",
"chars": 376,
"preview": "package me.silvernine.tutorial.dto;\n\nimport lombok.*;\n\nimport jakarta.validation.constraints.NotNull;\nimport jakarta.val"
},
{
"path": "src/main/java/me/silvernine/tutorial/dto/TokenDto.java",
"chars": 172,
"preview": "package me.silvernine.tutorial.dto;\n\nimport lombok.*;\n\n@Getter\n@Setter\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\np"
},
{
"path": "src/main/java/me/silvernine/tutorial/dto/UserDto.java",
"chars": 1162,
"preview": "package me.silvernine.tutorial.dto;\n\nimport com.fasterxml.jackson.annotation.JsonProperty;\nimport lombok.*;\nimport me.si"
},
{
"path": "src/main/java/me/silvernine/tutorial/entity/Authority.java",
"chars": 410,
"preview": "package me.silvernine.tutorial.entity;\n\nimport lombok.*;\n\nimport jakarta.persistence.Column;\nimport jakarta.persistence."
},
{
"path": "src/main/java/me/silvernine/tutorial/entity/User.java",
"chars": 934,
"preview": "package me.silvernine.tutorial.entity;\n\nimport lombok.*;\nimport jakarta.persistence.*;\nimport java.util.Set;\n\n@Entity\n@T"
},
{
"path": "src/main/java/me/silvernine/tutorial/exception/DuplicateMemberException.java",
"chars": 448,
"preview": "package me.silvernine.tutorial.exception;\n\npublic class DuplicateMemberException extends RuntimeException {\n public D"
},
{
"path": "src/main/java/me/silvernine/tutorial/exception/NotFoundMemberException.java",
"chars": 443,
"preview": "package me.silvernine.tutorial.exception;\n\npublic class NotFoundMemberException extends RuntimeException {\n public No"
},
{
"path": "src/main/java/me/silvernine/tutorial/handler/MethodArgumentNotValidExceptionHandler.java",
"chars": 1579,
"preview": "package me.silvernine.tutorial.handler;\n\nimport static org.springframework.http.HttpStatus.BAD_REQUEST;\n\nimport java.uti"
},
{
"path": "src/main/java/me/silvernine/tutorial/handler/RestResponseExceptionHandler.java",
"chars": 1465,
"preview": "package me.silvernine.tutorial.handler;\n\nimport static org.springframework.http.HttpStatus.CONFLICT;\nimport static org.s"
},
{
"path": "src/main/java/me/silvernine/tutorial/jwt/JwtAccessDeniedHandler.java",
"chars": 684,
"preview": "package me.silvernine.tutorial.jwt;\n\nimport org.springframework.security.access.AccessDeniedException;\nimport org.spring"
},
{
"path": "src/main/java/me/silvernine/tutorial/jwt/JwtAuthenticationEntryPoint.java",
"chars": 747,
"preview": "package me.silvernine.tutorial.jwt;\n\nimport org.springframework.security.core.AuthenticationException;\nimport org.spring"
},
{
"path": "src/main/java/me/silvernine/tutorial/jwt/JwtFilter.java",
"chars": 2053,
"preview": "package me.silvernine.tutorial.jwt;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframework"
},
{
"path": "src/main/java/me/silvernine/tutorial/jwt/JwtSecurityConfig.java",
"chars": 835,
"preview": "package me.silvernine.tutorial.jwt;\n\nimport org.springframework.security.config.annotation.SecurityConfigurerAdapter;\nim"
},
{
"path": "src/main/java/me/silvernine/tutorial/jwt/TokenProvider.java",
"chars": 3335,
"preview": "package me.silvernine.tutorial.jwt;\n\nimport io.jsonwebtoken.*;\nimport io.jsonwebtoken.io.Decoders;\nimport io.jsonwebtoke"
},
{
"path": "src/main/java/me/silvernine/tutorial/repository/AuthorityRepository.java",
"chars": 237,
"preview": "package me.silvernine.tutorial.repository;\n\nimport me.silvernine.tutorial.entity.Authority;\nimport org.springframework.d"
},
{
"path": "src/main/java/me/silvernine/tutorial/repository/UserRepository.java",
"chars": 425,
"preview": "package me.silvernine.tutorial.repository;\n\nimport me.silvernine.tutorial.entity.User;\nimport org.springframework.data.j"
},
{
"path": "src/main/java/me/silvernine/tutorial/service/CustomUserDetailsService.java",
"chars": 1857,
"preview": "package me.silvernine.tutorial.service;\n\nimport me.silvernine.tutorial.entity.User;\nimport me.silvernine.tutorial.reposi"
},
{
"path": "src/main/java/me/silvernine/tutorial/service/UserService.java",
"chars": 2304,
"preview": "package me.silvernine.tutorial.service;\n\nimport java.util.Collections;\nimport java.util.Optional;\nimport me.silvernine.t"
},
{
"path": "src/main/java/me/silvernine/tutorial/util/SecurityUtil.java",
"chars": 1183,
"preview": "package me.silvernine.tutorial.util;\n\nimport org.slf4j.Logger;\nimport org.slf4j.LoggerFactory;\nimport org.springframewor"
},
{
"path": "src/main/resources/application.yml",
"chars": 817,
"preview": "spring:\n\n h2:\n console:\n enabled: true\n\n datasource:\n url: jdbc:h2:mem:testdb\n driver-class-name: org.h2"
},
{
"path": "src/main/resources/data.sql",
"chars": 667,
"preview": "insert into \"user\" (username, password, nickname, activated) values ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZ"
},
{
"path": "src/test/java/me/silvernine/tutorial/JwtTutorialApplicationTests.java",
"chars": 219,
"preview": "package me.silvernine.tutorial;\n\nimport org.junit.jupiter.api.Test;\nimport org.springframework.boot.test.context.SpringB"
}
]
// ... and 1 more files (download for full content)
About this extraction
This page contains the full source code of the SilverNine/spring-boot-jwt-tutorial GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 38 files (42.8 KB), approximately 11.0k tokens, and a symbol index with 81 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.
Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.