Skip to content

[feat] 인증 블록 구현 (회원가입/로그인/JWT/역할부여) - #13

Merged
kangcheolung merged 7 commits into
developfrom
feature/9
Jul 14, 2026
Merged

[feat] 인증 블록 구현 (회원가입/로그인/JWT/역할부여)#13
kangcheolung merged 7 commits into
developfrom
feature/9

Conversation

@kangcheolung

@kangcheolung kangcheolung commented Jul 14, 2026

Copy link
Copy Markdown
Member

🔍 작업 내용

✨ 상세 설명

인증 블록 전체 구현

구현된 API

  • POST /auth/signup — 회원가입, 가입 시 USER role 자동 부여
  • POST /auth/login — 로그인, JWT(HS256, 1시간 만료) 발급
  • GET /auth/me — 내 정보 조회 (DB 직접 조회, role 승격 즉시 반영)
  • POST /admin/users/{userId}/roles — 역할 부여 (ADMIN 전용)
  • GET /departments — 활성 부서 목록 조회 (인증 불필요)

주요 설계 결정

  • JWT는 stateless 세션, JwtAuthenticationFilter로 매 요청 검증
  • /me는 토큰 payload 미사용 → DB 재조회로 역할 승격 즉시 반영
  • /admin/** URL 레벨 인가 처리 (SecurityConfig)
  • 최초 ADMIN 계정은 Flyway seed(V28)로 사전 등록
  • @CurrentUser 커스텀 어노테이션으로 컨트롤러에서 userId 주입

도메인 구조

  • domain/auth/ — 인증 흐름 전담 (로그인/회원가입/JWT)
  • domain/user/ — 유저 엔티티/레포지토리, 역할 관리

🛠 추후 리팩토링 및 고도화 계획

  • Refresh Token 구현 (현재 1단계 Out of Scope)
  • AuthCommandService 단위 테스트 추가
  • JwtProvider 단위 테스트 추가

📸 스크린샷 (선택)

로그인
image
부서 조회
image
회원가입
image
권한 부여
image

💬 리뷰 요구사항

  • JwtAuthenticationFilter에서 authentication.setDetails(userId)로 userId를 넘기는 방식이 적절한지
  • /admin/** URL 레벨 인가 vs @PreAuthorize 메서드 레벨 인가 선택에 대한 의견

Summary by CodeRabbit

  • 새 기능
    • 회원가입, 로그인, 현재 사용자 정보 조회 기능을 추가했습니다.
    • JWT 기반 인증과 사용자별 역할 정보를 제공합니다.
    • 부서 목록 조회 기능을 추가했습니다.
    • 관리자가 사용자에게 역할을 부여할 수 있습니다.
  • 보안
    • 비밀번호를 안전하게 암호화하고, 인증된 요청과 관리자 전용 기능을 구분합니다.
    • 계정 상태, 중복 이메일, 잘못된 인증 정보 등에 대한 오류 안내를 강화했습니다.
  • 문서화
    • API 요청·응답 설명과 Swagger 문서 작성 기준을 보완했습니다.

kangcheolung and others added 6 commits July 14, 2026 19:10
- roles(USER/ADMIN/DOCUMENT_MANAGER), departments(개발/인사/재무), 최초 ADMIN 계정 seed 추가
- @operation description 필수화 및 서비스 log.error 규칙 추가

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 활성 부서 목록 반환, 인증 불필요
- 회원가입 화면에서 부서 선택 드롭다운용

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- 이메일 중복 확인, 부서 활성 확인, BCrypt 해시 후 저장
- 가입 시 USER role 자동 부여
- 인증 관련 ErrorCode 추가, SecurityConfig PasswordEncoder 빈 등록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- jjwt 0.12.6 의존성 추가, JWT secret/expiration 환경변수화
- JwtProvider (HS256 토큰 생성/검증), JwtAuthenticationFilter 구현
- 로그인 시 accessToken 발급, SecurityConfig stateless 세션으로 전환

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- @currentuser 어노테이션 및 CurrentUserArgumentResolver 구현
- role 승격 즉시 반영을 위해 JWT payload 미사용, DB 직접 조회

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ADMIN만 접근 가능, SecurityConfig에서 /admin/** URL 레벨 인가 처리
- 중복 부여 방지, assignedBy에 ADMIN 기록

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 14, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kangcheolung, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 7d073740-4a36-40c4-b882-0daa86ef6d65

📥 Commits

Reviewing files that changed from the base of the PR and between 203f9a7 and c0f5b4f.

📒 Files selected for processing (3)
  • src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtAuthenticationFilter.java
  • src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtProvider.java
  • src/main/java/com/opensource/docgrid/domain/user/repository/UserRoleRepository.java
📝 Walkthrough

Walkthrough

회원가입·로그인과 JWT 인증을 추가하고, 현재 사용자·활성 부서 조회 및 관리자 역할 부여 API를 구현했습니다. 보안 설정, 요청·응답 DTO, 저장소, 예외 코드, 초기 역할·부서·관리자 시드 데이터도 함께 추가되었습니다.

Changes

인증 및 사용자 관리

Layer / File(s) Summary
인증 계약 및 초기 데이터
build.gradle, src/main/resources/application.yml, src/main/resources/db/migration/..., src/main/java/.../dto/**, src/main/java/.../ErrorCode.java
JWT 설정과 의존성, 인증 요청·응답 DTO, 오류 코드, @CurrentUser 어노테이션 및 역할·부서·관리자 시드 데이터를 추가했습니다.
JWT 발급 및 요청 인증
src/main/java/.../auth/jwt/*, src/main/java/.../auth/resolver/*, src/main/java/.../global/config/*
JWT 생성·검증, Bearer 토큰 필터 인증, 무상태 보안 정책, 권한 경로 설정, 현재 사용자 인자 해석기 등록을 구현했습니다.
회원가입 및 로그인 흐름
src/main/java/.../auth/controller/*, src/main/java/.../auth/service/command/*, src/main/java/.../user/repository/*
회원가입 시 이메일·부서·USER 역할을 검증하고 사용자와 역할을 저장하며, 로그인 시 비밀번호와 계정 상태를 확인해 JWT를 반환합니다.
조회 및 관리자 역할 부여
src/main/java/.../auth/service/query/*, src/main/java/.../user/controller/*, src/main/java/.../user/service/*
현재 사용자와 활성 부서 조회 API 및 관리자 역할 부여 API를 추가했습니다.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant AuthController
  participant AuthCommandService
  participant JwtProvider
  participant JwtAuthenticationFilter
  participant CurrentUserArgumentResolver
  Client->>AuthController: 회원가입 또는 로그인 요청
  AuthController->>AuthCommandService: 요청 전달
  AuthCommandService->>JwtProvider: 로그인 JWT 생성
  JwtProvider-->>AuthCommandService: access token 반환
  AuthCommandService-->>Client: 인증 응답 반환
  Client->>JwtAuthenticationFilter: Bearer 토큰 요청
  JwtAuthenticationFilter->>JwtProvider: 토큰 검증 및 클레임 조회
  JwtAuthenticationFilter->>CurrentUserArgumentResolver: SecurityContext 인증 정보 제공
  CurrentUserArgumentResolver-->>AuthController: 현재 userId 주입
  AuthController-->>Client: 현재 사용자 정보 반환
Loading

Possibly related PRs

  • DocGrid/backend#2: 공통 예외 처리·응답 기반과 SecurityConfig를 확장해 이번 JWT 인증 및 사용자 권한 기능을 연결합니다.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 5.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 제목이 회원가입·로그인·JWT·역할부여라는 핵심 변경을 간결하게 요약해 PR 내용과 일치합니다.
Description check ✅ Passed 필수 섹션인 작업 내용, 상세 설명, 추후 계획, 리뷰 요구사항이 모두 갖춰져 있어 템플릿을 충족합니다.
Linked Issues check ✅ Passed seed 데이터, 회원가입/로그인/JWT, 현재 사용자 조회, CurrentUser, 역할 승격, 부서 조회가 모두 구현되어 요구사항을 충족합니다.
Out of Scope Changes check ✅ Passed 인증 기능 구현에 필요한 설정, DTO, 필터, 시드, 보안 구성이 중심이며 눈에 띄는 별도 범위 변경은 없습니다.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/9

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

Comment @coderabbitai help to get the list of available commands.

@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.

🧹 Nitpick comments (2)
src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtAuthenticationFilter.java (1)

29-33: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

JWT 중복 파싱 제거 권장

현재 토큰이 유효한 경우 jwtProvider.validateToken()에서 한 번, 이후 getUserId(), getEmail(), getRoles()에서 각각 getClaims()를 호출하여 총 네 번 토큰을 파싱(검증)하게 됩니다. 암호학적 서명 검증이 반복되므로, 성능 최적화를 위해 한 번 파싱한 Claims 객체를 반환하여 재사용하는 방식을 권장합니다.

♻️ 리팩토링 제안

예를 들어, JwtProvider에서 Claims를 반환하는 getClaimsIfValid(String token)와 같은 메서드를 만들고 필터에서는 다음과 같이 사용할 수 있습니다:

// JwtAuthenticationFilter.java
Claims claims = jwtProvider.getClaimsIfValid(token);
if (claims != null) {
    Long userId = claims.get("userId", Long.class);
    String email = claims.getSubject();
    List<String> roles = claims.get("roles", List.class);
    // ...
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtAuthenticationFilter.java`
around lines 29 - 33, Update JwtAuthenticationFilter to parse and validate the
token once through a JwtProvider method such as getClaimsIfValid, then reuse the
returned Claims for userId, email, and roles instead of calling validateToken
and the individual JwtProvider getters. Add or adapt the JwtProvider API as
needed while preserving the existing invalid-token behavior.
src/main/java/com/opensource/docgrid/domain/user/repository/UserRoleRepository.java (1)

16-17: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Spring Data JPA의 기본 메서드 이름 규칙을 활용해 쿼리를 최적화하세요.

@Query를 사용해 COUNT로 존재 여부를 확인하면 조건에 맞는 모든 레코드를 세게 되어 성능에 불리할 수 있습니다. Spring Data JPA가 제공하는 existsBy 파생 쿼리를 사용하면 데이터베이스 수준에서 LIMIT 1 최적화가 이루어져 더 효율적입니다.

♻️ 제안하는 수정
-    `@Query`("SELECT COUNT(ur) > 0 FROM UserRole ur WHERE ur.user.id = :userId AND ur.role.code = :roleCode")
-    boolean existsByUserIdAndRoleCode(`@Param`("userId") Long userId, `@Param`("roleCode") String roleCode);
+    boolean existsByUserIdAndRoleCode(Long userId, String roleCode);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@src/main/java/com/opensource/docgrid/domain/user/repository/UserRoleRepository.java`
around lines 16 - 17, Update UserRoleRepository.existsByUserIdAndRoleCode to
remove the custom `@Query` and rely on Spring Data JPA’s derived existsBy method
naming, preserving the existing userId and roleCode filtering and boolean return
contract.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In
`@src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtAuthenticationFilter.java`:
- Around line 29-33: Update JwtAuthenticationFilter to parse and validate the
token once through a JwtProvider method such as getClaimsIfValid, then reuse the
returned Claims for userId, email, and roles instead of calling validateToken
and the individual JwtProvider getters. Add or adapt the JwtProvider API as
needed while preserving the existing invalid-token behavior.

In
`@src/main/java/com/opensource/docgrid/domain/user/repository/UserRoleRepository.java`:
- Around line 16-17: Update UserRoleRepository.existsByUserIdAndRoleCode to
remove the custom `@Query` and rely on Spring Data JPA’s derived existsBy method
naming, preserving the existing userId and roleCode filtering and boolean return
contract.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 582dc022-d14f-4423-8754-214c33b91460

📥 Commits

Reviewing files that changed from the base of the PR and between eecf255 and 203f9a7.

📒 Files selected for processing (30)
  • .claude/rules/code_style.md
  • build.gradle
  • src/main/java/com/opensource/docgrid/domain/auth/annotation/CurrentUser.java
  • src/main/java/com/opensource/docgrid/domain/auth/controller/AuthController.java
  • src/main/java/com/opensource/docgrid/domain/auth/dto/request/LoginRequest.java
  • src/main/java/com/opensource/docgrid/domain/auth/dto/request/SignupRequest.java
  • src/main/java/com/opensource/docgrid/domain/auth/dto/response/LoginResponse.java
  • src/main/java/com/opensource/docgrid/domain/auth/dto/response/MeResponse.java
  • src/main/java/com/opensource/docgrid/domain/auth/dto/response/SignupResponse.java
  • src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtAuthenticationFilter.java
  • src/main/java/com/opensource/docgrid/domain/auth/jwt/JwtProvider.java
  • src/main/java/com/opensource/docgrid/domain/auth/resolver/CurrentUserArgumentResolver.java
  • src/main/java/com/opensource/docgrid/domain/auth/service/command/AuthCommandService.java
  • src/main/java/com/opensource/docgrid/domain/auth/service/query/AuthQueryService.java
  • src/main/java/com/opensource/docgrid/domain/user/controller/AdminUserController.java
  • src/main/java/com/opensource/docgrid/domain/user/controller/DepartmentController.java
  • src/main/java/com/opensource/docgrid/domain/user/dto/request/AssignRoleRequest.java
  • src/main/java/com/opensource/docgrid/domain/user/dto/response/DepartmentResponse.java
  • src/main/java/com/opensource/docgrid/domain/user/dto/response/UserRoleResponse.java
  • src/main/java/com/opensource/docgrid/domain/user/repository/DepartmentRepository.java
  • src/main/java/com/opensource/docgrid/domain/user/repository/RoleRepository.java
  • src/main/java/com/opensource/docgrid/domain/user/repository/UserRepository.java
  • src/main/java/com/opensource/docgrid/domain/user/repository/UserRoleRepository.java
  • src/main/java/com/opensource/docgrid/domain/user/service/command/UserRoleCommandService.java
  • src/main/java/com/opensource/docgrid/domain/user/service/query/DepartmentQueryService.java
  • src/main/java/com/opensource/docgrid/global/config/SecurityConfig.java
  • src/main/java/com/opensource/docgrid/global/config/WebMvcConfig.java
  • src/main/java/com/opensource/docgrid/global/exception/ErrorCode.java
  • src/main/resources/application.yml
  • src/main/resources/db/migration/V28__seed_roles_departments_admin.sql

- JwtProvider.getClaimsIfValid()로 토큰 파싱 1회로 통합 (기존 4회 → 1회)
- UserRoleRepository.existsByUserIdAndRoleCode @query 제거, JPA 파생 쿼리로 대체 (DB LIMIT 1 최적화)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

✨ Feature 기능 개발

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Feat] 로그인 / JWT 발급 / 현재 사용자 조회

1 participant