[Sangwan] week08 미션#88
Conversation
kjhh2605
left a comment
There was a problem hiding this comment.
[키워드 조사]
Spring Security의 필터 체인, 인증/인가, UserDetailsService, PasswordEncoder, Stateful/Stateless 차이를 폭넓게 정리한 점이 좋습니다. 다만 CSRF는 REST API라는 이름만으로 항상 비활성화되는 개념이 아니라, 쿠키 기반 세션 인증인지 Bearer 토큰 기반 Stateless 인증인지에 따라 판단해야 합니다. formLogin과 세션을 사용하는 경우에는 CSRF 보호 필요성을 함께 검토하는 것을 권장합니다.
추가로 UsernamePasswordAuthenticationFilter, AuthenticationProvider, SecurityContextHolder, @AuthenticationPrincipal의 역할을 실제 로그인 요청 흐름과 연결해 정리하면 인증된 사용자 정보를 Controller에서 안전하게 사용하는 기준이 더 명확해집니다.
[코드 리뷰]
회원가입 로직에서 DTO 검증, 이메일 중복 검사, BCrypt 암호화, Converter 분리를 적용한 점이 좋습니다. 또한 SecurityFilterChain, CustomUserDetailsService, AuthMember를 추가하여 인증 흐름을 코드로 연결하려는 방향도 적절합니다.
다만 Security를 도입한 이후에도 /me 계열 API가 요청 파라미터나 요청 바디의 memberId를 신뢰하고 있어 인증된 사용자 경계가 아직 완전히 연결되지 않았습니다. @AuthenticationPrincipal로 현재 인증 사용자를 주입받고, Service에는 인증된 memberId만 전달하도록 정리하는 것을 권장합니다. 또한 컴파일 의존성에 포함되지 않은 NonNull 어노테이션 사용과 CSRF 비활성화 근거를 함께 점검하면 구현 안정성이 높아집니다.
| import com.example.umc10th.domain.member.enums.MemberStatus; | ||
| import lombok.Getter; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.jspecify.annotations.NonNull; |
There was a problem hiding this comment.
org.jspecify.annotations.NonNull을 사용하고 있지만 build.gradle에 jspecify 의존성이 명시되어 있지 않습니다. 학습 코드라도 컴파일 경로에 없는 어노테이션은 빌드 실패 원인이 될 수 있으므로, 해당 어노테이션을 제거하거나 필요한 의존성을 명확히 추가하는 것을 권장합니다.
| public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { | ||
| http | ||
| // REST API와 Postman 실습 흐름에서는 CSRF 토큰을 함께 보내지 않으므로 비활성화한다. | ||
| .csrf(csrf -> csrf.disable()) |
There was a problem hiding this comment.
현재 설정은 formLogin 기반 세션 인증을 함께 사용하므로, REST API/Postman 실습이라는 이유만으로 CSRF를 비활성화한다고 정리하면 개념이 모호해질 수 있습니다. 쿠키 기반 세션 인증에서는 CSRF 보호가 필요할 수 있고, Bearer 토큰 기반 Stateless API에서는 비활성화 근거가 더 명확해지므로 인증 방식을 먼저 정리한 뒤 CSRF 설정을 결정하는 것을 권장합니다.
| @@ -42,7 +44,8 @@ public ApiResponse<CursorPageRes<MemberResDTO.MissionItem>> getMissions( | |||
| @RequestParam Long memberId, // TODO: 인증 구현 후 SecurityContext로 대체 | |||
There was a problem hiding this comment.
Security 설정과 AuthMember가 추가되었으므로 /me 성격의 API는 요청 파라미터의 memberId보다 @AuthenticationPrincipal AuthMember에서 현재 로그인한 회원 ID를 가져오는 방향을 권장합니다. 이렇게 하면 클라이언트가 다른 회원 ID를 전달하는 흐름을 막고, 인증/인가 책임이 Controller 경계에서 더 명확해집니다.
| @Bean | ||
| public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { | ||
| http | ||
| .csrf(csrf -> csrf.disable()) // CSRF 비활성화 (REST API의 경우) |
There was a problem hiding this comment.
REST API는 CSRF 불필요라고 단정하기보다 인증 정보를 어디에 저장하는지까지 함께 적는 것을 권장합니다. 세션 쿠키를 사용하면 브라우저가 쿠키를 자동 전송하므로 CSRF 위험이 남고, Authorization 헤더의 Bearer 토큰을 사용하는 Stateless 구조에서는 CSRF 비활성화 근거가 더 명확해집니다.
|
|
||
| private Map<Long, Boolean> toAgreementMap(List<MemberReqDTO.TermAgreementReq> termAgreements) { | ||
| return termAgreements.stream() | ||
| .collect(Collectors.toMap( |
There was a problem hiding this comment.
termAgreements에서 같은 termId가 중복으로 들어오면 현재 로직은 마지막 값만 남깁니다. 약관 동의는 가입 시점의 중요한 도메인 불변식이므로, 중복 입력을 명시적으로 검증해 400 오류로 처리하거나 DTO 단계에서 중복을 허용하지 않는 방식으로 의도를 드러내는 것을 권장합니다.
🔗 연관 이슈
closes #86
🛠 작업 내용
week08 미션
🖼 스크린샷 (선택)
👀 리뷰 요구사항 (선택)
🤖 AI 활용
💬 나의 프롬프트
MemberRepository에 existsByEmail, findByEmail 추가 이거 역할은 뭐야?
이메일 중복 체크
생일 문자열 파싱
비밀번호 BCrypt 암호화
기본 권한 Role.USER
기본 상태 MemberStatus.ACTIVE
저장 후 응답 반환
이게 각각 어느 코드에 대응돼?
userDetails는 어떤 역할이야? 감싸면 뭐가 달라져?
그리고 authMember에서 isAccountNonLocked이런거 다 true 반환하는데 무슨 역할들이야 이 불리언들
어노테이션이 없는 메서드가 @NullMarked(으)로 어노테이션이 추가된 메서드를 재정의합니다라고 하는데 CustomUserDetailService의 loadUserByName이??
그리고 membermissionstatus에 관한 이넘이 원래 없어서 생성한거였어?
그리고 securiotyconfig는 노션에 있던거 어케 수정한거야?
그리고 멤버서비스에서는 ExistByemail로 이미 있는 메일인지 체크해서 에러코드 던지고, 아니면 생일 파싱 해서 member객체 하나에 저장해두고 그 멤버를 save 하는거야? 여기서 왜 savedMember를 따로 해? 레포지토리에서 제공하는 save 코드를 사용해야해 왜?그리고 existsByEmail은 기본 제공 함수야? 뭐 레포지토리 코드에서 구현 안해둔 것 같은데 작동해?.build()가 뭐 상관있나?
🧠 AI 응답
자세히 응답해줌.
✅ 내가 최종 선택한 방법 (이유)
이에 따라 추가 문답을 주고 받으며 흐름에 대한 확신을 갖고 추가 궁금점 을 해소함
💡 나만의 Tip (선택)