Skip to content

Feat/#208 social login#241

Merged
oculo0204 merged 6 commits intodevelopfrom
feat/#208-social-login
Feb 24, 2026
Merged

Feat/#208 social login#241
oculo0204 merged 6 commits intodevelopfrom
feat/#208-social-login

Conversation

@oculo0204
Copy link
Collaborator

@oculo0204 oculo0204 commented Feb 24, 2026

🔗 관련 이슈

#240 #208


📌 작업 내용

1️⃣ Non-Functional Requirement

  • @ApiV1 / @ApiV2 커스텀 어노테이션 기반 API 버전 관리 체계 도입
  • WebConfig.addPathPrefix()로 컨트롤러 클래스 단위 버전 prefix 자동 적용
  • validation/annotation/ 패키지에 ApiV1, ApiV2 어노테이션 추가
  • web/controller/user/ 하위 패키지로 컨트롤러 구조 정리
  • ErrorStatus 소셜 로그인 관련 에러 코드 prefix USERSOAUTH 로 정리

2️⃣ Functional Requirement

  • 딥링크 URL 토큰 직접 노출 문제 해결 — 단기 인증 코드 교환 방식 도입
    • 기존: ?accessToken=eyJ...&refreshToken=eyJ... URL에 직접 노출
    • 변경: ?code=a1b2c3d4... 30초 TTL 1회용 코드만 URL에 노출
    • POST /api/v1/auth/token/exchange?code= API로 실제 토큰 교환
  • OAuth2SuccessHandler — Redis에 auth:code:{uuid} 키로 토큰 임시 저장 (TTL 30초)
  • AuthCodeService 신규 추가 — 코드 검증, 1회용 삭제, 토큰 반환 로직
  • AuthCodeController 신규 추가 — POST /api/v1/auth/token/exchange
  • hmac() UTF-8 charset 고정 — token.getBytes()token.getBytes(StandardCharsets.UTF_8)
  • SecurityConfig permitAll 경로 /api/users/**/api/v1/users/**, /api/v1/auth/** 추가
  • UserController @RequestMapping("api/users")@RequestMapping("/users") + @ApiV1 적용

🧪 테스트 결과

  • POST /api/v1/auth/token/exchange?code={code} — 정상 코드 → accessToken + refreshToken 반환 확인
  • 만료된 코드(30초 경과) → OAUTH4007 에러 반환 확인
  • 동일 코드 2회 사용 → 1회 이후 OAUTH4007 에러 반환 확인
  • 카카오 소셜 로그인 → 딥링크 URL에 code만 포함되는지 확인
  • POST /api/v1/users/login, GET /api/v1/users/me 등 기존 API 정상 동작 확인

📸 스크린샷 (선택)

Swagger UI에서 /api/v1/auth/token/exchange 엔드포인트 확인 후 첨부


📎 참고 사항

  • AUTH_CODE_TTL_SECONDS = 30L 현재 하드코딩 → 추후 application.propertiesauth.code.ttl-seconds로 이전해야 할까요?
  • 딥링크 보안 강화를 위해 Android 측 App Links (android:autoVerify="true") 설정 별도 협의 필요
  • validation\annotation\ApiV1.java 가져다가 version업데이트해야 해요 현재 user, oauth만 되어 있음

Summary by CodeRabbit

  • 새로운 기능

    • API 버전(ApiV1/ApiV2) 적용으로 버전별 엔드포인트 지원
    • 일회용 인증 코드 교환용 엔드포인트(/auth/token/exchange) 추가 및 Redis 기반 코드 발급/교환 도입
  • 개선 사항

    • 소셜 로그인 오류 코드 체계 재정비(OAUTH 접두사, 신규 INVALID_AUTH_CODE)
    • OAuth2 로그인 흐름 및 세션 정책을 무상태(Stateless)로 조정하고 인증요청 영구화 개선

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 24, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9b228bd and 79889f2.

📒 Files selected for processing (4)
  • src/main/java/com/umc/linkyou/config/security/SecurityConfig.java
  • src/main/java/com/umc/linkyou/oauth/OAuth2SuccessHandler.java
  • src/main/java/com/umc/linkyou/oauth/RedisAuthorizationRequestRepository.java
  • src/main/java/com/umc/linkyou/service/users/AuthCodeService.java

Walkthrough

OAuth2 인증 성공 흐름을 Redis 기반 일회용 코드로 전환하고, API 버전 마커(@ApiV1/@apiv2)와 경로 자동 프리픽스를 도입했습니다. 소셜 로그인 관련 ErrorStatus 네임스페이스를 USERS→OAUTH로 변경하고, 인증 코드 교환 서비스·컨트롤러를 추가했습니다.

Changes

Cohort / File(s) Summary
오류 코드(에러 상태)
src/main/java/com/umc/linkyou/apiPayload/code/status/ErrorStatus.java
소셜 로그인 관련 에러 코드 네임스페이스를 USERS...OAUTH...로 변경하고 OAUTH4007(INVALID_AUTH_CODE) 추가. enum 선언부에 쉼표/세미콜론 관련 문법 변경 점검 필요.
OAuth2 성공 처리 및 Redis 임시저장
src/main/java/com/umc/linkyou/oauth/OAuth2SuccessHandler.java
ACTIVE 사용자에 대해 access::refresh 포맷을 Redis에 TTL(30s)으로 저장하고, 리다이렉트에는 일회용 코드(UUID)만 전달하도록 변경. StringRedisTemplate 주입 추가.
교환 로직(서비스 및 컨트롤러)
src/main/java/com/umc/linkyou/service/users/AuthCodeService.java, src/main/java/com/umc/linkyou/web/controller/user/AuthCodeController.java
AuthCodeService.exchangeCode(code)로 Redis에서 토큰을 조회·삭제 후 TokenPair 반환. POST /auth/token/exchange 엔드포인트 추가.
Redis 기반 OAuth 인가 요청 저장소
src/main/java/com/umc/linkyou/oauth/RedisAuthorizationRequestRepository.java
OAuth2AuthorizationRequest를 Redis에 직렬화하여 상태(state) 기반 키로 저장/조회/삭제(TTL 10분)하는 AuthorizationRequestRepository 구현체 추가.
API 버전 마커 및 자동 프리픽스
src/main/java/com/umc/linkyou/validation/annotation/ApiV1.java, src/main/java/com/umc/linkyou/validation/annotation/ApiV2.java, src/main/java/com/umc/linkyou/config/WebConfig.java
@ApiV1/@ApiV2 어노테이션 추가 및 WebMvcConfigurer로 해당 마커가 붙은 컨트롤러들에 /api/v1//api/v2 접두사 자동 적용.
보안 설정 변경
src/main/java/com/umc/linkyou/config/security/SecurityConfig.java
공개 경로 패턴을 /api/v1/users/**로 조정, 세션 정책을 STATELESS로 변경, OAuth2 인가 요청 저장소로 RedisAuthorizationRequestRepository 사용하도록 설정.
컨트롤러 위치·어노테이션 변경
src/main/java/com/umc/linkyou/web/controller/user/UserController.java
UserController를 web.controller.user 패키지로 이동하고 @ApiV1 적용, RequestMapping을 "api/users""/users"로 조정.

Sequence Diagram(s)

sequenceDiagram
    participant Client as Client
    participant OAuth2 as OAuth2SuccessHandler
    participant Redis as Redis
    participant AuthCtrl as AuthCodeController
    participant AuthSvc as AuthCodeService

    Client->>OAuth2: OAuth 인증 완료(redirect)
    OAuth2->>Redis: UUID 생성 및 "access::refresh" 저장<br/>TTL=30s
    Redis-->>OAuth2: 저장 완료
    OAuth2-->>Client: Deep link 리다이렉트 (code=UUID)

    Client->>AuthCtrl: POST /api/v1/auth/token/exchange?code=UUID
    AuthCtrl->>AuthSvc: exchangeCode(UUID)
    AuthSvc->>Redis: UUID로 값 조회 및 삭제
    Redis-->>AuthSvc: "access::refresh"
    AuthSvc-->>AuthCtrl: TokenPair(access, refresh)
    AuthCtrl-->>Client: ApiResponse(TokenPair)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested reviewers

  • HyeonJooooo
  • Hajin99
  • hyorim-jo

Poem

🐰 Redis 숲에 숨긴 작은 열쇠,
30초만 반짝이다 사라져요,
한 번 건네진 토큰은 다시 없고,
버전의 길 따라 컨트롤러들이 춤추네,
당근보다 달콤한 배포 축하해요~

🚥 Pre-merge checks | ✅ 2 | ❌ 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 (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed PR 제목이 소셜 로그인 기능 구현이라는 주요 변경사항을 명확하게 나타내며, 인증 코드 기반 토큰 교환, Redis 저장소 추가, API 버전 관리 등의 핵심 기능 변경을 포괄합니다.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/#208-social-login

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.

Copy link
Contributor

@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: 4

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/com/umc/linkyou/config/security/SecurityConfig.java (1)

42-56: ⚠️ Potential issue | 🟠 Major

/api/v1/auth/** 공개 경로 누락
Line 45의 permitAll 목록에 /api/v1/auth/**가 빠져 있어 /api/v1/auth/token/exchange가 인증 필요로 막힐 수 있습니다. 소셜 로그인 교환 흐름이 끊기니 공개 경로에 추가해주세요.

🛠️ 제안 변경
-                                "/api/v1/users/**",
+                                "/api/v1/users/**",
+                                "/api/v1/auth/**",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/config/security/SecurityConfig.java` around
lines 42 - 56, The permitAll list in SecurityConfig's authorizeHttpRequests
requestMatchers is missing the public auth endpoint; update the requestMatchers
in SecurityConfig (where .authorizeHttpRequests(...).requestMatchers(...) is
defined) to include "/api/v1/auth/**" alongside the other paths so endpoints
like /api/v1/auth/token/exchange are publicly accessible during the social login
exchange flow.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/umc/linkyou/apiPayload/code/status/ErrorStatus.java`:
- Around line 46-52: The enum constant _INVALID_AUTH_CODE in ErrorStatus has a
message missing a sentence terminator; update the third parameter for
_INVALID_AUTH_CODE (in the ErrorStatus enum) from "유효하지 않거나 만료된 인증 코드" to "유효하지
않거나 만료된 인증 코드입니다." so it matches the style of other messages.

In `@src/main/java/com/umc/linkyou/oauth/OAuth2SuccessHandler.java`:
- Around line 119-120: The log currently writes the full deep-link URL
(redirectUrl) including the sensitive OAuth `code`; update OAuth2SuccessHandler
to sanitize the URL before logging by removing or masking the `code` query
parameter (e.g., parse redirectUrl, strip or replace the value of the "code"
param with a fixed mask like "[REDACTED]" into a sanitizedRedirectUrl) and use
sanitizedRedirectUrl in the log.info call instead of redirectUrl; ensure any
helper logic lives near the existing method handling redirect generation so
references like redirectUrl and the log.info call are updated accordingly.

In `@src/main/java/com/umc/linkyou/service/users/AuthCodeService.java`:
- Around line 28-29: The code in AuthCodeService currently splits the token
string into tokens and accesses tokens[1] without validating the format, which
can throw when the input is malformed; update the token-parsing logic (the place
that creates UserResponseDTO.TokenPair from String[] tokens) to check that token
is not null/empty and that tokens.length >= 2 after token.split("::"), and if
the check fails return or throw the INVALID_AUTH_CODE error path instead of
accessing tokens[1]; ensure the same validation is applied wherever the tokens
variable and UserResponseDTO.TokenPair(...) construction are used.
- Around line 18-27: The exchangeCode method currently uses non-atomic get +
delete on Redis and accesses token.split("::") without checking length; change
the Redis access in exchangeCode to use
stringRedisTemplate.opsForValue().getAndDelete(redisKey) so the read-and-delete
is atomic (preventing reuse/race conditions), and after retrieving token
validate it is non-null and that token.split("::") has the expected number of
segments before indexing into the array—if validation fails, delete/ensure key
removal as needed and throw the same
GeneralException(ErrorStatus._INVALID_AUTH_CODE) (or an appropriate error) to
avoid ArrayIndexOutOfBoundsException.

---

Outside diff comments:
In `@src/main/java/com/umc/linkyou/config/security/SecurityConfig.java`:
- Around line 42-56: The permitAll list in SecurityConfig's
authorizeHttpRequests requestMatchers is missing the public auth endpoint;
update the requestMatchers in SecurityConfig (where
.authorizeHttpRequests(...).requestMatchers(...) is defined) to include
"/api/v1/auth/**" alongside the other paths so endpoints like
/api/v1/auth/token/exchange are publicly accessible during the social login
exchange flow.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6682e44 and 19098a4.

📒 Files selected for processing (9)
  • src/main/java/com/umc/linkyou/apiPayload/code/status/ErrorStatus.java
  • src/main/java/com/umc/linkyou/config/WebConfig.java
  • src/main/java/com/umc/linkyou/config/security/SecurityConfig.java
  • src/main/java/com/umc/linkyou/oauth/OAuth2SuccessHandler.java
  • src/main/java/com/umc/linkyou/service/users/AuthCodeService.java
  • src/main/java/com/umc/linkyou/validation/annotation/ApiV1.java
  • src/main/java/com/umc/linkyou/validation/annotation/ApiV2.java
  • src/main/java/com/umc/linkyou/web/controller/user/AuthCodeController.java
  • src/main/java/com/umc/linkyou/web/controller/user/UserController.java

Comment on lines +46 to +52
_AUTH_ACCOUNT_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5001", "소셜 계정 연결에 실패했습니다."),
_USER_SOCIAL_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5002", "소셜 사용자 생성에 실패했습니다."),
_SOCIAL_EMAIL_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4003", "소셜 로그인에 이메일이 필요합니다."),
_SOCIAL_UNSUPPORTED_PROVIDER(HttpStatus.BAD_REQUEST, "OAUTH4004", "지원하지 않는 소셜 제공자입니다."),
_SOCIAL_PROFILE_EXPIRED(HttpStatus.BAD_REQUEST, "OAUTH4005", "임시 프로필이 만료되었습니다."),
_SOCIAL_PROFILE_NOT_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4006", "임시 프로필이 필요하지 않습니다."),
_INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "OAUTH4007", "유효하지 않거나 만료된 인증 코드"),
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

에러 메시지 문장 마무리 누락

_INVALID_AUTH_CODE 메시지에 종결 어미가 빠져 사용자 노출 문구 품질이 떨어집니다. 다른 메시지와 통일해 “입니다.”를 붙이는 게 좋겠습니다.

✏️ 수정 제안
-    _INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "OAUTH4007", "유효하지 않거나 만료된 인증 코드"),
+    _INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "OAUTH4007", "유효하지 않거나 만료된 인증 코드입니다."),
📝 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
_AUTH_ACCOUNT_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5001", "소셜 계정 연결에 실패했습니다."),
_USER_SOCIAL_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5002", "소셜 사용자 생성에 실패했습니다."),
_SOCIAL_EMAIL_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4003", "소셜 로그인에 이메일이 필요합니다."),
_SOCIAL_UNSUPPORTED_PROVIDER(HttpStatus.BAD_REQUEST, "OAUTH4004", "지원하지 않는 소셜 제공자입니다."),
_SOCIAL_PROFILE_EXPIRED(HttpStatus.BAD_REQUEST, "OAUTH4005", "임시 프로필이 만료되었습니다."),
_SOCIAL_PROFILE_NOT_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4006", "임시 프로필이 필요하지 않습니다."),
_INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "OAUTH4007", "유효하지 않거나 만료된 인증 코드"),
_AUTH_ACCOUNT_SAVE_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5001", "소셜 계정 연결에 실패했습니다."),
_USER_SOCIAL_CREATION_FAILED(HttpStatus.INTERNAL_SERVER_ERROR, "OAUTH5002", "소셜 사용자 생성에 실패했습니다."),
_SOCIAL_EMAIL_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4003", "소셜 로그인에 이메일이 필요합니다."),
_SOCIAL_UNSUPPORTED_PROVIDER(HttpStatus.BAD_REQUEST, "OAUTH4004", "지원하지 않는 소셜 제공자입니다."),
_SOCIAL_PROFILE_EXPIRED(HttpStatus.BAD_REQUEST, "OAUTH4005", "임시 프로필이 만료되었습니다."),
_SOCIAL_PROFILE_NOT_REQUIRED(HttpStatus.BAD_REQUEST, "OAUTH4006", "임시 프로필이 필요하지 않습니다."),
_INVALID_AUTH_CODE(HttpStatus.BAD_REQUEST, "OAUTH4007", "유효하지 않거나 만료된 인증 코드입니다."),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/apiPayload/code/status/ErrorStatus.java` around
lines 46 - 52, The enum constant _INVALID_AUTH_CODE in ErrorStatus has a message
missing a sentence terminator; update the third parameter for _INVALID_AUTH_CODE
(in the ErrorStatus enum) from "유효하지 않거나 만료된 인증 코드" to "유효하지 않거나 만료된 인증 코드입니다."
so it matches the style of other messages.

Comment on lines 119 to 120
log.info("OAuth2 딥링크 리다이렉트: {} (user={}, status={})",
redirectUrl, email, user.getStatus());
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

딥링크 로그에 code 노출
Line 119-120에서 code가 포함된 URL이 그대로 로그에 남습니다. 로그 접근만으로 30초 내 교환이 가능하니 마스킹/제거 후 기록해주세요.

🛡️ 마스킹 예시
-        log.info("OAuth2 딥링크 리다이렉트: {} (user={}, status={})",
-                redirectUrl, email, user.getStatus());
+        String safeRedirectUrl = redirectUrl.replaceAll("code=[^&]+", "code=REDACTED");
+        log.info("OAuth2 딥링크 리다이렉트: {} (user={}, status={})",
+                safeRedirectUrl, email, user.getStatus());
📝 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
log.info("OAuth2 딥링크 리다이렉트: {} (user={}, status={})",
redirectUrl, email, user.getStatus());
String safeRedirectUrl = redirectUrl.replaceAll("code=[^&]+", "code=REDACTED");
log.info("OAuth2 딥링크 리다이렉트: {} (user={}, status={})",
safeRedirectUrl, email, user.getStatus());
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/oauth/OAuth2SuccessHandler.java` around lines
119 - 120, The log currently writes the full deep-link URL (redirectUrl)
including the sensitive OAuth `code`; update OAuth2SuccessHandler to sanitize
the URL before logging by removing or masking the `code` query parameter (e.g.,
parse redirectUrl, strip or replace the value of the "code" param with a fixed
mask like "[REDACTED]" into a sanitizedRedirectUrl) and use sanitizedRedirectUrl
in the log.info call instead of redirectUrl; ensure any helper logic lives near
the existing method handling redirect generation so references like redirectUrl
and the log.info call are updated accordingly.

Comment on lines 18 to 27
public UserResponseDTO.TokenPair exchangeCode(String code) {
String redisKey = AUTH_CODE_KEY + code;
String token = stringRedisTemplate.opsForValue().get(redisKey);

if (token == null) {
log.warn("AuthCodeService: 유효하지 않거나 만료된 code={}", code);
throw new GeneralException(ErrorStatus._INVALID_AUTH_CODE);
}

stringRedisTemplate.delete(redisKey); //1회용 즉시 삭제
Copy link
Contributor

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 . -name "AuthCodeService.java" -type f

Repository: LinkYou-2025/LinkU_backend

Length of output: 136


🏁 Script executed:

find . -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" | head -5

Repository: LinkYou-2025/LinkU_backend

Length of output: 84


🏁 Script executed:

find . -type f \( -name "pom.xml" -o -name "build.gradle" -o -name "build.gradle.kts" \) -exec grep -l "spring" {} \;

Repository: LinkYou-2025/LinkU_backend

Length of output: 84


🏁 Script executed:

cat -n ./build.gradle | head -100

Repository: LinkYou-2025/LinkU_backend

Length of output: 3755


🏁 Script executed:

cat -n ./src/main/java/com/umc/linkyou/service/users/AuthCodeService.java

Repository: LinkYou-2025/LinkU_backend

Length of output: 1436


🌐 Web query:

Spring Data Redis StringRedisTemplate getAndDelete method availability version

💡 Result:

getAndDelete is available in Spring Data Redis 2.6+ (it was introduced on ValueOperations/BoundValueOperations as getAndDelete(K key); StringRedisTemplate accesses it via stringRedisTemplate.opsForValue().getAndDelete(key)). [1]

This was added as part of Spring Data Redis 2.6’s support for the Redis GETDEL command. [2]

Sources:
[1] Spring Data Redis Javadoc (ValueOperations#getAndDelete), shows Since: 2.6. (docs.spring.io)
[2] Spring Data Redis 2.6 reference docs (“New in Spring Data Redis 2.6” includes GETDEL support). (docs.spring.io)

Citations:


🏁 Script executed:

# Check if there are any usages of getAndDelete in the codebase
rg "getAndDelete" --type java

Repository: LinkYou-2025/LinkU_backend

Length of output: 52


비원자적 get+delete로 인한 일회용 코드 재사용 가능성

Line 20-27의 getdelete 패턴은 원자적이지 않아 경합 조건 발생 시 여러 요청이 동일한 토큰을 획득할 수 있습니다. StringRedisTemplate.opsForValue().getAndDelete(key)로 변경하여 원자적 조회+삭제를 보장해주세요.

수정 예시
-        String token = stringRedisTemplate.opsForValue().get(redisKey);
-
-        if (token == null) {
-            log.warn("AuthCodeService: 유효하지 않거나 만료된 code={}", code);
-            throw new GeneralException(ErrorStatus._INVALID_AUTH_CODE);
-        }
-
-        stringRedisTemplate.delete(redisKey); //1회용 즉시 삭제
+        String token = stringRedisTemplate.opsForValue().getAndDelete(redisKey);
+
+        if (token == null) {
+            log.warn("AuthCodeService: 유효하지 않거나 만료된 code={}", code);
+            throw new GeneralException(ErrorStatus._INVALID_AUTH_CODE);
+        }

추가로 Line 28-29에서 token.split("::") 결과의 길이 검증 없이 배열 접근 시 ArrayIndexOutOfBoundsException이 발생할 수 있습니다.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/service/users/AuthCodeService.java` around
lines 18 - 27, The exchangeCode method currently uses non-atomic get + delete on
Redis and accesses token.split("::") without checking length; change the Redis
access in exchangeCode to use
stringRedisTemplate.opsForValue().getAndDelete(redisKey) so the read-and-delete
is atomic (preventing reuse/race conditions), and after retrieving token
validate it is non-null and that token.split("::") has the expected number of
segments before indexing into the array—if validation fails, delete/ensure key
removal as needed and throw the same
GeneralException(ErrorStatus._INVALID_AUTH_CODE) (or an appropriate error) to
avoid ArrayIndexOutOfBoundsException.

Comment on lines 28 to 29
String[] tokens = token.split("::");
return new UserResponseDTO.TokenPair(tokens[0], tokens[1]);
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

토큰 포맷 검증 없이 split 인덱스 접근
Line 28-29에서 값이 깨지면 tokens[1] 접근 시 500이 납니다. 길이 검증 후 INVALID_AUTH_CODE로 처리하는 게 안전합니다.

🛡️ 포맷 검증 추가 예시
-        String[] tokens = token.split("::");
-        return new UserResponseDTO.TokenPair(tokens[0], tokens[1]);
+        String[] tokens = token.split("::", -1);
+        if (tokens.length != 2) {
+            log.warn("AuthCodeService: invalid token format for code={}", code);
+            throw new GeneralException(ErrorStatus._INVALID_AUTH_CODE);
+        }
+        return new UserResponseDTO.TokenPair(tokens[0], tokens[1]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/service/users/AuthCodeService.java` around
lines 28 - 29, The code in AuthCodeService currently splits the token string
into tokens and accesses tokens[1] without validating the format, which can
throw when the input is malformed; update the token-parsing logic (the place
that creates UserResponseDTO.TokenPair from String[] tokens) to check that token
is not null/empty and that tokens.length >= 2 after token.split("::"), and if
the check fails return or throw the INVALID_AUTH_CODE error path instead of
accessing tokens[1]; ensure the same validation is applied wherever the tokens
variable and UserResponseDTO.TokenPair(...) construction are used.

Copy link
Contributor

@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: 1

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/com/umc/linkyou/config/security/SecurityConfig.java (1)

43-57: ⚠️ Potential issue | 🔴 Critical

/api/v1/auth/** 경로가 permitAll에 누락되어 토큰 교환 엔드포인트가 동작하지 않습니다.

AuthCodeController는 @ApiV1 어노테이션으로 인해 WebConfig에서 /api/v1 경로 프리픽스가 추가되므로, 실제 엔드포인트는 /api/v1/auth/token/exchange입니다. 그러나 현재 SecurityConfig의 permitAll 목록에 /api/v1/auth/**가 없으므로 .anyRequest().authenticated() 규칙에 의해 인증이 필요합니다.

OAuth2 인증 코드를 토큰으로 교환하는 시점에는 클라이언트가 아직 토큰을 보유하지 않으므로, 이 엔드포인트는 반드시 인증 없이 접근 가능해야 합니다. 현재 구성에서는 토큰 교환이 401로 차단되므로 OAuth2 흐름이 완성되지 않습니다.

🔧 수정 제안
                        .requestMatchers(
                                "/", "/css/**",
                                "/api/v1/users/**",
+                               "/api/v1/auth/**",
                                "/swagger-ui/**", "/v3/api-docs/**",
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/com/umc/linkyou/config/security/SecurityConfig.java` around
lines 43 - 57, The SecurityConfig authorizeHttpRequests permit list is missing
the OAuth token-exchange path, causing AuthCodeController endpoints (prefixed by
/api/v1 via WebConfig) like /api/v1/auth/** to be blocked; update the
.requestMatchers(...) in SecurityConfig (where authorizeHttpRequests(...) and
.permitAll() are configured) to include "/api/v1/auth/**" so the token exchange
endpoint is accessible without authentication.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/main/java/com/umc/linkyou/config/security/SecurityConfig.java`:
- Around line 63-73: SecurityConfig currently sets
SessionCreationPolicy.STATELESS while still using
HttpSessionOAuth2AuthorizationRequestRepository in
oauth2Login.authorizationRequestRepository, which breaks OAuth2 state in
multi-instance deployments; replace the session-backed repository with a
stateless one (e.g., implement and register a Redis-backed
AuthorizationRequestRepository or use a cookie-based repository) and wire it
into oauth2Login.authorizationRequestRepository instead of new
HttpSessionOAuth2AuthorizationRequestRepository(); ensure the new repository
(e.g., RedisAuthorizationRequestRepository or
HttpCookieOAuth2AuthorizationRequestRepository) is a Spring bean and uses your
existing RedisTemplate/serializer or cookie utilities so OAuth2 state persists
across instances without creating server sessions.

---

Outside diff comments:
In `@src/main/java/com/umc/linkyou/config/security/SecurityConfig.java`:
- Around line 43-57: The SecurityConfig authorizeHttpRequests permit list is
missing the OAuth token-exchange path, causing AuthCodeController endpoints
(prefixed by /api/v1 via WebConfig) like /api/v1/auth/** to be blocked; update
the .requestMatchers(...) in SecurityConfig (where authorizeHttpRequests(...)
and .permitAll() are configured) to include "/api/v1/auth/**" so the token
exchange endpoint is accessible without authentication.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 19098a4 and 9b228bd.

📒 Files selected for processing (1)
  • src/main/java/com/umc/linkyou/config/security/SecurityConfig.java

@oculo0204 oculo0204 merged commit 9b3c6d5 into develop Feb 24, 2026
1 of 2 checks passed
@coderabbitai coderabbitai bot mentioned this pull request Feb 24, 2026
2 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant