Skip to content

Conversation

@CromNEXT
Copy link
Contributor

@CromNEXT CromNEXT commented Jan 28, 2026

개요

PR 유형

어떤 변경 사항이 있나요?

  • 새로운 기능 추가
  • 버그 수정
  • 코드 리팩토링
  • 주석 추가 및 수정
  • 문서 수정
  • 테스트 추가, 테스트 리팩토링
  • 빌드 부분 혹은 패키지 매니저 수정
  • 파일 혹은 폴더명 수정
  • 파일 혹은 폴더 삭제

PR Checklist

PR이 다음 요구 사항을 충족하는지 확인하세요.

  • 커밋 메시지 컨벤션에 맞게 작성했습니다.
  • 변경 사항에 대한 테스트를 했습니다.(버그 수정/기능에 대한 테스트).

🧩 작업 내용

  • 회원가입, 로그인 기능 구현(JWT 기반)
  • Id가 String값이면 에러가 발생해서 Long으로 수정
  • 커스텀 Status추가(에러/성공 발생 시 Enum에 추가해서 전달하도록 통일)
  • swagger문서화는 컨트롤러에서 docs를 구현하도록 작성

📸 스크린샷(선택)

image image

📣 To Reviewers

  • Reviewers : 팀 선택
  • Labels : 작업 유형, 자기 자신

Summary by CodeRabbit

Release Notes

  • New Features
    • 회원가입 및 로그인 기능 추가
    • JWT 기반 인증 시스템 도입
    • 토큰 재발급 기능 지원

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link

coderabbitai bot commented Jan 28, 2026

📝 Walkthrough

Walkthrough

기본적인 인증 기능(회원가입, 로그인, 토큰 갱신)을 구현합니다. Spring Security와 JWT 라이브러리를 추가하고, 표준화된 API 응답, 예외 처리, JWT 필터, 토큰 제공자, 사용자 정보 서비스, 인증 컨트롤러 및 관련 DTOs를 포함한 완벽한 인증 인프라를 구축합니다.

Changes

Cohort / File(s) 변경 사항
의존성 및 애플리케이션 설정
build.gradle, src/main/java/ssurent/ssurentbe/SsurentbeApplication.java
Spring Security, JWT(JJWT) 라이브러리 추가; JPA Auditing 활성화
표준화된 응답 및 상태 관리
src/main/java/ssurent/ssurentbe/common/base/BaseResponse.java, src/main/java/ssurent/ssurentbe/common/status/SuccessStatus.java, src/main/java/ssurent/ssurentbe/common/status/ErrorStatus.java
BaseResponse 제네릭 클래스와 성공/에러 상태 열거형 정의
예외 처리
src/main/java/ssurent/ssurentbe/common/exception/GeneralException.java, src/main/java/ssurent/ssurentbe/common/exception/GlobalExceptionHandler.java
구조화된 예외 클래스 및 전역 예외 핸들러 추가
보안 설정
src/main/java/ssurent/ssurentbe/common/config/SecurityConfig.java
Spring Security HTTP 보안, JWT 필터 통합, BCrypt 인코더 구성
JWT 토큰 관리
src/main/java/ssurent/ssurentbe/common/jwt/JwtTokenProvider.java, src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java
액세스/리프레시 토큰 생성, 검증, 인증 필터 구현
사용자 상세 정보 서비스
src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java
UserDetailsService 구현으로 학생 번호 기반 사용자 로드
인증 API
src/main/java/ssurent/ssurentbe/domain/users/controller/AuthController.java, src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java
회원가입, 로그인, 토큰 갱신 엔드포인트 및 Swagger 문서
인증 비즈니스 로직
src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java
회원가입, 로그인, 토큰 갱신 서비스 로직; 중복 확인, 암호 검증, 상태 검증 포함
요청/응답 DTO
src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java, src/main/java/ssurent/ssurentbe/domain/users/dto/LoginRequest.java, src/main/java/ssurent/ssurentbe/domain/users/dto/TokenResponse.java
인증 엔드포인트용 Request/Response 레코드 정의
저장소
src/main/java/ssurent/ssurentbe/domain/users/repository/UserRepository.java
학생 번호 기반 사용자 조회/존재 확인 메서드
엔티티 수정
src/main/java/ssurent/ssurentbe/domain/users/entity/Users.java, src/main/java/ssurent/ssurentbe/domain/users/entity/Panalty.java, src/main/java/ssurent/ssurentbe/domain/users/entity/UserPanaltyLog.java, src/main/java/ssurent/ssurentbe/domain/assists/entity/Assists.java, src/main/java/ssurent/ssurentbe/domain/item/entity/Category.java, src/main/java/ssurent/ssurentbe/domain/item/entity/Items.java, src/main/java/ssurent/ssurentbe/domain/rental/entity/RentalHistory.java
ID 필드 타입 String → Long 변경; Users 엔티티에 password 필드 추가

Sequence Diagram(s)

sequenceDiagram
    actor Client
    participant AuthController as AuthController
    participant AuthService as AuthService
    participant UserRepository as UserRepository
    participant JwtTokenProvider as JwtTokenProvider
    participant SecurityContext as SecurityContext

    rect rgb(220, 100, 100, 0.5)
    Note over Client,JwtTokenProvider: 로그인 흐름
    Client->>AuthController: POST /api/auth/login<br/>(studentNum, password)
    AuthController->>AuthService: login(LoginRequest)
    AuthService->>UserRepository: findByStudentNum(studentNum)
    UserRepository-->>AuthService: User 객체
    AuthService->>AuthService: validatePassword()
    AuthService->>JwtTokenProvider: createAccessToken(studentNum)
    JwtTokenProvider-->>AuthService: accessToken
    AuthService->>JwtTokenProvider: createRefreshToken(studentNum)
    JwtTokenProvider-->>AuthService: refreshToken
    AuthService-->>AuthController: TokenResponse
    AuthController-->>Client: BaseResponse<TokenResponse>
    end

    rect rgb(100, 220, 100, 0.5)
    Note over Client,SecurityContext: JWT 토큰 검증 흐름
    Client->>Client: Authorization: Bearer {token}
    Client->>AuthController: GET /api/protected
    AuthController->>AuthController: JwtAuthenticationFilter 실행
    AuthController->>JwtTokenProvider: validateToken(token)
    JwtTokenProvider-->>AuthController: true
    AuthController->>JwtTokenProvider: getAuthentication(token)
    JwtTokenProvider->>UserRepository: loadUserByUsername(studentNum)
    UserRepository-->>JwtTokenProvider: UserDetails
    JwtTokenProvider-->>AuthController: Authentication
    AuthController->>SecurityContext: setAuthentication()
    AuthController-->>Client: 요청 처리
    end

    rect rgb(100, 100, 220, 0.5)
    Note over Client,JwtTokenProvider: 토큰 갱신 흐름
    Client->>AuthController: POST /api/auth/refresh<br/>Authorization: Bearer {refreshToken}
    AuthController->>AuthService: refresh(refreshToken)
    AuthService->>JwtTokenProvider: validateToken(refreshToken)
    JwtTokenProvider-->>AuthService: true
    AuthService->>JwtTokenProvider: isRefreshToken(refreshToken)
    JwtTokenProvider-->>AuthService: true
    AuthService->>JwtTokenProvider: getStudentNum(refreshToken)
    JwtTokenProvider-->>AuthService: studentNum
    AuthService->>UserRepository: findByStudentNum(studentNum)
    UserRepository-->>AuthService: User 객체
    AuthService->>JwtTokenProvider: createAccessToken(studentNum)
    JwtTokenProvider-->>AuthService: newAccessToken
    AuthService->>JwtTokenProvider: createRefreshToken(studentNum)
    JwtTokenProvider-->>AuthService: newRefreshToken
    AuthService-->>AuthController: TokenResponse
    AuthController-->>Client: BaseResponse<TokenResponse>
    end
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 인증의 성 지은 토끼의 노래

JWT 토큰 반짝이는 길,
암호는 안전하게 해시로 감싸고,
회원가입에서 로그인까지,
봄날의 보안으로 피어나네!
사용자의 정보, 소중한 당근처럼 보호하리! 🥕✨

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.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 PR 제목이 주요 변경사항(Auth 기능 구현)을 명확하게 요약하고 있으며, 간결하고 구체적입니다.
Description check ✅ Passed PR 설명에 종료된 이슈 번호, PR 유형 선택, 작업 내용 설명 및 체크리스트 항목이 포함되어 있습니다.
Linked Issues check ✅ Passed PR이 이슈 #3의 모든 요구사항을 충족합니다: 회원가입 기능, 로그인 기능, JWT 인증이 모두 구현되었습니다.
Out of Scope Changes check ✅ Passed 모든 변경사항이 이슈 #3의 범위 내에 있습니다. 엔티티 ID 타입 변경과 커스텀 Status 추가는 Auth 기능 구현 시 필요한 필수 변경입니다.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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

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

@CromNEXT CromNEXT requested review from Todom2 and fprtmjinho January 28, 2026 19:29
@CromNEXT CromNEXT self-assigned this Jan 28, 2026
@CromNEXT CromNEXT added the feature 새로운기능구현 label Jan 28, 2026
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/main/java/ssurent/ssurentbe/domain/users/entity/Users.java (1)

18-33: 스키마 마이그레이션/백필 없이는 운영 장애 가능성이 큽니다.
PK 타입 변경과 password NOT NULL 추가는 기존 데이터가 있는 환경에서 마이그레이션 실패나 런타임 오류를 유발할 수 있습니다. Flyway/Liquibase로 PK 타입 변경, 기존 사용자 비밀번호 백필(또는 단계적 nullable 전환) 계획을 명확히 해 주세요.

🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 49-55: Update the springdoc-openapi dependencies to the 3.0.x line
and add a Bouncy Castle override: replace the existing springdoc coordinates
(org.springdoc:springdoc-openapi-starter-webmvc-ui and
org.springdoc:springdoc-openapi-starter-webmvc-api currently at 2.8.13) with
3.0.0 (or latest 3.0.x) to be compatible with Spring Boot 4.0.x, and add an
explicit org.bouncycastle:bcpkix-jdk18on dependency at 1.79+ to override the
transitive Bouncy Castle pulled in by io.jsonwebtoken:jjwt-*
(jjwt-api/jjwt-impl/jjwt-jackson) and mitigate CVE-2025-8916.

In
`@src/main/java/ssurent/ssurentbe/common/exception/GlobalExceptionHandler.java`:
- Around line 12-17: The handler in
GlobalExceptionHandler.handleGeneralException currently casts e.getStatus() to
ErrorStatus which can throw ClassCastException at runtime; change the
implementation to avoid unsafe casting by checking e.getStatus() with instanceof
(or using a safe accessor) and handle non-ErrorStatus cases (e.g., map unknown
statuses to a default ErrorStatus or return a generic error response) before
calling BaseResponse.error; ensure you reference GeneralException.getStatus()
and ErrorStatus and produce a ResponseEntity with an appropriate HttpStatus for
fallback paths.

In `@src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java`:
- Around line 42-44: JwtAuthenticationFilter currently unsafely casts
GeneralException.getStatus() to ErrorStatus in the catch block; update the
handling so the cast is safe by either (A) changing GeneralException to only
accept ErrorStatus in its constructor (and update usages) or (B) adding a
runtime check in JwtAuthenticationFilter (and similarly in
GlobalExceptionHandler) that tests if e.getStatus() instanceof ErrorStatus
before casting—if true, call sendErrorResponse(response, (ErrorStatus)
e.getStatus()); otherwise map/convert the BaseStatus to a safe ErrorStatus (or
use a default/generic error response) and log the unexpected status type. Ensure
you update both JwtAuthenticationFilter and GlobalExceptionHandler to follow the
same safe pattern.

In
`@src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java`:
- Around line 22-31: loadUserByUsername currently only checks existence and
returns a UserDetails, allowing deleted/disabled accounts to authenticate;
update CustomUserDetailsService.loadUserByUsername to check the Users entity
flags (e.g., isDeleted() and getStatus()/status enum) after fetching from
userRepository and throw UsernameNotFoundException or a custom DisabledException
when the account is deleted or inactive. Ensure the exception uses a clear
ErrorStatus (or map to ErrorStatus.USER_NOT_FOUND or a new ErrorStatus for
disabled users) so getAuthentication() will reject such tokens, and keep
returning the Spring Security User with roles only for active users.

In `@src/main/java/ssurent/ssurentbe/domain/users/controller/AuthController.java`:
- Around line 41-43: AuthController.refresh currently unsafely extracts the
token with authorization.replace("Bearer ", "") which can NPE or return wrong
value; change it to the same safe parsing used by
JwtAuthenticationFilter.resolveToken — i.e., check for null/empty, verify the
header startsWith("Bearer "), then extract the substring after the prefix (or
call JwtAuthenticationFilter.resolveToken(authorization) if accessible) and
handle a missing/invalid token by returning an appropriate error (or throwing
the same exception path used elsewhere) before calling
authService.refresh(refreshToken).

In
`@src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java`:
- Around line 21-46: Create concrete wrapper response classes that extend
BaseResponse with the generic type parameter (e.g., class SignupResponse extends
BaseResponse<TokenResponse>, LoginResponse extends BaseResponse<TokenResponse>,
RefreshResponse extends BaseResponse<TokenResponse>) and update the
`@ApiResponse/`@Content annotations on the signup, login and refresh operations to
use `@Schema`(implementation = SignupResponse.class) / LoginResponse.class /
RefreshResponse.class respectively so springdoc-openapi can surface
TokenResponse fields (accessToken, refreshToken, tokenType) in Swagger; locate
the annotations around the methods named signup, login and the token refresh
operation and replace the existing `@Schema`(implementation = BaseResponse.class)
references with the new concrete classes.

In `@src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java`:
- Around line 3-8: SignupRequest currently uses the record-generated toString
which exposes sensitive fields; override SignupRequest#toString to return a safe
representation that masks phoneNum and password (e.g., show only last 2-4
digits/characters or replace with fixed ********), keep non-sensitive fields
like name/studentNum readable if desired, and ensure the method is implemented
on the record itself so any logging/exception handling uses the masked output.

In `@src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java`:
- Around line 69-72: The if-check in AuthService.refresh around
jwtTokenProvider.validateToken(refreshToken) is dead because validateToken
throws GeneralException on invalid tokens; remove the if-block and either (a)
simply call jwtTokenProvider.validateToken(refreshToken) and let its exception
propagate, or (b) wrap the call in a try-catch that catches the thrown exception
and rethrows/translate it to ErrorStatus.JWT_INVALID for clearer control flow;
update the method to proceed to the rest of TokenResponse creation only after a
successful validateToken call.
🧹 Nitpick comments (5)
src/main/java/ssurent/ssurentbe/common/status/ErrorStatus.java (1)

12-18: 사용하지 않는 중복 상수 제거

COMM_ERROR_STATUSBAD_REQUEST와 동일한 코드/메시지를 가지고 있으며 코드베이스에서 사용되지 않습니다. 중복 제거를 위해 삭제하는 것을 권장합니다.

src/main/java/ssurent/ssurentbe/domain/users/dto/LoginRequest.java (1)

3-6: 선택적: record의 민감정보 보호를 위한 toString() 오버라이드

LoginRequest는 password를 포함하는 record입니다. 현재 코드에서 요청 객체가 명시적으로 로깅되지는 않으나, Spring 내부 로깅, 모니터링 도구, 또는 예외 스택트레이스에서 기본 toString()이 호출될 가능성이 있습니다. 민감정보 보호를 위해 password를 마스킹하는 것을 권장합니다.

제안 수정
 public record LoginRequest(
         String studentNum,
         String password
 ) {
+    `@Override`
+    public String toString() {
+        return "LoginRequest[studentNum=" + studentNum + ", password=***]";
+    }
 }
src/main/java/ssurent/ssurentbe/common/config/SecurityConfig.java (1)

25-37: CORS 설정이 누락되어 있습니다.

프론트엔드 애플리케이션에서 API를 호출할 경우, CORS(Cross-Origin Resource Sharing) 설정이 없으면 브라우저에서 요청이 차단될 수 있습니다. 프론트엔드와 백엔드가 다른 도메인/포트에서 실행된다면 CORS 설정을 추가하는 것을 고려하세요.

♻️ CORS 설정 추가 예시
 http
+        .cors(cors -> cors.configurationSource(corsConfigurationSource()))
         .csrf(csrf -> csrf.disable())
         .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
         // ... 나머지 설정

별도의 CorsConfigurationSource 빈을 정의하거나, @CrossOrigin 어노테이션을 컨트롤러에 추가할 수 있습니다.

src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java (1)

51-67: 로그인 시 비밀번호 검증 후 isDeleted 체크 순서를 고려하세요.

현재 구현은 비밀번호가 맞더라도 탈퇴한 사용자에게 USER_WITHDRAWN 에러를 반환합니다. 이는 공격자에게 해당 학번으로 탈퇴한 계정이 존재한다는 정보를 노출할 수 있습니다. 보안 강화를 위해 탈퇴 여부와 관계없이 동일한 INVALID_CREDENTIALS 에러를 반환하는 것을 고려해 볼 수 있습니다.

src/main/java/ssurent/ssurentbe/common/jwt/JwtTokenProvider.java (1)

75-93: validateToken 메서드의 반환 타입이 실제 동작과 불일치합니다.

이 메서드는 boolean을 반환하도록 선언되어 있지만, 실제로는 유효한 토큰일 때만 true를 반환하고, 유효하지 않은 토큰일 때는 false 대신 예외를 던집니다. 이로 인해 호출하는 코드에서 if (!validateToken(token))과 같은 불필요한 조건문이 작성될 수 있습니다 (AuthService.refresh 참조).

반환 타입을 void로 변경하거나, 메서드 이름을 validateTokenOrThrow로 변경하여 예외를 던진다는 것을 명확히 하는 것을 권장합니다.

♻️ 수정 제안
-public boolean validateToken(String token) {
+public void validateToken(String token) {
     try {
         Jwts.parser()
                 .verifyWith(secretKey)
                 .build()
                 .parseSignedClaims(token);
-        return true;
     } catch (SignatureException e) {
         throw new GeneralException(ErrorStatus.JWT_INVALID_SIGNATURE);
     } // ... 나머지 catch 블록
 }

Comment on lines +49 to +55
// Spring Security
implementation 'org.springframework.boot:spring-boot-starter-security'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.6'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.6'
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the build.gradle file
fd build.gradle -type f

Repository: SSURENT/SSURENT-BE

Length of output: 232


🏁 Script executed:

# Check Spring Boot version in the build.gradle
rg "spring.boot|gradle.plugin" --max-count=20

Repository: SSURENT/SSURENT-BE

Length of output: 744


🌐 Web query:

Spring Boot 4.0.1 release notes

💡 Result:

Spring Boot 4.0.1 was released on December 18, 2025. It includes 88 bug fixes, documentation improvements, and dependency upgrades. [1]

Noteworthy changes (4.0.1)

  • Hibernate upgraded to 7.2.0.Final (as Hibernate 7.1 moved to limited support). [2]
  • Kotlin serialization starter rename:
    • spring-boot-starter-kotlin-serializationspring-boot-starter-kotlinx-serialization-json
    • spring-boot-starter-kotlin-serialization-testspring-boot-starter-kotlinx-serialization-json-test [2]
  • TestRestTemplate now requires an explicit dependency on spring-boot-restclient. [2]

Full release notes / changelog

  • GitHub release entry for v4.0.1 (bug fixes, docs, dependency upgrades). [2]

Sources: Spring blog announcement [1], Spring Boot GitHub releases (v4.0.1 notes) [2].


🌐 Web query:

JJWT 0.12.6 security CVE vulnerability

💡 Result:

  • Direct JJWT CVE: The main CVE commonly associated with JJWT is CVE-2024-31033, which affects JJWT through 0.12.5 (i.e., 0.12.6 is not in the affected range). The report is also marked disputed by the vendor. [1]
  • Why scanners still flag JJWT 0.12.6: Many tools flag JJWT 0.12.6 due to an indirect (transitive) dependency on Bouncy Castle: org.bouncycastle:bcpkix-jdk18on:1.78, which has CVE-2025-8916. In jjwt-impl:0.12.6, this shows up as a test dependency on MvnRepository’s dependency list. [2]
  • CVE-2025-8916 details/fix: CVE-2025-8916 is a potential DoS in Bouncy Castle PKIX processing, affecting BC Java 1.44–1.78, and is fixed in BC Java 1.79+. [3]

What to do

  • If you’re getting flagged for CVE-2025-8916, upgrade/override Bouncy Castle to ≥ 1.79 (e.g., current 1.83), or ensure the vulnerable Bouncy Castle artifact isn’t included in your production dependency graph. [2][3]

Sources
[1] https://osv.dev/vulnerability/CVE-2024-31033
[2] https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl/0.12.6/dependencies
[3] https://github.com/bcgit/bc-java/wiki/CVE%E2%80%902025%E2%80%908916


🌐 Web query:

Spring Security Springdoc 2.8.13 compatibility Spring Boot 3

💡 Result:

Compatibility (Spring Boot 3 + Springdoc 2.8.13)

  • springdoc-openapi 2.x is for Spring Boot 3.x (Jakarta / Spring Framework 6 era). [3]
  • 2.8.x (so including 2.8.13) is built against Spring Boot 3.5.x, and is also compatible with Spring Boot 3.4.x. [1]

Rule of thumb (from springdoc’s compatibility matrix): [1]

  • Boot 3.5.x → springdoc 2.8.x
  • Boot 3.4.x → springdoc 2.7.x – 2.8.x
  • Boot 3.3.x → springdoc 2.6.x
  • Boot 3.2.x → springdoc 2.3.x – 2.5.x
  • Boot 3.0.x → springdoc 2.0.x – 2.1.x

So: Spring Boot 3.4/3.5 + springdoc 2.8.13 is a compatible pairing. [1]

Spring Security (Boot 3 / Security 6) considerations

If Spring Security is on, you typically must explicitly allow the Swagger/OpenAPI endpoints, e.g.: [2]

`@Bean`
SecurityFilterChain api(HttpSecurity http) throws Exception {
  http.authorizeHttpRequests(auth -> auth
      .requestMatchers(
          "/v3/api-docs/**",
          "/v3/api-docs.yaml",
          "/swagger-ui/**",
          "/swagger-ui.html"
      ).permitAll()
      .anyRequest().authenticated()
  );
  return http.build();
}

Correct starters for Spring Boot 3

Use the starter artifacts (not the old v1 springdoc-openapi-ui): [2][3]

  • org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13 (Spring MVC)
  • org.springdoc:springdoc-openapi-starter-webflux-ui:2.8.13 (WebFlux)

Default endpoints: /v3/api-docs and /swagger-ui/index.html (and commonly /swagger-ui.html as an entry path). [2]

Sources: [1] springdoc compatibility matrix (FAQ), [2] springdoc GitHub docs (incl. SecurityFilterChain example + endpoints), [3] library metadata noting Boot v3 support / “use v2 for Boot 3”.


🏁 Script executed:

# Find and read the build.gradle file
find . -name "build.gradle" -type f | head -1 | xargs cat | head -80

Repository: SSURENT/SSURENT-BE

Length of output: 1767


🌐 Web query:

springdoc openapi Spring Boot 4.0 compatibility version

💡 Result:

For Spring Boot 4.0.x, use springdoc-openapi 3.0.x (the 3.x line is the Spring Boot 4 compatible line). (springdoc.org)

Citations:


springdoc-openapi 버전을 3.0.x로 업그레이드하세요.

Spring Boot 4.0.1은 springdoc-openapi 2.8.13과 호환되지 않습니다. Spring Boot 4.0.x는 springdoc-openapi 3.0.x 버전을 필요로 합니다.

// 변경 전
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13'

// 변경 후
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:3.0.0'
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:3.0.0'

또한 JJWT 0.12.6의 추이 의존성인 Bouncy Castle 1.78에서 CVE-2025-8916이 발견되었습니다. Bouncy Castle을 1.79 이상으로 업그레이드하여 해결할 수 있습니다:

implementation 'org.bouncycastle:bcpkix-jdk18on:1.79'
🤖 Prompt for AI Agents
In `@build.gradle` around lines 49 - 55, Update the springdoc-openapi dependencies
to the 3.0.x line and add a Bouncy Castle override: replace the existing
springdoc coordinates (org.springdoc:springdoc-openapi-starter-webmvc-ui and
org.springdoc:springdoc-openapi-starter-webmvc-api currently at 2.8.13) with
3.0.0 (or latest 3.0.x) to be compatible with Spring Boot 4.0.x, and add an
explicit org.bouncycastle:bcpkix-jdk18on dependency at 1.79+ to override the
transitive Bouncy Castle pulled in by io.jsonwebtoken:jjwt-*
(jjwt-api/jjwt-impl/jjwt-jackson) and mitigate CVE-2025-8916.

Comment on lines +12 to +17
@ExceptionHandler(GeneralException.class)
public ResponseEntity<BaseResponse<Void>> handleGeneralException(GeneralException e) {
ErrorStatus status = (ErrorStatus) e.getStatus();
return ResponseEntity
.status(status.getHttpStatus())
.body(BaseResponse.error(status));
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

상태 캐스팅은 런타임 예외를 유발할 수 있습니다.
GeneralExceptionErrorStatus 외의 상태가 들어오면 ClassCastException이 발생합니다. 타입을 강제하지 않거나 instanceof 가드로 안전하게 처리하는 방식을 고려해 주세요.

🐛 안전 캐스팅 예시
-    public ResponseEntity<BaseResponse<Void>> handleGeneralException(GeneralException e) {
-        ErrorStatus status = (ErrorStatus) e.getStatus();
-        return ResponseEntity
-                .status(status.getHttpStatus())
-                .body(BaseResponse.error(status));
-    }
+    public ResponseEntity<BaseResponse<Void>> handleGeneralException(GeneralException e) {
+        if (!(e.getStatus() instanceof ErrorStatus status)) {
+            return ResponseEntity.internalServerError()
+                    .body(BaseResponse.error(ErrorStatus.INTERNAL_SERVER_ERROR));
+        }
+        return ResponseEntity
+                .status(status.getHttpStatus())
+                .body(BaseResponse.error(status));
+    }
🤖 Prompt for AI Agents
In `@src/main/java/ssurent/ssurentbe/common/exception/GlobalExceptionHandler.java`
around lines 12 - 17, The handler in
GlobalExceptionHandler.handleGeneralException currently casts e.getStatus() to
ErrorStatus which can throw ClassCastException at runtime; change the
implementation to avoid unsafe casting by checking e.getStatus() with instanceof
(or using a safe accessor) and handle non-ErrorStatus cases (e.g., map unknown
statuses to a default ErrorStatus or return a generic error response) before
calling BaseResponse.error; ensure you reference GeneralException.getStatus()
and ErrorStatus and produce a ResponseEntity with an appropriate HttpStatus for
fallback paths.

Comment on lines +42 to +44
} catch (GeneralException e) {
sendErrorResponse(response, (ErrorStatus) e.getStatus());
return;
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

fd -t f -e java "GeneralException" | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 132


🏁 Script executed:

rg -t java "class GeneralException" -A 30

Repository: SSURENT/SSURENT-BE

Length of output: 919


🏁 Script executed:

rg -t java "getStatus()" -B 3 -A 3 | head -100

Repository: SSURENT/SSURENT-BE

Length of output: 1774


🏁 Script executed:

rg -t java "class BaseStatus|interface BaseStatus" -A 10

Repository: SSURENT/SSURENT-BE

Length of output: 472


🏁 Script executed:

rg -t java "class ErrorStatus|interface ErrorStatus" -A 15

Repository: SSURENT/SSURENT-BE

Length of output: 44


🏁 Script executed:

rg -t java "implements BaseStatus|extends BaseStatus" | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 287


🏁 Script executed:

rg -t java "new GeneralException" -B 2 -A 2 | head -100

Repository: SSURENT/SSURENT-BE

Length of output: 5281


🏁 Script executed:

rg -t java "throw new GeneralException" -B 2 -A 2 | head -100

Repository: SSURENT/SSURENT-BE

Length of output: 4418


제네릭한 설계에서 불안전한 타입 캐스팅

GeneralException은 생성자에서 BaseStatus 타입(부모 타입)을 수용하지만, 43번 줄에서 (ErrorStatus)로 캐스팅합니다. BaseStatus 인터페이스는 ErrorStatusSuccessStatus 모두에 의해 구현되므로, 설계상 SuccessStatus가 전달될 수 있습니다. 현재 코드베이스는 ErrorStatus만 전달하지만, 이 패턴은 타입 안전성을 위반합니다.

해결 방법:

  • GeneralException의 생성자를 ErrorStatus만 허용하도록 제한하거나
  • 캐스팅 전 타입 검사를 추가하세요

참고: 동일한 불안전한 캐스팅 패턴이 GlobalExceptionHandler.java에도 있으므로 함께 수정하세요.

🤖 Prompt for AI Agents
In `@src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java`
around lines 42 - 44, JwtAuthenticationFilter currently unsafely casts
GeneralException.getStatus() to ErrorStatus in the catch block; update the
handling so the cast is safe by either (A) changing GeneralException to only
accept ErrorStatus in its constructor (and update usages) or (B) adding a
runtime check in JwtAuthenticationFilter (and similarly in
GlobalExceptionHandler) that tests if e.getStatus() instanceof ErrorStatus
before casting—if true, call sendErrorResponse(response, (ErrorStatus)
e.getStatus()); otherwise map/convert the BaseStatus to a safe ErrorStatus (or
use a default/generic error response) and log the unexpected status type. Ensure
you update both JwtAuthenticationFilter and GlobalExceptionHandler to follow the
same safe pattern.

Comment on lines +22 to +31
@Override
public UserDetails loadUserByUsername(String studentNum) throws UsernameNotFoundException {
Users user = userRepository.findByStudentNum(studentNum)
.orElseThrow(() -> new UsernameNotFoundException(ErrorStatus.USER_NOT_FOUND.getMessage()));

return new User(
user.getStudentNum(),
user.getPassword(),
Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))
);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Users 엔티티 파일 찾기
find . -name "Users.java" -o -name "User.java" | grep -E "(entity|model|domain)" | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 126


🏁 Script executed:

# Status enum 찾기
find . -name "Status.java" | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 256


🏁 Script executed:

# ErrorStatus enum에서 USER_WITHDRAWN 확인
find . -name "ErrorStatus.java" -o -name "*ErrorStatus*" | head -10

Repository: SSURENT/SSURENT-BE

Length of output: 126


🏁 Script executed:

# CustomUserDetailsService 전체 코드 확인
cat src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java

Repository: SSURENT/SSURENT-BE

Length of output: 1405


🏁 Script executed:

# 인증 필터 또는 JWT 관련 보안 설정 파일 찾기
find . -type f -name "*SecurityConfig*" -o -name "*JwtProvider*" -o -name "*AuthenticationFilter*" | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 203


🏁 Script executed:

cat src/main/java/ssurent/ssurentbe/domain/users/entity/Users.java

Repository: SSURENT/SSURENT-BE

Length of output: 1367


🏁 Script executed:

cat src/main/java/ssurent/ssurentbe/domain/users/enums/Status.java

Repository: SSURENT/SSURENT-BE

Length of output: 154


🏁 Script executed:

cat src/main/java/ssurent/ssurentbe/common/status/ErrorStatus.java

Repository: SSURENT/SSURENT-BE

Length of output: 2612


🏁 Script executed:

cat src/main/java/ssurent/ssurentbe/common/jwt/JwtAuthenticationFilter.java

Repository: SSURENT/SSURENT-BE

Length of output: 2691


🏁 Script executed:

find . -name "JwtTokenProvider.java" -o -name "*JwtTokenProvider*" | head -5

Repository: SSURENT/SSURENT-BE

Length of output: 128


🏁 Script executed:

# JwtTokenProvider의 getAuthentication 메소드 확인
rg "getAuthentication" -A 10 -B 2

Repository: SSURENT/SSURENT-BE

Length of output: 3365


탈퇴/비활성 사용자를 인증에서 차단하는 로직이 필요합니다.

JWT 인증 흐름에서 getAuthentication()loadUserByUsername() 순으로 매 요청마다 실행되므로, isDeleted 또는 status가 비활성 상태인 사용자는 이 단계에서 차단해야 합니다. 현재 코드는 존재 여부만 확인하기 때문에 탈퇴 후에도 토큰 유효기간 동안 접근이 가능합니다.

수정 예시
     public UserDetails loadUserByUsername(String studentNum) throws UsernameNotFoundException {
         Users user = userRepository.findByStudentNum(studentNum)
                 .orElseThrow(() -> new UsernameNotFoundException(ErrorStatus.USER_NOT_FOUND.getMessage()));
+
+        if (user.isDeleted() || user.getStatus() != Status.ACTIVE) {
+            throw new UsernameNotFoundException(ErrorStatus.USER_WITHDRAWN.getMessage());
+        }
 
         return new User(
                 user.getStudentNum(),
                 user.getPassword(),
                 Collections.singletonList(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))
         );
     }
🤖 Prompt for AI Agents
In
`@src/main/java/ssurent/ssurentbe/common/security/CustomUserDetailsService.java`
around lines 22 - 31, loadUserByUsername currently only checks existence and
returns a UserDetails, allowing deleted/disabled accounts to authenticate;
update CustomUserDetailsService.loadUserByUsername to check the Users entity
flags (e.g., isDeleted() and getStatus()/status enum) after fetching from
userRepository and throw UsernameNotFoundException or a custom DisabledException
when the account is deleted or inactive. Ensure the exception uses a clear
ErrorStatus (or map to ErrorStatus.USER_NOT_FOUND or a new ErrorStatus for
disabled users) so getAuthentication() will reject such tokens, and keep
returning the Spring Security User with roles only for active users.

Comment on lines +41 to +43
public ResponseEntity<BaseResponse<TokenResponse>> refresh(@RequestHeader("Authorization") String authorization) {
String refreshToken = authorization.replace("Bearer ", "");
TokenResponse data = authService.refresh(refreshToken);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Bearer 토큰 파싱이 안전하지 않습니다.

authorization.replace("Bearer ", "")는 "Bearer " 접두사가 없는 경우에도 원본 문자열을 그대로 반환합니다. 또한 Authorization 헤더가 null일 경우 NullPointerException이 발생합니다.

JwtAuthenticationFilter.resolveToken()과 동일한 패턴을 사용하여 일관성을 유지하고 안전하게 처리하세요.

🐛 수정 제안
 `@Override`
 `@PostMapping`("/refresh")
 public ResponseEntity<BaseResponse<TokenResponse>> refresh(`@RequestHeader`("Authorization") String authorization) {
-    String refreshToken = authorization.replace("Bearer ", "");
+    if (authorization == null || !authorization.startsWith("Bearer ")) {
+        throw new GeneralException(ErrorStatus.JWT_TOKEN_NOT_FOUND);
+    }
+    String refreshToken = authorization.substring(7);
     TokenResponse data = authService.refresh(refreshToken);
     SuccessStatus status = SuccessStatus.REISSUE_TOKEN_SUCCESS;
     return ResponseEntity.status(status.getHttpStatus())
             .body(BaseResponse.success(status, data));
 }
📝 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
public ResponseEntity<BaseResponse<TokenResponse>> refresh(@RequestHeader("Authorization") String authorization) {
String refreshToken = authorization.replace("Bearer ", "");
TokenResponse data = authService.refresh(refreshToken);
`@Override`
`@PostMapping`("/refresh")
public ResponseEntity<BaseResponse<TokenResponse>> refresh(`@RequestHeader`("Authorization") String authorization) {
if (authorization == null || !authorization.startsWith("Bearer ")) {
throw new GeneralException(ErrorStatus.JWT_TOKEN_NOT_FOUND);
}
String refreshToken = authorization.substring(7);
TokenResponse data = authService.refresh(refreshToken);
SuccessStatus status = SuccessStatus.REISSUE_TOKEN_SUCCESS;
return ResponseEntity.status(status.getHttpStatus())
.body(BaseResponse.success(status, data));
}
🤖 Prompt for AI Agents
In `@src/main/java/ssurent/ssurentbe/domain/users/controller/AuthController.java`
around lines 41 - 43, AuthController.refresh currently unsafely extracts the
token with authorization.replace("Bearer ", "") which can NPE or return wrong
value; change it to the same safe parsing used by
JwtAuthenticationFilter.resolveToken — i.e., check for null/empty, verify the
header startsWith("Bearer "), then extract the substring after the prefix (or
call JwtAuthenticationFilter.resolveToken(authorization) if accessible) and
handle a missing/invalid token by returning an appropriate error (or throwing
the same exception path used elsewhere) before calling
authService.refresh(refreshToken).

Comment on lines +21 to +46
@ApiResponses({
@ApiResponse(responseCode = "201", description = "회원가입 성공",
content = @Content(schema = @Schema(implementation = BaseResponse.class))),
@ApiResponse(responseCode = "409", description = "이미 가입된 학번",
content = @Content(schema = @Schema(implementation = BaseResponse.class)))
})
ResponseEntity<BaseResponse<TokenResponse>> signup(@RequestBody SignupRequest request);

@Operation(summary = "로그인", description = "학번과 비밀번호로 로그인합니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "로그인 성공",
content = @Content(schema = @Schema(implementation = BaseResponse.class))),
@ApiResponse(responseCode = "401", description = "학번 또는 비밀번호 불일치",
content = @Content(schema = @Schema(implementation = BaseResponse.class))),
@ApiResponse(responseCode = "403", description = "탈퇴한 사용자",
content = @Content(schema = @Schema(implementation = BaseResponse.class)))
})
ResponseEntity<BaseResponse<TokenResponse>> login(@RequestBody LoginRequest request);

@Operation(summary = "토큰 갱신", description = "리프레시 토큰으로 새로운 액세스 토큰을 발급받습니다.")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "토큰 재발급 성공",
content = @Content(schema = @Schema(implementation = BaseResponse.class))),
@ApiResponse(responseCode = "401", description = "유효하지 않은 토큰",
content = @Content(schema = @Schema(implementation = BaseResponse.class)))
})
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Find and examine BaseResponse class
fd -t f "BaseResponse\.java" --exec head -50 {} +

Repository: SSURENT/SSURENT-BE

Length of output: 1138


🏁 Script executed:

# Find and examine TokenResponse class
fd -t f "TokenResponse\.java" --exec head -50 {} +

Repository: SSURENT/SSURENT-BE

Length of output: 534


🏁 Script executed:

# Check the full AuthApiDocs.java file for context
cat -n src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java

Repository: SSURENT/SSURENT-BE

Length of output: 3032


🏁 Script executed:

# Check for pom.xml or build.gradle to see springdoc version
fd -t f "(pom\.xml|build\.gradle)" --exec grep -l "springdoc\|springdoc-openapi" {} +

Repository: SSURENT/SSURENT-BE

Length of output: 128


🌐 Web query:

springdoc-openapi generic wrapper BaseResponse schema documentation best practices

💡 Result:

OpenAPI 3.x doesn’t have a true “generic type” concept (e.g., BaseResponse<T> that stays generic in the generated schema/clients). In practice, tools emit concrete schemas per instantiation (e.g., BaseResponseUser, BaseResponseOrder) and you generally lose the generic-origin information. [1]

Best practices for BaseResponse<T> with springdoc-openapi

  1. Prefer concrete, per-endpoint response DTOs (or “thin wrappers”)

    • Create small types that bind T, e.g. UserResponse extends BaseResponse<UserDto>.
    • This yields stable, explicit component schemas and better client generation than trying to keep T generic. (This is also a common workaround pattern in Swagger/OpenAPI tooling.) [1][2]
  2. Document the concrete wrapper explicitly with @ApiResponse + @Schema(implementation=...)

    • Even if your controller returns BaseResponse<UserDto>, point the OpenAPI response schema at the concrete wrapper class via @ApiResponse(... content=@Content(schema=@Schema(implementation=UserResponse.class))). [3]
  3. If you must keep a single wrapper class, constrain T with oneOf/anyOf (only when the set is small)

    • Example: @Schema(anyOf={Potato.class, Tomato.class}) private T data;
    • This is workable only when T is from a finite list; otherwise your spec becomes inaccurate or unmaintainable. [2]
  4. Don’t expect springdoc to name schemas like BaseResponse«User»

    • springdoc does not preserve/display generic parameterization in $ref names the way some other tooling might; this is a known pain point discussed in springdoc issues. [4]
  5. For organization-wide “envelope” consistency, generate/register concrete schemas at startup (advanced)

    • If you want a canonical envelope (BaseResponse<T>) and deterministic per-T component schemas, use an OpenApiCustomizer (or similar customization) to detect BaseResponse<X> return types and register composed schemas/thin wrappers programmatically. [5]

Minimal pattern (recommended)

class BaseResponse<T> { public T data; public String traceId; }

class UserResponse extends BaseResponse<UserDto> {}

`@Operation`(responses = {
  `@ApiResponse`(responseCode="200",
    content=@Content(mediaType="application/json",
      schema=@Schema(implementation = UserResponse.class)))
})
`@GetMapping`("/users/{id}")
public BaseResponse<UserDto> getUser(...) { ... }

Rule of thumb: for good docs + good client SDKs, emit concrete wrapper schemas per payload type (thin wrappers), and reference those in @ApiResponse. [3][5]

Sources:
[1] Stack Overflow: OpenAPI generics lose generic-origin info / concretize schemas per instantiation. (stackoverflow.com)
[2] Stack Overflow: workaround using anyOf/allOf (or anyOf) on the generic field. (stackoverflow.com)
[3] Example usage of @ApiResponse + @Schema(implementation=...) for response typing (springdoc). (bobsantosjr.com)
[4] springdoc issue: generic type markers not represented in generated schema $ref names. (github.com)
[5] Generics-aware approach using customizers to enrich/register wrapper schemas (pattern description). (medium.com)

Citations:


🏁 Script executed:

# Check for other `@ApiResponse` patterns in the codebase
rg "@ApiResponse" --type java -B 2 -A 2 | head -100

Repository: SSURENT/SSURENT-BE

Length of output: 3592


🏁 Script executed:

# Look for any existing response wrapper classes
fd -t f ".*Response.*\.java" src/main/java/ssurent/ssurentbe/domain/users/dto/ | xargs grep -l "extends BaseResponse\|BaseResponse<" 2>/dev/null || echo "No wrapper classes found"

Repository: SSURENT/SSURENT-BE

Length of output: 86


Swagger 응답 스키마에 TokenResponse 필드가 노출되지 않습니다.

현재 @Schema(implementation = BaseResponse.class)만 지정되어 있어 springdoc-openapi가 제네릭 타입 파라미터 <TokenResponse>를 인식하지 못하므로, Swagger UI에서 accessToken, refreshToken, tokenType 필드가 표시되지 않습니다.

구체적인 응답 래퍼 클래스(예: class SignupResponse extends BaseResponse<TokenResponse>)를 도입하고 @Schema(implementation = SignupResponse.class)로 참조하는 것이 권장됩니다. 이는 signup, login, refresh 세 엔드포인트 모두에 적용됩니다.

🤖 Prompt for AI Agents
In
`@src/main/java/ssurent/ssurentbe/domain/users/controller/docs/AuthApiDocs.java`
around lines 21 - 46, Create concrete wrapper response classes that extend
BaseResponse with the generic type parameter (e.g., class SignupResponse extends
BaseResponse<TokenResponse>, LoginResponse extends BaseResponse<TokenResponse>,
RefreshResponse extends BaseResponse<TokenResponse>) and update the
`@ApiResponse/`@Content annotations on the signup, login and refresh operations to
use `@Schema`(implementation = SignupResponse.class) / LoginResponse.class /
RefreshResponse.class respectively so springdoc-openapi can surface
TokenResponse fields (accessToken, refreshToken, tokenType) in Swagger; locate
the annotations around the methods named signup, login and the token refresh
operation and replace the existing `@Schema`(implementation = BaseResponse.class)
references with the new concrete classes.

Comment on lines +3 to +8
public record SignupRequest(
String studentNum,
String name,
String phoneNum,
String password
) {
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Find and examine the SignupRequest.java file
find . -name "SignupRequest.java" -type f

Repository: SSURENT/SSURENT-BE

Length of output: 131


🏁 Script executed:

#!/bin/bash
# Search for the file path mentioned in the review
if [ -f "src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java" ]; then
  echo "File found. Examining content..."
  cat -n "src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java"
else
  echo "File not found at specified path. Searching..."
  find . -name "SignupRequest.java" 2>/dev/null
fi

Repository: SSURENT/SSURENT-BE

Length of output: 334


🏁 Script executed:

#!/bin/bash
# Search for logging patterns in the codebase to see if DTOs are actually logged
rg "log\.|logger\." -A 2 -B 2 | head -100

Repository: SSURENT/SSURENT-BE

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check if there are other DTO classes and how they handle toString()
find . -path "*/dto/*.java" -type f | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 270


🏁 Script executed:

#!/bin/bash
# Search for custom toString implementations in DTOs
rg "public String toString\(\)" --type java | grep -i dto

Repository: SSURENT/SSURENT-BE

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check LoginRequest which likely has similar sensitivity
cat -n "./src/main/java/ssurent/ssurentbe/domain/users/dto/LoginRequest.java"

Repository: SSURENT/SSURENT-BE

Length of output: 240


🏁 Script executed:

#!/bin/bash
# Search for actual usage of SignupRequest in the codebase
rg "SignupRequest" -B 2 -A 2 | head -50

Repository: SSURENT/SSURENT-BE

Length of output: 4319


🏁 Script executed:

#!/bin/bash
# Search for logging statements more broadly
rg "System\.out|log\.info|log\.debug|log\.error" --type java | head -30

Repository: SSURENT/SSURENT-BE

Length of output: 44


🏁 Script executed:

#!/bin/bash
# Check Spring context - look for controller that uses SignupRequest
rg "SignupRequest|@RestController|@Controller" --type java | head -20

Repository: SSURENT/SSURENT-BE

Length of output: 1217


보안: 비밀번호/전화번호가 toString에 그대로 노출될 수 있음

Java record의 기본 toString()은 모든 필드를 포함하여 생성됩니다. SignupRequest는 민감정보인 비밀번호와 전화번호를 담고 있어, 예외 처리 및 로깅 과정에서 이 DTO가 기록될 경우 PII가 노출될 수 있습니다. 마스킹 처리된 toString() 구현이 필요합니다.

🔒 제안 수정
 public record SignupRequest(
         String studentNum,
         String name,
         String phoneNum,
         String password
 ) {
+    `@Override`
+    public String toString() {
+        return "SignupRequest[studentNum=" + studentNum
+                + ", name=" + name
+                + ", phoneNum=***"
+                + ", password=***]";
+    }
 }
📝 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
public record SignupRequest(
String studentNum,
String name,
String phoneNum,
String password
) {
public record SignupRequest(
String studentNum,
String name,
String phoneNum,
String password
) {
`@Override`
public String toString() {
return "SignupRequest[studentNum=" + studentNum
", name=" + name
", phoneNum=***"
", password=***]";
}
}
🤖 Prompt for AI Agents
In `@src/main/java/ssurent/ssurentbe/domain/users/dto/SignupRequest.java` around
lines 3 - 8, SignupRequest currently uses the record-generated toString which
exposes sensitive fields; override SignupRequest#toString to return a safe
representation that masks phoneNum and password (e.g., show only last 2-4
digits/characters or replace with fixed ********), keep non-sensitive fields
like name/studentNum readable if desired, and ensure the method is implemented
on the record itself so any logging/exception handling uses the masked output.

Comment on lines +69 to +72
public TokenResponse refresh(String refreshToken) {
if (!jwtTokenProvider.validateToken(refreshToken)) {
throw new GeneralException(ErrorStatus.JWT_INVALID);
}
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

validateToken이 실패 시 예외를 던지므로, 조건문이 항상 true입니다.

JwtTokenProvider.validateToken()은 유효하지 않은 토큰에 대해 false를 반환하지 않고 GeneralException을 던집니다. 따라서 Line 70의 조건문 if (!jwtTokenProvider.validateToken(refreshToken))에서 false 분기는 도달할 수 없습니다(dead code).

예외가 던져지면 Line 71-72는 실행되지 않으므로, 이 조건문은 제거하거나 예외 처리로 변경하는 것이 명확합니다.

🔧 수정 제안
 public TokenResponse refresh(String refreshToken) {
-    if (!jwtTokenProvider.validateToken(refreshToken)) {
-        throw new GeneralException(ErrorStatus.JWT_INVALID);
-    }
+    jwtTokenProvider.validateToken(refreshToken); // 실패 시 GeneralException 발생

     if (!jwtTokenProvider.isRefreshToken(refreshToken)) {
         throw new GeneralException(ErrorStatus.JWT_INVALID_TYPE);
     }
📝 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
public TokenResponse refresh(String refreshToken) {
if (!jwtTokenProvider.validateToken(refreshToken)) {
throw new GeneralException(ErrorStatus.JWT_INVALID);
}
public TokenResponse refresh(String refreshToken) {
jwtTokenProvider.validateToken(refreshToken); // 실패 시 GeneralException 발생
🤖 Prompt for AI Agents
In `@src/main/java/ssurent/ssurentbe/domain/users/service/AuthService.java` around
lines 69 - 72, The if-check in AuthService.refresh around
jwtTokenProvider.validateToken(refreshToken) is dead because validateToken
throws GeneralException on invalid tokens; remove the if-block and either (a)
simply call jwtTokenProvider.validateToken(refreshToken) and let its exception
propagate, or (b) wrap the call in a try-catch that catches the thrown exception
and rethrows/translate it to ErrorStatus.JWT_INVALID for clearer control flow;
update the method to proceed to the rest of TokenResponse creation only after a
successful validateToken call.

Copy link

@fprtmjinho fprtmjinho left a comment

Choose a reason for hiding this comment

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

아직 뭐가 뭔지 몰라서 보기만했어요

public record TokenResponse(
String accessToken,
String refreshToken,
String tokenType

Choose a reason for hiding this comment

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

토큰타입을 꼭 줘야하나용?

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] : 기본적인 Auth기능 구현

3 participants