refactor: yml 파일 수정 및 COMPANY_ID/HUB_ID 헤더 추가 - #7
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 11 minutes and 28 seconds. ⌛ 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: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughWalkthrough이번 변경은 사용자 컨텍스트 필터와 게이트웨이 라우팅 규칙을 업데이트합니다. UserContextFilter에서 JWT 클레임으로부터 companyId와 hubId를 추출하여 X-User-Company-Id와 X-User-Hub-Id 헤더를 추가로 전달하도록 변경되었습니다. 역할 추출 로직은 realm\_access의 roles 스캔 방식에서 JWT의 최상위 role 클레임을 직접 읽는 방식으로 변경되었으며, 관련 헬퍼 메서드가 제거되었습니다. application.yaml에는 item-service로의 라우팅 규칙이 새로 추가되어 /api/companies/\*/items 경로의 요청을 처리합니다. Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
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 |
user-service의 KeycloakIdentityProvider가 회원가입 시 사용자 attributes로
role/hubId/companyId/is_enabled를 직접 박아서 발행되는 JWT에는 top-level
"role" 클레임에 ROLE_MASTER/HUB/DELIVERY/COMPANY 형식으로 들어온다.
기존 extractRoleFromRealmAccess()는 realm_access.roles 배열에서 ROLE_*
키워드를 찾는 옛날 로직이라 새 JWT에서 빈 문자열만 반환했다. 그 결과
X-User-Role 헤더가 모두 빈 값으로 전달되어 도메인 서비스의 권한 체크가
모두 실패하던 것을 fix.
- claims.get("role")로 단순화
- 더 이상 사용하지 않는 extractRoleFromRealmAccess() 메서드 제거
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (1)
src/main/java/com/loopang/gateway/config/UserContextFilter.java (1)
67-69: role 단일 claim만 사용하면 전환 기간 토큰과 비호환될 수 있습니다.Line 69에서 top-level
role만 읽도록 바뀌어, 기존realm_access.roles형태 토큰이 아직 유통 중이면X-User-Role이 빈 값이 될 수 있습니다. 마이그레이션 기간에는 fallback 경로를 잠시 유지하는 게 안전합니다.호환성 fallback 예시
+ import java.util.List; + import java.util.Objects; ... - String role = (String) claims.get("role"); + String role = Objects.toString(claims.get("role"), ""); + if (role.isBlank() && claims.get("realm_access") instanceof Map<?, ?> realmAccess) { + Object rolesObj = realmAccess.get("roles"); + if (rolesObj instanceof List<?> roles && !roles.isEmpty()) { + role = Objects.toString(roles.get(0), ""); + } + }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@src/main/java/com/loopang/gateway/config/UserContextFilter.java` around lines 67 - 69, The code currently only reads the top-level role claim via claims.get("role") into the local variable role, which breaks compatibility with tokens that still use realm_access.roles; update the logic around String role (in UserContextFilter) to: first attempt to read the top-level "role" claim, and if that is null/empty, fall back to reading the "realm_access" claim and extracting its "roles" list (use the first role or join roles as appropriate) before setting the X-User-Role header; keep the existing variable names (claims, role) and header name (X-User-Role) so the change is minimal and clearly located.
🤖 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/loopang/gateway/config/UserContextFilter.java`:
- Around line 64-65: The UserContextFilter currently casts JWT claim values
directly (e.g., companyId, hubId, id, email, name, slackId, enabled, role) from
the claims Map<String,Object>, which can throw ClassCastException; update the
code in UserContextFilter to replace direct (String) casts with safe conversion
using Objects.toString(claims.get("..."), null) (or a default) for each claim
read and propagate the resulting String into your existing setters so nulls are
handled gracefully instead of throwing runtime exceptions.
In `@src/main/resources/application.yaml`:
- Around line 17-20: The route id "item-service" is duplicated causing routing
conflicts; locate both route definitions that use id: item-service (e.g., the
block with predicates Path=/api/companies/*/items, /api/companies/*/items/**)
and rename one of the ids to a unique value (for example item-service-company or
item-service-v2), then update any other references to that route id (if used
elsewhere in config or monitoring) so all route ids are unique and the gateway
resolves routes deterministically.
---
Nitpick comments:
In `@src/main/java/com/loopang/gateway/config/UserContextFilter.java`:
- Around line 67-69: The code currently only reads the top-level role claim via
claims.get("role") into the local variable role, which breaks compatibility with
tokens that still use realm_access.roles; update the logic around String role
(in UserContextFilter) to: first attempt to read the top-level "role" claim, and
if that is null/empty, fall back to reading the "realm_access" claim and
extracting its "roles" list (use the first role or join roles as appropriate)
before setting the X-User-Role header; keep the existing variable names (claims,
role) and header name (X-User-Role) so the change is minimal and clearly
located.
🪄 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: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: efa453e6-986a-4fbf-92ff-0a90ae810662
📒 Files selected for processing (2)
src/main/java/com/loopang/gateway/config/UserContextFilter.javasrc/main/resources/application.yaml
| String companyId = (String) claims.get("companyId"); | ||
| String hubId = (String) claims.get("hubId"); |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
fd -type f -name "UserContextFilter.java" | head -20Repository: MSA-Service-12th/gateway
Length of output: 238
🏁 Script executed:
head -100 src/main/java/com/loopang/gateway/config/UserContextFilter.javaRepository: MSA-Service-12th/gateway
Length of output: 4322
🏁 Script executed:
cat -n src/main/java/com/loopang/gateway/config/UserContextFilter.java | sed -n '50,100p'Repository: MSA-Service-12th/gateway
Length of output: 2587
🏁 Script executed:
cat -n src/main/java/com/loopang/gateway/config/UserContextFilter.java | sed -n '1,20p'Repository: MSA-Service-12th/gateway
Length of output: 1013
🏁 Script executed:
find . -type f -name "*KeycloakIdentityProvider*" -o -name "*KeycloakConfig*" | head -20Repository: MSA-Service-12th/gateway
Length of output: 50
🏁 Script executed:
rg -i "keycloak|claim" --type java -l | grep -E "(test|config|provider)" | head -20Repository: MSA-Service-12th/gateway
Length of output: 131
🏁 Script executed:
rg "companyId|hubId" --type java -A 2 -B 2 | head -50Repository: MSA-Service-12th/gateway
Length of output: 1470
Keycloak JWT 클레임의 무조건적인 String 캐스팅은 런타임 예외를 발생시킬 수 있습니다.
Map<String, Object>인 claims에 대한 직접 (String) 캐스팅은 클레임 값이 숫자, 불리언, 또는 다른 타입으로 들어올 경우 ClassCastException을 발생시킵니다. 필터에서 처리되지 않은 예외는 요청이 5xx 에러로 실패하게 합니다.
이 문제는 64-65번 라인의 companyId, hubId뿐만 아니라 59-63번 라인의 id, email, name, slackId, enabled 및 69번 라인의 role에도 동일하게 적용됩니다. 모든 클레임을 안전하게 문자열로 변환하려면 Objects.toString()을 사용하세요.
수정 예시
+ import java.util.Objects;
...
- String id = (String) claims.get("sub");
- String email = (String) claims.get("email");
- String name = (String) claims.get("name");
- String slackId = (String) claims.get("slack_id");
- String enabled = (String) claims.get("is_enabled");
- String companyId = (String) claims.get("companyId");
- String hubId = (String) claims.get("hubId");
- String role = (String) claims.get("role");
+ String id = Objects.toString(claims.get("sub"), "");
+ String email = Objects.toString(claims.get("email"), "");
+ String name = Objects.toString(claims.get("name"), "");
+ String slackId = Objects.toString(claims.get("slack_id"), "");
+ String enabled = Objects.toString(claims.get("is_enabled"), "");
+ String companyId = Objects.toString(claims.get("companyId"), "");
+ String hubId = Objects.toString(claims.get("hubId"), "");
+ String role = Objects.toString(claims.get("role"), "");🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@src/main/java/com/loopang/gateway/config/UserContextFilter.java` around lines
64 - 65, The UserContextFilter currently casts JWT claim values directly (e.g.,
companyId, hubId, id, email, name, slackId, enabled, role) from the claims
Map<String,Object>, which can throw ClassCastException; update the code in
UserContextFilter to replace direct (String) casts with safe conversion using
Objects.toString(claims.get("..."), null) (or a default) for each claim read and
propagate the resulting String into your existing setters so nulls are handled
gracefully instead of throwing runtime exceptions.
Spring Cloud Gateway는 route id가 unique해야 한다. PR에서 추가한 새 route(/api/companies/*/items)와 기존 /api/items/** route가 모두 'item-service' id를 사용해 라우트 해석이 불안정해질 위험이 있었음. 새로 추가한 company sub-resource route의 id를 'item-company-service'로 변경. CodeRabbit 리뷰 코멘트 반영. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
✨ 기능 PR 템플릿
📌 작업 유형
✅ 작업 내용
🧪 테스트 / 확인 방법
👀 리뷰 포인트 (선택)
리뷰어가 특히 봐줬으면 하는 부분이 있으면 작성합니다.
Summary by CodeRabbit
릴리스 노트
새 기능
개선 사항