Skip to content

[Suhhee] week9 미션#108

Open
Kimsuhhee04 wants to merge 8 commits into
Seohuifrom
Suhhee-Week9
Open

[Suhhee] week9 미션#108
Kimsuhhee04 wants to merge 8 commits into
Seohuifrom
Suhhee-Week9

Conversation

@Kimsuhhee04
Copy link
Copy Markdown
Contributor

@Kimsuhhee04 Kimsuhhee04 commented May 26, 2026

🔗 연관 이슈

🛠 작업 내용

  • JWT 토큰 방식의 회원가입, 로그인 구현하고 마이페이지도 워크북과 같은 형식으로 개선하기
  • JWT + OAuth 구현하기
  • [부록 미션] Filter 방식으로 패스키 구현하기

🖼 스크린샷 (선택)

👀 리뷰 요구사항 (선택)

🤖 AI 활용

  • AI 사용 안 함
  • 코드 작성 아이디어 참고
  • 테스트/리팩토링 보조
  • 문서/주석 작성 보조
  • 기타 (아래에 간단히 작성)

💬 나의 프롬프트

내가 작성한 코드에서 잘못 작성된 부분, 로직이 잘못된 부분이 있으면 해당 부분에 대해서 알려줘

🧠 AI 응답

잘못 작성된 로직에 대한 수정사항 + 오타 수정

✅ 내가 최종 선택한 방법 (이유)

💡 나만의 Tip (선택)

@chatgpt-codex-connector
Copy link
Copy Markdown

💡 Codex Review


P1 Badge JwtAuthFilter에서 비인증 예외를 401로 덮어쓰지 마세요

filterChain.doFilter 전체를 try로 감싼 뒤 catch (Exception)에서 항상 401을 반환하면, JWT 검증 실패가 아닌 예외(예: OAuth 처리 중 런타임 예외, 다른 필터/서블릿 예외)까지 인증 실패로 왜곡됩니다. 이렇게 되면 Spring의 예외 분리(인증/인가 vs 애플리케이션 오류)가 깨져 원인 추적이 어려워지고 잘못된 상태코드를 내려주게 됩니다. JWT 관련 예외만 좁혀서 처리하거나 AuthenticationException 계열만 위임하고, 나머지는 전역 예외 처리기로 전파되도록 분리해 주세요. 다음 학습 주제로는 ExceptionTranslationFilter와 커스텀 필터의 예외 전파 경계를 함께 보시면 좋습니다.


String email = attributes.get("email").toString();
String name = profile.get("nickname").toString();

P1 Badge Kakao 응답 필드 null 가능성을 먼저 검증하세요

카카오 계정에서 email 또는 profile 동의가 누락된 경우 attributes.get("email").toString()/profile.get("nickname").toString()에서 바로 NullPointerException이 발생해 OAuth 로그인 자체가 실패합니다. OAuth 매핑은 외부 입력 경계이므로 필드 존재 여부를 먼저 검사하고, 누락 시 명시적인 OAuth2AuthenticationException(또는 도메인 예외)으로 처리해야 흐름이 예측 가능해집니다. Map 파싱 시 null-safe 변환과 동의 스코프 검증을 분리하는 구조로 바꾸고, 다음으로는 OAuth 사용자 정보 매핑 계층(Provider DTO/검증 책임 분리)을 학습해 보세요.


userBirth = java.time.LocalDate.parse(requestDto.birth());

P2 Badge 생년월일 파싱 실패를 도메인 예외로 변환하세요

LocalDate.parse가 유효하지 않은 날짜 문자열에서 DateTimeParseException을 던지는데 현재는 별도 변환이 없어 회원가입 요청이 500 계열로 떨어질 수 있습니다. 입력 검증 실패는 비즈니스 오류로 명확히 반환해야 컨트롤러-서비스 책임이 분리되고 API 계약도 안정적입니다. birth 포맷 검증(Bean Validation 또는 파싱 예외 캐치 후 UserException)을 추가해 4xx로 응답하도록 바꾸고, 다음 학습 주제로는 DTO 검증(@Valid, 커스텀 validator)과 서비스 계층의 예외 설계를 같이 정리해 보세요.

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Copy link
Copy Markdown
Contributor

@kjhh2605 kjhh2605 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[키워드 조사]
이번 PR에는 Week9 keyword_summary 신규 파일이 포함되어 있지 않습니다. 코드 변경 범위가 JWT, OAuth2 로그인, stateless 세션 정책과 직접 연결되어 있으므로 Access Token/Refresh Token의 역할 분리, OAuth2 authorization code 흐름, JWT claim 설계, Spring Security 필터 체인과 ExceptionTranslationFilter의 경계를 추가로 정리하는 것을 권장합니다.

[코드 리뷰]
일반 로그인과 OAuth 로그인 모두 JWT 발급 흐름으로 연결하고, 인증된 사용자를 @AuthenticationPrincipal 기반으로 조회하려는 방향이 좋습니다. PasswordEncoder, CustomUserDetailsService, OAuthSuccessHandler처럼 책임을 나누어 구성한 점도 적절합니다. 다만 stateless 설정과 OAuth2 로그인 흐름의 세션 의존성, 로그인 DTO 검증, JWT 필터의 예외 처리 범위를 더 명확히 해야 인증/인가 흐름을 안정적으로 설명할 수 있습니다.

.authorizeHttpRequests(requests -> requests
.requestMatchers(allowUris).permitAll() // Public API는 허용
.anyRequest().authenticated() // 그 외의 Private API는 인증 필요
.sessionManagement(session -> session
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SessionCreationPolicy.STATELESS를 사용하면서 oauth2Login을 함께 활성화하면 OAuth2 authorization request 저장 방식이 함께 정리되어야 합니다. 기본 구현은 인가 요청 상태를 세션에 저장하는 흐름을 사용할 수 있으므로, 완전한 stateless JWT 구조를 의도했다면 쿠키 기반 AuthorizationRequestRepository를 두거나 OAuth 로그인 구간만 세션을 허용하는 정책을 명확히 분리하는 것을 권장합니다.

@Operation(summary = "로그인 API", description = "이메일과 비밀번호로 로그인하여 JWT 토큰을 발급받습니다.")
@PostMapping("/auth/login")
public ApiResponse<UserResponseDto.LoginResultDto> login(
@RequestBody UserRequestDto.LoginDto requestDto
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

로그인 요청 DTO를 그대로 받으면 이메일 형식, 비밀번호 공백 여부 같은 입력 계약이 컨트롤러 경계에서 검증되지 않습니다. LoginDto에 Bean Validation을 추가하고 컨트롤러에서 @Valid를 적용하면 인증 서비스는 검증된 값으로 비밀번호 비교와 토큰 발급 책임에 집중할 수 있습니다.

// 다음 필터로 이동
filterChain.doFilter(request, response);

} catch (Exception e) {
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

현재 filterChain.doFilter까지 try 범위에 포함되어 있어 JWT 검증 실패가 아닌 하위 필터·컨트롤러 예외도 모두 401 응답으로 변환될 수 있습니다. JWT 파싱/인증 객체 생성 과정의 예외만 좁게 처리하고, 나머지 예외는 Spring의 예외 처리 흐름으로 전파되도록 분리하는 것을 권장합니다.

> 1. 클라이언트가 로그인에 성공하면 서버는 사용자 식별 정보와 권한 등을 포함한 데이터(Payload)에 디지털 서명을 하여 토큰을 생성함
> 2. 서버는 이 토큰을 클라이언트에게 반환하고 서버는 이 토큰을 저장하지 않음
> 3. 클라이언트는 토큰을 로컬 스토리지나 쿠키에 저장하고 이후 요청마다 HTTP 헤더(`Authorization`)에 토큰을 담아 전송한다
> 4. 서버는 전달받은 토큰의 서명을 비밀키로 복호화하여 위변조 여부를 검증한 뒤 서명이 유효하면 토큰 내부의 데이터를 신뢰하고 요청을 처리해준다
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JWT 서명 검증은 비밀키로 ‘복호화’하는 과정이라기보다, Header/Payload와 비밀키로 서명을 다시 계산해 Signature와 비교하는 과정으로 이해하는 것이 정확합니다. JWT 자체는 기본적으로 암호화가 아니라 Base64URL 인코딩과 서명으로 구성되므로, Payload에 민감 정보를 넣지 않는 이유까지 함께 정리하는 것을 권장합니다.

> 4. 서버는 전달받은 토큰의 서명을 비밀키로 복호화하여 위변조 여부를 검증한 뒤 서명이 유효하면 토큰 내부의 데이터를 신뢰하고 요청을 처리해준다
> * **특징:**
> * **Stateless:** 서버는 클라이언트의 상태를 저장하지 않고 토큰 자체에 인증 정보를 포함한다
> * **확장성 우수:** 서버가 상태를 저장하지 않으므로 서버를 무한정 늘려도 인증 처리에 문제가 없다는 이점이 있다
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

토큰 방식이 서버 확장에 유리한 점은 맞지만, ‘무한정 늘려도 문제가 없음’은 운영 조건을 지나치게 단순화한 표현입니다. 서명 키 관리, 토큰 만료/재발급 정책, 블랙리스트 저장소, Refresh Token 저장 전략이 함께 맞아야 확장성이 안정적으로 확보됩니다. stateless 인증의 장점과 이를 보완하기 위해 필요한 상태 저장 요소를 구분해 정리하는 것을 권장합니다.

"/login/**",
"/passkey/**",
"/auth/**",
"/webauthn/**"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebAuthn 등록/인증 엔드포인트를 모두 permitAll로 열면 패스키 등록이 기존 사용자 인증 상태나 가입 흐름과 분리되어 실행될 수 있습니다. Passkey는 credential을 특정 사용자 계정에 안전하게 연결해야 하므로, 등록 옵션/검증 API는 인증된 사용자 또는 명확한 회원가입 세션에만 허용하고 로그인 검증 API와 권한 경계를 분리하는 것을 권장합니다. 다음 학습으로 WebAuthn registration ceremony와 authentication ceremony의 책임 차이를 함께 정리하면 좋습니다.


@GetMapping("/test")
public String test() {
return "/auth/index.html";
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Controller에서 정적 파일 경로를 문자열로 반환하면 ViewResolver 설정에 따라 템플릿 뷰 이름으로 해석될 수 있습니다. 정적 리소스 페이지로 명확히 이동하려면 redirect:/auth/index.html 또는 forward:/auth/index.html처럼 의도를 드러내는 반환값을 사용하는 것을 권장합니다. MVC에서 컨트롤러가 뷰 이름을 반환하는 경우와 정적 리소스를 직접 제공하는 경우의 차이를 함께 확인하면 좋습니다.

@Kimsuhhee04 Kimsuhhee04 self-assigned this May 27, 2026
@Kimsuhhee04 Kimsuhhee04 added the enhancement New feature or request label May 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants