feat(admin-api): enforce admin.* RBAC on every admin endpoint (out of the box)#45
Merged
Conversation
… the box The admin REST API was authenticated-only: any valid token could call any endpoint, even though the kit ships a full permission model and the docs claim the endpoints are "guarded by ... the kit's permissions". This closes that gap so the kit delivers real RBAC with zero consumer wiring — add the starter and the admin API is permission-enforced. - AdminSecurityConfig now maps every /admin/api/v1/** endpoint to the admin.* permission it requires (read endpoints -> *.read, mutating -> *.write), expressed as one auditable authorization matrix in the security chain (no @PreAuthorize scattered across 12 controllers, no @EnableMethodSecurity needed). login + bootstrap/status stay public; self-service change-password stays merely authenticated. - JwtAuthenticationFilter now populates the security context with the caller's effective permissions as authorities, resolved PER REQUEST from JpaPlatformPermissionRepository .findCodesForUserId (the same role+group effective-grant query PermissionChecker uses). Resolved fresh rather than embedded in the JWT, so a grant/revocation takes effect on the next request and the token stays small. Roles are still added as ROLE_* for any role-based rules a consumer layers on. - No seed change needed: the bootstrap already provisions all 16 admin.* permissions onto PLATFORM_ADMIN, so the first admin can use every endpoint immediately. - Both beans remain @ConditionalOnMissingBean, so a consumer can replace the chain or the filter wholesale. New AdminRbacEnforcementTests (sample-app, real HTTP + Testcontainers) proves both sides: the seeded admin (all permissions) gets 200 on a protected read; an authenticated caller holding no grants gets 403 on the same endpoint. The unauthenticated 401/403 case stays covered by BootstrapStatusEndpointTests. Verification: full `./gradlew build` green — sample-app suite ExternalConsumer 2/2, BootstrapStatus 2/2, SampleApplication 4/4, AdminRbacEnforcement 2/2; 0 failures across all modules.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What / 무엇
Makes the admin REST API enforce the kit's
admin.*permissions on every endpoint — with zero consumer wiring. Add the starter and the admin API is permission-enforced; the seeded first admin can use everything immediately.관리자 REST API의 모든 엔드포인트가 kit의
admin.*권한을 실제로 강제하도록 함 — 소비자는 설정할 게 전혀 없음. 스타터만 추가하면 권한이 강제되는 admin API가 그대로 나오고, 시드된 최초 관리자는 즉시 모든 기능을 사용 가능.Why / 왜
The admin API was authenticated-only: any valid token could call any endpoint, even though the kit ships a full permission model and the docs state the endpoints are "guarded by ... the kit's permissions". That promise wasn't actually enforced. This closes the gap.
기존엔 인증만 하면 통과였음 — 유효한 토큰만 있으면 권한과 무관하게 모든 엔드포인트 호출 가능. kit이 권한 모델을 제공하고 문서도 "권한으로 보호된다"고 했지만 실제 강제는 없었음. 그 간극을 닫음.
How / 어떻게
AdminSecurityConfig— one auditable authorization matrix in the security chain: each/admin/api/v1/**endpoint maps to itsadmin.*permission (GET→*.read, mutating →*.write). No@PreAuthorizescattered across 12 controllers, no@EnableMethodSecurity.login+bootstrap/statusstay public;change-passwordstays merely authenticated.JwtAuthenticationFilter— populates the security context with the caller's effective permissions as authorities, resolved per request fromfindCodesForUserId(the same role + group grant queryPermissionCheckeruses). Resolved fresh, not embedded in the JWT, so a grant/revocation takes effect on the next request and the token stays small. Roles still added asROLE_*.admin.*permissions toPLATFORM_ADMIN.@ConditionalOnMissingBean, so a consumer can replace the chain or filter wholesale.Tests / 테스트
New
AdminRbacEnforcementTests(sample-app, real HTTP + Testcontainers):Unauthenticated
401/403stays covered byBootstrapStatusEndpointTests.Verification / 검증
Local full
./gradlew buildgreen — ExternalConsumer 2/2, BootstrapStatus 2/2, SampleApplication 4/4, AdminRbacEnforcement 2/2; 0 failures across all modules. CI green on this branch.