❓
물음표살인마 블로그
  • README
  • ALGORITHM
    • Sieve of Eratosthenes
    • Round Up
    • Binary Search
    • Union Find
    • Sorting Array
    • Lcm, Gcd
  • TechTalk Review
    • Template
  • Books
    • CS Note for Interview
      • Ch1. Design Pattern & Programming paradigm
        • 1.1.1 Singleton Pattern
        • 1.1.2 Factory Pattern
        • 1.1.4 Observer Pattern
        • 1.1.5 Proxty Pattern & Proxy Server
        • 1.1.8 Model-View-Controller Pattern
        • 1.2.1 Declarative and Functional Programming
        • 1.2.2 Object Oriented Programming
      • Ch2. Network
        • 2.2.1 TCP/IP Four-Layer Model
        • 2.2.1-1 TCP 3, 4 way handshake
        • 2.3 Network Devices L4, L7
        • 2.4.1 ARP, RARP
        • 2.4.2 Hop By Hop Communication
        • 2.4.3 IP Addressing Scheme
      • Ch3. Operating System
        • 3.1.1 Roles and Structure of Operating Systems
        • 3.2.1 Memory Hierarchy
        • 3.2.2 Memory Management
        • 3.3.1 Processes and Compilation Process
        • 3.3.3 Memory Structure of a Process
        • 3.3.4 Process Control Block (PCB)
        • 3.3.5 Multiprocessing
        • 3.3.6 Threads and Multithreading
        • 3.3.7 Shared Resources and Critical Sections
        • 3.3.8 Deadlock
        • 3.4 CPU Scheduling Algorithm
      • Ch4. Database
        • 4.1 Database Basic
        • 4.2 Normalization
        • 4.3 Transaction and Integrity
        • 4.4 Types of Databases
        • 4.5 Indexes
        • 4.6 Types of Joins
        • 4.7 Principles of Joins
      • Ch5. Data Structure
    • Learning the Basics of Large-Scale System Design through Virtual Interview Cases
      • 1. Scalability based on user counts(1/2)
      • 1. Scalability based on user counts(2/2)
      • 2.Back-of-the-envelope estimation
      • 3. Strategies for System Design Interviews
      • 4. Rate Limiter
      • 5. Consistent Hashing
      • 6. Key-Value System Design
      • 7. Designing a Unique ID Generator for Distributed Systems
      • 8. Designing a URL Shortener
      • 9. Designing a Web Crawler
      • 10. Notification System Design
      • 11. Designing a News Feed System
      • 12. Chat System Design
      • 13. AutoComplete
      • 14. Design YouTube
      • 15. Design Google Drive
      • Loadbalancer Algorithms
      • Cache tier
      • CDN, Content Delivery Network
      • Stateless Web tier
    • Computer System A programmer's perspective
    • Effective Java
      • Item 1. Consider Static Factory Methods Instead of Constructors
      • Item 2. Consider a Builder When Faced with Many Constructor Parameters
      • Item 3. Ensure Singleton with Private Constructor or Enum Type
      • Item 4. Enforce Noninstantiability with a Private Constructor
      • Item 5. Prefer Dependency Injection to Hardwiring Resources
      • Item 6. Avoid Creating Unnecessary Objects
      • Item 7. Eliminate Obsolete Object References
      • Item 8. Avoid Finalizers and Cleaners
      • Item 9.Prefer try-with-resources to try-finally
      • Item10. Adhering to General Rules When Overriding equals
        • Handling Transitivity Issues
        • Ensuring Consistency
      • Item11. Override hashCode When You Override equals
      • Item12. Always Override toString
        • Always Override toString
      • Item13. Override Clone Judiciously
      • Item14. Consider Implementing Comparable
      • Item15. Minimize the Accessibility of Classes and Members
      • Item16. Accessor Methods Over Public Fields
      • Item17. Minimize Mutability
      • Item18. Composition over inherentance
      • Item19. Design and Document for Inheritance, or Else Prohibit It
      • Item20. Prefer Interfaces to Abstract Classes
      • Item21. Design Interfaces with Implementations in Mind
      • Item22. Use Interfaces Only to define Types
      • Item23. Prefer Class Hierarchies to Tagged Classes
      • Item24. Favor Static Member Classes Over Non-Static
      • Item28. Use Lists Instead of Arrays
      • Item29. Prefer Generic Types
      • Item30. Favor Generic Methods
    • Head First Design Patterns
      • Ch1. Strategy Pattern
      • Ch2. Observer Pattern
        • Ver1. Ch2. Observer Pattern
      • Ch3. Decorator Pattern
        • Ch3. Decorator Pattern
      • Ch4. Factory Pattern
      • Ch5. Singleton Pattern
      • Ch6. Command Pattern
      • Ch7. Adapter and Facade Pattern
      • Ch8. Template Method Pattern
    • Digging Deep into JVM
      • Chapter 2. Java Memory Area & Memory Overflow
      • Chapter 3. Garbage Collector & Memory Allocation Strategy (1/2)
      • Chapter 3. Garbage Collector & Memory Allocation Strategy (2/2)
      • Chapter 5. Optimization Practice
      • Chapter 6. Class file structure
      • Chapter 8. Bytecode Executor Engine (1/2)
  • Interview Practices
    • Restful API Practices
      • Url Shortener API
      • Event Ticket Reservation API
      • Course Management API
      • Search posts by tags API
      • Online Code platform API
      • Simple Task Management API
      • Event Participation API
      • Review System API
      • Car management API
      • Online Library
    • Tech Review
      • if(kakao)
        • Kakao Account Cache Migration / if(kakao)2022
        • Improving the Anomaly Detection System for KakaoTalk Messaging Metrics / if(kakao) 2022
        • Standardizing API Case Handling Without Redeployment / if(kakaoAI)2024
        • JVM warm up / if(kakao)2022
    • Naver Computer Science
      • Process & Thread
      • TCP & UDP
      • Spring & Servlet
      • Filter & Interceptor & AOP
      • Equals() & ==
      • Dependency Injection
      • Object Oriented Programming
  • F-Lab
    • Week1
      • Client & Server
      • HTTP
      • TCP/UDP
      • REST API
      • Questions
        • Object Oriented Programming
        • HTTP
        • Process & Thread
        • Data Structure
    • Week2
      • OSI 7 layer
      • Web vs WAS
    • Week3
      • RDB vs NoSQL
      • RDB Index
      • Cache
      • Redis
      • Messaging Queue
    • Week4
      • Project - Ecommerce
    • Week5
      • ERD - 1
    • Week6
      • Ecommerce - 2
      • Role
      • pw hashing && Salt
      • CreatedAt, ModifiedAt
      • JWT
      • Copy of ERD - 1
    • Week7
      • Vault (HashiCorp Vault)
    • Week 8
      • Api Endpoints
    • Week10
      • Product Create Workflow
  • TOY Project
    • CodeMentor
      • Implementation of Kafka
      • Project Improvement (Architectural Enhancements)
      • Communication between servers in msa
  • JAVA
    • MESI protocol in CAS
    • CAS (Compare and Set)
    • BlockingQueue
    • Producer & Consumer
    • Synchronized && ReentrantLock
    • Memory Visibility
    • Checked vs Unchecked Exception
    • Thread
    • Batch delete instead of Cascade
    • Java Questions
      • Week 1(1/2) - Basic Java
      • Week 1(2/2) - OOP
      • Week 2(1/2) - String, Exception, Generic
      • Week2(2/2) Lambda, Stream, Annotation, Reflection
      • Week3(1/2) Collections
      • Week3(2/2) Threads
      • Week4 Java Concurrency Programming
      • Week5 JVM & GC
    • Java 101
      • JVM Structure
      • Java Compiles and Execution Method
      • Override, Overload
      • Interface vs Abstract Class
      • Primitive vs Object Type
      • Identity and equality
      • String, StringBuilder, StringBuffer
      • Checked Exceptions and Unchecked Exceptions
      • Java 8 methods
      • Try-with-reources
      • Strong Coupling and Loose Coupling
      • Serialization and Deserialization
      • Concurrency Programming in Java
      • Mutable vs Immutable
      • JDK vs JRE
  • SPRING
    • DIP. Dependency Inversion Principal
    • Ioc container, di practice
    • @Transactional
    • Proxy Pattern
    • Strategy Pattern
    • Template Method Pattern
    • using profile name as variable
    • Spring Questions
      • Spring Framework
      • Spring MVC & Web Request
      • AOP (Aspect-Oriented Programming)
      • Spring Boot
      • ORM & Data Access
      • Security
      • ETC
  • DATABASE
    • Enhancing Query Performance & Stability - User list
    • Ensuring Data Consistency, Atomicity and UX Optimization (feat.Firebase)
    • Redis: Remote Dictionary Server
    • Database Questions
      • Week1 DBMS, RDBMS basics
      • Week2 SQL
      • Week3 Index
      • Week4 Anomaly, Functional Dependency, Normalization
      • Week5 DB Transaction, Recovery
    • Normalization
      • 1st Normal Form
      • 2nd Normal Form
      • 3rd Normal Form
  • NETWORK
    • HTTP & TCP head of line blocking
    • HTTP 0.9-3.0
    • Blocking, NonBlocking and Sync, Async
    • Network Questions
      • Week1 Computer Network Basic
      • Week2(1/3) Application Layer Protocol - HTTP
      • Week2(2/3) Application Layer Protocol - HTTPS
      • Week2(3/3) Application Layer Protocol - DNS
      • Week3 Application Layer
      • Week4 Transport Layer - UDP, TCP
      • Week5 Network Layer - IP Protocol
    • Network 101
      • https://www.google.com
      • TCP vs UDP
      • Http vs Https
      • TLS Handshake 1.2
      • HTTP Method
      • CORS & SOP
      • Web Server Software
  • OS
    • Operating System Questions
      • Week1 OS & How Computer Systems Work
      • Week2(1/2) Process
      • Week2(2/2) Thread
      • Week3 CPU Scheduling
      • Week4 Process Synchronize
      • Week5 Virtual Memory
    • Operating System 101
      • Operating system
        • The role of the operating system
        • The composition of the operating system.
      • Process
        • In Linux, are all processes except the initial process child processes?
        • Zombie process, orphan process
        • (Linux) Daemon process
        • Process address space
        • Where are uninitialized variables stored?
        • Determination of the size of the Stack and Heap
        • Access speed of Stack vs Heap
        • Reason for memory space partitioning
        • Process of compiling a process
        • sudo kill -9 $CURRENT_PID
      • Thread
        • Composition of a thread's address space
      • Process vs Thread
        • Creation of processes and threads in Linux
      • Multiprocessing
        • Web Browser
        • Implementation of multiprocessing
        • Application areas of multiprocessing
      • Multithreading
        • Application areas of multithreading
      • Interrupt
        • HW / SW Interrupt
        • Method of handling interrupts
        • Occurrence of two or more interrupts simultaneously
      • Polling
      • Dual Mode
        • Reason for distinguishing between user mode and kernel mode
      • System call
        • Differentiation between system calls
        • Types of system calls
        • Execution process of a system call
      • Process Control Block (PCB)
        • PCB의 구조
        • 쓰레드는 PCB를 갖고 있을까?
        • 프로세스 메모리 구조
      • Context switching
        • Timing of context switching
        • Registers saved during context switching
        • Context switching in processes
        • Context switching in threads
        • Difference between context switching in processes and threads
        • Information of the current process during context switching
      • Interprocess Communication (IPC)
        • Cases where IPC is used
        • Process address space in IPC Shared Memory technique
        • Types of IPC
  • COMPUTER SCIENCE
    • Computer Architecture 101
      • 3 components of a computer
      • RAM vs ROM
      • CPU vs GPU
      • SIMD
      • Two's complement
      • Harvard Architecture vs. von Neumann Architecture
      • The structure of a CPU.
      • Instruction cycle (CPU operation method)
      • Instruction pipelining
      • Bus
      • Memory area
      • Memory hierarchy structure
        • Reason for using memory hierarchy structure
      • Cache memory
      • L1, L2, L3 Cache
      • Locality of reference (cache)
      • Fixed-point vs Floating-point
        • epresentation of infinity and NaN (Not a Number) in floating-point
      • RISC vs CISC
      • Hamming code
      • Compiler
      • Linking
      • Compiler vs Interpreter
      • Mutex vs Semaphore
      • 32bit CPU and 64bit CPU
      • Local vs Static Variable
      • Page
  • Programming Paradigm
    • Declarative vs Imperative
  • JPA, QueryDsl
    • why fetchResults() is deprecated
  • PYTHON
    • Icecream
  • FASTAPI
    • Template Page
  • LINUX
    • Template Page
  • DATA STRUCTURE
    • Counting Sort
    • Array vs Linked List
  • GIT, Github
    • git clone, invalid path error
  • INFRA
    • Template Page
  • AWS
    • Server Log Archive Pipeline
    • Image Processing using Lambda
  • DOCKER
    • Docker and VM
    • Python Executable Environment
    • Docker commands
  • docker-compose
    • Kafka, Multi Broker
  • KUBERNATES
    • !Encountered Errors
      • my-sql restarts
      • kafka producer: disconnected
    • Kubernetes Components
    • Helm
      • Helm commands
    • Pod network
    • Service network
      • deployment.yaml
      • services.yaml
    • Service type
      • Cluster IP
      • NodePort
    • service-name-headless?
    • kube-proxy
  • GraphQL
    • Template Page
  • WEB
    • Template Page
  • Reviews
    • Graphic Intern Review
    • Kakao Brain Pathfinder Review
    • JSCODE 자바 1기 Review
  • 😁Dev Jokes
    • Image
      • Plot twist
      • Priorities
      • SQL join guide
      • Google is generous
      • Genie dislikes cloud
      • buggy bugs
      • last day of unpaid internship
      • what if clients know how to inspect
      • its just game
      • how i wrote my achievement on resume
      • self explanatory
      • chr(sum(range(ord(min(str(not))))))
Powered by GitBook
On this page
  • 1. 개요
  • 2. Enum 하드코딩의 한계
  • 2.1. 초기 Enum 설계
  • 2.2. 코드 중복과 분기 난립
  • 3. 권한 관리 체계의 필요성
  • 3.1. 역할(Role) vs 권한 (Permission)
  • 4. RBAC(Role-Based Access Control) 기초
  • 5. ACL(Access Control List), 정책 기반
  • 6. 실제 DB 설계 예시
  • 7. Java 코드 예시: 동적 RBAC
  • 7.1. 엔티티 설계
  • 7.2. 서비스 계층: 권한 검사
  • 7.3. 커스텀 어노테이션 정의
  • 7.4. AOP를 이용한 권한 검사 로직
  • 7.5. SecurityUtil 예시
  • 7.6. 컨트롤러 사용 예시
  • 7.7. 스프링 시큐리티 필터로 구현하는 방법
  • 8. 비교
  • 하드코딩(Enum) vs 동적(테이블) 요약 비교
  • 9. 주의사항
  • 10. 요약
  1. F-Lab
  2. Week6

Role

권한

1. 개요

개발에서 사용자에게 여러 역할을 부여하고, 그 역할에 따라 권한을 다르게 설정하는 경우가 많습니다. 유저와 관리자 등의 사용자는 모두 역할이 다르며, 가능한 행동과 접근 권한 역시 다릅니다.

역할 관리를 단순히 Enum 형태로 하드코딩하는 방식을 택하는 경우가 많은데, if (role == Role.ADMIN) {..} 같은 분기로 간단히 사용할 수 있습니다. 다만, 실무에서 유연성과 확장성을 심각하게 저해하게 됩니다. 예를 들어, 새로운 역할을 추가하거나 세부 권한을 변경해야 할 때마다 소스 코드를 직접 수정해야 하고, 배포도 다시 해야합니다.

Role이 있지만 권한이 없는 상태에서 시작해, 어떻게 하면 동적인 권한 관리 체계를 구축할 수 있는지, 하드코딩 없이 확장성 있는 구조를 만들 수 있는지 알아보려고합니다. RBAC(Role-Based Access Control), ACL(Access Control List), 혹은 정책 기반 권한 모델 등 다양한 관리 기법을 폭넓게 살펴보려고합니다.

2. Enum 하드코딩의 한계

2.1. 초기 Enum 설계

처음에는 다음과 같은 단순한 Enum을 만들 수 있습니다.

public enum Role {
    USER,
    SELLER,
    ADMIN
}

그리고, 사용자의 role 필드는 DB에서 VARCHAR 또는 INT 값으로 저장하든지, 애플리케이션 레벨에서 직접 Role Enum 값을 매핑해 사용하곤 합니다. 예를 들어, User 엔터티에 Role 필드를 둡니다:

@Entity
public class User {
    @Id
    @GeneratedValue
    private Long id;

    private String email;
    private String password;

    @Enumerated(EnumType.STRING)
    private Role role;

    // ... getters, setters ...
}

이 방식은 초기 프로토타입이나 단순 MVP에서는 빠르게 구현 가능하다는 장점이 있습니다. 그러나 실무에서 “어떤 API는 SELLER만 접근 가능하고, 어떤 페이지는 ADMIN만 편집할 수 있음” 같은 세부 권한 조건을 구현하려면, 결국 코드에서 if (role == ADMIN) 식의 분기가 곳곳에서 등장합니다.

  • 새로운 역할, 예컨대 SUPER_ADMIN, MARKETING_MANAGER 등이 추가되면?

  • ADMIN도 세분화해야 해서, “상품 관리만 가능한 ADMIN”과 “회원 관리도 가능한 ADMIN”이 따로 필요하다면?

결국 Enum 하드코딩으로는 대응하기 쉽지 않습니다. 게다가 역할이 늘어날 때마다 배포를 해야 하고, 이미 배포된 시스템에서는 곤란해지는 경우가 많습니다.

2.2. 코드 중복과 분기 난립

아래와 같은 코드를 곳곳에서 보게 됩니다:

if (currentUser.getRole() == Role.ADMIN) {
    // 관리자 전용 로직
} else if (currentUser.getRole() == Role.SELLER) {
    // 판매자 전용 로직
} else {
    // 일반 사용자 로직
}

이런 로직이 모든 서비스, 컨트롤러, UI 단에서 중복된다면, 프로젝트가 점점 복잡해지는 건 시간문제입니다. 또한, “상품 관리 권한”만 있는 ADMIN과, “회원 관리 권한”도 있는 ADMIN을 구분해야 하면, 또 다른 Enum 상수나 조건이 추가될 것입니다.

결국 Enum 하드코딩 모델은 확장성과 유연성이 부족합니다. 이를 해결하기 위해서는 권한(Permission) 을 별도로 정의하고, 역할(Role)과 권한을 매핑하는 구조가 필요합니다.

3. 권한 관리 체계의 필요성

3.1. 역할(Role) vs 권한 (Permission)

  • 역할(Role): “사용자가 어떤 그룹에 속하는가?”를 나타내는 상위 개념. 예) Admin, User, Moderator 등.

  • 권한(Permission): “이 사용자가 어떤 행동을 할 수 있는가?”를 나타내는 하위 개념. 예) 상품 등록, 상품 수정, 회원 차단, 매출 통계 보기 등.

둘을 분리하면, 역할은 단순히 “관리자, 회원, 게스트” 같은 범주이고, 권한은 실제 기능에 대한 허용 여부를 세분화합니다. 이때, RBAC(Role-Based Access Control) 모델에서는 특정 Role이 여러 개의 Permission을 갖게 설계합니다. 예를 들어, ADMIN Role은 “상품 등록, 상품 수정, 회원 차단” 권한을 모두 갖고, SELLER Role은 “상품 등록, 상품 수정” 권한만 갖게 됩니다.

4. RBAC(Role-Based Access Control) 기초

RBAC는 역할 기반 접근 제어입니다. 사용자는 하나 이상의 역할을 갖고, 각 역할은 여러 권한을 가집니다.

  1. 사용자가 로그인

  2. 서버가 사용자의 역할(들)을 식별

  3. 역할들은 어떤 권한을 가지는지 조회

  4. 클라이언트가 특정 요청을 할 때, 서버에서, 이 권한이 필요한데, 사용자 역할에 이 권한이 있는지 확인.

하나 이상의 역할의 예: 사용자가 마케팅 업무와 회계 업무를 같이 맡은 경우.

RBAC의 장점은 아래와 같습니다.

  • 유연성: 새로운 사용자에게 복잡한 권한을 일일이 부여하지 않아도 됨.

  • 확장성: Permission이 새로 생겨도 DB에 추가하면 되고, 특정 Role에 매핑만 하면 됨.

  • 정책성: 보안 정책을 역할 단위로 정의할 수 있음

5. ACL(Access Control List), 정책 기반

ACL은 주로 “리소스 관점”에서 접근 권한을 정의합니다. 예컨대 “문서 123번은 Alice는 읽기/쓰기 가능, Bob은 읽기만 가능” 이런 식이죠. 웹 애플리케이션에서 “게시글, 댓글, 파일” 등 개별 리소스에 대해 세밀하게 권한을 제어하려면 ACL이 필요할 수 있습니다. (하지만 이커머스 프로젝트처럼 기능 단위로 권한을 관리할 때는, RBAC가 더 단순 명료)

6. 실제 DB 설계 예시

이제 하드코딩(enum) 없이 DB에 Role, Permission을 정의하는 스키마 예시를 보겠습니다.

(유저 N : N 역할. 역할 N : N 권한)

-- roles 테이블
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) UNIQUE NOT NULL
);

-- permissions 테이블
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) UNIQUE NOT NULL
);

-- role_permissions (N:M 매핑)
CREATE TABLE role_permissions (
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id),
    FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

-- users 테이블 (role_id 저장 여부는 선택)
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    role_id BIGINT, --또는 user_roles_id
    FOREIGN KEY (role_id) REFERENCES roles(id),
    ...
);

6.1. 여러 역할을 가질 수 있는 구조

만약 한 사용자가 여러 Role을 가질 수 있어야 한다면, user_roles라는 N:M 매핑 테이블을 별도로 둡니다:

-- roles 테이블
CREATE TABLE roles (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) UNIQUE NOT NULL
);

-- permissions 테이블
CREATE TABLE permissions (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    name VARCHAR(50) UNIQUE NOT NULL
);

-- role_permissions 테이블 (Role : Permission = N:M)
CREATE TABLE role_permissions (
    role_id BIGINT NOT NULL,
    permission_id BIGINT NOT NULL,
    PRIMARY KEY (role_id, permission_id),
    FOREIGN KEY (role_id) REFERENCES roles(id,
    FOREIGN KEY (permission_id) REFERENCES permissions(id)
);

-- users 테이블
-- (role_id 컬럼 제거 -> 다중 role은 아래 user_roles 테이블로 관리)
CREATE TABLE users (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);

-- user_roles 테이블 (User : Role = N:M)
CREATE TABLE user_roles (
    user_id BIGINT NOT NULL,
    role_id BIGINT NOT NULL,
    PRIMARY KEY (user_id, role_id),
    FOREIGN KEY (user_id) REFERENCES users(id,
    FOREIGN KEY (role_id) REFERENCES roles(id)
);

이렇게 하면 사용자는 원하는 만큼 Role을 할당받을 수 있습니다.

6.2. Permission 예시

permissions 테이블에는 다음과 같은 Permission들이 들어갈 수 있습니다:

  • MANAGE_PRODUCTS

  • MANAGE_ORDERS

  • MANAGE_USERS

  • VIEW_REPORTS

  • ISSUE_COUPONS

  • ...

Role마다 어떤 권한이 필요한지 role_permissions 테이블에 매핑하면 됩니다. (ADMIN Role에는 모든 권한을, USER Role에는 기본 조회 권한만.)

7. Java 코드 예시: 동적 RBAC

7.1. 엔티티 설계

스프링 부트 + JPA 예시를 들어보겠습니다.

@Entity
@Table(name = "roles")
public class RoleEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name;

    // N:M 관계
    @ManyToMany
    @JoinTable(
        name = "role_permissions",
        joinColumns = @JoinColumn(name = "role_id"),
        inverseJoinColumns = @JoinColumn(name = "permission_id")
    )
    private Set<PermissionEntity> permissions = new HashSet<>();

    // getters, setters ...
}

@Entity
@Table(name = "permissions")
public class PermissionEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(unique = true, nullable = false)
    private String name;

    // Role의 ManyToMany에서 mappedBy를 설정할 수도 있으나,
    // 편의상 단방향 예시
}

UserEntity에서 role_id를 가질 수도 있고, 혹은 N:M 관계로 Set<RoleEntity> roles를 가질 수도 있습니다.

@Entity
@Table(name = "users")
public class UserEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String email;
    private String password;

    // 예시) 다중 Role 가능
    @ManyToMany
    @JoinTable(
        name = "user_roles",
        joinColumns = @JoinColumn(name = "user_id"),
        inverseJoinColumns = @JoinColumn(name = "role_id")
    )
    private Set<RoleEntity> roles = new HashSet<>();

    // getters, setters ...
}

7.2. 서비스 계층: 권한 검사

사용자가 어떤 API를 요청했을 때, 어떤 Permission이 필요한지 정하고, 아래 로직으로 검증합니다.

public boolean hasPermission(UserEntity user, String permissionName) {
    // 1) user의 모든 role을 순회
    for (RoleEntity role : user.getRoles()) {
        // 2) role이 갖고 있는 permissions 탐색
        for (PermissionEntity perm : role.getPermissions()) {
            if (perm.getName().equals(permissionName)) {
                return true;
            }
        }
    }
    return false;
}

이것을 AOP나 스프링 시큐리티 필터 등으로 추상화할 수도 있습니다. 예를 들어, API 컨트롤러 메서드에 @RequiresPermission("MANAGE_PRODUCTS") 같은 어노테이션을 달면, 인터셉터가 위 로직을 자동으로 실행해주도록 만들 수 있습니다.

7.3. 커스텀 어노테이션 정의

먼저, @RequiresPermission이라는 어노테이션을 정의합니다.

package com.example.demo.security;

import java.lang.annotation.*;

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD, ElementType.TYPE})
@Documented
public @interface RequiresPermission {
    String value(); // 예: "MANAGE_PRODUCTS" 등
}

이 어노테이션은 메서드(또는 클래스) 단위로 붙여서, 해당 메서드가 실행되기 전에 특정 Permission을 요구함을 나타냅니다.


7.4. AOP를 이용한 권한 검사 로직

어노테이션을 인식하려면, 스프링 AOP를 사용하여 특정 메서드가 호출될 때 “어노테이션 값”을 가져오고, DB에서 현재 사용자 권한을 체크하는 로직을 수행하면 됩니다. 이를 위해 Aspect를 하나 정의합시다.

package com.example.demo.security;

import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ResponseStatusException;
import org.springframework.http.HttpStatus;

import java.lang.reflect.Method;

@Slf4j
@Aspect
@Component
public class PermissionCheckAspect {

    // 1) @RequiresPermission 어노테이션이 붙은 모든 메서드에 대해 포인트컷 설정
    @Pointcut("@annotation(com.example.demo.security.RequiresPermission)")
    public void requiresPermissionAnnotationPointcut() { }

    // 2) 해당 메서드가 실행되기 *직전*에 권한 검사를 수행
    @Before("requiresPermissionAnnotationPointcut()")
    public void checkPermission(JoinPoint joinPoint) {
        // 2.1) 실행 대상 메서드 정보를 가져옴
        MethodSignature signature = (MethodSignature) joinPoint.getSignature();
        Method method = signature.getMethod();

        // 2.2) 메서드에 달린 @RequiresPermission 어노테이션의 value(권한명) 읽기
        RequiresPermission requiresPermission = method.getAnnotation(RequiresPermission.class);
        String requiredPermission = requiresPermission.value();

        // 2.3) 현재 인증된 사용자 정보 가져오기 (Session, SecurityContext 등)
        // 여기서는 예시로 간단하게 SecurityContextHolder에서 UserEntity를 받아온다고 가정
        UserEntity currentUser = SecurityUtil.getCurrentUser(); 
        if (currentUser == null) {
            // 비로그인 상태라면 권한 부족 → 401 Unauthorized
            throw new ResponseStatusException(HttpStatus.UNAUTHORIZED, "Please log in first.");
        }

        // 2.4) 실제 권한 검사 로직 (hasPermission)
        boolean hasPerm = hasPermission(currentUser, requiredPermission);
        if (!hasPerm) {
            // 권한이 없으면 403 Forbidden
            throw new ResponseStatusException(HttpStatus.FORBIDDEN, 
                "You do not have permission: " + requiredPermission);
        }

        // 2.5) 권한이 있다면 그냥 통과 (Proceed)
        log.debug("User '{}' has permission '{}', proceeding.", currentUser.getEmail(), requiredPermission);
    }

    // 예시용: 사용자 권한 검사
    private boolean hasPermission(UserEntity user, String permissionName) {
        // 사용자 -> 여러 Role -> Role마다 여러 Permission
        for (RoleEntity role : user.getRoles()) {
            for (PermissionEntity perm : role.getPermissions()) {
                if (perm.getName().equals(permissionName)) {
                    return true;
                }
            }
        }
        return false;
    }
}

설명

  1. @Aspect와 @Component 어노테이션을 통해, 이 클래스가 AOP에 의해 프록시로 관리됨을 선언.

  2. @Pointcut("@annotation(...)") 구문은 @RequiresPermission을 달고 있는 메서드를 포인트컷으로 지정.

  3. @Before("requiresPermissionAnnotationPointcut()")를 통해, 해당 메서드가 실행되기 전에 checkPermission 메서드를 호출.

  4. checkPermission 내부에서:

    • 실행 대상 메서드의 Method 객체를 가져오고, @RequiresPermission 어노테이션 값(requiredPermission)을 읽음.

    • 현재 로그인 사용자(UserEntity)를 가져와 권한 검사.

    • 권한이 없으면 예외(403) 발생, 있으면 정상 진행.

이렇게 하면, 컨트롤러나 서비스 메서드에 @RequiresPermission("MANAGE_PRODUCTS")만 달아주면 자동으로 권한 검사가 이뤄집니다.


7.5. SecurityUtil 예시

위 예시 중 SecurityUtil.getCurrentUser() 부분은, 스프링 시큐리티 또는 세션 기반 인증에서 현재 사용자 정보를 가져오는 로직입니다. 간단히 예시를 들면:

public class SecurityUtil {
    public static UserEntity getCurrentUser() {
        // 스프링 시큐리티의 SecurityContextHolder에서 인증 정보 꺼내기
        Object principal = SecurityContextHolder.getContext().getAuthentication().getPrincipal();
        if (principal instanceof CustomUserDetails) {
            return ((CustomUserDetails) principal).getUser();
        }
        return null;
    }
}
  • CustomUserDetails는 UserDetails를 구현한 클래스이며, 내부에 UserEntity를 보관해둔다고 가정.

혹은 세션 기반이라면 HttpSession에서 session.getAttribute("USER") 식으로 가져올 수도 있습니다.


7.6. 컨트롤러 사용 예시

이제 컨트롤러에 @RequiresPermission("...")을 달아봅시다:

@RestController
@RequestMapping("/api/products")
public class ProductController {

    // 예: 상품 등록
    @RequiresPermission("MANAGE_PRODUCTS")
    @PostMapping
    public ResponseEntity<?> createProduct(@RequestBody ProductDto dto) {
        // 실제 상품 등록 로직
        // ...
        return ResponseEntity.ok("Product created!");
    }

    // 예: 상품 조회(권한 불필요)
    @GetMapping("/{productId}")
    public ResponseEntity<ProductDto> getProduct(@PathVariable Long productId) {
        // ...
        return ResponseEntity.ok(productService.findById(productId));
    }
}
  • @RequiresPermission("MANAGE_PRODUCTS")가 붙은 createProduct() 메서드는, AOP Aspect에 의해 실행 전에 checkPermission이 호출됩니다.

  • 만약 현재 유저에게 MANAGE_PRODUCTS 권한이 없다면, 403 Forbidden 에러를 던지도록 처리했습니다.


7.7. 스프링 시큐리티 필터로 구현하는 방법

AOP 대신, 스프링 시큐리티의 HandlerInterceptor나 OncePerRequestFilter를 통해 “요청 경로 → 필요한 권한”을 매핑하는 방식도 있습니다. 그러나 어노테이션 기반으로 메서드를 구분하기가 AOP보다 조금 번거롭습니다.

5.1. HandlerInterceptor 예시

@Component
public class PermissionInterceptor implements HandlerInterceptor {

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) 
            throws Exception {
        
        // 1) Handler(Controller 메서드)에서 @RequiresPermission 파악
        if (handler instanceof HandlerMethod) {
            HandlerMethod handlerMethod = (HandlerMethod) handler;
            RequiresPermission annotation = handlerMethod.getMethodAnnotation(RequiresPermission.class);
            if (annotation != null) {
                String requiredPermission = annotation.value();
                UserEntity currentUser = SecurityUtil.getCurrentUser();
                if (currentUser == null) {
                    response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
                    return false;
                }
                // 권한 체크
                if (!hasPermission(currentUser, requiredPermission)) {
                    response.sendError(HttpServletResponse.SC_FORBIDDEN);
                    return false;
                }
            }
        }
        // 권한 문제 없으면 true
        return true;
    }

    private boolean hasPermission(UserEntity user, String permissionName) {
        // ...
    }
}

그리고 WebMvcConfigurer 또는 SecurityConfig에서 등록:

java복사편집@Configuration
public class WebConfig implements WebMvcConfigurer {

    @Autowired
    private PermissionInterceptor permissionInterceptor;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(permissionInterceptor)
                .addPathPatterns("/**"); // 모든 경로에서 인터셉터 동작
    }
}

이렇게 하면, 요청이 들어올 때 스프링 MVC HandlerInterceptor가 @RequiresPermission 어노테이션을 스캔하고, 권한이 없으면 즉시 에러를 반환합니다. 다만, 세부적인 “메서드 호출 전/후”가 아니라 “DispatcherServlet에서 Handler를 찾은 후” 시점에 동작한다는 점이 AOP와 다릅니다.

요약

  1. 커스텀 어노테이션 @RequiresPermission("...")을 만든다.

  2. AOP Aspect(또는 HandlerInterceptor)에서 메서드 실행 전 해당 어노테이션을 확인한다.

  3. 현재 사용자 정보를 가져와, DB에서 “이 권한을 갖고 있는지” 검사.

  4. 권한 부족 시 403 Forbidden(또는 401 Unauthorized) 응답을 반환.

이 과정을 통해, 컨트롤러나 서비스 메서드에 어노테이션 한 줄을 달아주기만 해도 자동으로 권한 검증 로직이 수행되어, 코드 중복을 최소화할 수 있습니다. 또한, 스프링 시큐리티의 UserDetailsService 등과 연계하면 권한 정보를 더욱 쉽게 로딩·검사할 수 있습니다.

추가

  • Aspect vs Interceptor vs Filter

    • Filter(예: OncePerRequestFilter)는 서블릿 레벨, 요청 초기에 동작. URL 기반 접근 제어에 유용.

    • HandlerInterceptor는 스프링 MVC Handler가 결정된 뒤, 실제 컨트롤러 로직 전후에 동작. @RequiresPermission 등 “메서드 어노테이션”을 읽으려면 Interceptor 수준이 편리.

    • Aspect는 스프링 빈 객체의 메서드가 호출되기 전/후를 잡아낼 수 있으므로, 서비스 계층, 리포지토리 계층까지도 적용 가능.

  • Spring Security를 사용할 경우, @PreAuthorize("hasAuthority('MANAGE_PRODUCTS')") 같은 어노테이션 기반 접근 제어 방식을 제공하므로, 굳이 커스텀 어노테이션을 만들지 않아도 됩니다. 다만, 프로젝트 요구사항이나 가독성, 소트웨어 아키텍처에 따라 @RequiresPermission처럼 별도로 만들고 싶을 수도 있습니다.

8. 비교

하드코딩(Enum) vs 동적(테이블) 요약 비교

9. 주의사항

  • Role-Permission 중복: 잘못된 설계로 Role이 너무 세분화되면, Permission이랑 중복되는 경우가 있음.

  • 권한 폭발: Permission을 과도하게 세분화해 ORDER_VIEW, ORDER_VIEW_DETAIL, ORDER_VIEW_ADDRESS 등등 만드는 건 유지보수를 어렵게 만듦. 적절한 추상화가 필요.

  • 캐싱 동기화: DB에서 권한을 변경했는데, 캐싱된 권한 정보가 갱신되지 않아 문제가 생기는 경우가 있음.

  • 로그인된 유저 세션/토큰은 어떻게 업데이트 하나?: 권한 업데이트 후 즉시 반영하려면, 세션/토큰을 갱신하거나, 인증 과정에서 매번 Role/Permission을 확인해야 할 수도 있음.

10. 요약

  1. Enum 하드코딩의 문제점: 새로운 Role/Permission 추가 시 코드 수정 및 재배포 필요, 확장성 저해.

  2. Role vs Permission: Role은 상위 개념, Permission은 실제 행동. RBAC를 사용하면 유연.

  3. DB 테이블 모델링: roles, permissions, role_permissions(N:M) 구조.

  4. User와 Role: 1:N 또는 M:N 관계. 필요 시 user_roles 중간 테이블 사용.

  5. Java 구현: JPA 엔티티로 만들고, @ManyToMany 관계 설정, hasPermission 로직으로 확인.

  6. 관리자 페이지에서 동적 수정: 운영 시 새로운 Role/Permission 추가, 삭제, 매핑 변경이 자유로움.

  7. 스프링 시큐리티 연동: @PreAuthorize("hasAuthority(...)") 등으로 접근 통제.

  8. 주의사항: 권한이 너무 세분화되지 않도록, 캐싱 동기화, 감사 로그, 세션 업데이트.

궁극적으로, 역할(Role)과 권한(Permission)을 분리하고, DB 기반으로 동적으로 관리함으로써 애플리케이션이 커질 때도 쉽게 확장할 수 있습니다. 이는 어떤 웹 서비스든 “사용자 그룹”이 여러 갈래로 나뉘고, “세분화된 기능 접근”을 허용해야 하는 경우에 유용합니다. 초반에 조금 복잡해 보일 수 있지만, 유지보수와 운영 단계에서 훨씬 유연하고 편리한 방식을 제공해 준다는 점이 핵심

PreviousEcommerce - 2Nextpw hashing && Salt

Last updated 3 months ago