diff --git a/.idea/compiler.xml b/.idea/compiler.xml
index 6415ed5..b633b0f 100644
--- a/.idea/compiler.xml
+++ b/.idea/compiler.xml
@@ -5,7 +5,7 @@
       
         
         
-          
+          
         
         
       
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index 5bf1db7..6674b96 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -1,11 +1,11 @@
 
 
   
-    
+    
       mysql.8
       true
       com.mysql.cj.jdbc.Driver
-      jdbc:mysql://localhost:3306/user?serverTimezone=UTC&characterEncoding=UTF-8
+      jdbc:mysql://localhost:3305?play_ground
       $ProjectFileDir$
     
   
diff --git a/README.md b/README.md
index 25feba4..d276ddb 100644
--- a/README.md
+++ b/README.md
@@ -31,4 +31,12 @@
   - https://console.cloud.google.com/ 개발자 센터 접속
   - 프로젝트 생성 후 OAuth 2.0 클라이언트 Id 생성
   - 승인된 리디렉션 URI: http://localhost:8080/login/oauth2/code/google path 고정
-  - 클라이언트ID, 보안 비밀번호 yml 설정 추가
\ No newline at end of file
+  - 클라이언트ID, 보안 비밀번호 yml 설정 추가
+
+### JWT
+- JSON 객체를 사용해서 토큰 자체에 정보들을 저장하고 있는 Web Token
+- 기존 세션 방식의 처리는 서버가 메모리 혹은 별도의 세션을 저장할 공간이 필요했음(하드디스크, DB)
+  - 서버의 부담
+  - auto scale이 되어 서버 인스턴스가 늘어났을 때, 메모리를 서로 공유하지 못하기 때문에 세션을 공유할 수 없음.
+  - 중간에 서버가 죽으면? 메모리에 있던 정보들 다 날라감. 별도 저장소를 구성하기엔 비용이 듦
+  - 이러한 이유 등으로 인해 jwt 웹 표준(RFC 7519) 등장.
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
index bf895e1..97e1240 100644
--- a/build.gradle
+++ b/build.gradle
@@ -24,15 +24,20 @@ repositories {
 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-oauth2-client'
+	implementation 'org.springframework.boot:spring-boot-starter-validation'
 	implementation 'org.springframework.boot:spring-boot-starter-web'
-	implementation 'org.springframework.boot:spring-boot-starter-mustache'
+	testImplementation 'org.springframework.boot:spring-boot-starter-test'
+	testImplementation 'org.springframework.security:spring-security-test'
 
 	compileOnly 'org.projectlombok:lombok'
 	annotationProcessor 'org.projectlombok:lombok'
-	testImplementation 'org.springframework.boot:spring-boot-starter-test'
-	testImplementation 'org.springframework.security:spring-security-test'
-	runtimeOnly 'com.mysql:mysql-connector-j:8.0.33'
+
+	runtimeOnly 'com.h2database:h2'
+//	runtimeOnly 'com.mysql:mysql-connector-j:8.0.33'
+
+	implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
+	runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
+	runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'
 }
 
 tasks.named('test') {
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..630bf30
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,24 @@
+## compose 파일 버전
+#version: '3'
+#services:
+#  # 서비스 명
+#  db:
+#    # 사용할 이미지
+#    image: mysql
+#    # 컨테이너 실행 시 재시작
+#    restart: always
+#    # 컨테이너명 설정
+#    container_name: mysql-container-by-compose
+#    # 접근 포트 설정(컨테이너 외부:컨테이너 내부)
+#    ports:
+#
+#      - 3307:3306
+#    # 환경 변수 설정
+#    environment:
+#      MYSQL_ROOT_PASSWORD: root
+#    # 명령어 설정
+#    command:
+#      - --character-set-server=utf8mb4
+#      - --collation-server=utf8mb4_unicode_ci
+##    volumes:
+##      -
\ No newline at end of file
diff --git a/src/main/java/com/example/security2/auth/PrincipalDetails.java b/src/main/java/com/example/security2/auth/PrincipalDetails.java
deleted file mode 100644
index 4a6caad..0000000
--- a/src/main/java/com/example/security2/auth/PrincipalDetails.java
+++ /dev/null
@@ -1,98 +0,0 @@
-package com.example.security2.auth;
-
-import com.example.security2.entity.User;
-import lombok.Getter;
-import lombok.Setter;
-import org.springframework.security.core.GrantedAuthority;
-import org.springframework.security.core.userdetails.UserDetails;
-import org.springframework.security.oauth2.core.user.OAuth2User;
-
-import java.util.Collection;
-import java.util.List;
-import java.util.Map;
-
-/**
- * 시큐리티가 /login 주소 요청이 오면 낚아채서 로그인을 진행시킨다.
- * 로그인이 진행이 완료가 되면 시큐리티 session을 만들어준다.(SecurityContextHolder)
- * 오브젝트 => Authentication 타입의 객체
- * Authentication 안에 User 정보가 있어야 됨.
- * User 객체는 UserDetails 타입 객체(Security)
- *
- * Security Session => Authentication => UserDetails(PrincipalDetails)
- */
-@Getter
-@Setter
-public class PrincipalDetails implements UserDetails, OAuth2User {
-
-    // composition
-    private final User user;
-    private Map attributes;
-
-    // 일반 로그인
-    public PrincipalDetails(User user) {
-        this.user = user;
-    }
-
-    // OAuth 로그인
-    public PrincipalDetails(User user, Map attributes) {
-        this(user);
-        this.attributes = attributes;
-    }
-
-    @Override
-    public Map getAttributes() {
-        return attributes;
-    }
-
-    // 해당 User의 권한을 리턴하는 곳!
-    @Override
-    public Collection extends GrantedAuthority> getAuthorities() {
-        return List.of((GrantedAuthority) user::getRole);
-    }
-
-    @Override
-    public String getPassword() {
-        return user.getPassword();
-    }
-
-    @Override
-    public String getUsername() {
-        return user.getEmail();
-    }
-
-    // 계정이 만료되지 않은지(true: 만료되지 않음 / false: 만료됨)
-    @Override
-    public boolean isAccountNonExpired() {
-        return true;
-    }
-
-    // 계정이 잠겨있지 않은지(true: 잠기지 않음 / false: 잠김)
-    @Override
-    public boolean isAccountNonLocked() {
-        return true;
-    }
-
-    // 비밀번호가 만료되지 않은지(true: 만료안됨 / false: 만료)
-    @Override
-    public boolean isCredentialsNonExpired() {
-        return true;
-    }
-
-    // 계정이 활성화되어 있는지(true: 활성화 / false: 비활)
-    @Override
-    public boolean isEnabled() {
-        /**
-         * TODO
-         * 만일 우리 사이트의 정책이 1년동안 로그인하지 않은 회원을 휴면처리 하기로 했다면?
-         * 현재 서버시간 - 마지막 로그인 시간 => 1년을 초과하면 return false;
-         * System.currentTimeMillis() - user.getLastLoginDate();
-         */
-
-        return true;
-    }
-
-    @Override
-    public String getName() {
-        return (String) attributes.get("sub");
-    }
-}
diff --git a/src/main/java/com/example/security2/auth/PrincipalDetailsService.java b/src/main/java/com/example/security2/auth/PrincipalDetailsService.java
deleted file mode 100644
index aa1278f..0000000
--- a/src/main/java/com/example/security2/auth/PrincipalDetailsService.java
+++ /dev/null
@@ -1,30 +0,0 @@
-package com.example.security2.auth;
-
-import com.example.security2.entity.User;
-import com.example.security2.repository.UserRepository;
-import lombok.RequiredArgsConstructor;
-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.Service;
-
-// 시큐리티 설정에서 .loginProcessingUrl("/login");
-// /login 요청이 오면 자동으로 UserDetailsService 타입으로 IOC 되어있는 loadUserByUsername 메서드가 실행
-@Service
-@RequiredArgsConstructor
-public class PrincipalDetailsService implements UserDetailsService {
-
-    private final UserRepository userRepository;
-
-    // 시큐리티 session(내부 Authentication(내부 UserDetails))
-    // 함수 종료 시 @AuthenticationPrincipal 어노테이션이 만들어진다.
-    @Override
-    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
-        User foundUser = userRepository.findByEmail(username)
-                .stream()
-                .findFirst()
-                .orElseThrow(() -> new UsernameNotFoundException("존재하지 유저"));
-
-        return new PrincipalDetails(foundUser);
-    }
-}
diff --git a/src/main/java/com/example/security2/config/CorsConfig.java b/src/main/java/com/example/security2/config/CorsConfig.java
new file mode 100644
index 0000000..2bc5680
--- /dev/null
+++ b/src/main/java/com/example/security2/config/CorsConfig.java
@@ -0,0 +1,25 @@
+package com.example.security2.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("/rest/api/**", config);
+        return new CorsFilter(source);
+    }
+}
diff --git a/src/main/java/com/example/security2/config/JwtSecurityConfig.java b/src/main/java/com/example/security2/config/JwtSecurityConfig.java
new file mode 100644
index 0000000..541882c
--- /dev/null
+++ b/src/main/java/com/example/security2/config/JwtSecurityConfig.java
@@ -0,0 +1,25 @@
+package com.example.security2.config;
+
+import com.example.security2.jwt.JwtFilter;
+import com.example.security2.jwt.TokenProvider;
+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 {
+
+    private final TokenProvider tokenProvider;
+
+    public JwtSecurityConfig(TokenProvider tokenProvider) {
+        this.tokenProvider = tokenProvider;
+    }
+
+    @Override
+    public void configure(HttpSecurity http) throws Exception {
+        // security 로직에 JwtFilter 등록
+        http.addFilterBefore(
+                new JwtFilter(tokenProvider),
+                UsernamePasswordAuthenticationFilter.class);
+    }
+}
diff --git a/src/main/java/com/example/security2/config/SecurityConfig.java b/src/main/java/com/example/security2/config/SecurityConfig.java
index 1a40adb..a96af67 100644
--- a/src/main/java/com/example/security2/config/SecurityConfig.java
+++ b/src/main/java/com/example/security2/config/SecurityConfig.java
@@ -1,59 +1,75 @@
 package com.example.security2.config;
 
-import com.example.security2.oauth.PrincipalOauth2UserService;
+import com.example.security2.jwt.JwtAccessDeniedHandler;
+import com.example.security2.jwt.JwtAuthenticationEntryPoint;
+import com.example.security2.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.web.SecurityFilterChain;
-import org.springframework.security.web.access.expression.WebExpressionAuthorizationManager;
+import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.util.matcher.AntPathRequestMatcher;
+import org.springframework.web.filter.CorsFilter;
 
 @Configuration
-@EnableWebSecurity // 스프링 필터체인에 등록
-@EnableMethodSecurity(securedEnabled = true, prePostEnabled = true)         // 권한 처리, Secured 어노테이션 활성화. preAuthorize, postAuthorize 어노테이션 활성화
+@EnableWebSecurity
+@EnableMethodSecurity
 public class SecurityConfig {
 
-    private final PrincipalOauth2UserService principalOauth2UserService;
+    private final CorsFilter corsFilter;
+    private final TokenProvider tokenProvider;
+    private final JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint;
+    private final JwtAccessDeniedHandler jwtAccessDeniedHandler;
 
-    public SecurityConfig(PrincipalOauth2UserService principalOauth2UserService) {
-        this.principalOauth2UserService = principalOauth2UserService;
+    public SecurityConfig(CorsFilter corsFilter, TokenProvider tokenProvider, JwtAuthenticationEntryPoint jwtAuthenticationEntryPoint, JwtAccessDeniedHandler jwtAccessDeniedHandler) {
+        this.corsFilter = corsFilter;
+        this.tokenProvider = tokenProvider;
+        this.jwtAuthenticationEntryPoint = jwtAuthenticationEntryPoint;
+        this.jwtAccessDeniedHandler = jwtAccessDeniedHandler;
     }
 
-    // SecurityConfig <-> PrincipalOauth2UserService 순환참조로 인해 분리(PasswordConfig)
-    /*@Bean
-    public PasswordEncoder passwordEncoder() {
-        return new BCryptPasswordEncoder();
-    }*/
-
     @Bean
     public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
         // csrf, cors 일단 비활성화
-        http.csrf(AbstractHttpConfigurer::disable);
-        http.cors(AbstractHttpConfigurer::disable);
+        http
+                .csrf(AbstractHttpConfigurer::disable)
+//                .cors(AbstractHttpConfigurer::disable)
 
-        http.authorizeHttpRequests((requests) -> requests
-                .requestMatchers("/user/**").authenticated()
-                .requestMatchers("/manager/**").access(
-                        new WebExpressionAuthorizationManager("hasRole('ROLE_ADMIN') or hasRole('ROLE_MANAGER')")
+                .addFilterBefore(corsFilter, UsernamePasswordAuthenticationFilter.class)
+                // jwt 예외핸들러 등록
+                .exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
+                        httpSecurityExceptionHandlingConfigurer
+                                .authenticationEntryPoint(jwtAuthenticationEntryPoint)
+                                .accessDeniedHandler(jwtAccessDeniedHandler)
                 )
-                .requestMatchers("/admin/**").access(
-                        new WebExpressionAuthorizationManager("hasRole('ROLE_ADMIN')")
+
+                // 세션을 사용하지 않기 때문에 "STATELESS"로 설정
+                .sessionManagement(sessionManagementConfigurer ->
+                        sessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
                 )
-                .anyRequest().permitAll())
-                .formLogin(form -> form
-                        .loginPage("/loginForm")
-                        .usernameParameter("email")
-                        .loginProcessingUrl("/login")
-                        .defaultSuccessUrl("/")
+
+                .authorizeHttpRequests((requests) -> requests
+                    .requestMatchers(PathRequest.toH2Console()).permitAll()
+                    .requestMatchers(
+                            new AntPathRequestMatcher("/api/v1/auth"),
+                            new AntPathRequestMatcher("/api/v1/user/hello"),
+                            new AntPathRequestMatcher("/api/v1/user/signup")
+                    ).permitAll()
+                    .anyRequest().authenticated()
                 )
-                .oauth2Login(oauth2 -> oauth2
-                        .loginPage("/loginForm")
-                        .userInfoEndpoint(userInfoEndpointConfig -> userInfoEndpointConfig
-                                .userService(principalOauth2UserService)
-                        )
-                );
+                // enable h2 console
+                .headers(header -> header
+                        .frameOptions(HeadersConfigurer.FrameOptionsConfig::sameOrigin)
+                )
+
+                // JwtFilter 적용
+                .apply(new JwtSecurityConfig(tokenProvider));
 
         return http.build();
     }
diff --git a/src/main/java/com/example/security2/config/WebMvcConfig.java b/src/main/java/com/example/security2/config/WebMvcConfig.java
deleted file mode 100644
index 2684341..0000000
--- a/src/main/java/com/example/security2/config/WebMvcConfig.java
+++ /dev/null
@@ -1,22 +0,0 @@
-package com.example.security2.config;
-
-import org.springframework.boot.web.servlet.view.MustacheViewResolver;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.web.servlet.config.annotation.ViewResolverRegistry;
-import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
-
-@Configuration
-public class WebMvcConfig implements WebMvcConfigurer {
-
-    @Override
-    public void configureViewResolvers(ViewResolverRegistry registry) {
-        // 머스테치를 html로 재설정.
-        MustacheViewResolver viewResolver = new MustacheViewResolver();
-        viewResolver.setCharset("UTF-8");
-        viewResolver.setContentType("text/html;charset=UTF-8");
-        viewResolver.setPrefix("classpath:/templates/");
-        viewResolver.setSuffix(".html");
-
-        registry.viewResolver(viewResolver);
-    }
-}
diff --git a/src/main/java/com/example/security2/controller/AuthController.java b/src/main/java/com/example/security2/controller/AuthController.java
new file mode 100644
index 0000000..6ed0086
--- /dev/null
+++ b/src/main/java/com/example/security2/controller/AuthController.java
@@ -0,0 +1,46 @@
+package com.example.security2.controller;
+
+import com.example.security2.dto.LoginDto;
+import com.example.security2.dto.TokenDto;
+import com.example.security2.jwt.JwtFilter;
+import com.example.security2.jwt.TokenProvider;
+import jakarta.validation.Valid;
+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;
+
+@RestController
+@RequestMapping("/api/v1/auth")
+public class AuthController {
+
+    private final TokenProvider tokenProvider;
+    private final AuthenticationManagerBuilder authenticationManagerBuilder;
+
+    public AuthController(TokenProvider tokenProvider, AuthenticationManagerBuilder authenticationManagerBuilder) {
+        this.tokenProvider = tokenProvider;
+        this.authenticationManagerBuilder = authenticationManagerBuilder;
+    }
+
+    @PostMapping
+    public ResponseEntity authenticate(@Valid @RequestBody LoginDto loginDto) {
+        UsernamePasswordAuthenticationToken authenticationToken =
+                new UsernamePasswordAuthenticationToken(loginDto.getUsername(), loginDto.getPassword());
+
+        Authentication authenticate = authenticationManagerBuilder.getObject().authenticate(authenticationToken);
+        SecurityContextHolder.getContext().setAuthentication(authenticate);
+        String jwt = tokenProvider.createToken(authenticate);
+
+        HttpHeaders httpHeaders = new HttpHeaders();
+        httpHeaders.add(JwtFilter.AUTHORIZATION_HEADER, "Bearer " + jwt);
+
+        return new ResponseEntity<>(new TokenDto(jwt), httpHeaders, HttpStatus.OK);
+    }
+}
diff --git a/src/main/java/com/example/security2/controller/HealthController.java b/src/main/java/com/example/security2/controller/HealthController.java
deleted file mode 100644
index 89126aa..0000000
--- a/src/main/java/com/example/security2/controller/HealthController.java
+++ /dev/null
@@ -1,15 +0,0 @@
-package com.example.security2.controller;
-
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-@RestController
-@RequestMapping("/health")
-public class HealthController {
-
-    @GetMapping
-    public String health() {
-        return "health";
-    }
-}
diff --git a/src/main/java/com/example/security2/controller/UserController.java b/src/main/java/com/example/security2/controller/UserController.java
new file mode 100644
index 0000000..72a2158
--- /dev/null
+++ b/src/main/java/com/example/security2/controller/UserController.java
@@ -0,0 +1,39 @@
+package com.example.security2.controller;
+
+import com.example.security2.dto.UserDto;
+import com.example.security2.service.UserService;
+import jakarta.servlet.http.HttpServletRequest;
+import lombok.RequiredArgsConstructor;
+import org.springframework.http.ResponseEntity;
+import org.springframework.security.access.prepost.PreAuthorize;
+import org.springframework.web.bind.annotation.*;
+
+@RestController
+@RequestMapping("/api/v1/user")
+@RequiredArgsConstructor
+public class UserController {
+
+    private final UserService userService;
+
+    @GetMapping("/hello")
+    public ResponseEntity hello() {
+        return ResponseEntity.ok("hello");
+    }
+
+    @PostMapping("/signup")
+    public ResponseEntity signUp(@RequestBody UserDto userDto) {
+        return ResponseEntity.ok(userService.signUp(userDto));
+    }
+
+    @GetMapping
+    @PreAuthorize("hasAnyRole('USER','ADMIN')")
+    public ResponseEntity getMyUserInfo(HttpServletRequest request) {
+        return ResponseEntity.ok(userService.getMyUserWithAuthorities());
+    }
+
+    @GetMapping("/{username}")
+    @PreAuthorize("hasAnyRole('ADMIN')")
+    public ResponseEntity getUserInfo(@PathVariable String username) {
+        return ResponseEntity.ok(userService.getUserWithAuthorities(username));
+    }
+}
diff --git a/src/main/java/com/example/security2/controller/ViewController.java b/src/main/java/com/example/security2/controller/ViewController.java
deleted file mode 100644
index c35f0e5..0000000
--- a/src/main/java/com/example/security2/controller/ViewController.java
+++ /dev/null
@@ -1,88 +0,0 @@
-package com.example.security2.controller;
-
-import com.example.security2.auth.PrincipalDetails;
-import com.example.security2.entity.User;
-import com.example.security2.enums.AuthType;
-import com.example.security2.repository.UserRepository;
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.security.access.annotation.Secured;
-import org.springframework.security.access.prepost.PreAuthorize;
-import org.springframework.security.core.annotation.AuthenticationPrincipal;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.stereotype.Controller;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.ResponseBody;
-
-/**
- * View
- */
-@Controller
-@Slf4j
-@RequiredArgsConstructor
-public class ViewController {
-
-    // 간편구현을 위해 레포지토리만 생성
-    private final UserRepository userRepository;
-    private final PasswordEncoder passwordEncoder;
-
-    @GetMapping({"", "/"})
-    public String index() {
-        return "index";
-    }
-
-    // @AuthenticationPrincipal 어노테이션으로 인증객체 내 User정보 확인 가능
-    @GetMapping("/user")
-    public @ResponseBody String user(@AuthenticationPrincipal PrincipalDetails principalDetails) {
-        log.info("principalDetails: {}", principalDetails.getUser());
-        return "user";
-    }
-
-    @GetMapping("/admin")
-    public @ResponseBody String admin() {
-        return "admin";
-    }
-
-    @GetMapping("/manager")
-    public @ResponseBody String manager() {
-        return "manager";
-    }
-
-    @GetMapping("/loginForm")
-    public String loginForm() {
-        return "loginForm";
-    }
-
-    @GetMapping("/joinForm")
-    public String joinForm() {
-        return "joinForm";
-    }
-
-    @PostMapping("/join")
-    public String join(User user) {
-        user.setRole(AuthType.ROLE_USER.name());
-        user.setPassword(passwordEncoder.encode(user.getPassword()));
-
-        userRepository.save(user);
-        return "redirect:/loginForm";
-    }
-
-    @Secured("ROLE_ADMIN")
-    @GetMapping("/admin-only")
-    public @ResponseBody String accessOnlyAdmin() {
-        return "어드민 권한만 접근 가능";
-    }
-
-    /**
-     * 권한 하나로만 걸고 싶으면 @Secured로 처리하기 쉽고, 여러개면 @PreAuthorize로 가능
-     *
-     * @PreAuthorize 함수 실행 전 실행되는 어노테이션
-     * @PostAuthorize 함수 실행 후 실행되는 어노테이션 -> 후처리는 잘 사용하지 않음
-     */
-    @PreAuthorize("hasRole('ROLE_MANAGER') or hasRole('ROLE_ADMIN')")
-    @GetMapping("/manager-and-admin")
-    public @ResponseBody String accessManagerAndAdmin() {
-        return "매니저, 관리자 권한만 접근 허용";
-    }
-}
diff --git a/src/main/java/com/example/security2/dto/AuthorityDto.java b/src/main/java/com/example/security2/dto/AuthorityDto.java
new file mode 100644
index 0000000..42b5beb
--- /dev/null
+++ b/src/main/java/com/example/security2/dto/AuthorityDto.java
@@ -0,0 +1,4 @@
+package com.example.security2.dto;
+
+public record AuthorityDto(String authorityName) {
+}
diff --git a/src/main/java/com/example/security2/dto/ErrorDto.java b/src/main/java/com/example/security2/dto/ErrorDto.java
new file mode 100644
index 0000000..271faf1
--- /dev/null
+++ b/src/main/java/com/example/security2/dto/ErrorDto.java
@@ -0,0 +1,18 @@
+package com.example.security2.dto;
+
+import org.springframework.validation.FieldError;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public record ErrorDto(int status, String message, List fieldErrors) {
+
+    public ErrorDto(int status, String message) {
+        this(status, message, new ArrayList<>());
+    }
+
+    public void addFieldError(String objectName, String path, String message) {
+        FieldError error = new FieldError(objectName, path, message);
+        fieldErrors.add(error);
+    }
+}
diff --git a/src/main/java/com/example/security2/dto/LoginDto.java b/src/main/java/com/example/security2/dto/LoginDto.java
new file mode 100644
index 0000000..ce6a047
--- /dev/null
+++ b/src/main/java/com/example/security2/dto/LoginDto.java
@@ -0,0 +1,21 @@
+package com.example.security2.dto;
+
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+
+@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;
+}
diff --git a/src/main/java/com/example/security2/dto/TokenDto.java b/src/main/java/com/example/security2/dto/TokenDto.java
new file mode 100644
index 0000000..f3bebaa
--- /dev/null
+++ b/src/main/java/com/example/security2/dto/TokenDto.java
@@ -0,0 +1,4 @@
+package com.example.security2.dto;
+
+public record TokenDto(String token) {
+}
diff --git a/src/main/java/com/example/security2/dto/UserDto.java b/src/main/java/com/example/security2/dto/UserDto.java
new file mode 100644
index 0000000..1359523
--- /dev/null
+++ b/src/main/java/com/example/security2/dto/UserDto.java
@@ -0,0 +1,47 @@
+package com.example.security2.dto;
+
+import com.example.security2.entity.Authority;
+import com.example.security2.entity.User;
+import com.fasterxml.jackson.annotation.JsonProperty;
+import jakarta.validation.constraints.NotNull;
+import jakarta.validation.constraints.Size;
+import lombok.*;
+
+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 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 -> new AuthorityDto(authority.getAuthorityName()))
+                        .collect(Collectors.toSet()))
+                .build();
+    }
+}
diff --git a/src/main/java/com/example/security2/entity/Authority.java b/src/main/java/com/example/security2/entity/Authority.java
new file mode 100644
index 0000000..f80574e
--- /dev/null
+++ b/src/main/java/com/example/security2/entity/Authority.java
@@ -0,0 +1,22 @@
+package com.example.security2.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;
+}
\ No newline at end of file
diff --git a/src/main/java/com/example/security2/entity/User.java b/src/main/java/com/example/security2/entity/User.java
index 3e65117..22fe5ba 100644
--- a/src/main/java/com/example/security2/entity/User.java
+++ b/src/main/java/com/example/security2/entity/User.java
@@ -3,48 +3,46 @@
 import jakarta.persistence.*;
 import lombok.*;
 import org.hibernate.annotations.CreationTimestamp;
+import org.hibernate.annotations.UpdateTimestamp;
 
 import java.time.LocalDateTime;
+import java.util.Set;
 
 @Entity
 @Getter
 @Setter
-@Table(name = "users")
+@Table(name = "`user`")
+@Builder
+@AllArgsConstructor
 @NoArgsConstructor
-@ToString
 public class User {
 
     @Id
+    @Column(name = "user_id")
     @GeneratedValue(strategy = GenerationType.IDENTITY)
-    private Long id;
+    private Long userId;
 
-    @Column(nullable = false, unique = true)
-    private String email;
+    @Column(length = 50, unique = true)
+    private String username;
 
-    @Column(nullable = false)
+    @Column(length = 100)
     private String password;
 
-    private String name;
+    @Column(length = 50)
+    private String nickname;
 
-    private Integer age;
-
-    private String role;
-
-    private String provider;
-    private String providerId;
+    private boolean activated;
 
     @CreationTimestamp
     private LocalDateTime createDate;
-//    private LocalDateTime lastLoginDate;
-
-
-    @Builder
-    public User(String email, String password, String name, String role, String provider, String providerId) {
-        this.email = email;
-        this.password = password;
-        this.name = name;
-        this.role = role;
-        this.provider = provider;
-        this.providerId = providerId;
-    }
+
+    @UpdateTimestamp
+    private LocalDateTime updateDate;
+
+    @ManyToMany
+    @JoinTable(
+            name = "user_authority",
+            joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "user_id")},
+            inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "authority_name")})
+    private Set authorities;
 }
diff --git a/src/main/java/com/example/security2/enums/AuthType.java b/src/main/java/com/example/security2/enums/AuthType.java
deleted file mode 100644
index f29fe93..0000000
--- a/src/main/java/com/example/security2/enums/AuthType.java
+++ /dev/null
@@ -1,8 +0,0 @@
-package com.example.security2.enums;
-
-public enum AuthType {
-
-    ROLE_ADMIN,
-    ROLE_MANAGER,
-    ROLE_USER;
-}
diff --git a/src/main/java/com/example/security2/exception/DuplicateMemberException.java b/src/main/java/com/example/security2/exception/DuplicateMemberException.java
new file mode 100644
index 0000000..27154cc
--- /dev/null
+++ b/src/main/java/com/example/security2/exception/DuplicateMemberException.java
@@ -0,0 +1,20 @@
+package com.example.security2.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);
+    }
+}
diff --git a/src/main/java/com/example/security2/exception/NotFoundMemberException.java b/src/main/java/com/example/security2/exception/NotFoundMemberException.java
new file mode 100644
index 0000000..a1106a5
--- /dev/null
+++ b/src/main/java/com/example/security2/exception/NotFoundMemberException.java
@@ -0,0 +1,20 @@
+package com.example.security2.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);
+    }
+}
diff --git a/src/main/java/com/example/security2/handler/MethodArgumentNotValidExceptionHandler.java b/src/main/java/com/example/security2/handler/MethodArgumentNotValidExceptionHandler.java
new file mode 100644
index 0000000..c5a2fde
--- /dev/null
+++ b/src/main/java/com/example/security2/handler/MethodArgumentNotValidExceptionHandler.java
@@ -0,0 +1,38 @@
+package com.example.security2.handler;
+
+import com.example.security2.dto.ErrorDto;
+import org.springframework.core.Ordered;
+import org.springframework.core.annotation.Order;
+import org.springframework.validation.BindingResult;
+import org.springframework.validation.FieldError;
+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;
+
+import java.util.List;
+
+import static org.springframework.http.HttpStatus.BAD_REQUEST;
+
+@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 fieldErrors = result.getFieldErrors();
+        return processFieldErrors(fieldErrors);
+    }
+
+    private ErrorDto processFieldErrors(List fieldErrors) {
+        ErrorDto errorDto = new ErrorDto(BAD_REQUEST.value(), "@Valid Error");
+        for (FieldError fieldError : fieldErrors) {
+            errorDto.addFieldError(fieldError.getObjectName(), fieldError.getField(), fieldError.getDefaultMessage());
+        }
+        return errorDto;
+    }
+}
diff --git a/src/main/java/com/example/security2/handler/RestResponseExceptionHandler.java b/src/main/java/com/example/security2/handler/RestResponseExceptionHandler.java
new file mode 100644
index 0000000..14385f3
--- /dev/null
+++ b/src/main/java/com/example/security2/handler/RestResponseExceptionHandler.java
@@ -0,0 +1,29 @@
+package com.example.security2.handler;
+
+import com.example.security2.dto.ErrorDto;
+import com.example.security2.exception.DuplicateMemberException;
+import com.example.security2.exception.NotFoundMemberException;
+import org.springframework.web.bind.annotation.*;
+import org.springframework.web.context.request.WebRequest;
+import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler;
+
+import java.nio.file.AccessDeniedException;
+
+import static org.springframework.http.HttpStatus.CONFLICT;
+import static org.springframework.http.HttpStatus.FORBIDDEN;
+
+@RestControllerAdvice
+public class RestResponseExceptionHandler extends ResponseEntityExceptionHandler {
+
+    @ResponseStatus(CONFLICT)
+    @ExceptionHandler(value = { DuplicateMemberException.class })
+    protected ErrorDto conflict(RuntimeException ex, WebRequest request) {
+        return new ErrorDto(CONFLICT.value(), ex.getMessage());
+    }
+
+    @ResponseStatus(FORBIDDEN)
+    @ExceptionHandler(value = { NotFoundMemberException.class, AccessDeniedException.class })
+    protected ErrorDto forbidden(RuntimeException ex, WebRequest request) {
+        return new ErrorDto(FORBIDDEN.value(), ex.getMessage());
+    }
+}
diff --git a/src/main/java/com/example/security2/jwt/JwtAccessDeniedHandler.java b/src/main/java/com/example/security2/jwt/JwtAccessDeniedHandler.java
new file mode 100644
index 0000000..393d827
--- /dev/null
+++ b/src/main/java/com/example/security2/jwt/JwtAccessDeniedHandler.java
@@ -0,0 +1,22 @@
+package com.example.security2.jwt;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.access.AccessDeniedException;
+import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.stereotype.Component;
+
+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);
+    }
+}
diff --git a/src/main/java/com/example/security2/jwt/JwtAuthenticationEntryPoint.java b/src/main/java/com/example/security2/jwt/JwtAuthenticationEntryPoint.java
new file mode 100644
index 0000000..c03f5c4
--- /dev/null
+++ b/src/main/java/com/example/security2/jwt/JwtAuthenticationEntryPoint.java
@@ -0,0 +1,23 @@
+package com.example.security2.jwt;
+
+import jakarta.servlet.http.HttpServletRequest;
+import jakarta.servlet.http.HttpServletResponse;
+import org.springframework.security.core.AuthenticationException;
+import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.stereotype.Component;
+
+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);
+    }
+}
diff --git a/src/main/java/com/example/security2/jwt/JwtFilter.java b/src/main/java/com/example/security2/jwt/JwtFilter.java
new file mode 100644
index 0000000..cf5b3ff
--- /dev/null
+++ b/src/main/java/com/example/security2/jwt/JwtFilter.java
@@ -0,0 +1,55 @@
+package com.example.security2.jwt;
+
+import jakarta.servlet.FilterChain;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.ServletRequest;
+import jakarta.servlet.ServletResponse;
+import jakarta.servlet.http.HttpServletRequest;
+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 java.io.IOException;
+
+public class JwtFilter extends GenericFilterBean {
+
+    private static final Logger logger = LoggerFactory.getLogger(JwtFilter.class);
+    public static final String AUTHORIZATION_HEADER = "Authorization";
+    public static final String BEARER_AUTH = "Bearer ";
+    private final TokenProvider tokenProvider;
+
+    public JwtFilter(TokenProvider tokenProvider) {
+        this.tokenProvider = tokenProvider;
+    }
+
+    // 실제 필터링 로직
+    // 토큰의 인증정보를 SecurityContext에 저장하는 역할 수행
+    @Override
+    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
+        HttpServletRequest httpServletRequest = (HttpServletRequest) request;
+        String jwt = resolveToken(httpServletRequest);
+        String requestURI = httpServletRequest.getRequestURI();
+
+        if (StringUtils.hasText(jwt)) {
+            Authentication authentication = tokenProvider.getAuthentication(jwt);
+            SecurityContextHolder.getContext().setAuthentication(authentication);
+            logger.debug("Security Context에 '{}' 인증 정보를 저장했습니다. uri: {}", authentication, requestURI);
+        } else {
+            logger.debug("유효한 JWT 토큰이 없습니다. uri: {}", requestURI);
+        }
+
+        chain.doFilter(request, response);
+    }
+
+    private String resolveToken(HttpServletRequest request) {
+        String bearerToken = request.getHeader(AUTHORIZATION_HEADER);
+        return isBearerToken(bearerToken) ? bearerToken.substring(7) : null;
+    }
+
+    private boolean isBearerToken(String token) {
+        return (StringUtils.hasText(token) && token.startsWith(BEARER_AUTH));
+    }
+}
diff --git a/src/main/java/com/example/security2/jwt/TokenProvider.java b/src/main/java/com/example/security2/jwt/TokenProvider.java
new file mode 100644
index 0000000..46fd206
--- /dev/null
+++ b/src/main/java/com/example/security2/jwt/TokenProvider.java
@@ -0,0 +1,92 @@
+package com.example.security2.jwt;
+
+import io.jsonwebtoken.*;
+import io.jsonwebtoken.io.Decoders;
+import io.jsonwebtoken.security.Keys;
+import io.jsonwebtoken.security.SecurityException;
+import lombok.extern.slf4j.Slf4j;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+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;
+
+/**
+ * JWT를 생성하고 검증
+ */
+@Component
+@Slf4j
+public class TokenProvider {
+
+    private static final Logger logger = LoggerFactory.getLogger(TokenProvider.class);
+    private static final String AUTHORITIES_KEY = "secret";
+    private final long tokenValidityInMilliseconds;
+    private final Key key;
+
+    public TokenProvider(
+            @Value("${jwt.secret}") String secret,
+            @Value("${jwt.token-validity-in-seconds}") long tokenValidityInMilliseconds) {
+        this.tokenValidityInMilliseconds = tokenValidityInMilliseconds * 1000;
+        this.key = Keys.hmacShaKeyFor(Decoders.BASE64.decode(secret));
+    }
+
+    public String createToken(Authentication authentication) {
+        String authorities = authentication.getAuthorities().stream()
+                .map(GrantedAuthority::getAuthority)
+                .collect(Collectors.joining(","));
+
+        // 토큰의 expire 시간을 설정
+        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();
+    }
+
+    // 토큰으로 클레임을 만들고 이를 이용해 유저 객체를 만들어서 최종적으로 authentication 객체를 리턴
+    public Authentication getAuthentication(String token) {
+        Claims claims = getTokenClaims(token);
+
+        Collection extends GrantedAuthority> authorities = Arrays.stream(claims.get(AUTHORITIES_KEY).toString().split(","))
+                .map(SimpleGrantedAuthority::new)
+                .toList();
+
+        User principal = new User(claims.getSubject(), "", authorities);
+
+        return new UsernamePasswordAuthenticationToken(principal, token, authorities);
+    }
+
+    // jwt 토큰 리턴
+    private Claims getTokenClaims(String token) {
+        try {
+            return Jwts.parserBuilder()
+                    .setSigningKey(key)
+                    .build()
+                    .parseClaimsJws(token)
+                    .getBody();
+        } catch (SecurityException | MalformedJwtException e) {
+            throw new JwtException("잘못된 JWT 서명입니다.");
+        } catch (ExpiredJwtException e) {
+            throw new JwtException("만료된 JWT 토큰입니다.");
+        } catch (UnsupportedJwtException e) {
+            throw new JwtException("지원하지않는 JWT 토큰입니다.");
+        } catch (IllegalArgumentException e) {
+            throw new JwtException("JWT 토큰이 잘못되없습니다.");
+        }
+    }
+
+}
diff --git a/src/main/java/com/example/security2/oauth/PrincipalOauth2UserService.java b/src/main/java/com/example/security2/oauth/PrincipalOauth2UserService.java
deleted file mode 100644
index 6e28197..0000000
--- a/src/main/java/com/example/security2/oauth/PrincipalOauth2UserService.java
+++ /dev/null
@@ -1,83 +0,0 @@
-package com.example.security2.oauth;
-
-import com.example.security2.auth.PrincipalDetails;
-import com.example.security2.entity.User;
-import com.example.security2.enums.AuthType;
-import com.example.security2.repository.UserRepository;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.security.crypto.password.PasswordEncoder;
-import org.springframework.security.oauth2.client.userinfo.DefaultOAuth2UserService;
-import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
-import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
-import org.springframework.security.oauth2.core.user.OAuth2User;
-import org.springframework.stereotype.Service;
-
-import java.util.Map;
-import java.util.Optional;
-import java.util.Random;
-
-@Service
-@Slf4j
-public class PrincipalOauth2UserService extends DefaultOAuth2UserService {
-
-    private final PasswordEncoder passwordEncoder;
-    private final UserRepository userRepository;
-
-    public PrincipalOauth2UserService(PasswordEncoder passwordEncoder, UserRepository userRepository) {
-        this.passwordEncoder = passwordEncoder;
-        this.userRepository = userRepository;
-    }
-
-    // 함수 종료 시 @AuthenticationPrincipal 어노테이션이 만들어진다.
-    // 구글로 부터 받은 userRequest 데이터에 대한 후처리되는 함수.
-    @Override
-    public OAuth2User loadUser(OAuth2UserRequest userRequest) throws OAuth2AuthenticationException {
-        OAuth2User oAuth2User = super.loadUser(userRequest);
-        Map attributes = oAuth2User.getAttributes();
-        log.info("attr: {}", attributes.toString());
-
-        String email = oAuth2User.getAttribute("email");
-        Optional optionalUser = userRepository.findByEmail(email);
-
-        User userEntity;
-        if (optionalUser.isEmpty()) {
-            userEntity = createUserEntityByOAuth2User(userRequest, oAuth2User);
-            userRepository.save(userEntity);
-        } else {
-            userEntity = optionalUser.get();
-        }
-
-        return new PrincipalDetails(userEntity, oAuth2User.getAttributes());
-    }
-
-    private User createUserEntityByOAuth2User(OAuth2UserRequest userRequest, OAuth2User oAuth2User) {
-        String provider = userRequest.getClientRegistration().getRegistrationId(); // google
-        String providerId = oAuth2User.getAttribute("sub");
-        String userName = String.format("%s_%s", provider, providerId);
-        String password = passwordEncoder.encode(randomPassword());
-        String email = oAuth2User.getAttribute("email");
-        String role = AuthType.ROLE_USER.name();
-
-        return User.builder()
-                .email(email)
-                .name(userName)
-                .password(password)
-                .role(role)
-                .provider(provider)
-                .providerId(providerId)
-                .build();
-    }
-
-    private String randomPassword() {
-        // 랜덤 비밀번호(0 ~ z까지 20자리 랜덤 비밀번호 생성)
-        Random random = new Random();
-        int leftLimit = 97;     // letter '0'
-        int rightLimit = 122;   // letter 'z'
-        int targetStringLength = 20;
-
-        return random.ints(leftLimit, rightLimit + 1)
-                .limit(targetStringLength)
-                .collect(StringBuilder::new, StringBuilder::appendCodePoint, StringBuilder::append)
-                .toString();
-    }
-}
diff --git a/src/main/java/com/example/security2/repository/AuthorityRepository.java b/src/main/java/com/example/security2/repository/AuthorityRepository.java
new file mode 100644
index 0000000..00e3176
--- /dev/null
+++ b/src/main/java/com/example/security2/repository/AuthorityRepository.java
@@ -0,0 +1,7 @@
+package com.example.security2.repository;
+
+import com.example.security2.entity.Authority;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface AuthorityRepository extends JpaRepository {
+}
diff --git a/src/main/java/com/example/security2/repository/UserRepository.java b/src/main/java/com/example/security2/repository/UserRepository.java
index 862c00e..e845ec7 100644
--- a/src/main/java/com/example/security2/repository/UserRepository.java
+++ b/src/main/java/com/example/security2/repository/UserRepository.java
@@ -1,10 +1,13 @@
 package com.example.security2.repository;
 
 import com.example.security2.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 {
-    Optional findByEmail(String email);
+
+    @EntityGraph(attributePaths = "authorities")
+    Optional findByUsername(String username);
 }
diff --git a/src/main/java/com/example/security2/service/CustomUserDetailService.java b/src/main/java/com/example/security2/service/CustomUserDetailService.java
new file mode 100644
index 0000000..af984e9
--- /dev/null
+++ b/src/main/java/com/example/security2/service/CustomUserDetailService.java
@@ -0,0 +1,46 @@
+package com.example.security2.service;
+
+import com.example.security2.entity.User;
+import com.example.security2.repository.UserRepository;
+import jakarta.transaction.Transactional;
+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 java.util.Collection;
+
+@Component("userDetailsService")
+public class CustomUserDetailService implements UserDetailsService {
+
+    private final UserRepository userRepository;
+
+    public CustomUserDetailService(UserRepository userRepository) {
+        this.userRepository = userRepository;
+    }
+
+    @Override
+    @Transactional
+    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
+        return userRepository.findByUsername(username)
+                .map(user -> createUser(username, user))
+                .orElseThrow(() -> new UsernameNotFoundException(username + " > DB에서 찾을 수 없음."));
+    }
+
+    private org.springframework.security.core.userdetails.User createUser(String username, User user) {
+        if (!user.isActivated())
+            throw new RuntimeException(username + " > 활성화된 유저가 아닙니다.");
+
+        Collection extends GrantedAuthority> grantedAuthorities = user.getAuthorities().stream()
+                .map(authority -> new SimpleGrantedAuthority(authority.getAuthorityName()))
+                .toList();
+
+        return new org.springframework.security.core.userdetails.User(
+                user.getUsername(),
+                user.getPassword(),
+                grantedAuthorities
+        );
+    }
+}
diff --git a/src/main/java/com/example/security2/service/UserService.java b/src/main/java/com/example/security2/service/UserService.java
new file mode 100644
index 0000000..0e52243
--- /dev/null
+++ b/src/main/java/com/example/security2/service/UserService.java
@@ -0,0 +1,62 @@
+package com.example.security2.service;
+
+import com.example.security2.dto.UserDto;
+import com.example.security2.entity.Authority;
+import com.example.security2.entity.User;
+import com.example.security2.exception.DuplicateMemberException;
+import com.example.security2.exception.NotFoundMemberException;
+import com.example.security2.repository.UserRepository;
+import com.example.security2.util.SecurityUtil;
+import org.springframework.security.crypto.password.PasswordEncoder;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.util.Collections;
+
+@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.findByUsername(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 getMyUserWithAuthorities() {
+        /*String findUsername = SecurityUtil.getCurrentUsername().stream().findFirst().orElseThrow(() -> new UsernameNotFoundException("Member not found"));
+        User user = userRepository.findByUsername(findUsername).orElseThrow(() -> new NotFoundMemberException("Member not found"));*/
+
+        return UserDto.from(
+                SecurityUtil.getCurrentUsername()
+                        .flatMap(userRepository::findByUsername)
+                        .orElseThrow(() -> new NotFoundMemberException("Member not found"))
+        );
+    }
+
+    @Transactional(readOnly = true)
+    public UserDto getUserWithAuthorities(String username) {
+        return UserDto.from(userRepository.findByUsername(username).orElse(null));
+    }
+}
diff --git a/src/main/java/com/example/security2/util/SecurityUtil.java b/src/main/java/com/example/security2/util/SecurityUtil.java
new file mode 100644
index 0000000..ee3abd8
--- /dev/null
+++ b/src/main/java/com/example/security2/util/SecurityUtil.java
@@ -0,0 +1,32 @@
+package com.example.security2.util;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.core.context.SecurityContextHolder;
+import org.springframework.security.core.userdetails.UserDetails;
+
+import java.util.Optional;
+
+@Slf4j
+public class SecurityUtil {
+
+    private SecurityUtil() {}
+
+    public static Optional getCurrentUsername() {
+        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
+
+        if (authentication == null) {
+            log.debug("Security Context에 인증 정보가 없습니다.");
+            return Optional.empty();
+        }
+
+        String username;
+        if (authentication.getPrincipal() instanceof UserDetails springSecurityUser) {
+            username = springSecurityUser.getUsername();
+        } else {
+            username = (String) authentication.getPrincipal();
+        }
+
+        return Optional.ofNullable(username);
+    }
+}
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 069b94e..3cfe1e4 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -1,34 +1,33 @@
 spring:
-  security:
-    oauth2:
-      client:
-        registration:
-          google:
-            client-id: 574898903244-qrjp11vrbl3dh7alaneqman8odgao4fb.apps.googleusercontent.com
-            client-secret: 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
-            scope:
-              - email
-              - profile
-#              - openid
-#          facebook:
+
+  h2:
+    console:
+      enabled: true
 
   datasource:
-    driver-class-name: com.mysql.cj.jdbc.Driver
-    username: root
-    password: root
-    url: jdbc:mysql://localhost:3306/user?serverTimezone=UTC&characterEncoding=UTF-8
+    url: jdbc:h2:~/test
+#    url: jdbc:h2:mem:testdb
+    driver-class-name: org.h2.Driver
+    username: sa
+    password:
 
   jpa:
-    open-in-view: true
+    database-platform: org.hibernate.dialect.H2Dialect
     hibernate:
-      ddl-auto: update
-      naming:
-        physical-strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl
-    show-sql: true
+      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 'jvmoverloads-spring-boot-jwt-tutorial-secret-jvmoverloads-spring-boot-jwt-tutorial-secret'| base64
+  secret: c2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQtc2lsdmVybmluZS10ZWNoLXNwcmluZy1ib290LWp3dC10dXRvcmlhbC1zZWNyZXQK
+  token-validity-in-seconds: 86400
 
 logging:
   level:
-    org.hibernate.sql: debug
\ No newline at end of file
+    com.example: DEBUG
\ No newline at end of file
diff --git a/src/main/resources/data.sql b/src/main/resources/data.sql
new file mode 100644
index 0000000..fd138b2
--- /dev/null
+++ b/src/main/resources/data.sql
@@ -0,0 +1,9 @@
+insert into "user" (username, password, nickname, activated, create_date, update_date) values ('admin', '$2a$08$lDnHPz7eUkSi6ao14Twuau08mzhWrL4kyZGGU5xfiGALO/Vxd5DOi', 'admin', 1, current_time, null);
+insert into "user" (username, password, nickname, activated, create_date, update_date) values ('user', '$2a$08$UkVvwpULis18S19S5pZFn.YHPZt3oaqHZnDwqbCW9pft6uFtkXKDC', 'user', 1, current_time, null);
+
+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');
\ No newline at end of file
diff --git a/src/main/resources/templates/index.html b/src/main/resources/templates/index.html
deleted file mode 100644
index 5a2cc2e..0000000
--- a/src/main/resources/templates/index.html
+++ /dev/null
@@ -1,12 +0,0 @@
-
-
-    
-    
-    
-    인덱스 페이지
-
-
-    인덱스 페이지입니다.
-
-
\ No newline at end of file
diff --git a/src/main/resources/templates/joinForm.html b/src/main/resources/templates/joinForm.html
deleted file mode 100644
index f06b897..0000000
--- a/src/main/resources/templates/joinForm.html
+++ /dev/null
@@ -1,19 +0,0 @@
-
-
-    
-    
-    
-    회원가입 페이지
-
-
-회원가입 페이지
-
-
-
-
\ No newline at end of file
diff --git a/src/main/resources/templates/loginForm.html b/src/main/resources/templates/loginForm.html
deleted file mode 100644
index 4e18845..0000000
--- a/src/main/resources/templates/loginForm.html
+++ /dev/null
@@ -1,22 +0,0 @@
-
-
-    
-    
-    
-    로그인 페이지
-
-
-로그인 페이지
-
-
-
-구글 로그인
-회원가입
-
-
-
\ No newline at end of file