[오라클 에러] ORA-28000 계정이 잠겼습니다 - 잠금 해제부터 재발 방지까지 (실무 DBA 정리)
테스트 환경: Oracle 11g / 12c / 19c / 21c
ORA-28000은 운영 DB에서 가장 자주 발생하는 계정 관련 에러입니다. "그냥 풀어주면 끝"이라고 생각하기 쉽지만, 풀어줘도 1분 만에 다시 잠기는 케이스가 운영 환경에선 매우 흔합니다.
이번 글에서는 ORA-28000의 4가지 원인을 분류하고, 잠금 해제뿐 아니라 "왜 잠겼는지" 추적하는 방법, 그리고 재발 방지를 위한 실무 가이드까지 정리했습니다. 특히 자동화 스크립트나 애플리케이션이 잘못된 비밀번호로 계속 시도해서 계정이 매일 잠기는 상황에 대한 진단법은 다른 글에서 잘 다루지 않는 부분입니다.
급하신 분은 빠른 잠금 해제 섹션부터 보세요.
에러 메시지 전문
ORA-28000: the account is locked
ORA-28000: 계정이 잠겼습니다.
SQL*Plus, SQL Developer, JDBC, 애플리케이션 로그 어디에서나 동일하게 발생합니다.
ORA-01017(비밀번호 무효)과 자주 혼동되는데, 둘은 명확히 다릅니다.
- ORA-01017: 비밀번호가 틀렸다 (로그인 자체는 시도 가능)
- ORA-28000: 계정이 잠겨서 시도조차 안 받는다
비밀번호를 맞게 입력해도 ORA-28000은 발생합니다. 보안상의 이유로 잠긴 상태이기 때문입니다.
빠른 잠금 해제 (1분 안에)
급하면 이 두 줄만 실행하면 됩니다.
-- DBA 권한 계정으로 접속해서
ALTER USER scott ACCOUNT UNLOCK;
비밀번호도 만료된 상태라면:
ALTER USER scott IDENTIFIED BY NewPassword2026 ACCOUNT UNLOCK;
하지만 이게 끝이 아닙니다. 실무에서는 "잠긴 원인"을 찾지 않으면 풀어도 또 잠깁니다. 아래 진단 과정을 반드시 거치세요.
ORA-28000 진단 - 왜 잠겼는지부터 확인
잠금 해제 전에 계정 상태와 잠금 시각부터 확인합니다.
SELECT username, account_status, lock_date, expiry_date, profile
FROM dba_users
WHERE UPPER(username) = UPPER('scott');
account_status 컬럼이 알려주는 정보:
상태 의미 원인
| LOCKED | DBA가 수동으로 잠금 | 관리자 잠금 (원인 2) |
| LOCKED(TIMED) | 로그인 실패 횟수 초과로 자동 잠김 | 원인 1 (가장 흔함) |
| EXPIRED & LOCKED | 만료 + 잠금 동시 | 원인 3 |
| EXPIRED(GRACE) & LOCKED | 만료 임박 + 잠금 | 정책 + 시도 실패 |
lock_date는 언제 잠겼는지 알려주는 핵심 정보입니다. 이 시각을 기준으로 로그를 추적하면 누가/무엇이 잘못된 비밀번호로 시도했는지 알 수 있습니다.
< 원인 1> 로그인 실패 횟수 초과 (FAILED_LOGIN_ATTEMPTS)
가장 흔한 원인입니다. 기본 설정은 보안 정책에 따라 3~10회 범위로 다양합니다.
정책 확인
해당 사용자가 어떤 프로파일에 속해 있는지, 그리고 그 프로파일의 잠금 정책이 어떻게 되어 있는지 확인합니다.
-- 1) 사용자가 속한 프로파일 확인
SELECT username, profile
FROM dba_users
WHERE UPPER(username) = UPPER('scott');
-- 2) 프로파일의 잠금 관련 정책 확인
SELECT resource_name, limit
FROM dba_profiles
WHERE profile = 'DEFAULT'
AND resource_name IN ('FAILED_LOGIN_ATTEMPTS', 'PASSWORD_LOCK_TIME');
결과 예시:
RESOURCE_NAME LIMIT
------------------------ --------
FAILED_LOGIN_ATTEMPTS 10
PASSWORD_LOCK_TIME 1
이 의미는 "10번 연속 비밀번호 틀리면, 1일 동안 자동 잠금" 입니다.
PASSWORD_LOCK_TIME에 따른 동작 차이 (★ 실무 함정)
이 두 가지 동작이 미묘하게 다르고, 모르면 진단을 헷갈리게 만듭니다.
- PASSWORD_LOCK_TIME = 숫자(예: 1): 시간 경과 후 자동 해제. account_status는 LOCKED(TIMED)로 표시.
- PASSWORD_LOCK_TIME = UNLIMITED: DBA가 수동으로 풀 때까지 해제 안 됨. account_status는 LOCKED로 표시.
해결 방법
즉시 해제:
ALTER USER scott ACCOUNT UNLOCK;
해제 후 반드시 해야 할 것: 실패한 IP/호스트 추적
해제만 하면 어디선가 잘못된 비밀번호로 계속 시도하는 주체가 똑같이 또 잠급니다. 누가 시도했는지 먼저 찾으세요.
SELECT username, userhost, terminal, os_username,
timestamp, returncode
FROM dba_audit_trail
WHERE username = UPPER('scott')
AND returncode = 1017 -- 로그인 실패 코드
AND timestamp > SYSDATE - 1
ORDER BY timestamp DESC;
returncode = 1017은 비밀번호 틀림으로 인한 실패를 의미합니다. userhost와 terminal로 어느 서버에서 시도했는지 확인하면, 거기 있는 잘못된 비밀번호의 자동화 스크립트나 애플리케이션을 찾을 수 있습니다.
참고: 12c 이상에서 통합 감사(Unified Auditing)를 쓴다면 unified_audit_trail 뷰를 조회하세요. 컬럼은 약간 다르지만 개념은 동일합니다.
< 원인 2> DBA가 수동으로 잠금
명시적 잠금은 다음 명령으로 발생합니다.
ALTER USER scott ACCOUNT LOCK;
어떨 때 발생하나
- 퇴사자 계정 비활성화
- 보안 사고 대응 (의심 계정 차단)
- 임시 점검 (애플리케이션 점검 중 계정 차단)
- 신규 DB 설치 시 기본 잠금 상태 (HR, SCOTT, OUTLN, DBSNMP 등)
진단 방법
dba_users.account_status가 LOCKED이고, lock_date가 NULL이 아니면 누군가 수동으로 잠근 것입니다.
신규 DB의 기본 잠금 계정을 확인하려면:
SELECT username, account_status
FROM dba_users
WHERE account_status LIKE 'LOCKED%'
ORDER BY username;
해결 방법
해제 자체는 동일합니다.
ALTER USER scott ACCOUNT UNLOCK;
다만 수동 잠금은 보통 이유가 있어서 잠근 것이므로, 무작정 풀기 전에 잠근 이유를 확인하세요. 보안 사고 대응 중인 계정을 풀면 큰 문제가 될 수 있습니다.
< 원인 3> 비밀번호 만료 후 grace period 종료
비밀번호 정책에 따라 일정 기간(PASSWORD_LIFE_TIME)이 지나면 비밀번호가 만료되고, 유예 기간(PASSWORD_GRACE_TIME) 안에 변경하지 않으면 계정이 잠깁니다.
진단 방법
SELECT username, account_status, expiry_date
FROM dba_users
WHERE UPPER(username) = UPPER('scott');
-- 정책 확인
SELECT resource_name, limit
FROM dba_profiles
WHERE profile = 'DEFAULT'
AND resource_name IN ('PASSWORD_LIFE_TIME', 'PASSWORD_GRACE_TIME');
account_status가 EXPIRED & LOCKED이면 이 케이스입니다.
해결 방법
-- 새 비밀번호 부여 + 잠금 해제
ALTER USER scott IDENTIFIED BY NewPassword2026 ACCOUNT UNLOCK;
만료된 계정은 비밀번호를 재설정해야 잠금이 풀립니다. 그냥 UNLOCK만 하면 다음 로그인 시 즉시 만료 상태로 돌아갑니다.
< 원인 4> Data Guard Standby 환경의 "보이지 않는 잠금" (★ 고급)
이건 일반 블로그에서 거의 다루지 않는 케이스인데, Active Data Guard 환경에서 운영 중인 분들에게는 필수 지식입니다.
증상
- Primary와 Standby 양쪽 모두 dba_users.account_status = 'OPEN'
- 그런데 Standby에 접속하면 ORA-28000 발생
dba_users만 보면 잠긴 흔적이 없어서 한참 헤매는 케이스입니다.
원인
Active Data Guard는 read-only이기 때문에 표준 DDL로 계정 잠금을 변경할 수 없습니다. 대신 Standby용 메모리 구조에 별도로 잠금 정보가 저장됩니다.
진단 방법 (Standby에서)
SELECT rua.con_id,
du.username,
rua.passw_locked,
rua.passw_lock_unlim,
TO_CHAR(rua.passw_lock_time, 'YYYY-MM-DD HH24:MI:SS') AS locked_date
FROM v$ro_user_account rua, dba_users du
WHERE rua.userid = du.user_id
AND (rua.passw_locked = 1 OR rua.passw_lock_unlim = 1);
이 결과에 행이 나오면 Standby에서 별도로 잠긴 것입니다.
해결 방법
12.1.0.2 이상에서는 Active Data Guard 환경에서도 Standby 직접 잠금 해제가 가능합니다. 자세한 구문은 Oracle MOS(My Oracle Support)에서 환경별로 확인하세요. 단순 환경이라면 Primary에서 잠금 해제 후 Redo Apply를 기다리는 방법이 가장 안전합니다.
재발 방지 - 자동화 스크립트가 매일 계정을 잠그는 케이스 (★ 실무 핵심)
운영 환경에서 가장 골치 아픈 시나리오입니다. DBA가 풀어주면 1분 만에 다시 잠기는 상황.
증상
매일 아침 출근하면 특정 계정이 잠겨 있고, HR 시스템이 안 돌아간다는 연락을 받습니다. 풀어주면 잠시 정상이지만, 어느 순간 또 잠깁니다.
원인
어딘가의 자동화 스크립트, 배치 작업, 또는 애플리케이션 설정에 옛날 비밀번호가 남아있어서, 일정 주기로 잘못된 비밀번호로 접속을 시도하고 있는 것입니다. 흔한 시나리오:
- 비밀번호 변경 후 일부 서버의 설정 파일만 업데이트되고 다른 서버는 빠짐
- 옛 백업 서버의 cron job이 살아있음
- 개발자 PC에 저장된 SQL Developer 연결이 옛 비밀번호로 자동 시도
- 모니터링 도구가 옛 비밀번호 사용
추적 방법
dba_audit_trail에서 최근 24시간 동안 실패한 로그인의 IP/호스트를 모두 추출합니다.
SELECT userhost, terminal, os_username,
COUNT(*) AS fail_count,
MIN(timestamp) AS first_attempt,
MAX(timestamp) AS last_attempt
FROM dba_audit_trail
WHERE username = UPPER('scott')
AND returncode = 1017
AND timestamp > SYSDATE - 1
GROUP BY userhost, terminal, os_username
ORDER BY fail_count DESC;
결과의 userhost(시도한 서버)와 os_username(시도한 OS 계정)을 들고 인프라 팀과 협의해서 그 서버의 설정 파일을 잡아내야 합니다.
임시 우회 (긴급할 때만)
서비스 장애가 너무 심해서 시간을 벌어야 한다면, 해당 사용자만 별도 프로파일에 묶어서 잠금 정책을 풀어둘 수 있습니다.
-- 임시 프로파일 생성
CREATE PROFILE TEMP_NO_LOCK LIMIT
FAILED_LOGIN_ATTEMPTS UNLIMITED
PASSWORD_LIFE_TIME UNLIMITED;
-- 해당 사용자에게 임시 적용
ALTER USER scott PROFILE TEMP_NO_LOCK;
ALTER USER scott ACCOUNT UNLOCK;
경고: 이건 어디까지나 임시 조치입니다. FAILED_LOGIN_ATTEMPTS = UNLIMITED는 무차별 대입 공격을 무방비로 받게 되는 설정이므로, 원인을 찾자마자 원복해야 합니다.
-- 원인 해결 후 원복
ALTER USER scott PROFILE DEFAULT;
DROP PROFILE TEMP_NO_LOCK;
절대 하지 말아야 할 것
블로그를 검색하면 FAILED_LOGIN_ATTEMPTS를 영구적으로 UNLIMITED로 풀어버리는 가이드가 종종 보이는데, 운영 환경에서는 절대 하지 마세요.
-- ★ 운영 환경에서 절대 사용 금지
ALTER PROFILE DEFAULT LIMIT FAILED_LOGIN_ATTEMPTS UNLIMITED;
이 설정은 모든 사용자에게 적용되며, 무차별 대입 공격(brute-force)을 막는 마지노선을 없애는 것입니다. 보안 감사에서도 즉시 지적되는 항목입니다.
권장 정책 (실무 기준)
- FAILED_LOGIN_ATTEMPTS: 5 ~ 10 사이가 일반적
- PASSWORD_LOCK_TIME: 1 (1일) — 너무 짧으면 공격에 취약, 너무 길면 운영 부담
- 자동화 계정은 별도 프로파일로 분리하고, 모니터링 강화
마무리
ORA-28000은 단순 잠금 해제로 끝나는 에러가 아니라, "왜 잠겼는가" 를 추적해야 진짜 해결되는 에러입니다. 풀어도 계속 잠기는 환경이라면 99%는 어딘가의 자동화 스크립트나 옛 설정이 원인이며, dba_audit_trail 추적이 그 답을 알려줍니다.
운영 환경에서는 계정 잠금 이벤트를 모니터링하는 알림 체계를 갖추는 것이 가장 좋은 재발 방지입니다. 잠긴 직후에 알림이 오면 원인 추적이 훨씬 쉬워지기 때문입니다.
비슷한 케이스를 겪으셨거나, Standby 환경에서 까다로운 잠금 이슈가 있다면 댓글로 공유해 주세요. 함께 진단해 보겠습니다.
관련 글
'DBA 실무 > Oracle(오라클)' 카테고리의 다른 글
| [오라클 에러] ORA-01017 사용자명/비밀번호 무효 - 6가지 원인과 해결방법 (12c, 19c, 21c 차이까지) (0) | 2026.05.29 |
|---|---|
| [오라클 에러] ORA-12541 TNS 리스너가 없습니다 - 5가지 원인과 해결방법 (실무 DBA 정리) (1) | 2026.05.28 |
| 오라클(Oracle) 데이터 타입 (0) | 2024.08.12 |