2강. 실습 준비 - DB 관리자 권한과 계정 분리

6. MySQL 관리자 권한과 DB 작업 경로 확인
→ (1)에서 구축한 DB에 사용한 관리자 연결의 실제 권한을 확인하고, 이후 DB 접근 경로를 역할별 계정으로 분리할 준비를 합니다.
💡 팁: 쿼리 지우기(Clear) 단축키
- Mac / Linux / Git Bash: Ctrl + L
- 참고: 이 단축키는 이전 기록을 지우는 게 아니라 화면을 아래로 밀어내어 새 화면처럼 보이게 만듭니다. 스크롤을 위로 올리면 이전 명령어들이 그대로 남아있어 안전합니다.
- macOS 기본 Terminal 전용 완전 삭제: Cmd + K
- 이 단축키는 스크롤 백(이전 기록)까지 완전히 지워버립니다.
앞 글에서 관리자 연결을 사용해 DB·테이블·기준 데이터를 구축했습니다. 이제 방금 사용한 연결이 실제로 어떤 권한을 가지는지 확인하고, 이후의 DB 접근 경로를 역할별 계정으로 분리합니다.
6.1 현재 관리자 계정의 권한 확인하는 방법
관리자 계정의 MySQL 입력창에서 다음 SQL을 실행합니다.
SELECT USER() AS login_identity;
SELECT CURRENT_USER() AS privilege_identity;
SHOW GRANTS FOR CURRENT_USER;
- 용어:
USER()— 클라이언트가 접속할 때 사용한 사용자와 호스트를 보여 줍니다. - 용어:
CURRENT_USER()— MySQL이 권한 검사에 실제로 적용한 계정을 보여 줍니다. - 용어:
SHOW GRANTS— 현재 계정에 부여된 권한을 보여 줍니다.
관리자 계정은 일반적으로 DB와 테이블 생성·변경·삭제, 데이터 변경, 사용자 생성, 권한 부여를 수행할 수 있습니다. 환경에 따라 표시되는 권한 문자열은 다를 수 있으므로 계정 이름만 보고 판단하지 않고 SHOW GRANTS 결과를 확인합니다.
6.2 관리자 계정을 MCP에 연결하면 안 되는 이유
관리자 계정은 DB·테이블·계정·권한을 모두 변경할 수 있습니다.
이 계정을 MCP에 연결하면 AI가 만든 SQL의 범위가 잘못되었을 때 실제 데이터와 DB 구조가 손상될 수 있습니다.
예를 들어 학습자가 "재고가 부족한 상품을 정리해 달라"고 요청했는데
AI가 의도를 잘못 해석해 넓은 UPDATE나 DELETE를 만들 수 있습니다.
관리자 계정이 연결되어 있으면 잘못된 SQL이 권한 단계에서 차단되지 않습니다.
이 강의에서는 다음 두 단계로 쓰기 작업을 막습니다.
1. MCP 서버 설정에서 INSERT, UPDATE, DELETE, DDL 실행을 비활성화한다.
2. MySQL의 inventory_reader 계정에도 SELECT 권한만 부여한다.
✔ 핵심: MCP 서버 설정이 잘못되더라도 MySQL 계정 권한이 마지막 제한으로 동작해야 합니다.
3.3 네 가지 DB 접근 경로 구분
| 접근 경로 | 계정 | 담당 작업 | 1강 이후 상태 |
| MySQL 관리자 계정 | 수업 환경의 로컬 관리자 | DB·테이블·기준 데이터·계정 구축 | MCP에 연결하지 않음 |
| Claude Code → MySQL MCP | inventory_reader |
스키마 탐색, 조회, SQL 초안 검증 | SELECT만 가능 |
| Python 실행 하니스 → Driver | inventory_operator |
5~7강의 승인된 재고 변경 | 필요한 DML만 가능 |
| PySide6 앱 → Driver | inventory_app |
8강의 앱 조회와 승인된 기능 실행 | 1강에서는 SELECT만 가능 |
세 계정은 이름만 분리하는 것이 아니라 실제 사용 위치와 책임을 분리합니다.
- 관리자 계정은 구축 작업이 끝난 뒤 MCP나 애플리케이션 설정에 저장하지 않습니다.
- MCP 설정에는
inventory_reader만 사용합니다. - Python 실행 하니스에는
inventory_operator만 사용합니다. - PySide6 앱에는
inventory_app만 사용합니다.
이 장에서는 계정과 연결 경로의 대응 관계만 확인합니다. 어떤 작업을 MCP, 실행 하니스, 앱 중 어디에서 수행할지 선택하는 세부 판단 기준은 3강에서 다룹니다.
7. 역할별 계정과 최소 권한 구성
→ inventory_reader·inventory_operator·inventory_app 세 계정을 만들고 최소 권한만 부여합니다.
7.1 세 계정의 책임 설계
inventory_reader
사용 위치: Claude Code의 MySQL MCP
허용 작업: inventory DB의 모든 실습 테이블 조회
차단 작업: INSERT, UPDATE, DELETE, CREATE·ALTER·DROP 등의 DDL
inventory_operator
사용 위치: 5~7강의 Python 실행 하니스
허용 작업: 실습 테이블 조회, products.stock_quantity 갱신, stock_logs 행 삽입, operation_requests 요청 삽입, operation_requests의 승인·완료 상태 열 갱신
차단 작업: 상품 행 삭제, 재고 이력 삭제, 요청 기록 삭제, 테이블 생성·변경·삭제, products의 상품명·가격 등 불필요한 열 갱신
inventory_app
사용 위치: 8강 PySide6 애플리케이션
1강의 권한: SELECT만 허용
8강에서 앱 기능을 연결할 때 검증된 최소 쓰기 권한만 추가합니다.
7.2 비밀번호와 계정 파일 처리 원칙
역할별 계정을 만들기 전에 비밀번호 처리 원칙을 정합니다.
- 세 계정의 비밀번호는 서로 다르게 설정합니다.
- 운영 DB 또는 다른 서비스에서 사용하는 비밀번호를 재사용하지 않습니다.
roles.sql을 공개 Git 저장소에 커밋하지 않습니다.- 관리자 계정 비밀번호를
roles.sql에 작성하지 않습니다. - 강의 종료 후 수업용 계정을 삭제하거나 비밀번호를 변경합니다.
7.3 Claude Code에 역할별 계정 SQL 초안 요청하기
프로젝트 폴더에서 Claude Code를 실행하고 다음 프롬프트를 입력합니다.

inventory 실습용 역할별 계정 SQL을 db/roles.sql 초안으로 작성해줘.
조건:
- inventory_reader@127.0.0.1은 inventory DB의 SELECT만 허용한다.
- inventory_operator@127.0.0.1은 SELECT와 이후 실행 하니스에 필요한 제한된 INSERT, 열 단위 UPDATE만 허용한다.
- inventory_app@127.0.0.1은 1강에서 SELECT만 허용한다.
- inventory_operator와 inventory_app에 DELETE와 DDL 권한을 부여하지 않는다.
- 세 계정의 비밀번호 자리에는 CHANGE_ME_... 형식의 값을 사용한다.
- 관리자 계정 비밀번호를 포함하지 않는다.
아직 파일을 수정하거나 SQL을 실행하지 마.
계정별로 허용되는 권한과 차단되는 권한을 먼저 설명하고
전체 SQL 초안을 보여준 뒤 내 검토를 기다려줘.
7.4 db/roles.sql 초안 구현안
다음 SQL과 Claude Code의 제안을 대조합니다. CHANGE_ME_... 값을 서로 다른 수업용 비밀번호로 바꿉니다.
CREATE USER IF NOT EXISTS
'inventory_reader'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_READER_PASSWORD'; -- ✏️ 비밀번호 입력
ALTER USER
'inventory_reader'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_READER_PASSWORD'; -- ✏️ 비밀번호 입력
CREATE USER IF NOT EXISTS
'inventory_operator'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_OPERATOR_PASSWORD'; -- ✏️ 비밀번호 입력
ALTER USER
'inventory_operator'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_OPERATOR_PASSWORD'; -- ✏️ 비밀번호 입력
CREATE USER IF NOT EXISTS
'inventory_app'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_APP_PASSWORD'; -- ✏️ 비밀번호 입력
ALTER USER
'inventory_app'@'127.0.0.1'
IDENTIFIED BY 'CHANGE_ME_APP_PASSWORD'; -- ✏️ 비밀번호 입력
GRANT SELECT
ON inventory.*
TO 'inventory_reader'@'127.0.0.1';
GRANT SELECT
ON inventory.*
TO 'inventory_operator'@'127.0.0.1';
GRANT UPDATE (stock_quantity)
ON inventory.products
TO 'inventory_operator'@'127.0.0.1';
GRANT INSERT
ON inventory.stock_logs
TO 'inventory_operator'@'127.0.0.1';
GRANT INSERT
ON inventory.operation_requests
TO 'inventory_operator'@'127.0.0.1';
GRANT UPDATE (
status,
approved_by,
approved_at,
completed_at
)
ON inventory.operation_requests
TO 'inventory_operator'@'127.0.0.1';
GRANT SELECT
ON inventory.*
TO 'inventory_app'@'127.0.0.1';
검토한 역할별 계정 SQL을 db/roles.sql에 작성해줘.
조건:
- 계정명, 호스트, 권한 범위를 바꾸지 않는다.
- db/roles.sql 이외의 파일은 수정하지 않는다.
- SQL과 터미널 명령은 실행하지 않는다.
- 비밀번호를 응답에 다시 출력하지 않는다.
변경할 파일을 확인하고 내 승인을 기다려줘.
CREATE USER, ALTER USER, GRANT를 실행하면 권한이 즉시 반영되므로 이 실습에서는 FLUSH PRIVILEGES가 필요하지 않습니다.
이 단계에서 작성된 내용은 inventory DB 테이블과 Data 입니다.
inventory-mysql-app/ # ✅ 프로젝트 구조 생성
├── db/
│ ├── schema.sql # ✅ 테이블 설계
│ └── roles.sql # ☑️ ✏️ 계정 설계
├── harness/ # ✅ (빈 폴더)
└── app/ # ✅ (빈 폴더)
inventory DB # ✅ DB 구현
├── products 8건 # ✅ 테이블과 데이터 구현
├── stock_logs 16건 # ✅ 테이블과 데이터 구현
└── operation_requests 0건 # ✅ 테이블과 데이터 구현
DB 접근 경로와 계정
├── Claude Code MCP ───── inventory_reader # ❌
├── Python 하니스 ───── inventory_operator # ❌
└── PySide6 앱 ───── inventory_app # ❌
8. 계정 생성 적용
→ inventory_reader·inventory_operator·inventory_app 세 계정을 만들고 최소 권한만 부여합니다.
8.1 관리자 연결에서 역할별 계정 생성하기
관리자 접속 방식에 맞는 명령 하나를 $ 프롬프트에서 실행합니다.
방법 1. root -p 접속 방식

mysql -u root -p < db/roles.sql
방법 2. sudo mysql 접속 방식
sudo mysql < db/roles.sql
8.2 계정 권한 확인하기
실행 후 관리자 연결의 MySQL 입력창에서 각 계정의 권한을 확인합니다.

SHOW GRANTS
FOR 'inventory_reader'@'127.0.0.1';
SHOW GRANTS
FOR 'inventory_operator'@'127.0.0.1';
SHOW GRANTS
FOR 'inventory_app'@'127.0.0.1';
| 계정 | 반드시 있어야 하는 권한 | 없어야 하는 권한 |
inventory_reader |
SELECT |
INSERT, UPDATE, DELETE, DDL |
inventory_operator |
SELECT, 제한된 열 UPDATE, 필요한 테이블 INSERT |
DELETE, DDL |
inventory_app |
SELECT |
이번 강의 기준 모든 쓰기 권한과 DDL |
이 단계에서 작성된 내용은 inventory DB 테이블과 Data 입니다.
inventory-mysql-app/ # ✅ 프로젝트 구조 생성
├── db/
│ ├── schema.sql # ✅ 테이블 설계
│ └── roles.sql # ✅ 계정 설계
├── harness/ # ✅ (빈 폴더)
└── app/ # ✅ (빈 폴더)
inventory DB # ✅ DB 구현
├── products 8건 # ✅ 테이블과 데이터 구현
├── stock_logs 16건 # ✅ 테이블과 데이터 구현
└── operation_requests 0건 # ✅ 테이블과 데이터 구현
DB 접근 경로와 계정
├── Claude Code MCP ───── inventory_reader # ☑️ ✏️ 계정만 구현 + 연동X
├── Python 하니스 ───── inventory_operator # ☑️ ✏️ 계정만 구현 + 연동X
└── PySide6 앱 ───── inventory_app # ☑️ ✏️ 계정만 구현 + 연동X
▶ 지금 해보세요
db/roles.sql한 파일만 작성하도록 승인합니다.- 관리자 연결에서 계정을 생성합니다.
SHOW GRANTS로 세 계정의 권한을 확인합니다.
✔ 확인 기준: inventory_reader, inventory_operator, inventory_app 세 계정이 표의 기준대로 분리되었습니다.
9. 역할별 계정과 권한 설정 검증
→ inventory_reader·inventory_operator·inventory_app 세 계정을 만들고 최소 권한만 부여합니다.
9.1 inventory_reader의 읽기 확인하기
터미널에서 inventory_reader로 직접 접속합니다.

mysql \
-h 127.0.0.1 \
-u inventory_reader \
-p \
inventory
- mysql: MySQL 명령줄 클라이언트 실행
- -h 127.0.0.1: 현재 컴퓨터의 MySQL 서버에 TCP로 연결
- -u inventory_reader: inventory_reader 계정으로 로그인
- -p: 비밀번호를 안전하게 입력받음
- inventory: 접속 후 기본으로 사용할 데이터베이스 이름
상품 목록을 조회합니다.

SELECT
product_id,
product_name,
stock_quantity
FROM products
ORDER BY product_id;
9.2 inventory_reader 쓰기 차단 확인하기
읽기 전용 권한을 확인하기 위해 현재 값을 같은 값으로 다시 저장하는 UPDATE를 실행합니다.

UPDATE products
SET stock_quantity = stock_quantity
WHERE product_id = 1;
UPDATE command denied와 같은 권한 오류가 나오면 정상입니다. 값이 바뀌지 않는 SQL이라도 UPDATE 권한이 필요하므로 읽기 전용 계정에서 거부되어야 합니다.
▶ 지금 해보세요
inventory_reader로 직접 접속합니다.- 상품 목록 조회가 성공하는지 확인합니다.
- 같은 값을 저장하는
UPDATE가 거부되는지 확인합니다.
✔ 확인 기준: inventory_reader는 조회할 수 있지만 UPDATE는 권한 오류로 거부됩니다.
9.3 inventory_operator의 삭제와 DDL 차단 확인하기
터미널에서 inventory_operator로 직접 접속합니다.
mysql \
-h 127.0.0.1 \
-u inventory_operator \
-p \
inventory
존재하지 않는 행을 대상으로 DELETE 권한을 확인합니다.

DELETE FROM stock_logs
WHERE log_id = 0;
테이블 생성 권한을 확인합니다.
CREATE TABLE permission_test (
id INT PRIMARY KEY
);
두 SQL 모두 권한 오류로 거부되어야 합니다. 관리자 연결에서 permission_test가 생성되지 않았는지 확인합니다.
SHOW TABLES LIKE 'permission_test';
조회 결과가 없어야 합니다.
▶ 지금 해보세요
inventory_operator로 직접 접속합니다.DELETE와CREATE TABLE이 거부되는지 확인합니다.- 관리자 연결에서
permission_test가 생성되지 않았는지 확인합니다.
✔ 확인 기준: inventory_operator는 DELETE와 DDL을 실행할 수 없고, permission_test 테이블이 생성되지 않았습니다.