-
Notifications
You must be signed in to change notification settings - Fork 6
[USER] 이메일 인증 개선, 쿠키(HttpOnly) 기반 JWT 인증 및 GitHub OAuth 로그인 추가 #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fbab528
312225a
6958994
83a7671
eb2ff95
d7f11f8
70f5979
f96b978
5315a7a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,23 +1,37 @@ | ||||||||||||||||||||||||||||||||||||||
| package io.github.petty.users.controller; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.dto.CustomUserDetails; | ||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.dto.JoinDTO; | ||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.dto.UserProfileEditDTO; | ||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.entity.Users; | ||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.service.JoinService; | ||||||||||||||||||||||||||||||||||||||
| import io.github.petty.users.service.UserService; | ||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.Cookie; | ||||||||||||||||||||||||||||||||||||||
| import jakarta.servlet.http.HttpServletResponse; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.http.ResponseEntity; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.authentication.AnonymousAuthenticationToken; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.core.context.SecurityContextHolder; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.stereotype.Controller; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.ui.Model; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.GetMapping; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.ModelAttribute; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PostMapping; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.bind.annotation.PutMapping; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.web.servlet.mvc.support.RedirectAttributes; | ||||||||||||||||||||||||||||||||||||||
| import org.springframework.security.core.Authentication; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| import java.util.UUID; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @Controller | ||||||||||||||||||||||||||||||||||||||
| public class UsersController { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| private final JoinService joinService; | ||||||||||||||||||||||||||||||||||||||
| private final UserService userService; | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| public UsersController(JoinService joinService) { | ||||||||||||||||||||||||||||||||||||||
| public UsersController(JoinService joinService, UserService userService) { | ||||||||||||||||||||||||||||||||||||||
| this.joinService = joinService; | ||||||||||||||||||||||||||||||||||||||
| this.userService = userService; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @GetMapping("/join") | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -27,9 +41,9 @@ public String joinForm(Model model) { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @PostMapping("/join") | ||||||||||||||||||||||||||||||||||||||
| public String joinProcess(JoinDTO joinDTO, RedirectAttributes redirectAttributes) { | ||||||||||||||||||||||||||||||||||||||
| public String joinProcess(JoinDTO JoinDTO, RedirectAttributes redirectAttributes) { | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Boolean joinResult = joinService.joinProcess(joinDTO); | ||||||||||||||||||||||||||||||||||||||
| Boolean joinResult = joinService.joinProcess(JoinDTO); | ||||||||||||||||||||||||||||||||||||||
| if (!joinResult) { | ||||||||||||||||||||||||||||||||||||||
| redirectAttributes.addFlashAttribute("error", "이미 존재하는 계정입니다!"); | ||||||||||||||||||||||||||||||||||||||
| return "redirect:/"; | ||||||||||||||||||||||||||||||||||||||
|
|
@@ -47,6 +61,49 @@ public String loginForm() { | |||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| return "login"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @GetMapping("/profile/edit") | ||||||||||||||||||||||||||||||||||||||
| public String editProfileForm(Model model) { | ||||||||||||||||||||||||||||||||||||||
| Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (authentication == null || !authentication.isAuthenticated() | ||||||||||||||||||||||||||||||||||||||
| || authentication instanceof AnonymousAuthenticationToken) { | ||||||||||||||||||||||||||||||||||||||
| return "redirect:/login"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Object principal = authentication.getPrincipal(); | ||||||||||||||||||||||||||||||||||||||
| UUID currentUserId = userService.getCurrentUserId(principal); | ||||||||||||||||||||||||||||||||||||||
| UserProfileEditDTO userProfile = userService.getUserById(currentUserId); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| model.addAttribute("userProfile", userProfile); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| return "profile_edit"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| @PostMapping("/profile/update") | ||||||||||||||||||||||||||||||||||||||
| public String updateProfile(@ModelAttribute UserProfileEditDTO userProfileEditDTO, | ||||||||||||||||||||||||||||||||||||||
| RedirectAttributes redirectAttributes) { | ||||||||||||||||||||||||||||||||||||||
| Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| if (authentication == null || !authentication.isAuthenticated() | ||||||||||||||||||||||||||||||||||||||
| || authentication instanceof AnonymousAuthenticationToken) { | ||||||||||||||||||||||||||||||||||||||
| return "redirect:/login"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Object principal = authentication.getPrincipal(); | ||||||||||||||||||||||||||||||||||||||
| UUID currentUserId = userService.getCurrentUserId(principal); | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||
| // 사용자 정보 수정 | ||||||||||||||||||||||||||||||||||||||
| userService.updateUserProfile(currentUserId, userProfileEditDTO); | ||||||||||||||||||||||||||||||||||||||
| redirectAttributes.addFlashAttribute("successMessage", "프로필이 성공적으로 수정되었습니다."); | ||||||||||||||||||||||||||||||||||||||
| } catch (Exception e) { | ||||||||||||||||||||||||||||||||||||||
| redirectAttributes.addFlashAttribute("errorMessage", "프로필 수정 중 오류가 발생했습니다: " + e.getMessage()); | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+96
to
+102
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 예외 처리 범위가 너무 넓습니다. 현재 모든 예외(Exception)를 catch하고 있는데, 이는 너무 광범위합니다. UserService에서 발생할 수 있는 특정 예외들을 명시적으로 처리하는 것이 더 좋습니다. try {
// 사용자 정보 수정
userService.updateUserProfile(currentUserId, userProfileEditDTO);
redirectAttributes.addFlashAttribute("successMessage", "프로필이 성공적으로 수정되었습니다.");
- } catch (Exception e) {
+ } catch (RuntimeException e) {
redirectAttributes.addFlashAttribute("errorMessage", "프로필 수정 중 오류가 발생했습니다: " + e.getMessage());
+ } catch (Exception e) {
+ redirectAttributes.addFlashAttribute("errorMessage", "서버 오류가 발생했습니다. 관리자에게 문의하세요.");
+ // 로깅 추가
+ // log.error("프로필 수정 중 예상치 못한 오류 발생: ", e);
}📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| // 수정 완료 후 메인 페이지로 | ||||||||||||||||||||||||||||||||||||||
| return "redirect:/"; | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,15 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| package io.github.petty.users.dto; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.AllArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Getter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.NoArgsConstructor; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| import lombok.Setter; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Getter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @Setter | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @NoArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| @AllArgsConstructor | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| public class UserProfileEditDTO { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String displayName; // 사용자 이름 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| private String phone; // 전화번호 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+8
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🛠️ Refactor suggestion 유효성 검증 추가 필요. 사용자 프로필 편집을 위한 DTO 생성은 좋은 접근법이지만, 입력값에 대한 유효성 검증이 누락되어 있습니다. Jakarta Validation API를 사용하여 필드에 유효성 검증 어노테이션을 추가하는 것이 좋습니다. 다음과 같이 개선할 수 있습니다: +import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Pattern;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class UserProfileEditDTO {
+ @NotBlank(message = "이름은 필수 입력 항목입니다")
private String displayName; // 사용자 이름
+ @NotBlank(message = "전화번호는 필수 입력 항목입니다")
+ @Pattern(regexp = "^\\d{3}-\\d{3,4}-\\d{4}$", message = "유효한 전화번호 형식이 아닙니다")
private String phone; // 전화번호
}이렇게 하면 컨트롤러에서 📝 Committable suggestion
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,13 +1,8 @@ | ||
| package io.github.petty.users.entity; | ||
|
|
||
| import jakarta.persistence.Entity; | ||
| import jakarta.persistence.GeneratedValue; | ||
| import jakarta.persistence.GenerationType; | ||
| import jakarta.persistence.Id; | ||
| import lombok.Getter; | ||
| import lombok.NoArgsConstructor; | ||
| import lombok.RequiredArgsConstructor; | ||
| import lombok.Setter; | ||
| import com.fasterxml.jackson.annotation.JsonIgnore; | ||
| import jakarta.persistence.*; | ||
| import lombok.*; | ||
| import org.hibernate.annotations.CreationTimestamp; | ||
|
|
||
| import java.time.LocalDateTime; | ||
|
|
@@ -20,12 +15,32 @@ public class Users { | |
| @Id | ||
| @GeneratedValue(strategy = GenerationType.UUID) | ||
| private UUID id; | ||
|
|
||
| @Column(nullable = false, unique = true, length = 255) | ||
| private String username; // email | ||
|
|
||
| @Column(nullable = false, length = 60) // BCrypt로 해시된 비밀번호는 보통 60자의 길이를 가짐 | ||
| @ToString.Exclude // toString()에서 비밀번호가 출력되지 않도록 제외 | ||
| @JsonIgnore // 응답에서 비밀번호가 직렬화되지 않도록 제외 | ||
| private String password; | ||
|
|
||
| @Column(nullable = false, length = 50) | ||
| private String displayName; | ||
|
|
||
| @Column(length = 20) | ||
| private String phone; | ||
|
|
||
| @Column(nullable = false, length = 20) | ||
| private String role; | ||
|
|
||
|
|
||
| // Oauth2 관련 | ||
| @Column(nullable = false, length = 20) | ||
| private String provider; // oauth2 provider | ||
|
|
||
| @Column(length = 100) | ||
| private String providerId; // oauth2 provider id | ||
|
|
||
|
Comment on lines
+37
to
+43
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 💡 Verification agent 🧩 Analysis chainOAuth2 관련 필드 추가 GitHub OAuth2 로그인 지원을 위한 필드 추가가 적절히 이루어졌습니다. provider 필드는 인증 제공자를 구분하기 위해 다만, 기존 사용자 데이터가 있을 경우 provider 필드의 🏁 Script executed: #!/bin/bash
# OAuth2 관련 필드 마이그레이션 검증
echo "JPA 설정 확인"
grep -E "spring\.jpa\.hibernate\.ddl-auto" --include="*.yml" --include="*.properties" -r src/main/resources/
echo "---"
echo "마이그레이션 관련 코드(JoinService) 확인"
grep -E "provider" --include="*.java" -r src/main/java/io/github/petty/users/service/Length of output: 307 마이그레이션 전략 추가 필요:
기존 사용자 레코드에
위 전략을 반영해 배포 전 데이터 무결성을 보장하시기 바랍니다. |
||
| @CreationTimestamp | ||
| private LocalDateTime createdAt; | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
💡 Verification agent
🧩 Analysis chain
OAuth2 로그인 구성 검증
OAuth2 로그인 구성이 적절히 설정되었지만, OAuth2 클라이언트 설정(client-id, client-secret 등)이 application.yml 또는 properties 파일에 올바르게 구성되어 있는지 확인이 필요합니다.
🏁 Script executed:
Length of output: 310
🏁 Script executed:
Length of output: 230
OAuth2 클라이언트 설정 누락 확인
src/main/resources경로에 OAuth2 로그인에 필요한 클라이언트 등록 정보(client-id,client-secret등)가 발견되지 않습니다. OAuth2 로그인이 정상 동작하려면 아래와 같이 설정을 추가해야 합니다.src/main/resources/application.yml또는src/main/resources/application.propertiesapplication.yml):client-secret등 민감 정보는 환경 변수나 CI/CD 비밀 관리자를 통해 안전하게 관리하세요.위 설정 추가 후 OAuth2 로그인 흐름이 정상 동작하는지 테스트해주세요.