Skip to content

[feat] Refresh Token 기반 JWT 인증 로직 추가 및 로그아웃/재발급 API 구현#53

Merged
seung-in-Yoo merged 9 commits into
developfrom
feature/seungin
Jul 18, 2025
Merged

[feat] Refresh Token 기반 JWT 인증 로직 추가 및 로그아웃/재발급 API 구현#53
seung-in-Yoo merged 9 commits into
developfrom
feature/seungin

Conversation

@seung-in-Yoo

@seung-in-Yoo seung-in-Yoo commented Jul 18, 2025

Copy link
Copy Markdown
Member

#️⃣ 연관된 이슈

#41

📝 작업 내용

  • Refresh Token 저장 -> RDB 저장 방식으로 구현하였습니다.
  • 로그인 시 accessToken과 refreshToken을 함께 발급하고, refreshToken을 DB에 저장합니다.
  • refreshtoken 관련 엔티티를 새로 추가하였습니다.
  • accessToken 만료 시, refreshToken을 통해 새로운 accessToken/refreshToken을 재발급할수 있는 토큰 재발급 API를 구현하였습니다.
  • accessToken으로 인증 후, DB에 저장된 refreshToken 삭제하여 로그아웃 처리하는 로그아웃 API를 구현하였습니다.

📸 스크린샷

회원가입 및 토큰 발급

회원가입토큰발급성공(1)

refresh token 토큰 DB에 저장

토큰발급디비(1)

토큰 재발급

토큰재발급성공(1)

로그아웃

로그아웃성공(1)

💬 리뷰 요구사항 (선택)

  • 원래 redis를 사용하여 refresh token을 저장하고 싶었는데 aws 프리티어 사용의 한계로 DB 기반 refresh token 저장 방식으로 구현하였습니다.

Summary by CodeRabbit

  • 신규 기능

    • 리프레시 토큰 발급 및 관리 기능이 추가되었습니다. 로그인 시 액세스 토큰과 리프레시 토큰이 함께 발급됩니다.
    • 리프레시 토큰을 통한 액세스 토큰 재발급(/reissue) 및 로그아웃(/logout) API가 추가되었습니다.
  • 기타

    • 리프레시 토큰 관련 데이터가 안전하게 저장 및 관리됩니다.
    • 관련 API 엔드포인트가 인증 없이 접근 가능하도록 변경되었습니다.

@seung-in-Yoo seung-in-Yoo self-assigned this Jul 18, 2025
@seung-in-Yoo seung-in-Yoo added 💡 feature 기능 구현 및 개발 🔧 refactor 코드 리팩토링 🛠️ fix 기능 오류 및 코드 개선이 필요한 곳 수정 labels Jul 18, 2025
@coderabbitai

coderabbitai Bot commented Jul 18, 2025

Copy link
Copy Markdown

"""

Walkthrough

리프레시 토큰 기반의 인증 기능이 추가되었습니다. 신규 엔티티와 리포지토리로 리프레시 토큰을 관리하며, 로그인 시 액세스/리프레시 토큰을 모두 발급합니다. /reissue/logout 엔드포인트가 추가되어 토큰 재발급과 로그아웃 처리를 제공합니다. 관련 보안 설정도 갱신되었습니다.

Changes

파일/그룹 변경 요약
.../config/SecurityConfig.java /reissue, /logout 엔드포인트를 인증 없이 접근 가능하도록 보안 설정 변경
.../config/security/JwtProperties.java 리프레시 토큰 만료값 필드 및 getter/setter 추가
.../config/security/jwt/JwtTokenProvider.java 리프레시 토큰 생성 및 토큰 만료 조회 메서드 추가
.../user/controller/UserController.java /reissue, /logout API 엔드포인트 추가 및 Swagger 문서화, JwtTokenProvider 의존성 주입
.../user/dto/token/TokenResponseDto.java accessToken, refreshToken 필드로 record 확장
.../user/entity/RefreshToken.java RefreshToken JPA 엔티티 신설 (userId, token, expiry 등 포함)
.../user/repository/RefreshTokenRepository.java RefreshTokenRepository JPA 리포지토리 신설 및 사용자/토큰별 조회, 삭제 메서드 추가
.../user/service/auth/AuthService.java 로그인 시 리프레시 토큰 발급/저장, reissue 및 logout 메서드 추가, login 반환값 확장

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant UserController
    participant AuthService
    participant JwtTokenProvider
    participant RefreshTokenRepository

    Client->>UserController: POST /login (로그인 요청)
    UserController->>AuthService: login()
    AuthService->>JwtTokenProvider: generateAccessToken()
    AuthService->>JwtTokenProvider: generateRefreshToken()
    AuthService->>RefreshTokenRepository: deleteByUserId()
    AuthService->>RefreshTokenRepository: save(refreshToken)
    AuthService-->>UserController: TokenResponseDto (access+refresh)
    UserController-->>Client: 응답 (access+refresh 토큰)

    Client->>UserController: POST /reissue (refreshToken 전달)
    UserController->>AuthService: reissue(refreshToken)
    AuthService->>RefreshTokenRepository: findByToken()
    AuthService->>JwtTokenProvider: getUserId(refreshToken)
    AuthService->>JwtTokenProvider: generateAccessToken()
    AuthService-->>UserController: TokenResponseDto (access+refresh)
    UserController-->>Client: 응답 (새 access, 기존 refresh)

    Client->>UserController: POST /logout (Authorization: Bearer accessToken)
    UserController->>JwtTokenProvider: getUserId(accessToken)
    UserController->>AuthService: logout(userId)
    AuthService->>RefreshTokenRepository: deleteByUserId()
    AuthService-->>UserController: 완료
    UserController-->>Client: 성공 메시지
Loading

Possibly related PRs

Suggested labels

⚙️ chore

Suggested reviewers

  • KiSeungMin

Poem

🐰
토큰이 두 개, 깡총깡총 hop!
액세스와 리프레시, 로그인도 척척
재발급도 로그아웃도
이제는 걱정 마세요
깡총이처럼 안전하게
인증의 들판을 달려요!
🥕
"""


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f90dc79 and 7add545.

📒 Files selected for processing (5)
  • src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java (2 hunks)
  • src/main/java/com/wayble/server/user/controller/UserController.java (3 hunks)
  • src/main/java/com/wayble/server/user/entity/RefreshToken.java (1 hunks)
  • src/main/java/com/wayble/server/user/repository/RefreshTokenRepository.java (1 hunks)
  • src/main/java/com/wayble/server/user/service/auth/AuthService.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
  • src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java
  • src/main/java/com/wayble/server/user/repository/RefreshTokenRepository.java
  • src/main/java/com/wayble/server/user/controller/UserController.java
  • src/main/java/com/wayble/server/user/entity/RefreshToken.java
🔇 Additional comments (3)
src/main/java/com/wayble/server/user/service/auth/AuthService.java (3)

7-7: 의존성 및 임포트가 올바르게 추가되었습니다.

리프레시 토큰 기능을 위해 필요한 엔티티, 리포지토리, 그리고 트랜잭션 어노테이션이 적절히 추가되었습니다.

Also applies to: 10-10, 12-12, 22-22


26-46: 로그인 메서드 구현이 우수합니다.

리프레시 토큰 생성 및 저장 로직이 올바르게 구현되었습니다:

  • 기존 리프레시 토큰 삭제로 토큰 중복 방지
  • 액세스 토큰과 리프레시 토큰 모두 생성
  • 만료 시간 저장으로 후속 검증 지원

79-82: 로그아웃 메서드가 올바르게 구현되었습니다.

트랜잭션 어노테이션과 함께 리프레시 토큰을 안전하게 삭제하는 간단하고 효과적인 구현입니다.

✨ Finishing Touches
  • 📝 Generate Docstrings

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@seung-in-Yoo seung-in-Yoo linked an issue Jul 18, 2025 that may be closed by this pull request
8 tasks

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 5

🧹 Nitpick comments (3)
src/main/java/com/wayble/server/user/entity/RefreshToken.java (2)

24-25: 만료 시간 필드 타입 개선 제안

expiry 필드를 Long 타입으로 사용하는 것보다 LocalDateTime을 사용하는 것이 더 명확하고 안전합니다.

+import java.time.LocalDateTime;
+
 @Column(nullable = false)
-private Long expiry; // 만료 시간
+private LocalDateTime expiry; // 만료 시간

이 경우 JwtTokenProvider에서도 해당 변경사항을 반영해야 합니다.


12-26: User 엔티티와의 관계 설정 고려

현재 userId를 Long 타입으로 직접 참조하고 있는데, JPA의 @manytoone 관계를 사용하여 User 엔티티와의 관계를 명시적으로 설정하는 것을 고려해 보세요.

+@ManyToOne(fetch = FetchType.LAZY)
+@JoinColumn(name = "user_id", nullable = false, unique = true)
+private User user;
-
-@Column(nullable = false, unique = true) // 1인 1토큰
-private Long userId;

단, 현재 구조도 충분히 동작하므로 선택사항입니다.

src/main/java/com/wayble/server/user/service/auth/AuthService.java (1)

26-46: 로그인 로직이 잘 구현되었습니다.

기존 refresh token을 삭제하고 새로 생성하는 방식이 적절합니다. 다만, 동시성 문제를 완전히 방지하려면 이 메서드에도 @Transactional 어노테이션을 추가하는 것을 고려해보세요.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73bef6e and f90dc79.

📒 Files selected for processing (8)
  • src/main/java/com/wayble/server/common/config/SecurityConfig.java (1 hunks)
  • src/main/java/com/wayble/server/common/config/security/JwtProperties.java (2 hunks)
  • src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java (2 hunks)
  • src/main/java/com/wayble/server/user/controller/UserController.java (3 hunks)
  • src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java (1 hunks)
  • src/main/java/com/wayble/server/user/entity/RefreshToken.java (1 hunks)
  • src/main/java/com/wayble/server/user/repository/RefreshTokenRepository.java (1 hunks)
  • src/main/java/com/wayble/server/user/service/auth/AuthService.java (3 hunks)
🧰 Additional context used
🧠 Learnings (1)
src/main/java/com/wayble/server/user/entity/RefreshToken.java (1)
Learnt from: seung-in-Yoo
PR: Wayble-Project/wayble-spring#37
File: src/main/java/com/wayble/server/user/entity/User.java:61-62
Timestamp: 2025-07-13T15:08:59.288Z
Learning: Java에서 같은 패키지 내의 클래스들은 import 구문 없이 서로를 참조할 수 있다. com.wayble.server.user.entity 패키지 내의 클래스들은 명시적인 import 없이 사용 가능하다.
🔇 Additional comments (8)
src/main/java/com/wayble/server/user/dto/token/TokenResponseDto.java (1)

3-3: 리프레시 토큰 필드 추가 승인

accessToken과 refreshToken을 모두 포함하는 DTO 구조로 적절하게 수정되었습니다. record 타입 사용으로 불변성과 간결성을 동시에 확보했습니다.

src/main/java/com/wayble/server/common/config/security/JwtProperties.java (2)

12-12: 리프레시 토큰 만료 시간 설정 추가 승인

리프레시 토큰의 만료 시간을 설정할 수 있는 refreshExp 필드가 적절하게 추가되었습니다.


31-33: 환경 설정에 refreshExp 프로퍼티 추가 확인 요청
JwtProperties.java에 새로 추가된 refreshExp 필드가 애플리케이션 설정 파일에 반영되어 있는지 검토해 주세요.

확인 대상 파일:

  • application.yml / application.yaml (jwt.refresh-exp: 또는 refreshExp:)
  • application.properties (jwt.refresh-exp=)

필요 시 해당 키를 추가하거나, 기본값을 문서화해 주세요.

src/main/java/com/wayble/server/user/entity/RefreshToken.java (1)

18-19: 1인 1토큰 제약조건 적절히 설정됨

userId에 unique 제약조건을 설정하여 사용자당 하나의 리프레시 토큰만 유지하도록 한 것은 보안상 적절한 설계입니다.

src/main/java/com/wayble/server/user/repository/RefreshTokenRepository.java (1)

8-12: 리포지토리 메서드 구조 적절함

필요한 CRUD 작업을 위한 메서드들이 Spring Data JPA 네이밍 컨벤션에 따라 적절히 정의되었습니다.

src/main/java/com/wayble/server/common/config/security/jwt/JwtTokenProvider.java (1)

34-41: Refresh token 생성 로직이 적절합니다.

Refresh token에는 최소한의 정보만 포함하고 role claim을 제외한 것이 보안 관점에서 좋은 선택입니다.

src/main/java/com/wayble/server/user/controller/UserController.java (1)

63-82: Token 재발급 엔드포인트가 잘 구현되었습니다.

RESTful 규칙을 잘 따르고 있으며, OpenAPI 문서화도 명확합니다.

src/main/java/com/wayble/server/user/service/auth/AuthService.java (1)

66-69: 로그아웃 구현이 깔끔합니다.

Refresh token을 삭제하여 로그아웃을 처리하는 방식이 적절합니다.

Comment on lines +39 to +40
"/api/v1/users/reissue",
"/api/v1/users/logout",

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

로그아웃 엔드포인트의 보안 설정 재검토 필요

/api/v1/users/logout 엔드포인트를 인증 없이 접근 가능하도록 설정하는 것은 보안상 위험할 수 있습니다. 로그아웃은 인증된 사용자만 수행해야 하는 작업입니다.

제안사항:

  • reissue 엔드포인트는 리프레시 토큰 검증이 필요하므로 permitAll() 유지 가능
  • logout 엔드포인트는 액세스 토큰 검증이 필요하므로 authenticated() 요구 권장
                        .requestMatchers(
                                "/api/v1/users/signup",
                                "/api/v1/users/login",
                                "/api/v1/users/reissue",
-                               "/api/v1/users/logout",
                                "/swagger-ui/**",
                                "/v3/api-docs/**"
                        ).permitAll()
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
"/api/v1/users/reissue",
"/api/v1/users/logout",
http
.authorizeRequests()
.requestMatchers(
"/api/v1/users/signup",
"/api/v1/users/login",
"/api/v1/users/reissue",
"/swagger-ui/**",
"/v3/api-docs/**"
).permitAll()
// all other endpoints (including /api/v1/users/logout) now require authentication
.anyRequest().authenticated();
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/common/config/SecurityConfig.java around
lines 39 to 40, the /api/v1/users/logout endpoint is currently configured to
allow access without authentication, which is a security risk. Modify the
security configuration to require authentication for the logout endpoint by
replacing permitAll() with authenticated() for this path, while keeping
permitAll() for the /api/v1/users/reissue endpoint as it requires refresh token
validation.

public interface RefreshTokenRepository extends JpaRepository<RefreshToken, Long> {
Optional<RefreshToken> findByUserId(Long userId);
Optional<RefreshToken> findByToken(String token);
void deleteByUserId(Long userId);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

삭제 메서드에 @transactional 추가 권장

deleteByUserId 메서드는 데이터를 삭제하는 작업이므로 @transactional 어노테이션을 추가하는 것이 좋습니다.

+import org.springframework.transaction.annotation.Transactional;
+
+@Transactional
 void deleteByUserId(Long userId);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
void deleteByUserId(Long userId);
import org.springframework.transaction.annotation.Transactional;
@Transactional
void deleteByUserId(Long userId);
🤖 Prompt for AI Agents
In src/main/java/com/wayble/server/user/repository/RefreshTokenRepository.java
at line 11, the deleteByUserId method performs a data deletion operation and
should be annotated with @Transactional to ensure the operation is executed
within a transaction. Add the @Transactional annotation above the deleteByUserId
method declaration to properly manage the transaction scope during deletion.

Comment thread src/main/java/com/wayble/server/user/controller/UserController.java
Comment thread src/main/java/com/wayble/server/user/service/auth/AuthService.java
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 feature 기능 구현 및 개발 🛠️ fix 기능 오류 및 코드 개선이 필요한 곳 수정 🔧 refactor 코드 리팩토링

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEAT] JWT 기반 로그인 및 회원가입 구현

1 participant