Skip to content

[feat] 커뮤니티 태그/좋아요/인기글 기능 및 API 응답 개선, JWT sub 표준화#109

Merged
iamseojin merged 5 commits intowith-travel:developfrom
iamseojin:feat/issue-#102(2)
Sep 19, 2025
Merged

[feat] 커뮤니티 태그/좋아요/인기글 기능 및 API 응답 개선, JWT sub 표준화#109
iamseojin merged 5 commits intowith-travel:developfrom
iamseojin:feat/issue-#102(2)

Conversation

@iamseojin
Copy link
Contributor

이슈

구현 기능

변경 요약

  • JWT 파싱 기준 변경: 토큰에서 이메일을 aud가 아닌 sub로 파싱하도록 변경

    • aud는 “토큰 대상(서비스)” 식별자, sub는 “주체(사용자)” 식별자이므로 표준 의미에 맞게 정정.
    • aud 배열/검증 충돌 이슈 제거, 파싱 단순화.
  • 커뮤니티 태그 도입(4종): RESTAURANT(맛집추천), CAFE(카페탐방), INFO(정보공유), TIPS(꿀팁)

    • 태그별 최신순 목록/검색 가능(대륙/국가/도시/키워드 필터와 병행).
  • 좋아요 기능 추가

    • 토글 API: 같은 버튼으로 좋아요/취소, 서버가 카운트 자동 증감(+1/−1)
    • 인기 글 API: 좋아요 상위 2개 조회(메인 노출용).
  • 수정/삭제 응답 정리

    • 수정 API: 편의를 위해 업데이트된 상세 DTO 즉시 반환(추가 조회 불필요)
    • 삭제 API: HTTP 204 No Content로 REST 관례 준수(본문 없음).
  • SecurityUtils 보강

    • principal 타입을 AuthenticatedMember와 PrincipalDetails 모두 지원하도록 분기 → ClassCastException 방지, 인증 주체 ID 추출 로직 단순화/안정화.

주요 변경 파일/포인트

  • JwtFilter/검증 로직: sub=email 사용, aud는 서비스 식별자 검증용으로 유지
  • CommunityTag enum: 4개 카테고리 추가
  • 목록/검색/태그별/인기글/좋아요 토글 API 추가/정리(최신순 정렬)
  • 수정 API 응답: 업데이트된 CommunityDetailResponse 반환
  • 삭제 API 응답: 204 No Content
  • SecurityUtils.currentMemberIdOrThrow():
    • 인증 객체 null/미인증 가드 단순화
    • principal 다형성 분기로 안전한 memberId 추출
    • 미지원 principal 시 AUTH_UNSUPPORTED_PRINCIPAL 처리

변경 이유

  • JWT 표준/안정성: sub는 사용자, aud는 대상 서비스 → 의미대로 쓰면 파싱/검증 충돌이 사라짐.
  • UX/레이아웃: 태그/인기글/좋아요로 카드형 목록 구성 쉬움, 메인 섹션 바로 구성 가능.
  • REST 관례: 수정 시 리소스 상태 반환, 삭제는 204 → 클라이언트 로직 단순화.
  • 안전성: SecurityUtils에서 principal 타입 케이스를 명시 처리 → 캐스팅 오류 예방.

@iamseojin iamseojin requested a review from Copilot September 19, 2025 08:19
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR implements community tagging, like functionality, and JWT standardization to enhance the community platform. The changes focus on improving user experience with categorized content browsing and engagement features while adhering to JWT best practices.

  • Standardizes JWT token parsing to use sub (subject) for user email instead of aud (audience)
  • Introduces community tags (RESTAURANT, CAFE, INFO, TIPS) for content categorization
  • Adds like/unlike functionality with toggle API and popular posts feature

Reviewed Changes

Copilot reviewed 19 out of 19 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
SecurityUtils.java Enhanced principal type handling to support both AuthenticatedMember and PrincipalDetails
JwtProvider.java Refactored JWT generation and parsing to use standard sub claim for user identification
JwtFilter.java Updated authentication flow to use new email extraction method
CommunityTag.java New enum defining 4 community categories with Korean labels
Community.java Added tag, like count, and reply count fields with database indexes
CommunityService.java Implemented tag filtering, like toggle, and popular posts functionality
Various DTOs Updated to include tag and engagement metrics in API responses

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +100 to +117
// .parseSignedClaims(token);
// if (claims.getPayload()
// .getExpiration()
// .before(new Date())) {
// throw BaseException.from(EXPIRED_ACCESS_TOKEN);
// }
// return claims.getPayload()
// .getAudience()
// .iterator()
// .next();
// } catch (JwtException | IllegalArgumentException e) {
// log.warn("[parseAudience] {} :{}", INVALID_TOKEN, token);
// throw BaseException.from(INVALID_TOKEN);
// } catch (BaseException e) {
// log.warn("[parseAudience] {} :{}", EXPIRED_ACCESS_TOKEN, token);
// throw BaseException.from(EXPIRED_ACCESS_TOKEN);
// }
// }
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

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

Remove commented-out code block. Dead code should be deleted rather than commented out to maintain code cleanliness and reduce confusion.

Suggested change
// .parseSignedClaims(token);
// if (claims.getPayload()
// .getExpiration()
// .before(new Date())) {
// throw BaseException.from(EXPIRED_ACCESS_TOKEN);
// }
// return claims.getPayload()
// .getAudience()
// .iterator()
// .next();
// } catch (JwtException | IllegalArgumentException e) {
// log.warn("[parseAudience] {} :{}", INVALID_TOKEN, token);
// throw BaseException.from(INVALID_TOKEN);
// } catch (BaseException e) {
// log.warn("[parseAudience] {} :{}", EXPIRED_ACCESS_TOKEN, token);
// throw BaseException.from(EXPIRED_ACCESS_TOKEN);
// }
// }

Copilot uses AI. Check for mistakes.
Comment on lines +78 to 92
public void validateAudience(String token, String expectedAud) {
try {
Jws<Claims> claims = Jwts.parser()
.verifyWith(SECRET_KEY)
.build()
.parseSignedClaims(token);
if (claims.getPayload()
.getExpiration()
.before(new Date())) {
Jws<Claims> claims = Jwts.parser().verifyWith(SECRET_KEY).build().parseSignedClaims(token);
Claims body = claims.getPayload();
if (body.getExpiration().before(new Date())) {
throw BaseException.from(EXPIRED_ACCESS_TOKEN);
}
return claims.getPayload()
.getAudience()
.iterator()
.next();
} catch (JwtException | IllegalArgumentException e) {
log.warn("[parseAudience] {} :{}", INVALID_TOKEN, token);
throw BaseException.from(INVALID_TOKEN);
var audiences = body.getAudience();
if (audiences == null || !audiences.contains(expectedAud)) {
throw BaseException.from(INVALID_TOKEN);
}
} catch (BaseException e) {
log.warn("[parseAudience] {} :{}", EXPIRED_ACCESS_TOKEN, token);
// 만료 등 우리 쪽 예외만 캐치해서 동일 코드로 재던짐(스크린샷 흐름 반영)
throw BaseException.from(EXPIRED_ACCESS_TOKEN);
}
Copy link

Copilot AI Sep 19, 2025

Choose a reason for hiding this comment

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

The validateAudience method is missing a catch block for JwtException which could cause unhandled exceptions. Add a catch block for JwtException to throw INVALID_TOKEN, similar to the original parseAudience method.

Copilot uses AI. Check for mistakes.
iamseojin and others added 2 commits September 19, 2025 17:23
…java

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@iamseojin iamseojin merged commit 8c47e86 into with-travel:develop Sep 19, 2025
5 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant