Skip to content

[FEAT] Member&Auth 구현 완료#96

Merged
SungMinju merged 43 commits intodevelopfrom
feature/auth
Feb 4, 2026
Merged

[FEAT] Member&Auth 구현 완료#96
SungMinju merged 43 commits intodevelopfrom
feature/auth

Conversation

@SungMinju
Copy link
Contributor

@SungMinju SungMinju commented Feb 3, 2026

💡 작업 개요

  • 무엇을 구현했는지 간단히 설명해주세요.
  • 회원가입/로그인/정보 수정&조회/회원탈퇴 등을 구현완료했습니다.

✅ 작업 내용

  • 기능 개발
  • 버그 수정
  • 리팩토링
  • 주석/포맷 정리
  • 기타 설정

🧪 테스트 내용

  • 테스트 방식이나 결과 요약 (예: Postman, Swagger로 테스트 완료 등)
  • 보안 강화를 위해 refreshToken을 HTTP-only 쿠키에 담아 전송하도록 구현했습니다. 쿠키의 Secure 옵션 적용으로 인해 HTTPS 환경이 필요한 테스트는 운영 서버에서 진행할 예정이며, 로컬 환경에서는 보안 유지를 위해 해당 값이 null로 반환되도록 설정하여 데이터 노출을 방지했습니다.

📝 기타 참고 사항

Summary by CodeRabbit

  • New Features

    • Full user auth: signup, login, logout, refresh-token cookie handling.
    • Profile management: view/update profile, profile image upload, account withdrawal.
    • Consent tracking (terms/privacy/marketing) and new user-related responses.
  • Security

    • Spring Security enabled with JWT-based auth, OAuth2 client support, custom auth handlers, CORS configuration.
  • Validation & Errors

    • Password-match validation and new image size error for uploads.

@coderabbitai
Copy link

coderabbitai bot commented Feb 3, 2026

📝 Walkthrough

Walkthrough

Introduces Spring Security + JWT auth, user registration/login/profile endpoints, JWT filter and providers, cookie-based refresh token handling, password validation, consent Term entity, BaseEntity package move, and several user/domain enums, DTOs, services, repositories, and error/status additions.

Changes

Cohort / File(s) Summary
Build / Dependencies
build.gradle
Enables Spring Security starter and adds JJWT and OAuth2 client dependencies.
BaseEntity move
src/main/java/com/eatsfine/eatsfine/global/common/BaseEntity.java, src/main/java/com/eatsfine/eatsfine/domain/.../entity/*
Relocated BaseEntity package and updated imports in multiple domain entities (Booking, BusinessHours, Menu, Payment, Store, StoreTable, TableLayout, TableBlock, TableImage).
Authentication Core
src/main/java/com/eatsfine/eatsfine/global/config/..., src/main/java/com/eatsfine/eatsfine/global/auth/*, src/main/java/com/eatsfine/eatsfine/global/config/jwt/*, src/main/java/com/eatsfine/eatsfine/global/config/properties/*
Adds SecurityConfig, JwtAuthenticationFilter, JwtTokenProvider, JwtProperties, Constants, UserDetailsServiceImpl, CustomAuthenticationEntryPoint, CustomAccessDeniedHandler, AuthCookieProvider, and password encoder/CORS setup.
User Domain (entity/repo/service/controller)
src/main/java/com/eatsfine/eatsfine/domain/user/entity/User.java, .../repository/UserRepository.java, .../service/UserService.java, .../service/UserServiceImpl.java, .../controller/UserController.java
Adds/extends User entity with auth fields and update methods; repository query methods; service interface and full implementation (signup, login, get/update info, logout, withdraw); controller endpoints wired to JWT and cookies.
User DTOs & Converters
src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java, .../dto/response/UserResponseDto.java, .../converter/UserConverter.java
New request/response DTOs for join/login/update/change-password and conversion helpers (including Term mapping).
Consent Term
src/main/java/com/eatsfine/eatsfine/domain/term/entity/Term.java, .../repository/TermRepository.java
Adds Term entity and repository to store user consent flags (tos, privacy, marketing) with one-to-one relation to User.
Validation
src/main/java/com/eatsfine/eatsfine/global/validator/annotation/PasswordMatch.java, .../valid/PasswordMatchValidator.java
Adds custom Bean Validation annotation and validator for matching password fields.
Errors / Status
src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java, .../domain/user/status/UserErrorStatus.java, .../domain/user/exception/UserException.java, src/main/java/com/eatsfine/eatsfine/global/apiPayload/code/status/ErrorStatus.java
Adds FILE_TOO_LARGE image error; introduces UserErrorStatus enum and UserException; sets error isSuccess flag to false in ErrorReason responses.
Misc / Config
src/main/resources/application-local.yml
Adds jwt.secret property binding placeholder.
Minor cleanup
src/main/java/com/eatsfine/eatsfine/domain/user/dto/UserRequest.java (removed), src/main/java/com/eatsfine/eatsfine/domain/user/enums/*
Removes empty DTO file; adds Role, SocialType, Grade enums (Grade empty).

Sequence Diagram(s)

sequenceDiagram
    actor User
    participant Controller as UserController
    participant Service as UserServiceImpl
    participant Repo as UserRepository
    participant Encoder as PasswordEncoder
    participant Jwt as JwtTokenProvider
    participant Cookie as AuthCookieProvider

    User->>Controller: POST /api/auth/signup (JoinDto)
    Controller->>Service: signup(joinDto)
    Service->>Repo: findByEmail(email)
    Repo-->>Service: Optional.empty / present
    alt email exists
        Service-->>Controller: throw UserException
        Controller-->>User: 409 CONFLICT
    else create user
        Service->>Encoder: encode(password)
        Encoder-->>Service: encodedPassword
        Service->>Repo: save(User)
        Repo-->>Service: saved User
        Service-->>Controller: JoinResultDto
        Controller-->>User: 200 OK
    end
Loading
sequenceDiagram
    actor User
    participant Controller as UserController
    participant Service as UserServiceImpl
    participant Repo as UserRepository
    participant Encoder as PasswordEncoder
    participant Jwt as JwtTokenProvider
    participant Cookie as AuthCookieProvider

    User->>Controller: POST /api/auth/login (LoginDto)
    Controller->>Service: login(loginDto)
    Service->>Repo: findByEmail(email)
    Repo-->>Service: Optional<User>
    Service->>Encoder: matches(password, user.password)
    alt invalid
        Service-->>Controller: throw UserException
        Controller-->>User: 400 BAD_REQUEST
    else valid
        Service->>Jwt: createAccessToken(email)
        Jwt-->>Service: accessToken
        Service->>Jwt: createRefreshToken(email)
        Jwt-->>Service: refreshToken
        Service->>Repo: save(user with refreshToken)
        Service->>Cookie: refreshTokenCookie(refreshToken)
        Cookie-->>Controller: Set-Cookie
        Controller-->>User: 200 OK + cookie
    end
Loading
sequenceDiagram
    actor Client
    participant Filter as JwtAuthenticationFilter
    participant Jwt as JwtTokenProvider
    participant UserDetails as UserDetailsServiceImpl
    participant Repo as UserRepository
    participant Security as SecurityContextHolder

    Client->>Filter: Request with Authorization header
    Filter->>Jwt: resolveToken(request)
    Jwt-->>Filter: token
    alt token present and valid
        Filter->>Jwt: validateToken(token)
        Jwt-->>Filter: true
        Filter->>Jwt: extractAuthentication(request)
        Jwt->>UserDetails: loadUserByUsername(email)
        UserDetails->>Repo: findByEmail(email)
        Repo-->>UserDetails: User
        Filter->>Security: set Authentication
        Filter-->>Client: proceed (authenticated)
    else invalid or absent
        Filter-->>Client: proceed (unauthenticated)
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

feature

Suggested reviewers

  • sonjunkyu
  • zerochani
  • twodo0

Poem

🐰 A cookie warm, a token bright,
I hop through signup in the night,
Filters guard the carrot lane,
Consent and profiles in my name,
Hooray — secure bytes take flight! 🎉

🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 12.31% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main feature: member and authentication implementation. It matches the substantial changes in signup, login, profile management, and withdrawal functionality.
Description check ✅ Passed The description follows the template with all major sections completed: overview explains member features, checklist shows progress, testing details describe security considerations and testing approach, and references issue #64.

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

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/auth

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

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: 20

🤖 Fix all issues with AI agents
In `@build.gradle`:
- Around line 35-44: Replace the hardcoded jjwt module versions in build.gradle
with the official BOM: add a platform import for io.jsonwebtoken:jjwt-bom:0.13.0
and remove versions from the jjwt dependencies (jjwt-api, jjwt-impl,
jjwt-jackson) so they are resolved by the BOM; keep jjwt-api as implementation
and jjwt-impl/jjwt-jackson as runtimeOnly but do not specify versions, and
ensure the BOM line appears before those dependency declarations.

In `@src/main/java/com/eatsfine/eatsfine/domain/term/entity/Term.java`:
- Around line 22-24: The Term.user OneToOne mapping should cascade deletes so
removing a User also deletes its Term; update the Term entity's `@OneToOne`
annotation on the user field to include cascade = CascadeType.REMOVE (or
CascadeType.ALL) and/or orphanRemoval = true so the related Term is deleted when
the User is deleted; ensure the mapping stays consistent with any inverse
mapping on User (mappedBy) if present and run tests for the user withdraw/delete
flow to verify no FK/orphan issues.
- Around line 26-33: In class Term, remove the `@Builder.Default` annotation and
the "= true" default assignment for the tosConsent and privacyConsent fields so
the entity does not silently default to consent; keep the `@Column`(name =
"tos_consent", nullable = false) and `@Column`(name = "privacy_consent", nullable
= false) annotations so persistence still enforces non-null, and rely on
callers/DTOs to provide explicit values (ensuring Term.builder() requires
explicit values rather than inheriting true).

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/controller/UserController.java`:
- Around line 119-128: The logout endpoint (UserController.logout) currently
calls userService.logout(request) but does not clear the client's HTTP-only
refresh token cookie; update the controller to include the cookie-clearing
response header by adding the cookie from a new
AuthCookieProvider.clearRefreshTokenCookie() method to the ResponseEntity (so
the controller returns ResponseEntity.ok().header(...set-cookie...).body(...)).
Add AuthCookieProvider.clearRefreshTokenCookie() that creates the same cookie
name/path/domain/HttpOnly/secure settings as your existing refresh cookie but
with maxAge(0) (or equivalent) so the browser removes it when the logout
response is received.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/converter/UserConverter.java`:
- Around line 65-73: The signup flow currently converts JoinDto to a User via
UserConverter.toUser and saves only the User, so the consent fields
(JoinDto.getTosConsent, getPrivacyConsent, getMarketingConsent) never persist;
after UserServiceImpl.signup() creates and saves the User, construct a Term
entity (using the Term class or Term.builder) populated with the three consent
booleans and associate it to the saved User (set the one-to-one link), then
persist it via the TermRepository (e.g., termRepository.save(term)); ensure the
Term is created using the saved User reference and that any transactional
boundaries in UserServiceImpl encompass both saves so the user and term are
stored atomically.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java`:
- Around line 71-90: The PasswordMatch validator currently expects methods
getPassword()/getPasswordConfirm() but ChangePasswordDto uses
newPassword/newPasswordConfirm, causing runtime reflection failures; fix by
making the PasswordMatch annotation/validator generic (add annotation attributes
like firstField/secondField or fieldNames) and update its validator to read
those attribute names and compare the corresponding getters, or alternatively
create a new annotation/validator (e.g., NewPasswordMatch) specifically for
ChangePasswordDto and apply it to ChangePasswordDto; ensure the validator
references the unique symbols PasswordMatch (or the new annotation), its
validator class, and ChangePasswordDto/newPassword/newPasswordConfirm so the
correct fields are compared.
- Around line 39-45: In UserRequestDto, replace the `@NotNull` on tosConsent and
privacyConsent with `@AssertTrue` so these fields are validated to be true
(mandatory consent) rather than merely non-null; keep the existing `@Schema`
descriptions/examples, update the validation message strings if desired, and add
the javax.validation.constraints.AssertTrue import; leave marketingConsent
validation as-is since it is optional.

In `@src/main/java/com/eatsfine/eatsfine/domain/user/entity/User.java`:
- Around line 46-47: The User entity currently persists plaintext refresh tokens
in the refreshToken field (and similarly at the other occurrence), which is
unsafe; change the entity to store a hashed value or an opaque token identifier
(rename field to refreshTokenHash or refreshTokenId) and update setters/getters
and any code paths that call User.setRefreshToken/getRefreshToken to instead
hash the raw token (using a secure hash like HMAC-SHA256 with app secret or
salted SHA-256) before persisting and to verify by hashing the presented token
and performing a constant-time compare; also update token issuance/rotation
logic to persist the hash/ID and adjust revocation/expiry checks accordingly.

In `@src/main/java/com/eatsfine/eatsfine/domain/user/enums/Grade.java`:
- Around line 1-4: The Grade enum is empty and should not remain as a
placeholder; either populate the enum Grade with the intended constants (e.g.,
add the project's membership/role levels like BASIC, SILVER, GOLD or the actual
names used elsewhere) and any necessary fields/methods, or delete the Grade enum
class and remove/update any references to it; if this file is intentionally
scaffolded for future work, add a clear TODO javadoc on the Grade type
indicating it is intentionally empty and when it will be implemented. Locate the
class by the symbol Grade in the package com.eatsfine.eatsfine.domain.user.enums
to apply the fix.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/service/UserServiceImpl.java`:
- Around line 170-175: The logout method calls user.updateRefreshToken(null) but
the change is never flushed because the entity is detached; annotate the
logout(HttpServletRequest) method in UserServiceImpl with `@Transactional` (or
mark the class) so the persistence context will track and flush the update, and
ensure the proper import of
org.springframework.transaction.annotation.Transactional; alternatively, if you
prefer not to use transactions here, call the repository save method after
getCurrentUser(request) (e.g., userRepository.save(user)) to persist the null
refresh token—refer to logout(HttpServletRequest), getCurrentUser(...), and
User.updateRefreshToken(...) to locate the code.
- Around line 34-48: The signup method in UserServiceImpl performs a write but
lacks `@Transactional`; add the transactional annotation to the signup method (or
class) to ensure proper transaction boundaries for UserServiceImpl.signup, and
ensure the save is executed within that transaction. Also inspect JoinDto
consent fields (tosConsent, privacyConsent, marketingConsent) and either map and
persist them (e.g., add fields to User or create a Term/Consent entity and
persist alongside User within the same transaction in UserConverter.toUser or
immediately after save) or remove/adjust the DTO validation if they are not
intended to be stored.
- Around line 104-127: The code uploads the new profile image via
s3Service.upload before the DB transaction commits, risking orphaned objects if
the transaction rolls back; change the flow so that after obtaining newKey from
s3Service.upload you register a TransactionSynchronization (via
TransactionSynchronizationManager.registerSynchronization) that deletes newKey
in afterCompletion when the transaction status is not COMMITTED (or
alternatively perform the actual upload inside afterCommit if MultipartFile is
still available), and keep the existing afterCommit deletion of oldKey; update
calls referencing validateProfileImage, s3Service.upload,
user.updateProfileImage,
TransactionSynchronizationManager.registerSynchronization,
afterCommit/afterCompletion and s3Service.deleteByKey accordingly so new uploads
are cleaned up on rollback.
- Around line 161-168: Add a transactional boundary and ensure S3 cleanup before
deleting the User: annotate withdraw with `@Transactional` (or run within a
transactional context), retrieve the current User via getCurrentUser(request),
call the service that removes the user's S3 profile image (e.g.,
s3Service.deleteProfileImageIfExists(user) or removeProfileImage(user)) while
the entity is still managed, then clear the refresh token
(user.updateRefreshToken(null)) and delete via userRepository.delete(user); this
ensures the delete runs in a transaction and the S3 object is removed prior to
entity deletion.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/AuthCookieProvider.java`:
- Around line 10-17: The refreshTokenCookie method in AuthCookieProvider can
emit a cookie with a null/blank value; add defensive validation at the start of
refreshTokenCookie(String refreshToken) to check
StringUtils.hasText(refreshToken) (or refreshToken == null ||
refreshToken.trim().isEmpty()) and if invalid either throw an
IllegalArgumentException (or a custom RuntimeException) with a clear message
like "refreshToken must not be null or blank", so the method returns/aborts
before calling ResponseCookie.from("refreshToken", refreshToken). Ensure the
validation is applied before any call to ResponseCookie.from.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/UserDetailsServiceImpl.java`:
- Around line 26-29: In loadUserByUsername (UserDetailsServiceImpl) don't
default a null password to ""; instead detect when user.getPassword() == null
and throw an authentication exception (e.g., UsernameNotFoundException or
BadCredentialsException) with a clear message indicating the account uses social
login, so password-based authentication is rejected for that user; update any
downstream code expecting a non-null password accordingly.

In
`@src/main/java/com/eatsfine/eatsfine/global/config/properties/JwtProperties.java`:
- Around line 8-13: Add fail-fast validation to JwtProperties by marking the
class with validation support and the secret field as required: annotate the
JwtProperties class with the validation trigger (e.g., `@Validated`) and annotate
the secret field with a non-blank constraint (e.g., `@NotBlank`) so Spring fails
on startup when jwt.secret is missing or empty; if validation dependencies are
missing, ensure javax/jakarta validation (spring-boot-starter-validation) is on
the classpath. Use the class name JwtProperties and the field secret to locate
where to add these annotations.

In `@src/main/java/com/eatsfine/eatsfine/global/config/SecurityConfig.java`:
- Line 74: The CORS config currently calls
config.setAllowedOriginPatterns(List.of("*")) while also calling
config.setAllowCredentials(true), which permits credentials from any origin; in
SecurityConfig you should restrict allowed origins to your production frontend
domains (replace the wildcard with an explicit list of origins) or remove
allowCredentials(true) if credentials are not needed; update the code paths that
call config.setAllowedOriginPatterns and config.setAllowCredentials in
SecurityConfig (and the similar occurrence around line 79) so only trusted
origins are listed (e.g., a configurable list via application properties) and
ensure allowCredentials remains true only when using explicit origins.
- Around line 77-78: The second call to config.setExposedHeaders in
SecurityConfig overwrites the first and drops "Set-Cookie"; change the code so
the exposed headers include both values in one call (i.e., ensure
config.setExposedHeaders is invoked with both "Authorization" and "Set-Cookie"
together) or merge the lists before setting so the HTTP-only refreshToken cookie
is not lost.
- Line 60: Remove the redundant/incorrect permit rule in SecurityConfig that
references requestMatchers("/auth/**", "/login", "/signup") — locate the
HttpSecurity configuration method in the SecurityConfig class (the block that
builds authorizeHttpRequests()/requestMatchers()) and delete that
requestMatchers(...) call so only the correct permitted pattern "/api/auth/**"
remains; this eliminates the dead/unused path patterns and keeps authorization
rules consistent with the controller endpoints.

In
`@src/main/java/com/eatsfine/eatsfine/global/validator/valid/PasswordMatchValidator.java`:
- Around line 8-28: PasswordMatchValidator is currently typed to
UserRequestDto.JoinDto so it never runs for ChangePasswordDto and also uses
JoinDto-specific getters (getPassword/getPasswordConfirm) that ChangePasswordDto
doesn't have; change the validator to accept Object (class
PasswordMatchValidator implements ConstraintValidator<PasswordMatch, Object>)
and update isValid(Object dto, ConstraintValidatorContext) to detect the DTO
type (instanceof UserRequestDto.JoinDto or instanceof
UserRequestDto.ChangePasswordDto), read the appropriate fields (join:
getPassword/getPasswordConfirm, change: getNewPassword/getNewPasswordConfirm or
their getters), perform null/mismatch checks, and build the constraint violation
on the correct property node ("passwordConfirm" or "newPasswordConfirm") via
context.disableDefaultConstraintViolation()/buildConstraintViolationWithTemplate(...).addPropertyNode(...).addConstraintViolation();
ensure the method returns true only when matching for the detected DTO type and
false otherwise.
🧹 Nitpick comments (18)
src/main/java/com/eatsfine/eatsfine/domain/image/status/ImageErrorStatus.java (1)

18-18: Consider using HTTP 413 for oversized uploads.

Line 18 currently uses BAD_REQUEST, but PAYLOAD_TOO_LARGE (413) better reflects an exceeded upload size and improves client-side handling.

💡 Suggested change
-    FILE_TOO_LARGE(HttpStatus.BAD_REQUEST, "IMAGE4005", "첨부하는 이미지의 크기가 너무 큽니다.")
+    FILE_TOO_LARGE(HttpStatus.PAYLOAD_TOO_LARGE, "IMAGE4005", "첨부하는 이미지의 크기가 너무 큽니다.")
src/main/java/com/eatsfine/eatsfine/global/auth/CustomAuthenticationEntryPoint.java (1)

13-16: Align 401 responses with JSON error format.

Consider returning JSON like the access-denied handler to keep clients consistent.

♻️ Suggested fix (JSON response)
     `@Override`
     public void commence(HttpServletRequest request, HttpServletResponse response,
                          AuthenticationException authException) throws IOException {
-        response.sendError(HttpServletResponse.SC_UNAUTHORIZED, "인증이 필요합니다.");
+        response.setStatus(HttpServletResponse.SC_UNAUTHORIZED);
+        response.setContentType("application/json;charset=UTF-8");
+        response.getWriter().write("{\"message\": \"인증이 필요합니다.\"}");
     }
src/main/java/com/eatsfine/eatsfine/global/auth/UserDetailsServiceImpl.java (1)

31-31: Populate authorities from the user role.
Returning an empty authority list will break role-based authorization rules.

✅ Suggested adjustment
+import org.springframework.security.core.authority.SimpleGrantedAuthority;
 ...
-        return new User(user.getEmail(), password, List.of());
+        return new User(
+                user.getEmail(),
+                password,
+                List.of(new SimpleGrantedAuthority("ROLE_" + user.getRole().name()))
+        );
src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java (2)

18-19: Align nickname length validation with DB constraints.
User.nickName is limited to 20 chars; validating here prevents persistence errors.

✅ Suggested validation
+import jakarta.validation.constraints.Size;
 ...
         `@NotBlank`(message = "이름은 필수입니다.")
+        `@Size`(max = 20, message = "이름은 20자 이내여야 합니다.")
         private String nickName;  // 이름

63-68: Add validation to UpdateDto to prevent invalid updates.
Bean Validation ignores nulls, so these annotations won’t block partial updates.

✅ Suggested validation
     `@Getter`
     `@Setter`
     public static class UpdateDto {
+        `@Email`(message = "유효한 이메일 형식이어야 합니다.")
         private String email;
+        `@Size`(max = 20, message = "이름은 20자 이내여야 합니다.")
         private String nickName;
+        `@Pattern`(regexp = "^010\\d{8}$", message = "휴대전화 번호는 010으로 시작하는 11자리 숫자여야 합니다.")
         private String phoneNumber;
     }
src/main/java/com/eatsfine/eatsfine/global/config/jwt/JwtAuthenticationFilter.java (3)

32-32: Replace System.out.println with proper logging.

Production code should use SLF4J logger instead of System.out.println. Add @Slf4j annotation and use log.debug() or log.error() accordingly.

♻️ Proposed fix
+import lombok.extern.slf4j.Slf4j;
+
 `@Component`
+@Slf4j
 `@RequiredArgsConstructor`
 public class JwtAuthenticationFilter extends OncePerRequestFilter {
-        System.out.println("요청 URI: " + uri);
+        log.debug("요청 URI: {}", uri);
             } catch (Exception e) {
-                // 예외 로그 출력
-                System.out.println("JWT 인증 오류: " + e.getMessage());
+                log.error("JWT 인증 오류: {}", e.getMessage());
             }

Also applies to: 58-58


35-41: Path bypass logic duplicates SecurityConfig.permitAll() rules.

The hardcoded paths here (/api/auth/login, /api/auth/signup, /oauth2, /login) duplicate the configuration in SecurityConfig. This creates maintenance risk when one is updated but not the other. Consider using a shared constant or relying solely on Spring Security's shouldNotFilter() override with requestMatchers.


56-59: Silently catching all exceptions may hide important authentication failures.

The broad catch (Exception e) swallows all errors and continues the filter chain without authentication. While this allows the request to proceed to the 401 handler, consider logging at warn or error level with the full stack trace for debugging purposes in non-production environments.

src/main/java/com/eatsfine/eatsfine/domain/user/service/UserService.java (1)

14-22: Consider extracting user identity before passing to service layer.

Passing HttpServletRequest to service methods couples the service layer to HTTP transport. A cleaner approach is to extract the authenticated user's email/ID in the controller (or via @AuthenticationPrincipal) and pass it to the service. This improves testability and separation of concerns.

♻️ Example signature change
-    UserResponseDto.UserInfoDto getMemberInfo(HttpServletRequest request);
+    UserResponseDto.UserInfoDto getMemberInfo(String email);

-    String updateMemberInfo(UserRequestDto.UpdateDto updateDto, MultipartFile profileImage, HttpServletRequest request);
+    String updateMemberInfo(UserRequestDto.UpdateDto updateDto, MultipartFile profileImage, String email);

-    void withdraw(HttpServletRequest request);
+    void withdraw(String email);

-    void logout(HttpServletRequest request);
+    void logout(String email);
src/main/java/com/eatsfine/eatsfine/global/config/jwt/JwtTokenProvider.java (3)

36-37: Token validity durations should be externalized to configuration.

Hardcoding accessTokenValidity and refreshTokenValidity makes it difficult to adjust token lifetimes across environments without code changes. Consider moving these to JwtProperties or application.yml.


7-7: Unused import ErrorStatus.

The import com.eatsfine.eatsfine.global.apiPayload.code.status.ErrorStatus is not used in this file.

♻️ Proposed fix
-import com.eatsfine.eatsfine.global.apiPayload.code.status.ErrorStatus;

73-84: getAuthentication creates a principal with empty authorities.

This method creates a User with Collections.emptyList() for authorities. While JwtAuthenticationFilter loads actual UserDetails via userDetailsService.loadUserByUsername(), this getAuthentication method is still public and could be called directly, returning an auth token with no roles. Consider adding a note or deprecating if not intended for direct use.

src/main/java/com/eatsfine/eatsfine/domain/user/controller/UserController.java (3)

36-36: Unused dependency jwtTokenProvider.

JwtTokenProvider is injected but never used in this controller. Consider removing it to keep dependencies clean.

♻️ Proposed fix
 public class UserController {
     private final UserService userService;
-    private final JwtTokenProvider jwtTokenProvider;
     private final AuthCookieProvider authCookieProvider;

41-43: Inconsistent response wrapper: signup returns raw DTO while others use ApiResponse.

The signup endpoint returns ResponseEntity<JoinResultDto> directly, whereas login, getMyInfo, and other endpoints wrap responses in ApiResponse. Consider wrapping for consistency.

♻️ Proposed fix
-    public ResponseEntity<UserResponseDto.JoinResultDto> signup(`@RequestBody` `@Valid` UserRequestDto.JoinDto joinDto) {
+    public ResponseEntity<ApiResponse<UserResponseDto.JoinResultDto>> signup(`@RequestBody` `@Valid` UserRequestDto.JoinDto joinDto) {
         UserResponseDto.JoinResultDto result = userService.signup(joinDto);
-        return ResponseEntity.ok(result);
+        return ResponseEntity.ok(ApiResponse.onSuccess(result));
     }

114-114: Avoid wildcard return type ResponseEntity<?>.

Using ResponseEntity<?> loses type information. Use a concrete type like ResponseEntity<ApiResponse<String>> for consistency with other endpoints.

♻️ Proposed fix
-    public ResponseEntity<?> withdraw(HttpServletRequest request) {
+    public ResponseEntity<ApiResponse<String>> withdraw(HttpServletRequest request) {
src/main/java/com/eatsfine/eatsfine/domain/user/dto/response/UserResponseDto.java (1)

41-49: @Setter on response DTO is unusual.

Response DTOs are typically immutable. UpdateResponseDto has @Setter which could allow unintended mutation. If setters aren't needed, consider removing @Setter and relying solely on the builder pattern.

♻️ Proposed fix
     `@Getter`
-    `@Setter`
     `@Builder`
+    `@AllArgsConstructor`
     public static class UpdateResponseDto{
src/main/java/com/eatsfine/eatsfine/domain/user/service/UserServiceImpl.java (2)

76-81: Consider @Transactional(readOnly = true) for read-only operation.

This method only reads data. Using readOnly = true enables Hibernate optimizations (skips dirty checking) and allows routing to read replicas if configured.

Proposed fix
 `@Override`
-@Transactional
+@Transactional(readOnly = true)
 public UserResponseDto.UserInfoDto getMemberInfo(HttpServletRequest request) {

134-136: Redundant save() and flush() calls.

Within a @Transactional method, the entity is already managed. JPA dirty checking will automatically persist changes on commit. These explicit calls are unnecessary.

Proposed fix
-        userRepository.save(user);
-        userRepository.flush();
-        
         log.info("[Service] Updated userId={}, nickname={}, phone={}, profileKey={}",

Comment on lines 35 to +44
//security
//implementation 'org.springframework.boot:spring-boot-starter-security'
implementation 'org.springframework.boot:spring-boot-starter-security'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5'

// oauth
implementation 'org.springframework.boot:spring-boot-starter-oauth2-client'
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

🌐 Web query:

io.jsonwebtoken jjwt latest version 2025 2026

💡 Result:

As of February 3, 2026, the latest JJWT (io.jsonwebtoken) release is 0.13.0. [1][2]

  • Latest artifacts (all at 0.13.0): jjwt-api, jjwt-impl, jjwt-jackson, jjwt-gson, etc. [2]
  • Latest BOM: io.jsonwebtoken:jjwt-bom:0.13.0 (Aug 20, 2025). [3]

Gradle (recommended via BOM):

dependencies {
  implementation platform("io.jsonwebtoken:jjwt-bom:0.13.0")
  implementation "io.jsonwebtoken:jjwt-api"
  runtimeOnly  "io.jsonwebtoken:jjwt-impl"
  runtimeOnly  "io.jsonwebtoken:jjwt-jackson" // or jjwt-gson
}

Maven:

<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>io.jsonwebtoken</groupId>
      <artifactId>jjwt-bom</artifactId>
      <version>0.13.0</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

Sources

[1] GitHub Releases – jwtk/jjwt (shows latest release 0.13.0)
[2] javadoc.io – io.jsonwebtoken artifacts (shows latest version 0.13.0)
[3] mvnrepository – io.jsonwebtoken:jjwt-bom (shows latest 0.13.0, dated Aug 20, 2025)


🌐 Web query:

CVE io.jsonwebtoken jjwt 0.11.5 security vulnerabilities

💡 Result:

For io.jsonwebtoken (jjwt) 0.11.5, the main publicly tracked issue you’ll see in scanners is:

  • CVE-2024-31033 (aka GHSA-r65j-6h5f-4f92)“ignored characters” in key handling may cause users to overestimate signing-key strength (reported as affecting JJWT “through 0.12.5,” i.e., it includes 0.11.5). Status: disputed by the JJWT vendor in the NVD record. [1]
    • GitHub’s advisory entry for this was withdrawn as disputed, but OSV still lists 0.11.5 among affected versions in the withdrawn GHSA record, which is why many tools still flag it. [2]
    • The JJWT project discusses this CVE report (and dispute context) in issue #930. [3]
    • Some advisory databases mark this as a false positive for io.jsonwebtoken:jjwt-impl. [4]

Are there other CVEs for jjwt 0.11.5?
From the sources above, CVE-2024-31033 is the one commonly associated with jjwt 0.11.5; other “JWT CVEs” you might encounter are typically for different libraries/products with similar names.

Practical guidance

  • If you’re being blocked purely by policy/scanner findings: document that CVE-2024-31033 is disputed/withdrawn and reference the vendor discussion. [1][2][3]
  • If you still want to reduce risk/noise: consider upgrading—Snyk currently reports jjwt-impl latest as 0.13.0 and “latest non vulnerable version” as 0.13.0. [5]

Sources:
[1] NVD – CVE-2024-31033 https://nvd.nist.gov/vuln/detail/CVE-2024-31033
[2] OSV – GHSA-r65j-6h5f-4f92 (withdrawn) https://osv.dev/vulnerability/GHSA-r65j-6h5f-4f92
[3] jwtk/jjwt Issue #930 jwtk/jjwt#930
[4] GitLab Advisory DB (marked false positive) https://advisories.gitlab.com/pkg/maven/io.jsonwebtoken/jjwt-impl/CVE-2024-31033/
[5] Snyk package page for jjwt-impl@0.11.5 https://security.snyk.io/package/maven/io.jsonwebtoken%3Ajjwt-impl/0.11.5


🌐 Web query:

jjwt library maintenance status current version

💡 Result:

  • Current version (latest release): 0.13.0, released Aug 20, 2025. [1][2][3]
  • Maintenance status: The project is not archived and is described by maintainers as “supported and maintained by a community of contributors.” [4] The latest release notes also indicate ongoing development is intended (future work planned for 0.14.0, with 0.13.x reserved for emergency fixes). [1]

Sources: [1] GitHub releases, [2] Javadoc.io latest versions, [3] MVNRepository release info, [4] GitHub README.


Upgrade JJWT to 0.13.0 and use the official Bill of Materials approach.

Version 0.11.5 is outdated (0.13.0 released Aug 20, 2025) and has a CVE entry (CVE-2024-31033, though disputed by the vendor). Use the official BOM pattern for proper version alignment across JJWT modules:

Recommended: use jjwt-bom for version management
     implementation 'org.springframework.boot:spring-boot-starter-security'
 
+    // JJWT (via BOM)
+    implementation platform('io.jsonwebtoken:jjwt-bom:0.13.0')
     implementation 'io.jsonwebtoken:jjwt-api'
     runtimeOnly 'io.jsonwebtoken:jjwt-impl'
     runtimeOnly 'io.jsonwebtoken:jjwt-jackson'
-
-    // oauth
🤖 Prompt for AI Agents
In `@build.gradle` around lines 35 - 44, Replace the hardcoded jjwt module
versions in build.gradle with the official BOM: add a platform import for
io.jsonwebtoken:jjwt-bom:0.13.0 and remove versions from the jjwt dependencies
(jjwt-api, jjwt-impl, jjwt-jackson) so they are resolved by the BOM; keep
jjwt-api as implementation and jjwt-impl/jjwt-jackson as runtimeOnly but do not
specify versions, and ensure the BOM line appears before those dependency
declarations.

Comment on lines 22 to 24
@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false, unique = true)
private User user;
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
# First, locate the Term entity and examine its current configuration
find src -name "Term.java" -type f | head -5

Repository: Eatsfine/BE

Length of output: 119


🏁 Script executed:

#!/bin/bash
# Search for User deletion logic (회원탈퇴)
rg -n "delete|withdraw|탈퇴" src/main/java --type java -B 2 -A 5 | head -100

Repository: Eatsfine/BE

Length of output: 10362


🏁 Script executed:

#!/bin/bash
# Look for repository interfaces and their delete methods
rg -n "TermRepository|UserRepository" src/main/java --type java

Repository: Eatsfine/BE

Length of output: 1207


🏁 Script executed:

#!/bin/bash
# Search for cascade and orphanRemoval configurations in Term entity and related entities
rg -n "cascade|orphanRemoval|CascadeType" src/main/java --type java

Repository: Eatsfine/BE

Length of output: 1192


🏁 Script executed:

#!/bin/bash
# Get the Term entity definition
cat -n src/main/java/com/eatsfine/eatsfine/domain/term/entity/Term.java

Repository: Eatsfine/BE

Length of output: 1353


🏁 Script executed:

#!/bin/bash
# Get the User entity definition to check if it has a relationship with Term
cat -n src/main/java/com/eatsfine/eatsfine/domain/user/entity/User.java

Repository: Eatsfine/BE

Length of output: 2173


🏁 Script executed:

#!/bin/bash
# Verify if there are any other places where Term deletion is handled
rg -n "termRepository\.delete|Term.*delete" src/main/java --type java

Repository: Eatsfine/BE

Length of output: 37


Add cascade deletion to Term.user relationship.
When a user withdraws (deletes account), the related Term record is not deleted, causing FK constraint violations or orphaning data. The withdraw flow directly deletes the User without handling the Term record.

Recommended fix
-        `@OneToOne`(fetch = FetchType.LAZY)
+        `@OneToOne`(fetch = FetchType.LAZY, cascade = CascadeType.REMOVE, orphanRemoval = true)
         `@JoinColumn`(name = "user_id", nullable = false, unique = true)
         private User user;
🤖 Prompt for AI Agents
In `@src/main/java/com/eatsfine/eatsfine/domain/term/entity/Term.java` around
lines 22 - 24, The Term.user OneToOne mapping should cascade deletes so removing
a User also deletes its Term; update the Term entity's `@OneToOne` annotation on
the user field to include cascade = CascadeType.REMOVE (or CascadeType.ALL)
and/or orphanRemoval = true so the related Term is deleted when the User is
deleted; ensure the mapping stays consistent with any inverse mapping on User
(mappedBy) if present and run tests for the user withdraw/delete flow to verify
no FK/orphan issues.

"/swagger-resources/**"
).permitAll()

.requestMatchers("/auth/**", "/login", "/signup").permitAll()
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:

# First, let's find and read the SecurityConfig.java file to understand the context around line 60
find . -name "SecurityConfig.java" -type f | head -5

Repository: Eatsfine/BE

Length of output: 126


🏁 Script executed:

# Search for controller endpoints and their path mappings
rg -n '@(GetMapping|PostMapping|PutMapping|DeleteMapping|PatchMapping|RequestMapping)' --type=java -A 2 | grep -B 2 -E '(auth|login|signup)' | head -50

Repository: Eatsfine/BE

Length of output: 1498


🏁 Script executed:

# Also search for any controller class definitions to understand the base paths
rg -n '@RestController|@Controller' --type=java -A 3 | grep -E '(RequestMapping|auth|login|signup)' | head -50

Repository: Eatsfine/BE

Length of output: 1000


🏁 Script executed:

# Read the SecurityConfig.java file to see the context around line 60
cat -n ./src/main/java/com/eatsfine/eatsfine/global/config/SecurityConfig.java | sed -n '50,75p'

Repository: Eatsfine/BE

Length of output: 1336


🏁 Script executed:

# Get the full content of SecurityConfig to understand the entire config
wc -l ./src/main/java/com/eatsfine/eatsfine/global/config/SecurityConfig.java

Repository: Eatsfine/BE

Length of output: 129


Remove unused path patterns that don't match actual controller endpoints.

Line 60 permits /auth/**, /login, and /signup, but all actual authentication controller endpoints use the /api/auth/** prefix (e.g., /api/auth/signup, /api/auth/login). The /api/auth/** pattern is already permitted on line 52, making line 60 redundant and dead code. Remove it to reduce confusion.

🤖 Prompt for AI Agents
In `@src/main/java/com/eatsfine/eatsfine/global/config/SecurityConfig.java` at
line 60, Remove the redundant/incorrect permit rule in SecurityConfig that
references requestMatchers("/auth/**", "/login", "/signup") — locate the
HttpSecurity configuration method in the SecurityConfig class (the block that
builds authorizeHttpRequests()/requestMatchers()) and delete that
requestMatchers(...) call so only the correct permitted pattern "/api/auth/**"
remains; this eliminates the dead/unused path patterns and keeps authorization
rules consistent with the controller endpoints.

@Bean
public CorsConfigurationSource corsConfigurationSource() { // cors 설정
CorsConfiguration config = new CorsConfiguration();
config.setAllowedOriginPatterns(List.of("*")); // 운영 환경에서는 정확한 도메인만 명시
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion | 🟠 Major

Security: Wildcard origin pattern with allowCredentials(true) is risky for production.

setAllowedOriginPatterns(List.of("*")) combined with setAllowCredentials(true) allows any origin to send credentials. Before deploying to production, restrict origins to your actual frontend domains to prevent CSRF-like attacks.

Also applies to: 79-79

🤖 Prompt for AI Agents
In `@src/main/java/com/eatsfine/eatsfine/global/config/SecurityConfig.java` at
line 74, The CORS config currently calls
config.setAllowedOriginPatterns(List.of("*")) while also calling
config.setAllowCredentials(true), which permits credentials from any origin; in
SecurityConfig you should restrict allowed origins to your production frontend
domains (replace the wildcard with an explicit list of origins) or remove
allowCredentials(true) if credentials are not needed; update the code paths that
call config.setAllowedOriginPatterns and config.setAllowCredentials in
SecurityConfig (and the similar occurrence around line 79) so only trusted
origins are listed (e.g., a configurable list via application properties) and
ensure allowCredentials remains true only when using explicit origins.

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: 3

🤖 Fix all issues with AI agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/user/controller/UserController.java`:
- Around line 45-65: The login controller is not triggering bean validation
because the `@Valid` annotation is missing on the request body parameter; update
the login(...) method signature in UserController to annotate the
UserRequestDto.LoginDto parameter with `@Valid` (i.e., change the parameter from
`@RequestBody` UserRequestDto.LoginDto loginDto to `@RequestBody` `@Valid`
UserRequestDto.LoginDto loginDto) and ensure the proper
javax/jakarta.validation.Valid import is added so the `@NotBlank/`@Email
constraints on LoginDto are enforced.

In
`@src/main/java/com/eatsfine/eatsfine/domain/user/service/UserServiceImpl.java`:
- Around line 89-107: updateMemberInfo currently assumes updateDto is non-null
and calls updateDto.getNickName()/getPhoneNumber(), causing an NPE when
updateDto is null (e.g., called from updateProfileImage). Fix by null-checking
updateDto at the start of updateMemberInfo (or treat it as optional): if
updateDto == null skip nickname/phone update logic (or create a safe local
reference), then proceed with profileImage handling and transaction logic;
update references to UserRequestDto.UpdateDto and calls like
updateDto.getNickName() and updateDto.getPhoneNumber() in the method to be
guarded by this null check so no dereference occurs when updateDto is null.

In `@src/main/java/com/eatsfine/eatsfine/global/auth/UserDetailsServiceImpl.java`:
- Around line 23-24: Replace the detailed exception message that includes the
email in UserDetailsServiceImpl where userRepository.findByEmail(email) throws
UsernameNotFoundException; throw a generic message (e.g., "User not found" or
"Invalid credentials") instead of including the email, and if you need the email
for debugging record it via the class logger (e.g., logger.debug or logger.info)
rather than exposing it in the exception message.
🧹 Nitpick comments (3)
src/main/java/com/eatsfine/eatsfine/domain/user/controller/UserController.java (1)

34-36: Unused dependency: jwtTokenProvider.

JwtTokenProvider is injected but never used in this controller. Token validation is handled within UserServiceImpl.getCurrentUser(). Consider removing this unused field.

♻️ Proposed fix
 public class UserController {
     private final UserService userService;
-    private final JwtTokenProvider jwtTokenProvider;
     private final AuthCookieProvider authCookieProvider;
src/main/java/com/eatsfine/eatsfine/domain/user/dto/request/UserRequestDto.java (1)

60-66: Consider adding validation to UpdateDto.email.

The email field in UpdateDto has no validation constraints. If a user attempts to update their email to an invalid format, it will pass through without validation. If email updates are supported, consider adding @Email validation.

♻️ Proposed fix (if email updates are allowed)
 `@Getter`
 `@Setter`
 public static class UpdateDto {
+    `@Email`(message = "유효한 이메일 형식이어야 합니다.")
     private String email;
     private String nickName;
     private String phoneNumber;
 }
src/main/java/com/eatsfine/eatsfine/domain/user/service/UserServiceImpl.java (1)

82-87: Consider readOnly = true for read-only transaction.

getMemberInfo only reads data. Using @Transactional(readOnly = true) enables potential optimizations (e.g., no dirty checking, replica routing).

♻️ Proposed fix
 `@Override`
-@Transactional
+@Transactional(readOnly = true)
 public UserResponseDto.UserInfoDto getMemberInfo(HttpServletRequest request) {

Comment on lines +45 to +65
@PostMapping("/api/auth/login")
@Operation(summary = "로그인 API", description = "사용자 로그인을 처리하는 API입니다.")
public ResponseEntity<ApiResponse<UserResponseDto.LoginResponseDto>> login(@RequestBody UserRequestDto.LoginDto loginDto) {
UserResponseDto.LoginResponseDto loginResult = userService.login(loginDto);

if (loginResult.getRefreshToken() == null || loginResult.getRefreshToken().isBlank()) {
throw new UserException(UserErrorStatus.REFRESH_TOKEN_NOT_ISSUED);
}

ResponseCookie refreshCookie = authCookieProvider.refreshTokenCookie(loginResult.getRefreshToken());

UserResponseDto.LoginResponseDto body = UserResponseDto.LoginResponseDto.builder()
.id(loginResult.getId())
.accessToken(loginResult.getAccessToken())
.refreshToken(null)
.build();

return ResponseEntity.ok()
.header(HttpHeaders.SET_COOKIE, refreshCookie.toString())
.body(ApiResponse.onSuccess(body));
}
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

Missing @Valid annotation on login request body.

The LoginDto has @NotBlank and @Email validation constraints, but @Valid is not applied to the parameter. Validation will not be triggered.

🛡️ Proposed fix
-    public ResponseEntity<ApiResponse<UserResponseDto.LoginResponseDto>> login(`@RequestBody` UserRequestDto.LoginDto loginDto) {
+    public ResponseEntity<ApiResponse<UserResponseDto.LoginResponseDto>> login(`@RequestBody` `@Valid` UserRequestDto.LoginDto loginDto) {
🤖 Prompt for AI Agents
In
`@src/main/java/com/eatsfine/eatsfine/domain/user/controller/UserController.java`
around lines 45 - 65, The login controller is not triggering bean validation
because the `@Valid` annotation is missing on the request body parameter; update
the login(...) method signature in UserController to annotate the
UserRequestDto.LoginDto parameter with `@Valid` (i.e., change the parameter from
`@RequestBody` UserRequestDto.LoginDto loginDto to `@RequestBody` `@Valid`
UserRequestDto.LoginDto loginDto) and ensure the proper
javax/jakarta.validation.Valid import is added so the `@NotBlank/`@Email
constraints on LoginDto are enforced.

Comment on lines +89 to +107
@Override
@Transactional
public String updateMemberInfo(UserRequestDto.UpdateDto updateDto,
MultipartFile profileImage,
HttpServletRequest request) {

User user = getCurrentUser(request);

boolean changed = false;

//닉네임/전화번호 부분 수정
if (updateDto.getNickName() != null && !updateDto.getNickName().isBlank()) {
user.updateNickname(updateDto.getNickName());
changed = true;
}
if (updateDto.getPhoneNumber() != null && !updateDto.getPhoneNumber().isBlank()) {
user.updatePhoneNumber(updateDto.getPhoneNumber());
changed = true;
}
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

Null updateDto will cause NullPointerException.

When called from updateProfileImage() controller endpoint, updateDto is null. Accessing updateDto.getNickName() at line 100 will throw NPE.

🐛 Proposed fix
     public String updateMemberInfo(UserRequestDto.UpdateDto updateDto,
                                    MultipartFile profileImage,
                                    HttpServletRequest request) {

         User user = getCurrentUser(request);

         boolean changed = false;

         //닉네임/전화번호 부분 수정
-        if (updateDto.getNickName() != null && !updateDto.getNickName().isBlank()) {
-            user.updateNickname(updateDto.getNickName());
-            changed = true;
-        }
-        if (updateDto.getPhoneNumber() != null && !updateDto.getPhoneNumber().isBlank()) {
-            user.updatePhoneNumber(updateDto.getPhoneNumber());
-            changed = true;
+        if (updateDto != null) {
+            if (updateDto.getNickName() != null && !updateDto.getNickName().isBlank()) {
+                user.updateNickname(updateDto.getNickName());
+                changed = true;
+            }
+            if (updateDto.getPhoneNumber() != null && !updateDto.getPhoneNumber().isBlank()) {
+                user.updatePhoneNumber(updateDto.getPhoneNumber());
+                changed = true;
+            }
         }
🤖 Prompt for AI Agents
In `@src/main/java/com/eatsfine/eatsfine/domain/user/service/UserServiceImpl.java`
around lines 89 - 107, updateMemberInfo currently assumes updateDto is non-null
and calls updateDto.getNickName()/getPhoneNumber(), causing an NPE when
updateDto is null (e.g., called from updateProfileImage). Fix by null-checking
updateDto at the start of updateMemberInfo (or treat it as optional): if
updateDto == null skip nickname/phone update logic (or create a safe local
reference), then proceed with profileImage handling and transaction logic;
update references to UserRequestDto.UpdateDto and calls like
updateDto.getNickName() and updateDto.getPhoneNumber() in the method to be
guarded by this null check so no dereference occurs when updateDto is null.

Comment on lines +23 to +24
com.eatsfine.eatsfine.domain.user.entity.User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다: " + email));
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

Avoid user enumeration via detailed not-found messages.

Including the email in the exception message can leak account existence if the message is exposed. Prefer a generic message and log the email internally if needed.

✅ Suggested change
-        com.eatsfine.eatsfine.domain.user.entity.User user = userRepository.findByEmail(email)
-                .orElseThrow(() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다: " + email));
+        com.eatsfine.eatsfine.domain.user.entity.User user = userRepository.findByEmail(email)
+                .orElseThrow(() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다."));
📝 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
com.eatsfine.eatsfine.domain.user.entity.User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다: " + email));
com.eatsfine.eatsfine.domain.user.entity.User user = userRepository.findByEmail(email)
.orElseThrow(() -> new UsernameNotFoundException("해당 사용자를 찾을 수 없습니다."));
🤖 Prompt for AI Agents
In `@src/main/java/com/eatsfine/eatsfine/global/auth/UserDetailsServiceImpl.java`
around lines 23 - 24, Replace the detailed exception message that includes the
email in UserDetailsServiceImpl where userRepository.findByEmail(email) throws
UsernameNotFoundException; throw a generic message (e.g., "User not found" or
"Invalid credentials") instead of including the email, and if you need the email
for debugging record it via the class logger (e.g., logger.debug or logger.info)
rather than exposing it in the exception message.

@SungMinju SungMinju merged commit 4e709a7 into develop Feb 4, 2026
1 of 2 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.

3 participants