[feat] 애플 로그인 구현#680
Conversation
|
Warning Rate limit exceeded
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 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 configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
개요PR은 Apple 로그인 지원을 추가하고 Google 및 Apple 로그인을 위한 공급자 중립적인 사회 인증 시스템으로 리팩토링합니다. 특정 공급자의 IdTokenParser를 해석하는 새로운 변경사항Apple 로그인 및 사회 인증 리팩토링
예상 코드 리뷰 노력🎯 3 (보통) | ⏱️ ~25분 🚥 Pre-merge checks | ✅ 3 | ❌ 2❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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.
Built for teams:
One agent for your entire SDLC. Right inside Slack. 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. Comment |
There was a problem hiding this comment.
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/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/google→SocialLoginResponseV1,/api/v2/auth/login/google→SocialLoginResponseV2(경로와 응답 버전이 일치)- 애플:
/api/v1/auth/login/apple→SocialLoginResponseV2(경로와 응답 버전 불일치)애플 로그인이 신규 기능이어서 처음부터 최신 응답 형식을 사용한 의도적 설계인지, 아니면
/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
📒 Files selected for processing (11)
backend/turip-app/build.gradlebackend/turip-app/src/main/java/turip/account/domain/Provider.javabackend/turip-app/src/main/java/turip/auth/controller/AuthController.javabackend/turip-app/src/main/java/turip/auth/controller/dto/request/GoogleLoginRequest.javabackend/turip-app/src/main/java/turip/auth/controller/dto/request/SocialLoginRequest.javabackend/turip-app/src/main/java/turip/auth/service/AuthService.javabackend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.javabackend/turip-app/src/main/java/turip/auth/token/IdTokenParserResolver.javabackend/turip-app/src/test/java/turip/auth/api/AuthApiTest.javabackend/turip-app/src/test/java/turip/auth/service/AuthServiceTest.javabackend/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
| public AppleTokenParser() { | ||
| try { | ||
| this.jwtProcessor = createJwtProcessor(); | ||
| } catch (Exception e) { | ||
| throw new IllegalStateException("AppleTokenParser 초기화 실패"); | ||
| } | ||
| } |
There was a problem hiding this comment.
예외의 원인(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.
| 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; | ||
| } |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
rg -nP 'apple|APPLE' -g 'application*.yml' -g 'application*.properties' -C2
rg -nP '@Value\(.*apple' -C2Repository: woowacourse-teams/2025-Turip
Length of output: 54
🏁 Script executed:
cat -n backend/turip-app/src/main/java/turip/auth/token/AppleTokenParser.javaRepository: 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' -iRepository: 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 1Repository: 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 3Repository: 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 2Repository: 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 5Repository: 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 -20Repository: 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:
- 1: https://javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/9.40/com/nimbusds/jose/jwk/source/RemoteJWKSet.html
- 2: https://www.javadoc.io/static/com.nimbusds/nimbus-jose-jwt/9.35/com/nimbusds/jose/jwk/source/RemoteJWKSet.html
- 3: https://javadoc.io/static/com.nimbusds/nimbus-jose-jwt/10.0/com/nimbusds/jose/jwk/source/JWKSourceBuilder.html
- 4: Remove Deprecated Usages of RemoteJWKSet spring-projects/spring-security#16251
🌐 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:
- 1: https://javadoc.io/static/com.nimbusds/nimbus-jose-jwt/10.0/com/nimbusds/jwt/proc/DefaultJWTClaimsVerifier.html
- 2: https://www.javadoc.io/doc/com.nimbusds/nimbus-jose-jwt/latest/com/nimbusds/jwt/proc/DefaultJWTClaimsVerifier.html
- 3: https://developer.apple.com/documentation/signinwithapple/verifying-a-user
- 4: https://developer.apple.com/documentation/signinwithapplejs/authorizationi/id_token
🏁 Script executed:
# Check GoogleTokenParser implementation for comparison on how it validates claims
cat -n backend/turip-app/src/main/java/turip/auth/token/GoogleTokenParser.javaRepository: 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 2Repository: woowacourse-teams/2025-Turip
Length of output: 675
보안 위험: iss/aud 클레임 검증이 누락되어 있습니다.
DefaultJWTProcessor는 별도의 JWTClaimsSetVerifier 없이는 exp/nbf 만 검증합니다. 현재 구현은 setJWTClaimsSetVerifier()를 호출하지 않기 때문에, Apple이 발급한 다른 앱의 ID 토큰으로도 인증이 가능합니다. Apple Sign-In 공식 가이드에 따라 반드시 iss = https://appleid.apple.com과 aud = <앱 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.properties에apple.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.
Issues
✔️ Check-list
🗒️ Work Description
📷 Screenshot
📚 Reference