From 06b6d706c16eebaee311327a89ffe4116d504413 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Sun, 3 May 2026 20:08:58 +0900 Subject: [PATCH 01/12] =?UTF-8?q?feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/user/entity/Role.java | 4 ++++ src/main/java/com/example/user/entity/User.java | 4 ++++ src/main/resources/{application.yaml => application.yml} | 0 3 files changed, 8 insertions(+) create mode 100644 src/main/java/com/example/user/entity/Role.java create mode 100644 src/main/java/com/example/user/entity/User.java rename src/main/resources/{application.yaml => application.yml} (100%) diff --git a/src/main/java/com/example/user/entity/Role.java b/src/main/java/com/example/user/entity/Role.java new file mode 100644 index 0000000..2e0e9bc --- /dev/null +++ b/src/main/java/com/example/user/entity/Role.java @@ -0,0 +1,4 @@ +package com.example.user.entity; + +public enum Role { +} diff --git a/src/main/java/com/example/user/entity/User.java b/src/main/java/com/example/user/entity/User.java new file mode 100644 index 0000000..01c7f46 --- /dev/null +++ b/src/main/java/com/example/user/entity/User.java @@ -0,0 +1,4 @@ +package com.example.user.entity; + +public class User { +} diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yml similarity index 100% rename from src/main/resources/application.yaml rename to src/main/resources/application.yml From f01f8d92d68dc0f477a626affc30ce82049b0e7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Sun, 3 May 2026 21:00:32 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat:=20User=20=EC=97=94=ED=8B=B0?= =?UTF-8?q?=ED=8B=B0=EC=97=90=20create=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80/dto=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/user/dto/request/SignUpRequest.java | 15 +++++++++++++++ .../user/dto/request/UserUpdateRequest.java | 11 +++++++++++ .../user/dto/response/UserSearchResponse.java | 4 ++++ 3 files changed, 30 insertions(+) create mode 100644 src/main/java/com/example/user/dto/request/SignUpRequest.java create mode 100644 src/main/java/com/example/user/dto/request/UserUpdateRequest.java create mode 100644 src/main/java/com/example/user/dto/response/UserSearchResponse.java diff --git a/src/main/java/com/example/user/dto/request/SignUpRequest.java b/src/main/java/com/example/user/dto/request/SignUpRequest.java new file mode 100644 index 0000000..e3c3320 --- /dev/null +++ b/src/main/java/com/example/user/dto/request/SignUpRequest.java @@ -0,0 +1,15 @@ +package com.example.user.dto; + +import jakarta.validation.constraints.NotNull; + +public record SignUpRequest( + @NotNull + String email, + + @NotNull + String password, + + @NotNull + String name +) { +} diff --git a/src/main/java/com/example/user/dto/request/UserUpdateRequest.java b/src/main/java/com/example/user/dto/request/UserUpdateRequest.java new file mode 100644 index 0000000..c54f24d --- /dev/null +++ b/src/main/java/com/example/user/dto/request/UserUpdateRequest.java @@ -0,0 +1,11 @@ +package com.example.user.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; + +public record UserUpdateRequest( + String name, + + @JsonProperty("image_url") + String imageUrl +) { +} diff --git a/src/main/java/com/example/user/dto/response/UserSearchResponse.java b/src/main/java/com/example/user/dto/response/UserSearchResponse.java new file mode 100644 index 0000000..b20535a --- /dev/null +++ b/src/main/java/com/example/user/dto/response/UserSearchResponse.java @@ -0,0 +1,4 @@ +package com.example.user.dto.response; + +public record UserSearchResponse() { +} From 9132b8dbd17c7e84e6030d7aaa853d37bc21a76f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Sun, 3 May 2026 21:04:49 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat:=20repository=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/user/repository/UserRepository.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/com/example/user/repository/UserRepository.java diff --git a/src/main/java/com/example/user/repository/UserRepository.java b/src/main/java/com/example/user/repository/UserRepository.java new file mode 100644 index 0000000..6c79dfa --- /dev/null +++ b/src/main/java/com/example/user/repository/UserRepository.java @@ -0,0 +1,4 @@ +package com.example.user.repository; + +public interface UserRepository { +} From c13b825ea96e7a4257cbe668396ff84222613228 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Mon, 4 May 2026 13:27:11 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat:=20service=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/user/service/UserService.java | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/main/java/com/example/user/service/UserService.java diff --git a/src/main/java/com/example/user/service/UserService.java b/src/main/java/com/example/user/service/UserService.java new file mode 100644 index 0000000..399c04c --- /dev/null +++ b/src/main/java/com/example/user/service/UserService.java @@ -0,0 +1,4 @@ +package com.example.user.service; + +public class UserService { +} From 759c05f033d471b280a6c74e8508ada0e8ae5d27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Mon, 4 May 2026 16:25:34 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat:=20jwt=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20/=20controller=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/UserController.java | 4 +++ .../user/dto/response/SignUpResponse.java | 4 +++ .../example/user/global/dto/ApiResponse.java | 4 +++ .../user/global/security/JwtFilter.java | 30 +++++++++++++++++++ .../user/global/security/JwtProvider.java | 4 +++ .../user/global/security/UserPrincipal.java | 4 +++ .../example/user/global/util/HttpUtil.java | 4 +++ 7 files changed, 54 insertions(+) create mode 100644 src/main/java/com/example/user/controller/UserController.java create mode 100644 src/main/java/com/example/user/dto/response/SignUpResponse.java create mode 100644 src/main/java/com/example/user/global/dto/ApiResponse.java create mode 100644 src/main/java/com/example/user/global/security/JwtFilter.java create mode 100644 src/main/java/com/example/user/global/security/JwtProvider.java create mode 100644 src/main/java/com/example/user/global/security/UserPrincipal.java create mode 100644 src/main/java/com/example/user/global/util/HttpUtil.java diff --git a/src/main/java/com/example/user/controller/UserController.java b/src/main/java/com/example/user/controller/UserController.java new file mode 100644 index 0000000..296cd71 --- /dev/null +++ b/src/main/java/com/example/user/controller/UserController.java @@ -0,0 +1,4 @@ +package com.example.user.controller; + +public class UserController { +} diff --git a/src/main/java/com/example/user/dto/response/SignUpResponse.java b/src/main/java/com/example/user/dto/response/SignUpResponse.java new file mode 100644 index 0000000..6bca829 --- /dev/null +++ b/src/main/java/com/example/user/dto/response/SignUpResponse.java @@ -0,0 +1,4 @@ +package com.example.user.dto.response; + +public record UserCreateResponse() { +} diff --git a/src/main/java/com/example/user/global/dto/ApiResponse.java b/src/main/java/com/example/user/global/dto/ApiResponse.java new file mode 100644 index 0000000..60a03e4 --- /dev/null +++ b/src/main/java/com/example/user/global/dto/ApiResponse.java @@ -0,0 +1,4 @@ +package com.example.user.global.dto; + +public record ApiResponse() { +} diff --git a/src/main/java/com/example/user/global/security/JwtFilter.java b/src/main/java/com/example/user/global/security/JwtFilter.java new file mode 100644 index 0000000..74705b8 --- /dev/null +++ b/src/main/java/com/example/user/global/security/JwtFilter.java @@ -0,0 +1,30 @@ +package com.example.user.global.security; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Component; +import org.springframework.web.filter.OncePerRequestFilter; + +import java.io.IOException; + +@Component +@RequiredArgsConstructor +public class UserAuthenticationFilter extends OncePerRequestFilter { + + @Override + protected void doFilterInternal(HttpServletRequest request, + HttpServletResponse response, + FilterChain filterChain) + throws ServletException, IOException { + String token = request.getHeader("Authorization"); + + if (token != null && token.startsWith("Bearer ")) { + token = token.substring(7); + + String userId = jwtProvider.get + } + } +} diff --git a/src/main/java/com/example/user/global/security/JwtProvider.java b/src/main/java/com/example/user/global/security/JwtProvider.java new file mode 100644 index 0000000..b79f222 --- /dev/null +++ b/src/main/java/com/example/user/global/security/JwtProvider.java @@ -0,0 +1,4 @@ +package com.example.user.global.security; + +public class JwtProvider { +} diff --git a/src/main/java/com/example/user/global/security/UserPrincipal.java b/src/main/java/com/example/user/global/security/UserPrincipal.java new file mode 100644 index 0000000..2d2fe29 --- /dev/null +++ b/src/main/java/com/example/user/global/security/UserPrincipal.java @@ -0,0 +1,4 @@ +package com.example.user.global.security; + +public class UserPrincipal { +} diff --git a/src/main/java/com/example/user/global/util/HttpUtil.java b/src/main/java/com/example/user/global/util/HttpUtil.java new file mode 100644 index 0000000..28b810b --- /dev/null +++ b/src/main/java/com/example/user/global/util/HttpUtil.java @@ -0,0 +1,4 @@ +package com.example.user.global.util; + +public class HttpUtil { +} From 78786af26ad7b8265cb784a39ce8dc46b24af0f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Mon, 4 May 2026 17:52:39 +0900 Subject: [PATCH 06/12] =?UTF-8?q?fix:=20JwtFilter=EC=97=90=EC=84=9C=20@Com?= =?UTF-8?q?ponent=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 8 +- .../user/controller/TestController.java | 21 ++++++ .../user/controller/UserController.java | 60 +++++++++++++++ .../user/dto/request/SignUpRequest.java | 12 +-- .../user/dto/request/UserUpdateRequest.java | 2 +- .../user/dto/response/SignUpResponse.java | 11 ++- .../user/dto/response/UserSearchResponse.java | 25 ++++++- .../java/com/example/user/entity/Role.java | 2 + .../java/com/example/user/entity/User.java | 73 +++++++++++++++++++ .../user/global/config/SecurityConfig.java | 36 +++++++++ .../example/user/global/dto/ApiResponse.java | 7 +- .../user/global/security/JwtFilter.java | 27 ++++++- .../user/global/security/JwtProvider.java | 50 +++++++++++++ .../user/global/security/UserPrincipal.java | 9 +++ .../example/user/global/util/HttpUtil.java | 9 +++ .../user/repository/UserRepository.java | 8 +- .../com/example/user/service/UserService.java | 65 +++++++++++++++++ src/main/resources/application.yml | 12 +++ 18 files changed, 420 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/example/user/controller/TestController.java create mode 100644 src/main/java/com/example/user/global/config/SecurityConfig.java diff --git a/build.gradle b/build.gradle index 9f9d4b1..8ffbcc3 100644 --- a/build.gradle +++ b/build.gradle @@ -20,15 +20,19 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-session-jdbc' compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.postgresql:postgresql' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' - testImplementation 'org.springframework.boot:spring-boot-starter-session-jdbc-test' testCompileOnly 'org.projectlombok:lombok' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' testAnnotationProcessor 'org.projectlombok:lombok' + implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'io.jsonwebtoken:jjwt-api:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6' + implementation 'org.springframework.boot:spring-boot-starter-security' } tasks.named('test') { diff --git a/src/main/java/com/example/user/controller/TestController.java b/src/main/java/com/example/user/controller/TestController.java new file mode 100644 index 0000000..c33a41f --- /dev/null +++ b/src/main/java/com/example/user/controller/TestController.java @@ -0,0 +1,21 @@ +package com.example.user.controller; + +import com.example.user.global.security.JwtProvider; +import com.example.user.service.UserService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/test") +public class TestController { + + private final JwtProvider jwtProvider; + private final UserService userService; + + @GetMapping("/token") + public String createToken(@RequestParam String userId) { + userService.getMyProfile(userId); // 유저 없으면 여기서 예외 발생 + return jwtProvider.createTestToken(userId); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/user/controller/UserController.java b/src/main/java/com/example/user/controller/UserController.java index 296cd71..bef42d2 100644 --- a/src/main/java/com/example/user/controller/UserController.java +++ b/src/main/java/com/example/user/controller/UserController.java @@ -1,4 +1,64 @@ package com.example.user.controller; +import com.example.user.dto.request.SignUpRequest; +import com.example.user.dto.request.UserUpdateRequest; +import com.example.user.dto.response.SignUpResponse; +import com.example.user.dto.response.UserSearchResponse; +import com.example.user.global.dto.ApiResponse; +import com.example.user.global.security.UserPrincipal; +import com.example.user.global.util.HttpUtil; +import com.example.user.service.UserService; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/users") public class UserController { + + private final UserService userService; + + @PostMapping("/signup") + public ResponseEntity> signup( + @Valid @RequestBody SignUpRequest request + ) { + SignUpResponse response = userService.signup(request); + + ApiResponse apiResponse = HttpUtil.success("create user", response); + + return ResponseEntity.status(201).body(apiResponse); + } + + @GetMapping("/me") + public ResponseEntity> getMyProfile( + @AuthenticationPrincipal UserPrincipal principal + ) { + UserSearchResponse response = userService.getMyProfile(principal.getUserId()); + + ApiResponse apiResponse = HttpUtil.success("select my profile", response); + + return ResponseEntity.ok(apiResponse); + } + + @PatchMapping("/me") + public ResponseEntity update( + @AuthenticationPrincipal UserPrincipal principal, + @RequestBody UserUpdateRequest request + ) { + userService.update(principal.getUserId(), request); + + return ResponseEntity.noContent().build(); + } + + @DeleteMapping("/me") + public ResponseEntity delete( + @AuthenticationPrincipal UserPrincipal principal + ) { + userService.delete(principal.getUserId()); + + return ResponseEntity.noContent().build(); + } } diff --git a/src/main/java/com/example/user/dto/request/SignUpRequest.java b/src/main/java/com/example/user/dto/request/SignUpRequest.java index e3c3320..9623b90 100644 --- a/src/main/java/com/example/user/dto/request/SignUpRequest.java +++ b/src/main/java/com/example/user/dto/request/SignUpRequest.java @@ -1,15 +1,17 @@ -package com.example.user.dto; +package com.example.user.dto.request; -import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Email; +import jakarta.validation.constraints.NotBlank; public record SignUpRequest( - @NotNull + @NotBlank + @Email String email, - @NotNull + @NotBlank String password, - @NotNull + @NotBlank String name ) { } diff --git a/src/main/java/com/example/user/dto/request/UserUpdateRequest.java b/src/main/java/com/example/user/dto/request/UserUpdateRequest.java index c54f24d..e69f11f 100644 --- a/src/main/java/com/example/user/dto/request/UserUpdateRequest.java +++ b/src/main/java/com/example/user/dto/request/UserUpdateRequest.java @@ -1,4 +1,4 @@ -package com.example.user.dto; +package com.example.user.dto.request; import com.fasterxml.jackson.annotation.JsonProperty; diff --git a/src/main/java/com/example/user/dto/response/SignUpResponse.java b/src/main/java/com/example/user/dto/response/SignUpResponse.java index 6bca829..617780d 100644 --- a/src/main/java/com/example/user/dto/response/SignUpResponse.java +++ b/src/main/java/com/example/user/dto/response/SignUpResponse.java @@ -1,4 +1,13 @@ package com.example.user.dto.response; -public record UserCreateResponse() { +import com.example.user.entity.User; +import com.fasterxml.jackson.annotation.JsonProperty; + +public record SignUpResponse( + @JsonProperty("user_id") + String userId +) { + public static SignUpResponse from(User user) { + return new SignUpResponse(user.getId()); + } } diff --git a/src/main/java/com/example/user/dto/response/UserSearchResponse.java b/src/main/java/com/example/user/dto/response/UserSearchResponse.java index b20535a..cf38a58 100644 --- a/src/main/java/com/example/user/dto/response/UserSearchResponse.java +++ b/src/main/java/com/example/user/dto/response/UserSearchResponse.java @@ -1,4 +1,27 @@ package com.example.user.dto.response; -public record UserSearchResponse() { +import com.example.user.entity.User; +import com.fasterxml.jackson.annotation.JsonProperty; + +import java.time.LocalDateTime; + +public record UserSearchResponse( + @JsonProperty("image_url") + String imageUrl, + + String email, + + String name, + + @JsonProperty("created_at") + LocalDateTime createdAt +) { + public static UserSearchResponse from(User user) { + return new UserSearchResponse( + user.getImageUrl(), + user.getEmail(), + user.getName(), + user.getCreatedAt() + ); + } } diff --git a/src/main/java/com/example/user/entity/Role.java b/src/main/java/com/example/user/entity/Role.java index 2e0e9bc..2b4b6be 100644 --- a/src/main/java/com/example/user/entity/Role.java +++ b/src/main/java/com/example/user/entity/Role.java @@ -1,4 +1,6 @@ package com.example.user.entity; public enum Role { + USER, + ADMIN } diff --git a/src/main/java/com/example/user/entity/User.java b/src/main/java/com/example/user/entity/User.java index 01c7f46..64a28ad 100644 --- a/src/main/java/com/example/user/entity/User.java +++ b/src/main/java/com/example/user/entity/User.java @@ -1,4 +1,77 @@ package com.example.user.entity; +import jakarta.persistence.*; +import lombok.*; +import org.hibernate.annotations.CreationTimestamp; +import org.hibernate.annotations.UpdateTimestamp; + +import java.time.LocalDateTime; +import java.util.UUID; + +@Entity +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Getter +@Table(name = "users") public class User { + @Id + private String id; + + @Column(nullable = false, unique = true) + private String email; + + private String password; + + @Column(nullable = false) + private String name; + + @Column(name = "image_url") + private String imageUrl; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private Role role; + + @Column(name = "auth_provider") + private String authProvider; + + @Column(name = "provider_id") + private String providerId; + + @CreationTimestamp + @Column(nullable = false, name = "created_at") + private LocalDateTime createdAt; + + @UpdateTimestamp + @Column(name = "updated_at") + private LocalDateTime updatedAt; + + @Column(name = "deleted_at") + private LocalDateTime deletedAt; + + public static User create(String email, String password, String name) { + return User.builder() + .id(UUID.randomUUID().toString()) + .email(email) + .password(password) + .name(name) + .role(Role.USER) + .build(); + } + + public void update(String name, String imageUrl) { + if (this.deletedAt != null) { + throw new IllegalStateException("삭제된 유저"); + } + if (name != null) this.name = name; + if (imageUrl != null) this.imageUrl = imageUrl; + } + + public void delete() { + if (this.deletedAt != null) { + throw new IllegalStateException("삭제된 유저"); + } + this.deletedAt = LocalDateTime.now(); + } } diff --git a/src/main/java/com/example/user/global/config/SecurityConfig.java b/src/main/java/com/example/user/global/config/SecurityConfig.java new file mode 100644 index 0000000..ce31e0b --- /dev/null +++ b/src/main/java/com/example/user/global/config/SecurityConfig.java @@ -0,0 +1,36 @@ +package com.example.user.global.security; + +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.web.SecurityFilterChain; +import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; + +@Configuration +@EnableWebSecurity +@RequiredArgsConstructor +public class SecurityConfig { + + private final JwtProvider jwtProvider; + + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + return http + .csrf(csrf -> csrf.disable()) + .formLogin(formLogin -> formLogin.disable()) + .httpBasic(httpBasic -> httpBasic.disable()) + .sessionManagement(session -> + session.sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/users/signup").permitAll() + .requestMatchers("/users/me").authenticated() + .anyRequest().permitAll() + ) + .addFilterBefore(new JwtFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class) + .build(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/user/global/dto/ApiResponse.java b/src/main/java/com/example/user/global/dto/ApiResponse.java index 60a03e4..7b5ab93 100644 --- a/src/main/java/com/example/user/global/dto/ApiResponse.java +++ b/src/main/java/com/example/user/global/dto/ApiResponse.java @@ -1,4 +1,7 @@ package com.example.user.global.dto; -public record ApiResponse() { -} +public record ApiResponse( + String message, + T data +) { +} \ No newline at end of file diff --git a/src/main/java/com/example/user/global/security/JwtFilter.java b/src/main/java/com/example/user/global/security/JwtFilter.java index 74705b8..6953ca9 100644 --- a/src/main/java/com/example/user/global/security/JwtFilter.java +++ b/src/main/java/com/example/user/global/security/JwtFilter.java @@ -5,14 +5,18 @@ import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Component; +import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; +import org.springframework.security.core.Authentication; +import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; +import java.util.Collections; -@Component @RequiredArgsConstructor -public class UserAuthenticationFilter extends OncePerRequestFilter { +public class JwtFilter extends OncePerRequestFilter { + + private final JwtProvider jwtProvider; @Override protected void doFilterInternal(HttpServletRequest request, @@ -24,7 +28,22 @@ protected void doFilterInternal(HttpServletRequest request, if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); - String userId = jwtProvider.get + if (jwtProvider.validateToken(token)) { + String userId = jwtProvider.getUserId(token); + + UserPrincipal principal = new UserPrincipal(userId); + + Authentication authentication = + new UsernamePasswordAuthenticationToken( + principal, + null, + Collections.emptyList() + ); + + SecurityContextHolder.getContext().setAuthentication(authentication); + } } + + filterChain.doFilter(request, response); } } diff --git a/src/main/java/com/example/user/global/security/JwtProvider.java b/src/main/java/com/example/user/global/security/JwtProvider.java index b79f222..b5bb0f3 100644 --- a/src/main/java/com/example/user/global/security/JwtProvider.java +++ b/src/main/java/com/example/user/global/security/JwtProvider.java @@ -1,4 +1,54 @@ package com.example.user.global.security; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.JwtException; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.security.Keys; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Component; + +import javax.crypto.SecretKey; +import java.nio.charset.StandardCharsets; +import java.util.Date; + +@Component public class JwtProvider { + + private final SecretKey secretKey; + + public JwtProvider(@Value("${jwt.secret}") String secret) { + this.secretKey = Keys.hmacShaKeyFor(secret.getBytes(StandardCharsets.UTF_8)); + } + + public String getUserId(String token) { + Claims claims = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + + return claims.getSubject(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token); + + return true; + } catch (JwtException | IllegalArgumentException e) { + return false; + } + } + public String createTestToken(String userId) { + return Jwts.builder() + .subject(userId) + .issuedAt(new Date()) + .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) + .signWith(secretKey) + .compact(); + } } diff --git a/src/main/java/com/example/user/global/security/UserPrincipal.java b/src/main/java/com/example/user/global/security/UserPrincipal.java index 2d2fe29..31f86d9 100644 --- a/src/main/java/com/example/user/global/security/UserPrincipal.java +++ b/src/main/java/com/example/user/global/security/UserPrincipal.java @@ -1,4 +1,13 @@ package com.example.user.global.security; +import lombok.Getter; + +@Getter public class UserPrincipal { + + private final String userId; + + public UserPrincipal(String userId) { + this.userId = userId; + } } diff --git a/src/main/java/com/example/user/global/util/HttpUtil.java b/src/main/java/com/example/user/global/util/HttpUtil.java index 28b810b..0f9fad8 100644 --- a/src/main/java/com/example/user/global/util/HttpUtil.java +++ b/src/main/java/com/example/user/global/util/HttpUtil.java @@ -1,4 +1,13 @@ package com.example.user.global.util; +import com.example.user.global.dto.ApiResponse; + public class HttpUtil { + public static ApiResponse success(String message, T data) { + return new ApiResponse<>(message, data); + } + + public static ApiResponse success(String message) { + return new ApiResponse<>(message, null); + } } diff --git a/src/main/java/com/example/user/repository/UserRepository.java b/src/main/java/com/example/user/repository/UserRepository.java index 6c79dfa..681b96b 100644 --- a/src/main/java/com/example/user/repository/UserRepository.java +++ b/src/main/java/com/example/user/repository/UserRepository.java @@ -1,4 +1,10 @@ package com.example.user.repository; -public interface UserRepository { +import com.example.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface UserRepository extends JpaRepository { + boolean existsByEmail(String email); } diff --git a/src/main/java/com/example/user/service/UserService.java b/src/main/java/com/example/user/service/UserService.java index 399c04c..03d46b8 100644 --- a/src/main/java/com/example/user/service/UserService.java +++ b/src/main/java/com/example/user/service/UserService.java @@ -1,4 +1,69 @@ package com.example.user.service; +import com.example.user.dto.request.SignUpRequest; +import com.example.user.dto.request.UserUpdateRequest; +import com.example.user.dto.response.SignUpResponse; +import com.example.user.dto.response.UserSearchResponse; +import com.example.user.entity.User; +import com.example.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor public class UserService { + + private final UserRepository userRepository; + + // 회원가입 (유저 생성) + @Transactional + public SignUpResponse signup(SignUpRequest request) { + if (userRepository.existsByEmail(request.email())) { + throw new IllegalArgumentException("이미 존재하는 이메일"); + } + User user = User.create( + request.email(), + request.password(), + request.name() + ); + userRepository.save(user); + + return SignUpResponse.from(user); + } + + // 내 정보 조회 + public UserSearchResponse getMyProfile(String userId) { + User user = getActiveUser(userId); + return UserSearchResponse.from(user); + } + + // 수정 + @Transactional + public void update(String userId, UserUpdateRequest request) { + User user = getActiveUser(userId); + if (request.name() != null && request.name().isBlank()) { + throw new IllegalArgumentException("이름은 비어있을 수 없음"); + } + user.update(request.name(), request.imageUrl()); + } + + // 삭제 + @Transactional + public void delete(String userId) { + User user = getActiveUser(userId); + user.delete(); + } + + // 삭제 안 된 유저 찾기 + private User getActiveUser(String userId) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new IllegalArgumentException("유저 없음")); + + if (user.getDeletedAt() != null) { + throw new IllegalStateException("삭제된 유저"); + } + + return user; + } } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 02be784..c91c425 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,15 @@ spring: application: name: User + + datasource: + url: jdbc:postgresql://localhost:5432/momentlit_user + username: jhlarry1109 + + jpa: + hibernate: + ddl-auto: update + show-sql: true + +jwt: + secret: momentlit-user-service-jwt-secret-key-for-local-test-1234567890 \ No newline at end of file From 0e153c4a75d5c4207e4dfa72efdc90eec9817188 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Mon, 4 May 2026 18:48:46 +0900 Subject: [PATCH 07/12] =?UTF-8?q?fix:=20JwtFilter=20=EC=9D=B8=EC=A6=9D=20?= =?UTF-8?q?=EC=8B=A4=ED=8C=A8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/global/config/SecurityConfig.java | 7 ++--- .../user/global/security/JwtFilter.java | 26 ++++++++++++------- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/example/user/global/config/SecurityConfig.java b/src/main/java/com/example/user/global/config/SecurityConfig.java index ce31e0b..50324ae 100644 --- a/src/main/java/com/example/user/global/config/SecurityConfig.java +++ b/src/main/java/com/example/user/global/config/SecurityConfig.java @@ -1,5 +1,6 @@ -package com.example.user.global.security; +package com.example.user.global.config; +import com.example.user.global.security.JwtFilter; import lombok.RequiredArgsConstructor; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -14,7 +15,7 @@ @RequiredArgsConstructor public class SecurityConfig { - private final JwtProvider jwtProvider; + private final JwtFilter jwtFilter; @Bean public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { @@ -30,7 +31,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti .requestMatchers("/users/me").authenticated() .anyRequest().permitAll() ) - .addFilterBefore(new JwtFilter(jwtProvider), UsernamePasswordAuthenticationFilter.class) + .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } } \ No newline at end of file diff --git a/src/main/java/com/example/user/global/security/JwtFilter.java b/src/main/java/com/example/user/global/security/JwtFilter.java index 6953ca9..2818993 100644 --- a/src/main/java/com/example/user/global/security/JwtFilter.java +++ b/src/main/java/com/example/user/global/security/JwtFilter.java @@ -8,11 +8,13 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; import org.springframework.security.core.context.SecurityContextHolder; +import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; +@Component @RequiredArgsConstructor public class JwtFilter extends OncePerRequestFilter { @@ -28,19 +30,23 @@ protected void doFilterInternal(HttpServletRequest request, if (token != null && token.startsWith("Bearer ")) { token = token.substring(7); - if (jwtProvider.validateToken(token)) { - String userId = jwtProvider.getUserId(token); + try { + if (jwtProvider.validateToken(token)) { + String userId = jwtProvider.getUserId(token); - UserPrincipal principal = new UserPrincipal(userId); + UserPrincipal principal = new UserPrincipal(userId); - Authentication authentication = - new UsernamePasswordAuthenticationToken( - principal, - null, - Collections.emptyList() - ); + Authentication authentication = + new UsernamePasswordAuthenticationToken( + principal, + null, + Collections.emptyList() + ); - SecurityContextHolder.getContext().setAuthentication(authentication); + SecurityContextHolder.getContext().setAuthentication(authentication); + } + } catch (Exception e) { + SecurityContextHolder.clearContext(); } } From bf078347c43ac7ac9be683acfe0245ac81f95941 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Mon, 4 May 2026 18:50:31 +0900 Subject: [PATCH 08/12] =?UTF-8?q?delete:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../user/controller/TestController.java | 21 ------------------- .../user/global/security/JwtProvider.java | 8 ------- 2 files changed, 29 deletions(-) delete mode 100644 src/main/java/com/example/user/controller/TestController.java diff --git a/src/main/java/com/example/user/controller/TestController.java b/src/main/java/com/example/user/controller/TestController.java deleted file mode 100644 index c33a41f..0000000 --- a/src/main/java/com/example/user/controller/TestController.java +++ /dev/null @@ -1,21 +0,0 @@ -package com.example.user.controller; - -import com.example.user.global.security.JwtProvider; -import com.example.user.service.UserService; -import lombok.RequiredArgsConstructor; -import org.springframework.web.bind.annotation.*; - -@RestController -@RequiredArgsConstructor -@RequestMapping("/test") -public class TestController { - - private final JwtProvider jwtProvider; - private final UserService userService; - - @GetMapping("/token") - public String createToken(@RequestParam String userId) { - userService.getMyProfile(userId); // 유저 없으면 여기서 예외 발생 - return jwtProvider.createTestToken(userId); - } -} \ No newline at end of file diff --git a/src/main/java/com/example/user/global/security/JwtProvider.java b/src/main/java/com/example/user/global/security/JwtProvider.java index b5bb0f3..c64740a 100644 --- a/src/main/java/com/example/user/global/security/JwtProvider.java +++ b/src/main/java/com/example/user/global/security/JwtProvider.java @@ -43,12 +43,4 @@ public boolean validateToken(String token) { return false; } } - public String createTestToken(String userId) { - return Jwts.builder() - .subject(userId) - .issuedAt(new Date()) - .expiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) - .signWith(secretKey) - .compact(); - } } From ee404d0e6413f67b7b350313516a11586f577195 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Wed, 6 May 2026 08:28:22 +0900 Subject: [PATCH 09/12] =?UTF-8?q?fix:=20UserPrincipal=EC=97=90=20role=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20/=20HttpUtil=EC=9D=84=20ResponseUtil?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/user/controller/UserController.java | 6 +++--- .../example/user/global/security/JwtFilter.java | 8 ++++++-- .../example/user/global/security/JwtProvider.java | 14 ++++++++++++++ .../user/global/security/UserPrincipal.java | 6 +++++- .../util/{HttpUtil.java => ResponseUtil.java} | 2 +- 5 files changed, 29 insertions(+), 7 deletions(-) rename src/main/java/com/example/user/global/util/{HttpUtil.java => ResponseUtil.java} (92%) diff --git a/src/main/java/com/example/user/controller/UserController.java b/src/main/java/com/example/user/controller/UserController.java index bef42d2..e156198 100644 --- a/src/main/java/com/example/user/controller/UserController.java +++ b/src/main/java/com/example/user/controller/UserController.java @@ -6,7 +6,7 @@ import com.example.user.dto.response.UserSearchResponse; import com.example.user.global.dto.ApiResponse; import com.example.user.global.security.UserPrincipal; -import com.example.user.global.util.HttpUtil; +import com.example.user.global.util.ResponseUtil; import com.example.user.service.UserService; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; @@ -27,7 +27,7 @@ public ResponseEntity> signup( ) { SignUpResponse response = userService.signup(request); - ApiResponse apiResponse = HttpUtil.success("create user", response); + ApiResponse apiResponse = ResponseUtil.success("create user", response); return ResponseEntity.status(201).body(apiResponse); } @@ -38,7 +38,7 @@ public ResponseEntity> getMyProfile( ) { UserSearchResponse response = userService.getMyProfile(principal.getUserId()); - ApiResponse apiResponse = HttpUtil.success("select my profile", response); + ApiResponse apiResponse = ResponseUtil.success("select my profile", response); return ResponseEntity.ok(apiResponse); } diff --git a/src/main/java/com/example/user/global/security/JwtFilter.java b/src/main/java/com/example/user/global/security/JwtFilter.java index 2818993..b43b3ff 100644 --- a/src/main/java/com/example/user/global/security/JwtFilter.java +++ b/src/main/java/com/example/user/global/security/JwtFilter.java @@ -1,5 +1,6 @@ package com.example.user.global.security; +import com.example.user.entity.Role; import jakarta.servlet.FilterChain; import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; @@ -7,12 +8,14 @@ import lombok.RequiredArgsConstructor; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.Authentication; +import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; +import java.util.List; @Component @RequiredArgsConstructor @@ -33,14 +36,15 @@ protected void doFilterInternal(HttpServletRequest request, try { if (jwtProvider.validateToken(token)) { String userId = jwtProvider.getUserId(token); + Role role = jwtProvider.getRole(token); - UserPrincipal principal = new UserPrincipal(userId); + UserPrincipal principal = new UserPrincipal(userId, role); Authentication authentication = new UsernamePasswordAuthenticationToken( principal, null, - Collections.emptyList() + List.of(new SimpleGrantedAuthority("ROLE_" + role.name())) ); SecurityContextHolder.getContext().setAuthentication(authentication); diff --git a/src/main/java/com/example/user/global/security/JwtProvider.java b/src/main/java/com/example/user/global/security/JwtProvider.java index c64740a..3ecc7f0 100644 --- a/src/main/java/com/example/user/global/security/JwtProvider.java +++ b/src/main/java/com/example/user/global/security/JwtProvider.java @@ -1,6 +1,7 @@ package com.example.user.global.security; +import com.example.user.entity.Role; import io.jsonwebtoken.Claims; import io.jsonwebtoken.JwtException; import io.jsonwebtoken.Jwts; @@ -31,6 +32,19 @@ public String getUserId(String token) { return claims.getSubject(); } + public Role getRole(String token) { + Claims claims = Jwts.parser() + .verifyWith(secretKey) + .build() + .parseSignedClaims(token) + .getPayload(); + + String role = claims.get("role", String.class); + + return Role.valueOf(role); + } + + public boolean validateToken(String token) { try { Jwts.parser() diff --git a/src/main/java/com/example/user/global/security/UserPrincipal.java b/src/main/java/com/example/user/global/security/UserPrincipal.java index 31f86d9..2cca408 100644 --- a/src/main/java/com/example/user/global/security/UserPrincipal.java +++ b/src/main/java/com/example/user/global/security/UserPrincipal.java @@ -1,5 +1,6 @@ package com.example.user.global.security; +import com.example.user.entity.Role; import lombok.Getter; @Getter @@ -7,7 +8,10 @@ public class UserPrincipal { private final String userId; - public UserPrincipal(String userId) { + private final Role role; + + public UserPrincipal(String userId, Role role) { this.userId = userId; + this.role = role; } } diff --git a/src/main/java/com/example/user/global/util/HttpUtil.java b/src/main/java/com/example/user/global/util/ResponseUtil.java similarity index 92% rename from src/main/java/com/example/user/global/util/HttpUtil.java rename to src/main/java/com/example/user/global/util/ResponseUtil.java index 0f9fad8..87bf288 100644 --- a/src/main/java/com/example/user/global/util/HttpUtil.java +++ b/src/main/java/com/example/user/global/util/ResponseUtil.java @@ -2,7 +2,7 @@ import com.example.user.global.dto.ApiResponse; -public class HttpUtil { +public class ResponseUtil { public static ApiResponse success(String message, T data) { return new ApiResponse<>(message, data); } From 64ea9deb5af4da2babdaa99de7f90512bee3de6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Wed, 6 May 2026 16:01:33 +0900 Subject: [PATCH 10/12] =?UTF-8?q?fix:=20=ED=8F=AC=ED=8A=B8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=208081?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/resources/application.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index c91c425..e86389a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,3 +1,6 @@ +server: + port: 8081 + spring: application: name: User From 3d13dabc27f854f9e9792e0ed4dca6fa7a0a642e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=A3=BC=ED=99=98?= Date: Thu, 7 May 2026 20:10:34 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20=EC=9D=B8=EC=BD=94=EB=94=A9=20=EC=B6=94=EA=B0=80=20?= =?UTF-8?q?/=20Auth=20=EC=99=80=EC=9D=98=20=EB=82=B4=EB=B6=80=20=EB=A1=9C?= =?UTF-8?q?=EA=B7=B8=EC=9D=B8=20API=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/InternalUserController.java | 29 +++++++++++++ .../user/dto/request/SignInRequest.java | 7 +++ .../user/dto/response/UserAuthResponse.java | 18 ++++++++ .../global/config/PasswordEncoderConfig.java | 15 +++++++ .../user/repository/UserRepository.java | 4 ++ .../user/service/InternalUserService.java | 43 +++++++++++++++++++ .../com/example/user/service/UserService.java | 7 ++- 7 files changed, 122 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/user/controller/InternalUserController.java create mode 100644 src/main/java/com/example/user/dto/request/SignInRequest.java create mode 100644 src/main/java/com/example/user/dto/response/UserAuthResponse.java create mode 100644 src/main/java/com/example/user/global/config/PasswordEncoderConfig.java create mode 100644 src/main/java/com/example/user/service/InternalUserService.java diff --git a/src/main/java/com/example/user/controller/InternalUserController.java b/src/main/java/com/example/user/controller/InternalUserController.java new file mode 100644 index 0000000..355f9ee --- /dev/null +++ b/src/main/java/com/example/user/controller/InternalUserController.java @@ -0,0 +1,29 @@ +package com.example.user.controller; + +import com.example.user.dto.request.SignInRequest; +import com.example.user.dto.response.UserAuthResponse; +import com.example.user.service.InternalUserService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +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 +@RequiredArgsConstructor +@RequestMapping("/internal/users") +public class InternalUserController { + + private final InternalUserService internalUserService; + + @PostMapping("/authenticate") + public ResponseEntity authenticate( + @RequestBody SignInRequest request + ) { + UserAuthResponse response = internalUserService.authenticate(request); + + return ResponseEntity.ok(response); + } + +} diff --git a/src/main/java/com/example/user/dto/request/SignInRequest.java b/src/main/java/com/example/user/dto/request/SignInRequest.java new file mode 100644 index 0000000..a838ba5 --- /dev/null +++ b/src/main/java/com/example/user/dto/request/SignInRequest.java @@ -0,0 +1,7 @@ +package com.example.user.dto.request; + +public record SignInRequest( + String email, + String password +) { +} diff --git a/src/main/java/com/example/user/dto/response/UserAuthResponse.java b/src/main/java/com/example/user/dto/response/UserAuthResponse.java new file mode 100644 index 0000000..121a77d --- /dev/null +++ b/src/main/java/com/example/user/dto/response/UserAuthResponse.java @@ -0,0 +1,18 @@ +package com.example.user.dto.response; + +import com.example.user.entity.Role; +import com.example.user.entity.User; + +public record UserAuthResponse( + String userId, + String name, + String role +) { + public static UserAuthResponse from(User user) { + return new UserAuthResponse( + user.getId(), + user.getName(), + user.getRole().name() + ); + } +} diff --git a/src/main/java/com/example/user/global/config/PasswordEncoderConfig.java b/src/main/java/com/example/user/global/config/PasswordEncoderConfig.java new file mode 100644 index 0000000..ef298af --- /dev/null +++ b/src/main/java/com/example/user/global/config/PasswordEncoderConfig.java @@ -0,0 +1,15 @@ +package com.example.user.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; + +@Configuration +public class PasswordEncoderConfig { + + @Bean + public PasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} diff --git a/src/main/java/com/example/user/repository/UserRepository.java b/src/main/java/com/example/user/repository/UserRepository.java index 681b96b..5a98f40 100644 --- a/src/main/java/com/example/user/repository/UserRepository.java +++ b/src/main/java/com/example/user/repository/UserRepository.java @@ -4,7 +4,11 @@ import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; +import java.util.Optional; + @Repository public interface UserRepository extends JpaRepository { boolean existsByEmail(String email); + + Optional findByEmail(String email); } diff --git a/src/main/java/com/example/user/service/InternalUserService.java b/src/main/java/com/example/user/service/InternalUserService.java new file mode 100644 index 0000000..aa7487e --- /dev/null +++ b/src/main/java/com/example/user/service/InternalUserService.java @@ -0,0 +1,43 @@ +package com.example.user.service; + +import com.example.user.dto.request.SignInRequest; +import com.example.user.dto.response.UserAuthResponse; +import com.example.user.entity.User; +import com.example.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class InternalUserService { + + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + + @Transactional(readOnly = true) + public UserAuthResponse authenticate(SignInRequest request) { + User user = userRepository.findByEmail(request.email()) + .orElseThrow(() -> new IllegalArgumentException("유저 없음")); + + validateActiveUser(user); + validatePassword(request.password(), user.getPassword()); + + return UserAuthResponse.from(user); + } + + // 삭제 여부 확인 + private void validateActiveUser(User user) { + if (user.getDeletedAt() != null) { + throw new IllegalStateException("삭제된 유저"); + } + } + + // 비밀번호 확인 + private void validatePassword(String rawPassword, String encodedPassword) { + if (!passwordEncoder.matches(rawPassword, encodedPassword)) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + } +} diff --git a/src/main/java/com/example/user/service/UserService.java b/src/main/java/com/example/user/service/UserService.java index 03d46b8..b21bb23 100644 --- a/src/main/java/com/example/user/service/UserService.java +++ b/src/main/java/com/example/user/service/UserService.java @@ -7,6 +7,7 @@ import com.example.user.entity.User; import com.example.user.repository.UserRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -15,6 +16,7 @@ public class UserService { private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; // 회원가입 (유저 생성) @Transactional @@ -22,9 +24,12 @@ public SignUpResponse signup(SignUpRequest request) { if (userRepository.existsByEmail(request.email())) { throw new IllegalArgumentException("이미 존재하는 이메일"); } + + String encodedPassword = passwordEncoder.encode(request.password()); + User user = User.create( request.email(), - request.password(), + encodedPassword, request.name() ); userRepository.save(user); From 78b06ae8cf3b8f558e328851200049acab05ddc2 Mon Sep 17 00:00:00 2001 From: hyun731 Date: Thu, 7 May 2026 20:34:23 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat:=20Auth=EC=99=80=20Role=20=ED=86=B5?= =?UTF-8?q?=EC=9D=BC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/user/global/security/JwtProvider.java | 4 ++-- src/main/resources/application.yml | 12 +++++++----- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/src/main/java/com/example/user/global/security/JwtProvider.java b/src/main/java/com/example/user/global/security/JwtProvider.java index 3ecc7f0..dd61c56 100644 --- a/src/main/java/com/example/user/global/security/JwtProvider.java +++ b/src/main/java/com/example/user/global/security/JwtProvider.java @@ -11,7 +11,6 @@ import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; -import java.util.Date; @Component public class JwtProvider { @@ -39,7 +38,8 @@ public Role getRole(String token) { .parseSignedClaims(token) .getPayload(); - String role = claims.get("role", String.class); + String role = claims.get("role", String.class) + .replace("ROLE_", ""); return Role.valueOf(role); } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e86389a..f2e5405 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -1,18 +1,20 @@ server: - port: 8081 + port: ${SERVER_PORT:8081} spring: application: name: User datasource: - url: jdbc:postgresql://localhost:5432/momentlit_user - username: jhlarry1109 + url: "jdbc:postgresql://${DB_HOST:localhost}:${DB_PORT:5432}/${DB_NAME:momentlit_user}" + username: "${DB_USERNAME:user_user}" + password: "${DB_PASSWORD:user_password}" + driver-class-name: org.postgresql.Driver jpa: hibernate: - ddl-auto: update + ddl-auto: "${JPA_DDL_AUTO:update}" show-sql: true jwt: - secret: momentlit-user-service-jwt-secret-key-for-local-test-1234567890 \ No newline at end of file + secret: "${JWT_SECRET:momentlit-user-service-jwt-secret-key-for-local-test-1234567890}"