Skip to content

Comments

[Feat/admin authorization] - 사용자 권한에 따른 시큐리티 설정#103

Merged
ekgns33 merged 11 commits intomainfrom
feat/admin-authorization
Jun 24, 2025
Merged

[Feat/admin authorization] - 사용자 권한에 따른 시큐리티 설정#103
ekgns33 merged 11 commits intomainfrom
feat/admin-authorization

Conversation

@ekgns33
Copy link
Contributor

@ekgns33 ekgns33 commented Jun 23, 2025

작업 내역

시큐리티 Config 수정 및 리팩토링

  • /api/v1/admin에 대한 url기반 권한 필터링 추가 : aec6b40
  • 중복되는 코드 추출, 환경 (prod, dev, test)에 따른 시큐리티 설정 메소드 분리 : 897b138
  • 엔드포인트 String 리터럴 SecurityConstants 클래스에 선언, package-private 공개

에러코드 추가

  • UserErrorCode에 권한 에러 추가 HTTP 403
  • UnauthorizedAccessException 추가

핸들러 추가

  • NoResourceFound 404 추가
  • UnauthorizedAccessException 핸들러 추가 /exceptions/CustomAccessDeniedHandler

jwt 필터에 AuthContext가 존재하면 필터링 넘어가도록 설정 : f8fd195

  • 러니모 서비스는 전체 회원-전용 서비스라서 모든 API에 Authorization-헤더를 요구함.
    • 기존 테스트에서도 직접 헤더에 토큰을 주입했었음. << pain point
    • 테스트에서 일일이 JWT를 넣지않고 @WithMockUser을 사용해도 실행되도록 설정

짚고 넘어갈점

  • 우리 서비스는 모든 기능이 회원 전용 기능입니다! 따라서 토큰이 없으면 무조건 401로 주고있는데, 이게 테스트할때 너무 불편해서
    SecurityContext가 채워진 경우에는 넘어가도록 기능 추가했어요.
  • SecurityContext는 ThreadLocal한 값으로 외부에서 설정이 불가능하고 테스트환경에서 @WithMockUser로 설정가능합니다.
  • 어드민에 대한 필터링을 url 기반으로 작성했습니다. 최소한의 구현 + 이미 사용하는 시큐리티의 기능을 사용하고자 했어요.
  • 아직 서비스 레이어 단위 (Usecase, Service) 에서 유저를 구별할 일이 없어서 AOP 기반의 검증을 사용하지 않았어요 :)
    • admin은 어차피 api 엔드포인트가 분리되니까요

Summary by CodeRabbit

Summary by CodeRabbit

  • New Features

    • Introduced improved handling for unauthorized access and insufficient permissions, providing clear error messages and HTTP 403 responses.
    • Added centralized constants for security endpoint patterns and user roles, enhancing maintainability and consistency.
    • Enhanced global error handling to cover cases of missing resources and unauthorized access with appropriate status codes and messages.
    • Added a custom access denied handler to return standardized JSON error responses on authorization failures.
    • Added comprehensive security tests verifying access rules and error responses across different profiles and roles.
  • Refactor

    • Modularized and streamlined security configuration for better clarity and maintainability.
    • Improved JWT authentication filter logic to avoid redundant processing when a user is already authenticated.

@ekgns33 ekgns33 requested a review from jeeheaG June 23, 2025 06:04
@ekgns33 ekgns33 self-assigned this Jun 23, 2025
Copilot AI review requested due to automatic review settings June 23, 2025 06:04
@coderabbitai
Copy link

coderabbitai bot commented Jun 23, 2025

Walkthrough

The changes introduce centralized handling for unauthorized access and insufficient permissions within the authentication and security layers. A custom access denied handler, new exception classes, error codes, and security constants are added. The security configuration is refactored for modularity, and global exception handling is expanded to cover new error scenarios.

Changes

File(s) Change Summary
.../auth/exceptions/UnauthorizedAccessException.java Added new exception class for handling insufficient permissions with specific error code and constructors.
.../auth/filters/JwtAuthenticationFilter.java Enhanced filter to skip JWT processing if user is already authenticated and not anonymous.
.../auth/filters/UserErrorCode.java Added new enum constant INSUFFICIENT_PERMISSIONS for 403 Forbidden errors.
.../config/SecurityConstants.java Introduced utility class with static constants for endpoint patterns and role names used in security configuration.
.../config/SecurityConfig.java Refactored security configuration: modularized setup, used constants, separated prod/dev authorization logic, added custom handlers.
.../exceptions/CustomAccessDeniedHandler.java Added custom access denied handler to return standardized JSON error response for insufficient permissions.
.../exceptions/GlobalExceptionHandler.java Added handlers for NoResourceFoundException and UnauthorizedAccessException, updated error responses and logging.
.../security/SecurityConfigDevTest.java Added tests verifying security config in dev profile, including Swagger access and basic security enforcement.
.../security/SecurityConfigTest.java Added comprehensive tests for security access rules and error response formats under different authentication scenarios.
.../security/SecurityFilterIntegrationTest.java Added integration tests for JWT authentication filter behavior with various token and header conditions.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant SecurityFilter
    participant SecurityContext
    participant JwtAuthFilter
    participant AccessDeniedHandler
    participant ExceptionHandler

    Client->>SecurityFilter: HTTP Request
    SecurityFilter->>JwtAuthFilter: doFilterInternal(request)
    JwtAuthFilter->>SecurityContext: Check authentication
    alt Already authenticated and not anonymous
        JwtAuthFilter-->>SecurityFilter: Proceed with filterChain
    else Not authenticated
        JwtAuthFilter->>JwtAuthFilter: Validate JWT
        alt JWT valid and authorized
            JwtAuthFilter-->>SecurityFilter: Proceed with filterChain
        else JWT invalid or insufficient permissions
            JwtAuthFilter->>AccessDeniedHandler: handle(request, response, exception)
            AccessDeniedHandler->>Client: JSON 403 Forbidden error response
        end
    end
    alt Exception occurs
        SecurityFilter->>ExceptionHandler: Handle exception
        ExceptionHandler->>Client: JSON error response (404/403)
    end
Loading

Suggested reviewers

  • jeeheaG

Poem

In the warren of code, a new rule takes hold,
With constants and handlers, permissions are polled.
Unauthorized bunnies now get a clear sign,
"No hopping here, friend, this carrot’s not thine!"
Errors are caught with a hop and a cheer—
Security’s tighter, so bugs disappear!
🥕

✨ Finishing Touches
  • 📝 Generate Docstrings

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR refactors the security configuration to support finer-grained URL authorization, exception handling, and error reporting for admin and user endpoints.

  • Introduces new exception handlers in GlobalExceptionHandler for resource not found and unauthorized access.
  • Adds a custom access denied handler and refactors the security filter chain with environment-specific authorization.
  • Extracts duplicated configurations and adds new error codes and exception types for proper handling of authorization errors.

Reviewed Changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java Adds new exception handlers for NoResourceFoundException and UnauthorizedAccessException.
src/main/java/org/runimo/runimo/exceptions/CustomAccessDeniedHandler.java Implements a custom access denied handler for better error responses.
src/main/java/org/runimo/runimo/config/SecurityConstants.java Consolidates endpoint patterns and role constants.
src/main/java/org/runimo/runimo/config/SecurityConfig.java Refactors security filter chain construction and authorization settings.
src/main/java/org/runimo/runimo/auth/filters/UserErrorCode.java Introduces a new error code for insufficient permissions (HTTP 403).
src/main/java/org/runimo/runimo/auth/filters/JwtAuthenticationFilter.java Adds a condition to bypass filtering when an authenticated context is already set.
src/main/java/org/runimo/runimo/auth/exceptions/UnauthorizedAccessException.java Creates a new exception type for unauthorized access scenarios.
Comments suppressed due to low confidence (1)

src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java:33

  • [nitpick] Consider extracting the error message '요청한 리소스를 찾을 수 없습니다.' into a constant to promote reuse and ensure consistency.
        NoResourceFoundException e) {

@ExceptionHandler(UnauthorizedAccessException.class)
public ResponseEntity<ErrorResponse> handleUnauthorizedAccessException(
UnauthorizedAccessException e) {
log.debug("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
Copy link

Copilot AI Jun 23, 2025

Choose a reason for hiding this comment

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

[nitpick] Consider using a higher log level (e.g., warn) for UnauthorizedAccessException to ensure that potentially critical security issues are adequately logged in production.

Suggested change
log.debug("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);

Copilot uses AI. Check for mistakes.
Copy link
Contributor Author

Choose a reason for hiding this comment

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

@jeeheaG 권한 없는 사용자에 대한 로그를 warn으로 두는게 좋을까요?

Copy link

@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

🔭 Outside diff range comments (1)
src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java (1)

69-74: Fix misleading method name.

The method is handling UserException but is named handleUserJwtException, which appears to be a copy-paste error from the JWT exception handler above.

-    public ResponseEntity<ErrorResponse> handleUserJwtException(UserException e) {
+    public ResponseEntity<ErrorResponse> handleUserException(UserException e) {
🧹 Nitpick comments (2)
src/main/java/org/runimo/runimo/config/SecurityConstants.java (1)

1-29: Well-designed constants class with good separation of concerns.

The implementation effectively centralizes security constants with appropriate access modifiers and follows the utility class pattern. The separation between common and development-specific endpoints is particularly good for environment-specific configurations.

Consider adding JavaDoc comments to document the purpose of each constant group, especially for DEV_PUBLIC_ENDPOINTS to clarify when these should be used.

src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java (1)

31-37: Consider internationalizing the error message.

The hardcoded Korean message "요청한 리소스를 찾을 수 없습니다." limits the API's usability for non-Korean users. Consider using message keys with internationalization support.

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5ffc95b and c6e126e.

📒 Files selected for processing (7)
  • src/main/java/org/runimo/runimo/auth/exceptions/UnauthorizedAccessException.java (1 hunks)
  • src/main/java/org/runimo/runimo/auth/filters/JwtAuthenticationFilter.java (2 hunks)
  • src/main/java/org/runimo/runimo/auth/filters/UserErrorCode.java (1 hunks)
  • src/main/java/org/runimo/runimo/config/SecurityConfig.java (2 hunks)
  • src/main/java/org/runimo/runimo/config/SecurityConstants.java (1 hunks)
  • src/main/java/org/runimo/runimo/exceptions/CustomAccessDeniedHandler.java (1 hunks)
  • src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (5)
src/main/java/org/runimo/runimo/auth/filters/UserErrorCode.java (1)

19-21: LGTM! Well-structured error code addition.

The new INSUFFICIENT_PERMISSIONS error code is properly placed in the 403 section, follows the established naming convention, and uses appropriate HTTP status code and Korean error messages consistent with the rest of the codebase.

src/main/java/org/runimo/runimo/auth/filters/JwtAuthenticationFilter.java (2)

18-18: LGTM! Proper import addition.

The import for AnonymousAuthenticationToken is necessary for the authentication bypass logic.


44-49: ```shell
#!/bin/bash

Refined search for admin endpoint protections

echo "=== antMatchers for admin paths ==="
rg -B2 -A2 "antMatchers.*admin" --type java src/main/java/

echo -e "\n=== Role restrictions in SecurityConfig ==="
rg -B2 -A2 "hasRole.*ADMIN" --type java src/main/java/

echo -e "\n=== Controller mappings for admin paths ==="
rg -n '@(GetMapping|PostMapping|RequestMapping).*"/.*admin' --type java src/main/java/

echo -e "\n=== Role-based annotations on controller methods ==="
rg -n "@(PreAuthorize|Secured|RolesAllowed)" --type java src/main/java/


</details>
<details>
<summary>src/main/java/org/runimo/runimo/config/SecurityConfig.java (2)</summary>

`32-78`: **Well-structured security configuration refactoring!**

The refactoring effectively separates common security configuration from profile-specific authorization rules, improving maintainability and reducing code duplication. The use of constants instead of hardcoded strings is also a good practice.

---

`3-8`: **Verify SecurityConstants definitions.**

Ensure that all imported constants from `SecurityConstants` are properly defined and contain the expected values.


```shell
#!/bin/bash
# Description: Verify that SecurityConstants class exists and contains all the imported constants

# Check if SecurityConstants.java exists and examine its content
fd -t f "SecurityConstants.java" --exec cat {} \;

# Search for the specific constant definitions
ast-grep --pattern 'static final $_ ADMIN_ENDPOINT_PATTERN = $_'
ast-grep --pattern 'static final $_ ADMIN_ROLE = $_'
ast-grep --pattern 'static final $_ USER_ROLE = $_'

Comment on lines +39 to +45
@ExceptionHandler(UnauthorizedAccessException.class)
public ResponseEntity<ErrorResponse> handleUnauthorizedAccessException(
UnauthorizedAccessException e) {
log.debug("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorResponse.of(e.getErrorCode()));
}
Copy link

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Increase log level for security violations.

Unauthorized access attempts should be logged at a higher level than debug for security monitoring. Consider using log.warn() or log.info() for better visibility in production logs.

-        log.debug("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
+        log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
📝 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
@ExceptionHandler(UnauthorizedAccessException.class)
public ResponseEntity<ErrorResponse> handleUnauthorizedAccessException(
UnauthorizedAccessException e) {
log.debug("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorResponse.of(e.getErrorCode()));
}
@ExceptionHandler(UnauthorizedAccessException.class)
public ResponseEntity<ErrorResponse> handleUnauthorizedAccessException(
UnauthorizedAccessException e) {
log.warn("{} {}", ERROR_LOG_HEADER, e.getMessage(), e);
return ResponseEntity.status(HttpStatus.FORBIDDEN)
.body(ErrorResponse.of(e.getErrorCode()));
}
🤖 Prompt for AI Agents
In src/main/java/org/runimo/runimo/exceptions/GlobalExceptionHandler.java around
lines 39 to 45, the log level for unauthorized access exceptions is set to
debug, which is too low for security-related events. Change the log method from
log.debug() to log.warn() or log.info() to increase visibility of these security
violations in production logs.

Copy link

@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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6e126e and 3af71a4.

📒 Files selected for processing (4)
  • src/main/java/org/runimo/runimo/exceptions/CustomAccessDeniedHandler.java (1 hunks)
  • src/test/java/org/runimo/runimo/security/SecurityConfigDevTest.java (1 hunks)
  • src/test/java/org/runimo/runimo/security/SecurityConfigTest.java (1 hunks)
  • src/test/java/org/runimo/runimo/security/SecurityFilterIntegrationTest.java (1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • src/main/java/org/runimo/runimo/exceptions/CustomAccessDeniedHandler.java
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: Build and analyze
🔇 Additional comments (9)
src/test/java/org/runimo/runimo/security/SecurityConfigDevTest.java (3)

14-20: Well-structured test class setup.

The test class is properly configured with the necessary Spring Boot test annotations for integration testing in the dev profile.


22-30: Correct Swagger accessibility test.

The test properly verifies that Swagger endpoints are accessible in the development environment. The 302 redirect for /swagger-ui.html is expected behavior.


32-40: Important security verification test.

This test correctly ensures that basic security rules remain enforced in the development environment, preventing unauthorized access to protected API endpoints even when Swagger is accessible.

src/test/java/org/runimo/runimo/security/SecurityFilterIntegrationTest.java (2)

16-22: Proper integration test setup.

The test class is correctly configured for integration testing of JWT authentication filters with appropriate annotations and profile selection.


59-67: Correct whitelist endpoint verification.

The test properly verifies that whitelisted endpoints bypass JWT filtering by expecting 404 (endpoint not found) rather than JWT authentication errors.

src/test/java/org/runimo/runimo/security/SecurityConfigTest.java (4)

17-23: Comprehensive security test class setup.

The test class is well-configured with appropriate annotations for testing security configurations across different scenarios.


25-33: Correct public endpoint accessibility test.

The test properly verifies that public endpoints are accessible without authentication, with appropriate status code expectations for each endpoint.


51-74: Excellent role-based authorization testing.

The tests thoroughly verify role-based access control:

  • USER role can access user endpoints
  • USER role is denied admin endpoints (403 Forbidden)
  • ADMIN role can access all endpoints

This provides comprehensive coverage of the authorization rules.


76-95: Important error response format validation.

These tests ensure consistent error response formats for both unauthorized (401) and forbidden (403) scenarios, verifying proper JSON structure with required fields. This helps maintain API consistency.

@sonarqubecloud
Copy link

@ekgns33
Copy link
Contributor Author

ekgns33 commented Jun 23, 2025

endpoint에 따라 필터링되는지 확인하는 테스트 코드를 추가했습니다.
Null-check 추가했습니다

Copy link
Contributor

@jeeheaG jeeheaG left a comment

Choose a reason for hiding this comment

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

고생하셨습니다!!

@ekgns33 ekgns33 merged commit 9aa4fb8 into main Jun 24, 2025
4 checks passed
@ekgns33 ekgns33 deleted the feat/admin-authorization branch June 24, 2025 05:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants