Notice
Recent Posts
Recent Comments
Link
| 일 | 월 | 화 | 수 | 목 | 금 | 토 |
|---|---|---|---|---|---|---|
| 1 | 2 | 3 | 4 | 5 | 6 | |
| 7 | 8 | 9 | 10 | 11 | 12 | 13 |
| 14 | 15 | 16 | 17 | 18 | 19 | 20 |
| 21 | 22 | 23 | 24 | 25 | 26 | 27 |
| 28 | 29 | 30 |
Tags
- IntelliJ IDEA
- Thread
- this예약어
- 반복문
- 메서드 오버로딩
- multi-threading
- Java
- for문
- 메서드
- 자바 멀티스레딩
- 시스템 환경 변수 편집
- While
- java변수
- continue문
- JAVA객체지향
- break문
- 컴파일
- 인텔리제이 한글 깨짐 해결법
- 접근제어지시자
- 인텔리제이 기초 설정
- Java데이터 타입
- 집합관계
- 포함관계
- 생성자
- OPP개념
- 상수
- function
- 형 변환
- 연관관계
- JAVA기초
Archives
- Today
- Total
최원종의 개발 블로그
V11 - 1 역할 기반 접근 제어 본문
역할 기반 접근 제어(RBAC, Role-Based Access Control)

소프트웨어 시스템에서 사용자가 부여받은 역할(Role)에 따라
특정 페이지나 기능에 대한 접근을 허락하거나 차단하는 보안 기법
단순히 로그인을 했느냐 안 했느냐를 넘어,
로그인한 사람이 '누구인가'를 판별하여 권한을 제어
사용자에게 역할(Role)을 부여하고, 역할에 따라 관리자 전용 메뉴/페이지에 접근을 제어하는 기능구현
- ADMIN (관리자)
- 권한: 시스템의 모든 것을 제어할 수 있는 최고 권한.
- 접근 가능 영역: 일반 페이지는 물론, '관리자 전용 대시보드',
'회원 관리 메뉴', '설정 페이지' 등 민감한 관리자 전용 URL에 자유롭게 접근할 수 있음.
- USER (일반 사용자)
- 권한: 서비스의 기본적인 기능만 사용할 수 있는 제한된 권한.
- 접근 제어: 본인의 프로필이나 일반 게시판은 볼 수 있지만,
브라우저 주소창에 관리자 대시보드 주소를 직접 치고 들어가려고 하면
시스템이 이를 인식하고 접근 거부(403 Forbidden) 처리를 하여 쫓아냄.
자바 열거 타입(enum) 활용
타입 안전성 확보 (Type Safety) : 허용되지 않은 엉뚱한 값이 들어오는 것을
컴파일 시점에 완벽히 차단하여 런타임 오류 방지
자바 enum은 숨겨진 클래스 : 내부적으로 java.lang.Enum 클래스를
상속받는 고유 클래스 구조이며 상수는 각각 독립된 객체로 존재
데이터와 로직의 한 묶음 관리 : 상수 오른쪽에 추가 속성(값)을 부여하고
필드와 생성자를 통해 데이터와 비즈니스 로직을 하나로 결합
강력한 내장 메서드 기본 제공 : 문자열 변환(name), 순서(ordinal),
배열 반환(values), 문자열 기반 낚아채기(valueOf) 기본 지원
가독성과 유지보수성 폭발 : 소스 코드 내 하드코딩된 의미 없는 숫자나
문자열을 직관적인 상수명으로 대체하여 가치 명확화
/user/Role.java

package com.tenco.blog.user;
/**
* - ADMIN : 관리자
* - USER : 일반 사용자
*/
public enum Role {
ADMIN, USER
}
"ADMIN", "USER" 문자열 대신 타입 안전한 enum 사용하면
오타를 컴파일 시점에 잡을 수 있어서 보다 안정적인 코드로 활용 될 수 있음.
UserRole 엔티티 만들기
UserRole 엔티티를 만들어 주는 이유는 회원가입시 이 사용자의 역할을 1:N 으로 관리하기 위함.
즉, 한 명의 사용자가 여러 개의 권한(예: 일반 유저이면서 동시에 관리자 임)을 가질 수 있도록
User와 권한 정보를 1:N(일대다) 관계로 안전하고 유연하게 관리하기 위해 설계 한다.
SQL문
create table user_role_tb(
id int primary key auto_increment,
user_id int not null ,
role varchar(20) not null,
unique key uk_user_role (user_id, role),
foreign key (user_id) references user_tb(id)
);
중요내용 요약
테이블 유니크 복합 제약 조건 : @UniqueConstraint를 활용해
user_id와 role의 조합이 테이블 내에 단 하나만 존재하도록 uk_user_role 제약 명시
문자열 기반의 열거형 매핑 : @Enumerated(EnumType.STRING) 설정을 적용하여
무의미한 숫자 인덱스가 아닌 enum 상수 이름 자체를 DB에 안전하게 기록
데이터 식별자 자동 생성 전략 : GenerationType.IDENTITY 방식을 채택하여
MySQL 등 인메모리 및 영속성 환경에서 PK(id) 번호가 자동 증가하도록 설정
자율적 빌더 패턴 적용 : @Builder를 클래스 상단이 아닌 필요한 필드만
수용하는 커스텀 생성자에 한정 배치하여 엔티티 생성 시 가독성과 안정성 확보
Lombok 어노테이션 조화 : @Data와 @NoArgsConstructor의 조합을 통해
비즈니스 DTO 및 JPA 영속 컨텍스트 엔티티 프레임 구조의 표준 명세 준수
UserRole 코드⬇️
더보기
package com.tenco.blog.user;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@Entity
@Table(name = "user_role_tb",
uniqueConstraints = {
@UniqueConstraint(name = "uk_user_role", columnNames = {"user_id", "role"})
})
public class UserRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// private Integer user_id 컬럼은 User 부모 엔티에서 명시가 되어 자동 생성 됨.
@Enumerated(EnumType.STRING)
@Column(nullable = false, length = 20)
private Role role;
@Builder
public UserRole(Integer id, Role role) {
// id 는 UserRole 에 pk 이다.
this.id = id;
this.role = role;
}
}
User 핵심 코드
일대다(1:N) 단방향 매핑에서 @JoinColumn의 작동 원리
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private List<UserRole> roles = new ArrayList<>();
이유: 주석 설명에 적힌 것처럼 관계형 데이터베이스(RDB)의 대원칙상
외래키(FK) 컬럼은 무조건 N쪽 테이블(user_role_tb)에 생성되어야 함.
하지만 자바 코드에서는 1쪽 엔티티(User)가 자식 리스트(List<UserRole>)를 쥐고 흔드는 단방향 구조.
이때 @JoinColumn(name = "user_id")를 선언해 주면, JPA는 "주소는 부모 클래스에 적혀있지만
실제 FK 컬럼은 상대방 테이블인 user_role_tb에 user_id라는 이름으로 만들고
내가 제어하겠다"라고 인식하여 매핑을 성공시킴.
User 코드 ⬇️
더보기
package com.tenco.blog.user;
import jakarta.persistence.*;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.CreationTimestamp;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.List;
@Data
@NoArgsConstructor
@Table(name = "user_tb")
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
// 사용자명 중복 방지를 위한 유니크 제약 조건 설정
@Column(unique = true)
private String username;
private String password;
private String email;
// 엔티티가 영속화 될 때 자동으로 현재 시간을 주입해라 pc -> db
@CreationTimestamp
private Timestamp createdAt;
// User 테이블에는 이미지 파일명만 저장할 예정 (실제 데이터는 내 서버 컴퓨터 로컬에 저장할 예정)
@Column(nullable = true) // null 허용, 기본값
private String profileImage; // 프로필 이미지는 선택 사항(회원 가입 시)
// User : UserRole 연관 관계를 단방향 1 : N 구조 설계
// JPA가 1 : N 구조일 경우 (User , UserRole) , @JoinColumn(name ="user_id") 의미는
// 여기 테이블에 컬럼 user_id 생성해 라는 의미이다. 그런데 1 : N 구조에서 FK 컬럼이
// 1 쪽 테이블에 생성되는 경우는 없다. 무조건 N 쪽에 FK 컬럼이 만들어져야 하기 때문에
// 자동으로 User 테이블에 @JoinColumn("user_id") 하더라도 알아서 UserRole 컬럼을 지가 생성 한다
/**
* 사용자 권한 목록
* User (1) : UserRole (N) 연관 관계를 정의 함
*
* 1. @OneToMany + @JoinColumn(name ="user_id")
* - User 가 UserRole 리스트를 관리 합니다. (단방향)
* - 실제 DB user_role_tb 테이블에 FK 컬럼은 user_id 명이 user_role_tb 생성 된다.
*
* 2. CascadeType.ALL (운명 공동체)
* Java 기준에서 User 저장하면 Role 도 자동 저장되고, User 삭제하면 가지고 있던
* Role들도 다 삭제가 됩니다. DB 에서 실제 delete 쿼리가 발생 됩니다.
*
* 3. orphanRemoval (리스와 DBㄹ르 동기화)
* DB 에서 실제 delete 쿼리가 발생 됩니다. = true 처리
*
* 4. fetch = FetchType.EAGER (특별취급)
* 데이터 양이 얼마 되지 않습니다. 그래서 한번에 데이터를 채워서 가지고 오는것이
* 편리하다
*/
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
@JoinColumn(name = "user_id")
private List<UserRole> roles = new ArrayList<>();
@Builder
public User(Integer id, String username, String password,
String email, Timestamp createdAt,
String profileImage) {
this.id = id;
this.username = username;
this.password = password;
this.email = email;
this.createdAt = createdAt;
this.profileImage = profileImage;
}
// 편의 기능 추가 - 회원 정보 수정
public void update(UserRequest.UpdateDTO updateDTO, String newProfileImageFileName) {
this.password = updateDTO.getPassword();
this.profileImage = newProfileImageFileName;
}
// == User 엔티티에 권한 관련 편의 기능 만들기 보기 ==
// Role 추가 편의 메서드
// Role.ADMIN, Role.USER
public void addRole(Role role) {
//this.roles.get(0) = new UserRole(1, Role.USER);
this.roles.add(UserRole.builder().role(role).build());
}
// 해당 Role 을 가지고 있는 여부 확인
// boolean isAdmin = user.haRole(Role.USER);
public boolean hasRole(Role role) {
// 1. 방어적 코드 작성
if (this.roles == null || this.roles.isEmpty()) {
// Role(해당 유저에 권한이) 자체가 설정 되지 않은 상태
return false;
}
for(UserRole userRole: this.roles) {
if(userRole.getRole() == role) {
return true;
}
}
return false;
}
// 관리지 여부 확인 편의 메서드
public boolean isAdmin() {
return hasRole(Role.ADMIN);
}
// 머스태치 화면에서 사용할 편의 메서드
public String getRoleDisplay() {
return isAdmin() ? "ADMIN" : "USER";
}
}
팁💡
권한(Role) 정보에 한해서는 EAGER(즉시 로딩)로 특별 취급하는 것이
실무에서도 매우 흔하고 안전하며, 권장되는 패턴이다.
일반적으로 @OneToMany는 성능 문제(N+1 문제 등) 때문에 LAZY(지연 로딩)를 사용하는 것이 철칙.
하지만 UserRole은 예외적으로 항상 함께 사용하는 단짝 데이터이고 데이터의 양이 극히 적어 안전함
'Spring boot 입문' 카테고리의 다른 글
| V12-1 Oauth 2.0 (카카오 소셜 로그인) (0) | 2026.05.26 |
|---|---|
| V11 - 2 역할 기반 접근 제어 (0) | 2026.05.26 |
| V10-5 운영체제 별 경로 설정 (0) | 2026.05.22 |
| V10-4 프로필 이미지 수정 하기 (0) | 2026.05.22 |
| V10-3 프로필 이미지 삭제 하기 (0) | 2026.05.22 |