Skip to content

[feat] 애플 로그인 구현#680

Open
seaniiio wants to merge 4 commits into
developfrom
feature/#678
Open

[feat] 애플 로그인 구현#680
seaniiio wants to merge 4 commits into
developfrom
feature/#678

Conversation

@seaniiio
Copy link
Copy Markdown
Member

@seaniiio seaniiio commented May 9, 2026

Issues

✔️ Check-list

  • : Label을 지정해 주세요.
  • : Merge할 브랜치를 확인해 주세요.

🗒️ Work Description

  • 애플 로그인 구현
  • 구글, 애플 로그인 요청에 공통으로 사용하기 위한 SocialLoginRequest 추가
    • 두 로그인 요청 모두 idToken만 요청으로 받아오기 때문에 중복 사용
  • IdTokenParserResolver 추가
    • provider(소셜 로그인 종류)에 대한 IdTokenParser를 얻어올 수 있는 IdTokenParserResolver를 추가함으로써 AuthService에서는 구체적은 Parser type에 의존하지 않도록 설정

📷 Screenshot

📚 Reference

@seaniiio seaniiio self-assigned this May 9, 2026
@seaniiio seaniiio added 🤙🏽 메이 우아한 테크코스 7기 백엔드 메이 💻 BackEnd 백엔드얌 📝 feat labels May 9, 2026
@github-actions github-actions Bot requested a review from eunseongu May 9, 2026 08:42
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 9, 2026

Warning

Rate limit exceeded

@seaniiio has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 52 minutes and 35 seconds before requesting another review.

You’ve run out of usage credits. Purchase more in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 21e6a4fc-1340-4577-b445-4c8261e9dab5

📥 Commits

Reviewing files that changed from the base of the PR and between afb4e79 and 5d2ea65.

📒 Files selected for processing (1)
  • backend/turip-app/src/test/java/turip/auth/token/IdTokenParserResolverTest.java

개요

PR은 Apple 로그인 지원을 추가하고 Google 및 Apple 로그인을 위한 공급자 중립적인 사회 인증 시스템으로 리팩토링합니다. 특정 공급자의 IdTokenParser를 해석하는 새로운 IdTokenParserResolver 아키텍처를 도입합니다.

변경사항

Apple 로그인 및 사회 인증 리팩토링

레이어 / 파일 요약
데이터 계약 및 의존성
backend/turip-app/build.gradle, backend/turip-app/src/main/java/turip/account/domain/Provider.java, backend/turip-app/src/main/java/turip/auth/controller/dto/request/SocialLoginRequest.java, backend/turip-app/src/main/java/turip/auth/controller/dto/request/GoogleLoginRequest.java
Provider 열거형을 KAKAO에서 APPLE로 변경합니다. nimbus-jose-jwt 라이브러리를 추가하고, GoogleLoginRequest를 제거하며 SocialLoginRequest로 통합합니다.
토큰 파싱 인프라
backend/turip-app/src/main/java/turip/auth/token/IdTokenParserResolver.java, backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java
제공자별로 IdTokenParser를 선택하는 새로운 IdTokenParserResolver를 도입합니다. Apple JWKS 엔드포인트를 사용하여 토큰을 검증하는 AppleTokenParser를 구현합니다.
서비스 로직 마이그레이션
backend/turip-app/src/main/java/turip/auth/service/AuthService.java
AuthService의 loginWithSocial을 Google 특정에서 공급자 중립적으로 변경합니다. IdTokenParserResolver를 주입받아 제공자별 파서를 동적으로 선택하고, 토큰 검증 및 회원 처리를 수행합니다.
컨트롤러 엔드포인트 통합
backend/turip-app/src/main/java/turip/auth/controller/AuthController.java
Google 로그인 엔드포인트를 SocialLoginRequest로 변경합니다. POST /api/v1/auth/login/apple 새 엔드포인트를 추가하여 Apple 로그인을 처리합니다.
테스트 인프라 및 구성
backend/turip-app/src/test/java/turip/auth/api/AuthApiTest.java, backend/turip-app/src/test/java/turip/auth/service/AuthServiceTest.java, backend/turip-app/src/test/java/turip/auth/token/IdTokenParserResolverTest.java
TestConfiguration으로 Google 및 Apple 토큰 파서 모의 객체를 제공합니다. AuthServiceTest와 AuthApiTest를 해석기 기반 접근 방식으로 마이그레이션하고, Apple 로그인 테스트를 추가합니다.

예상 코드 리뷰 노력

🎯 3 (보통) | ⏱️ ~25분

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

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.
Out of Scope Changes check ❓ Inconclusive 모든 변경 사항이 Apple 로그인 기능 구현 범위 내에 있습니다. 다만 GoogleLoginRequest를 SocialLoginRequest로 통합한 리팩토링은 scope 확장에 해당할 수 있습니다. GoogleLoginRequest 제거 및 SocialLoginRequest 통합이 이슈 #678의 필수 요구사항인지 확인하고, 필요시 리팩토링 범위를 별도 이슈로 분리 검토하시기 바랍니다.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed PR 제목이 변경 내용의 핵심을 정확하게 반영하고 있습니다. '[feat] 애플 로그인 구현'은 Apple 로그인 기능 구현이라는 주요 변경 사항을 명확하게 설명합니다.
Linked Issues check ✅ Passed 연결된 이슈 #678의 요구사항인 '애플 로그인 구현'이 완전히 충족되었습니다. AppleTokenParser, Apple 로그인 엔드포인트, Provider.APPLE 지원 등 모든 필요한 코드 변경이 구현되어 있습니다.
Description check ✅ Passed 풀 리퀘스트는 필수 섹션을 모두 포함하고 있으며, 작업 내용이 명확하게 설명되어 있습니다.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/#678

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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
Copy Markdown

@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

🧹 Nitpick comments (4)
backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java (2)

47-61: ⚖️ Poor tradeoff

getProviderId/getEmail이 매 호출마다 토큰을 재검증합니다.

AuthService.loginWithSocial은 동일 토큰에 대해 두 메서드를 연속 호출하므로 parseIdToken(서명 검증, JWKS 조회 캐시 hit이라 해도 클레임 파싱)이 중복 수행됩니다. 단일 토큰을 한 번만 파싱해 providerId/email을 함께 반환하도록 인터페이스를 조정하면 명확성과 효율 모두에서 이점이 있습니다(예: IdTokenPayload parse(String idToken) 형태). 현행 인터페이스를 유지해야 한다면 최소한 한 호출 흐름 내에서 결과를 캐시하는 방안도 가능합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java`
around lines 47 - 61, The current getProviderId and getEmail each call
parseIdToken and re-parse/verify the same idToken; update the design so
parseIdToken is invoked only once per token—either change the interface to
return a single payload object (e.g., introduce IdTokenPayload and replace
getProviderId/getEmail with IdTokenPayload parse(String idToken) used by
AuthService.loginWithSocial), or add a simple per-call cache inside
AppleTokenParser that stores the last-parsed token and JWTClaimsSet (use
parseIdToken, then expose providerId/email from that cached claims set) to avoid
duplicate signature and claim parsing; ensure error handling still throws
UnauthorizedException(ErrorTag.ID_TOKEN_NOT_VALID, e) when parse fails.

36-36: ⚡ Quick win

RemoteJWKSet은 nimbus-jose-jwt 9.40부터 deprecated 되었습니다.

현재 프로젝트에서 사용 중인 9.40 버전에서 RemoteJWKSet은 이미 @Deprecated 표시가 붙어 있으며, 새 코드에서는 JWKSourceBuilder를 사용하는 것이 권장됩니다. JWKSourceBuilder는 캐싱(refresh-ahead), 레이트 리미팅, 재시도, outage tolerance 등 외부 IdP 의존 시 안정성에 중요한 기능을 기본 제공합니다. Apple JWKS 엔드포인트 일시 장애 대비 측면에서도 유리합니다.

♻️ 변경 예시
-import com.nimbusds.jose.jwk.source.JWKSource;
-import com.nimbusds.jose.jwk.source.RemoteJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSource;
+import com.nimbusds.jose.jwk.source.JWKSourceBuilder;
...
-        JWKSource<SecurityContext> keySource = new RemoteJWKSet<>(new URL(APPLE_JWKS_URL));
+        JWKSource<SecurityContext> keySource =
+                JWKSourceBuilder.<SecurityContext>create(URI.create(APPLE_JWKS_URL).toURL())
+                        .build();

(추가로 new URL(...) 생성자도 Java 20+에서 deprecated 되었으므로 URI.create(...).toURL() 사용을 권장합니다.)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java` at
line 36, Replace the deprecated RemoteJWKSet usage in AppleTokenParser: instead
of creating JWKSource<SecurityContext> via new RemoteJWKSet<>(new
URL(APPLE_JWKS_URL)), build the JWK source with JWKSourceBuilder (e.g.,
JWKSourceBuilder.forUrl/forUri or equivalent) and configure sensible defaults
for caching/refresh, retry and rate-limiting to mirror RemoteJWKSet behavior;
also replace new URL(APPLE_JWKS_URL) with URI.create(APPLE_JWKS_URL).toURL() (or
use JWKSourceBuilder overload that accepts a URI) so the code uses the
non-deprecated APIs and gains outage-tolerance features.
backend/turip-app/src/test/java/turip/auth/api/AuthApiTest.java (1)

180-181: 💤 Low value

불필요한 중복 스터빙 제거 권장

when(googleTokenParserMock.getProvider()).thenReturn(Provider.GOOGLE); 호출이 여러 테스트 메서드에서 반복됩니다. 이 스터빙은 이미 setUp() 메서드의 85번째 줄에서 수행되므로 중복입니다.

애플 로그인 테스트(LoginWithAppleTest)에서는 이러한 중복 스터빙이 없어 더 깔끔합니다. 일관성을 위해 구글 테스트에서도 제거하는 것을 권장합니다.

♻️ 중복 스터빙 제거 예시 (Line 180-182)
-            when(googleTokenParserMock.getProvider()).thenReturn(Provider.GOOGLE);
             when(googleTokenParserMock.getProviderId(idToken)).thenReturn("google-user-123");
             when(googleTokenParserMock.getEmail(idToken)).thenReturn("newuser@gmail.com");

Also applies to: 213-213, 241-241, 272-272, 305-305, 333-333, 363-363, 524-524, 591-591, 664-664, 705-705

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/test/java/turip/auth/api/AuthApiTest.java` around lines
180 - 181, Remove the redundant stubbing of googleTokenParserMock.getProvider()
in individual test methods (e.g., the
when(googleTokenParserMock.getProvider()).thenReturn(Provider.GOOGLE); calls
around the google login tests) because that behavior is already configured in
the setUp() method; delete those duplicate when(...) lines in the tests (leave
the single stub in setUp()), and only add an explicit stub in a test if you need
to override the default provider for that specific case.
backend/turip-app/src/main/java/turip/auth/controller/AuthController.java (1)

339-339: 엔드포인트 경로와 응답 타입의 버전 일관성 확인 필요

애플 로그인 엔드포인트가 /api/v1/auth/login/apple 경로를 사용하면서 SocialLoginResponseV2를 반환합니다. 구글 엔드포인트와 비교하면:

  • 구글: /api/v1/auth/login/googleSocialLoginResponseV1, /api/v2/auth/login/googleSocialLoginResponseV2 (경로와 응답 버전이 일치)
  • 애플: /api/v1/auth/login/appleSocialLoginResponseV2 (경로와 응답 버전 불일치)

애플 로그인이 신규 기능이어서 처음부터 최신 응답 형식을 사용한 의도적 설계인지, 아니면 /api/v2/auth/login/apple 엔드포인트도 추가해야 하는지 명확히 해 주세요.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/main/java/turip/auth/controller/AuthController.java` at
line 339, The Apple login mapping in AuthController currently uses
`@PostMapping`("/api/v1/auth/login/apple") but returns SocialLoginResponseV2,
causing version mismatch with the Google endpoints; either change the mapping to
`@PostMapping`("/api/v2/auth/login/apple") to match the V2 response or switch the
controller method to return SocialLoginResponseV1 (and adjust
payload/serialization accordingly) so path and response version are
consistent—update the method in AuthController that handles Apple login and
ensure any routing/tests expecting /api/v1 or /api/v2 are aligned.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java`:
- Around line 26-32: The AppleTokenParser constructor swallows the original
exception when createJwtProcessor() fails; update the catch block in the
AppleTokenParser() constructor to include the caught exception as the cause when
throwing the IllegalStateException (i.e., pass 'e' into the
IllegalStateException) so the original stacktrace and cause from
createJwtProcessor() and jwtProcessor initialization are preserved for
debugging.
- Around line 34-40: The createJwtProcessor() in AppleTokenParser currently only
verifies exp/nbf; add a JWTClaimsSetVerifier to validate iss ==
"https://appleid.apple.com" and aud == configured apple.client-id (read from
config) and include the original exception when throwing IllegalStateException
in parseIdToken(); also replace the deprecated RemoteJWKSet usage with
JWKSourceBuilder (or the current Nimbus-recommended API) when building the JWK
source used by the JWSVerificationKeySelector; finally, avoid parsing the ID
token twice by refactoring getProviderId() and getEmail() to call parseIdToken()
once and reuse the resulting claims.

In
`@backend/turip-app/src/test/java/turip/auth/token/IdTokenParserResolverTest.java`:
- Around line 3-16: Add a negative unit test for the unregistered Provider
branch: create an IdTokenParserResolver with an empty list and call
IdTokenParserResolver.resolve(Provider.GOOGLE) asserting that it throws
UnauthorizedException with ErrorTag.UNAUTHORIZED message using
assertThatThrownBy; if you intend not to add the test instead remove the
now-unused imports assertThatThrownBy, ErrorTag, and UnauthorizedException from
IdTokenParserResolverTest.

---

Nitpick comments:
In `@backend/turip-app/src/main/java/turip/auth/controller/AuthController.java`:
- Line 339: The Apple login mapping in AuthController currently uses
`@PostMapping`("/api/v1/auth/login/apple") but returns SocialLoginResponseV2,
causing version mismatch with the Google endpoints; either change the mapping to
`@PostMapping`("/api/v2/auth/login/apple") to match the V2 response or switch the
controller method to return SocialLoginResponseV1 (and adjust
payload/serialization accordingly) so path and response version are
consistent—update the method in AuthController that handles Apple login and
ensure any routing/tests expecting /api/v1 or /api/v2 are aligned.

In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java`:
- Around line 47-61: The current getProviderId and getEmail each call
parseIdToken and re-parse/verify the same idToken; update the design so
parseIdToken is invoked only once per token—either change the interface to
return a single payload object (e.g., introduce IdTokenPayload and replace
getProviderId/getEmail with IdTokenPayload parse(String idToken) used by
AuthService.loginWithSocial), or add a simple per-call cache inside
AppleTokenParser that stores the last-parsed token and JWTClaimsSet (use
parseIdToken, then expose providerId/email from that cached claims set) to avoid
duplicate signature and claim parsing; ensure error handling still throws
UnauthorizedException(ErrorTag.ID_TOKEN_NOT_VALID, e) when parse fails.
- Line 36: Replace the deprecated RemoteJWKSet usage in AppleTokenParser:
instead of creating JWKSource<SecurityContext> via new RemoteJWKSet<>(new
URL(APPLE_JWKS_URL)), build the JWK source with JWKSourceBuilder (e.g.,
JWKSourceBuilder.forUrl/forUri or equivalent) and configure sensible defaults
for caching/refresh, retry and rate-limiting to mirror RemoteJWKSet behavior;
also replace new URL(APPLE_JWKS_URL) with URI.create(APPLE_JWKS_URL).toURL() (or
use JWKSourceBuilder overload that accepts a URI) so the code uses the
non-deprecated APIs and gains outage-tolerance features.

In `@backend/turip-app/src/test/java/turip/auth/api/AuthApiTest.java`:
- Around line 180-181: Remove the redundant stubbing of
googleTokenParserMock.getProvider() in individual test methods (e.g., the
when(googleTokenParserMock.getProvider()).thenReturn(Provider.GOOGLE); calls
around the google login tests) because that behavior is already configured in
the setUp() method; delete those duplicate when(...) lines in the tests (leave
the single stub in setUp()), and only add an explicit stub in a test if you need
to override the default provider for that specific case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 23744e61-fad8-4328-bf4a-ee658c3cb797

📥 Commits

Reviewing files that changed from the base of the PR and between 3d1e531 and afb4e79.

📒 Files selected for processing (11)
  • backend/turip-app/build.gradle
  • backend/turip-app/src/main/java/turip/account/domain/Provider.java
  • backend/turip-app/src/main/java/turip/auth/controller/AuthController.java
  • backend/turip-app/src/main/java/turip/auth/controller/dto/request/GoogleLoginRequest.java
  • backend/turip-app/src/main/java/turip/auth/controller/dto/request/SocialLoginRequest.java
  • backend/turip-app/src/main/java/turip/auth/service/AuthService.java
  • backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java
  • backend/turip-app/src/main/java/turip/auth/token/IdTokenParserResolver.java
  • backend/turip-app/src/test/java/turip/auth/api/AuthApiTest.java
  • backend/turip-app/src/test/java/turip/auth/service/AuthServiceTest.java
  • backend/turip-app/src/test/java/turip/auth/token/IdTokenParserResolverTest.java
💤 Files with no reviewable changes (1)
  • backend/turip-app/src/main/java/turip/auth/controller/dto/request/GoogleLoginRequest.java

Comment on lines +26 to +32
public AppleTokenParser() {
try {
this.jwtProcessor = createJwtProcessor();
} catch (Exception e) {
throw new IllegalStateException("AppleTokenParser 초기화 실패");
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

예외의 원인(cause)이 누락됩니다.

IllegalStateException을 던질 때 원본 예외 e를 전달하지 않아 초기화 실패 원인 추적이 어렵습니다.

🛠 제안 수정안
-        } catch (Exception e) {
-            throw new IllegalStateException("AppleTokenParser 초기화 실패");
-        }
+        } catch (Exception e) {
+            throw new IllegalStateException("AppleTokenParser 초기화 실패", e);
+        }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java`
around lines 26 - 32, The AppleTokenParser constructor swallows the original
exception when createJwtProcessor() fails; update the catch block in the
AppleTokenParser() constructor to include the caught exception as the cause when
throwing the IllegalStateException (i.e., pass 'e' into the
IllegalStateException) so the original stacktrace and cause from
createJwtProcessor() and jwtProcessor initialization are preserved for
debugging.

Comment on lines +34 to +40
private ConfigurableJWTProcessor<SecurityContext> createJwtProcessor() throws Exception {
ConfigurableJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
JWKSource<SecurityContext> keySource = new RemoteJWKSet<>(new URL(APPLE_JWKS_URL));
JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, keySource);
processor.setJWSKeySelector(keySelector);
return processor;
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -nP 'apple|APPLE' -g 'application*.yml' -g 'application*.properties' -C2
rg -nP '@Value\(.*apple' -C2

Repository: woowacourse-teams/2025-Turip

Length of output: 54


🏁 Script executed:

cat -n backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java

Repository: woowacourse-teams/2025-Turip

Length of output: 3084


🏁 Script executed:

# Find where appleClientId or Apple configuration might be defined
rg -n 'appleClientId|APPLE_CLIENT|apple_client|clientId.*apple|apple.*clientId' -i

Repository: woowacourse-teams/2025-Turip

Length of output: 54


🏁 Script executed:

# Check nimbus-jose-jwt version in pom.xml
rg -n 'nimbus-jose-jwt' -A 1 -B 1

Repository: woowacourse-teams/2025-Turip

Length of output: 341


🏁 Script executed:

# Check if there's any JWTClaimsSetVerifier configuration already in place
rg -n 'JWTClaimsSetVerifier|JWTClaimsVerifier' -C 3

Repository: woowacourse-teams/2025-Turip

Length of output: 54


🏁 Script executed:

# Search for any Apple-related configuration or constants
rg -n 'APPLE|Apple' -i --type java -C 2

Repository: woowacourse-teams/2025-Turip

Length of output: 17347


🏁 Script executed:

# Check if there are other IdTokenParser implementations for reference
rg -n 'class.*TokenParser|IdTokenParser' --type java -A 5

Repository: woowacourse-teams/2025-Turip

Length of output: 11403


🏁 Script executed:

# Search for any application.yml or properties files that might have Apple config
find . -name 'application*.yml' -o -name 'application*.properties' -o -name 'application*.yaml' | head -20

Repository: woowacourse-teams/2025-Turip

Length of output: 806


🏁 Script executed:

# Check the structure of test files to see how AppleTokenParser is tested
fd -e java 'AppleTokenParser' -x cat {}

Repository: woowacourse-teams/2025-Turip

Length of output: 2587


🌐 Web query:

RemoteJWKSet deprecation nimbus-jose-jwt 9.40

💡 Result:

In nimbus-jose-jwt 9.40, RemoteJWKSet is deprecated, and the recommended replacement is to construct a JWK source via JWKSourceBuilder (rather than directly instantiating RemoteJWKSet). RemoteJWKSet’s Javadoc (v9.40) explicitly marks the class as @Deprecated and points you to “Construct a JWKSource using JWKSourceBuilder.” [1] What this means for “RemoteJWKSet deprecation nimbus-jose-jwt 9.40” (as of today, 2026-05-09): 1) You should remove direct usage of RemoteJWKSet in code compiled against 9.40. 2) Replace it with JWKSourceBuilder that wraps the remote URL/JWK set source. 3) (Optional) If you previously relied on RemoteJWKSet’s built-in caching behavior, note that the RemoteJWKSet Javadoc also describes caching behavior; you’ll want to ensure the new builder-based approach preserves the caching/failover settings you depend on. RemoteJWKSet’s Javadoc describes that the retrieved JWK set is cached and refreshed/updated when keys are unknown or the cache expires. [2] Concrete guidance (high level): - Prefer JWKSourceBuilder when building the JWKSource for a remote JWKS endpoint. The RemoteJWKSet Javadoc in 9.40 states to use JWKSourceBuilder. [1] - JWKSourceBuilder is designed to create a JWKSource from a JWK set URL and resource retriever settings. [3] Related ecosystem signal: - Spring Security maintainers opened work to “Remove Deprecated Usages of RemoteJWKSet,” noting that Nimbus deprecated RemoteJWKSet in favor of using JWKSourceBuilder. [4]

Citations:


🌐 Web query:

nimbus-jose-jwt DefaultJWTClaimsVerifier iss aud validation Apple Sign In

💡 Result:

In Nimbus JOSE + JWT (nimbus-jose-jwt), DefaultJWTClaimsVerifier validates the standard JWT registered claims you configure, including matching “iss” (issuer) and “aud” (audience) exactly when you provide them as exact match claims. If you don’t configure an audience, “aud” is not restricted (it may accept any audience), and expiration/not-before (“exp”/“nbf”) are only checked if they’re present and parsed successfully (unless you mark them as required). [1][2] Apple Sign in with Apple uses an identity token (“id_token”) that must be checked as follows: 1) Verify the JWS signature using Apple’s public key. 2) Verify nonce. 3) Verify that iss is exactly https://appleid.apple.com. 4) Verify that aud is your app’s developer client_id. 5) Verify exp is in the future. [3] How to wire this together with Nimbus DefaultJWTClaimsVerifier - Create a JWTClaimsSet with the expected exact values for issuer and audience (iss/aud) and pass it as “exactMatchClaims” to DefaultJWTClaimsVerifier. - Also require time-based claims if your logic needs them to be present; Nimbus can check exp/nbf when present, but required-claims ensures they must exist. [1][2] Practical note specific to Apple Sign in with Apple: - The aud claim of Apple id_tokens is the client_id your app configured with Apple, so you should expect aud to equal your specific Apple client_id. [4][3] If your search goal is specifically “DefaultJWTClaimsVerifier iss aud validation Apple Sign In”, the key mapping is: - Nimbus exactMatchClaims (iss, aud) corresponds to Apple’s required verification of iss==https://appleid.apple.com and aud==. [1][3]

Citations:


🏁 Script executed:

# Check GoogleTokenParser implementation for comparison on how it validates claims
cat -n backend/turip-app/src/main/java/turip/auth/token/GoogleTokenParser.java

Repository: woowacourse-teams/2025-Turip

Length of output: 2383


🏁 Script executed:

# Check if getStringClaim returns null or throws when claim is missing
rg -n 'getStringClaim' --type java -B 2 -A 2

Repository: woowacourse-teams/2025-Turip

Length of output: 675


보안 위험: iss/aud 클레임 검증이 누락되어 있습니다.

DefaultJWTProcessor는 별도의 JWTClaimsSetVerifier 없이는 exp/nbf 만 검증합니다. 현재 구현은 setJWTClaimsSetVerifier()를 호출하지 않기 때문에, Apple이 발급한 다른 앱의 ID 토큰으로도 인증이 가능합니다. Apple Sign-In 공식 가이드에 따라 반드시 iss = https://appleid.apple.comaud = <앱 Bundle ID 또는 Service ID>를 검증해야 합니다.

제안 수정안
+import com.nimbusds.jwt.proc.DefaultJWTClaimsVerifier;
+import java.util.Set;
+import org.springframework.beans.factory.annotation.Value;

 `@Component`
 public class AppleTokenParser implements IdTokenParser {
 
     private static final String APPLE_JWKS_URL = "https://appleid.apple.com/auth/keys";
+    private final String appleClientId;
     private final ConfigurableJWTProcessor<SecurityContext> jwtProcessor;
 
-    public AppleTokenParser() {
+    public AppleTokenParser(`@Value`("${apple.client-id}") String appleClientId) {
+        this.appleClientId = appleClientId;
         try {
             this.jwtProcessor = createJwtProcessor();
         } catch (Exception e) {
-            throw new IllegalStateException("AppleTokenParser 초기화 실패");
+            throw new IllegalStateException("AppleTokenParser 초기화 실패", e);
         }
     }
 
     private ConfigurableJWTProcessor<SecurityContext> createJwtProcessor() throws Exception {
         ConfigurableJWTProcessor<SecurityContext> processor = new DefaultJWTProcessor<>();
         JWKSource<SecurityContext> keySource = new RemoteJWKSet<>(new URL(APPLE_JWKS_URL));
         JWSKeySelector<SecurityContext> keySelector = new JWSVerificationKeySelector<>(JWSAlgorithm.RS256, keySource);
         processor.setJWSKeySelector(keySelector);
+        processor.setJWTClaimsSetVerifier(new DefaultJWTClaimsVerifier<>(
+                new JWTClaimsSet.Builder()
+                        .issuer("https://appleid.apple.com")
+                        .audience(appleClientId)
+                        .build(),
+                Set.of("sub", "iss", "aud", "exp", "iat")
+        ));
         return processor;
     }

또한 RemoteJWKSet은 nimbus-jose-jwt 9.40부터 deprecated되었으므로, JWKSourceBuilder로 교체하세요:

-import com.nimbusds.jose.jwk.source.RemoteJWKSet;
+import com.nimbusds.jose.jwk.source.JWKSourceBuilder;

-JWKSource<SecurityContext> keySource = new RemoteJWKSet<>(new URL(APPLE_JWKS_URL));
+JWKSource<SecurityContext> keySource = JWKSourceBuilder.create(new URL(APPLE_JWKS_URL)).build();

관련 사항:

  • application.yml 또는 application.propertiesapple.client-id 설정을 추가하세요 (iOS Bundle ID 또는 Web Service ID).
  • 현재 getProviderId()getEmail() 메서드가 각각 parseIdToken()을 호출하므로, 로그인 시 토큰이 두 번 파싱됩니다. 효율성을 위해 한 번의 파싱으로 통합하는 것을 권장합니다.
  • 라인 30의 IllegalStateException은 원본 예외의 원인을 전달하도록 수정하세요 (throw new IllegalStateException(..., e)).
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.java`
around lines 34 - 40, The createJwtProcessor() in AppleTokenParser currently
only verifies exp/nbf; add a JWTClaimsSetVerifier to validate iss ==
"https://appleid.apple.com" and aud == configured apple.client-id (read from
config) and include the original exception when throwing IllegalStateException
in parseIdToken(); also replace the deprecated RemoteJWKSet usage with
JWKSourceBuilder (or the current Nimbus-recommended API) when building the JWK
source used by the JWSVerificationKeySelector; finally, avoid parsing the ID
token twice by refactoring getProviderId() and getEmail() to call parseIdToken()
once and reuse the resulting claims.

Comment thread backend/turip-app/src/test/java/turip/auth/token/IdTokenParserResolverTest.java Outdated
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 BackEnd 백엔드얌 📝 feat 🤙🏽 메이 우아한 테크코스 7기 백엔드 메이

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

[feat] 애플 로그인 구현

1 participant