
안녕하세요
벌써 파이썬을 급진적으로 나가게 된 4일차라니..
시작해볼게요...
일단 아침 과제 30분 시작해볼게요..
위치를 가리키는 말
| 찾아볼 말 | 무엇을 알아 오면 되나 (힌트) |
| 폴더 · 디렉터리 | 컴퓨터에서 파일을 보관하고 정리하는 동일한 공간을 의미하지만, 사용되는 맥락과 관점에서 약간의 차이가 있다. 폴더는 시각적인 개념으로 윈도우나 맥 등 그래픽 화면에서 일반 사용자가 이해하기 쉽도록 만든 단어이고, 디렉토리는 기술적인 개념으로 리눅스나 명령 프롬프트 등 텍스트 화면에서 개발자나 시스템 관리자가 사용하는 단어이다. |
| 절대경로 | 파일 시스템의 최상위 루트 디렉터리(/ 또는 C:\) 부터 시작하여 특정 파일이나 폴더까지의 전체 파일 위치를 빠짐없이 컴퓨터에게 알려주는 주소, 항상 동일한 파일을 가리킴. |
| 상대경로 | 현재 작업 중인 파일이나 디렉터리(현재 위치)를 기준으로 목적지 파일의 위치를 표현하는 주소 |
| 홈 디렉터리 | 운영체제에서 로그인한 개별 사용자(User)의 전용 폴더, 기호 '~' |
| . 과 .. | . 은 현재 작업 중인 폴더를 의미 .. 은 현재 폴더 바로 한 단계 위에 있는 상위 폴더를 의미 둘 다 상대 경로 시스템의 기호이다. |
| / 와 \ | 컴퓨터 운영체제에 따라 경로를 구분하는 슬래시의 방향이 다르다. 윈도우는 백슬래시(\)를 사용하고, 맥이나 리눅스는 슬래시(/)를 주로 사용한다. |
명령 이름의 뜻
| 명령 | 무엇의 줄임말인지 찾아봅니다 |
| pwd | Print Working Directory |
| ls | List |
| cd | Change Directory |
| mkdir | Make Directory |
명령 한 줄의 구조
# 명령은 이렇게 생겼습니다.
mkdir docs 명령 + 대상
ls -a 명령 + 옵션
cd .. 명령 + 대상(한 칸 위)
정리한 것을 보지 않고 답해 봅니다. 막히면 위로 돌아가 다시 읽습니다.
- 지금 있는 폴더를 화면에 띄우는 명령은 무엇입니까?
- pwd
- cd .. 를 입력하면 어디로 갑니까?
- 현재 있는 디렉토리보다 상위 디렉토리
- /c/Users/hong 을 윈도우 탐색기 주소창에 적으면 어떤 모양이 됩니까?
- c:\Users\hong
docs 안에 있는 상태에서, 홈으로 돌아가지 않고 agent_core 로 바로 이동해 봅니다. .. 를 쓰면 됩니다. 성공하면 그때 입력한 명령을 기록 파일에 적어 둡니다.
User@ ~/security-agent-toolkit/docs
$ cd ../agent_core/
모닝30분.. 끝..!
Python 정규표현식, 탐지 룰, API와 HTTP 정리
오늘은 로그 데이터를 단순히 읽는 수준을 넘어, 비정형 로그에서 원하는 값을 찾아 정형 데이터로 바꾸고, 그 데이터를 이용해 수상한 행동을 탐지하는 방법을 배웠다.
오후에는 파일 안의 정보만 보는 것을 넘어 외부 서비스와 데이터를 주고받기 위한 API와 HTTP의 기본 구조, 그리고 상태코드와 인증·인가까지 학습했다.
오늘 오전의 순서
- split()이 조용히 틀리는 이유
- 정규표현식 기호
- re.search, re.findall
- 그룹과 named group
- 안 맞는 줄 따로 남기기
- 탐지 룰 ① 브루트포스
- 탐지 룰 ② 의심 IP
1. 정형 로그와 비정형 로그
정형 로그
정형 로그는 데이터의 구조가 미리 정해져 있다.
예를 들어 다음처럼 쉼표 기준으로 각 항목이 명확하게 나뉜다.
09:01,kim01,LOGIN_OK,10.0.3.21
구조를 보면 다음과 같다.
위치값
| 0 | 시간 |
| 1 | 사용자 |
| 2 | 이벤트 |
| 3 | IP |
이런 데이터는 split(",")을 이용하기 쉽다.
비정형 로그
비정형 로그는 표처럼 열과 행이 정확히 나뉘어 있지 않고, 한 줄의 텍스트 형태로 기록된다.
2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5
사람이 보면 시간, 로그 레벨, 사용자, IP를 알 수 있지만 프로그램 입장에서는 그냥 하나의 문자열이다.
그래서 원하는 값을 몇 번째 칸에 있는가가 아니라 어떤 모양을 가지고 있는가로 찾아야 한다.
이때 사용하는 것이 정규표현식이다.
2. 정규표현식이란?
정규표현식(Regular Expression) 은 문자열 속에서 원하는 패턴을 찾기 위한 규칙이다.
기존 방식이
몇 번째 위치에 있는 값인가?
를 기준으로 찾는 방식이었다면,
정규표현식은
숫자인가?
특정 단어 주변에 있는가?
특정 형태를 가지고 있는가?
를 기준으로 값을 찾는다.
즉, 위치가 아니라 모양으로 찾는 방법이다.
3. split()은 왜 위험할까?
다음 로그가 있다고 하자.
line = "2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5"
parts = line.split()
print(parts)
결과는 다음과 같다.
[
'2026-09-29',
'09:12:00',
'WARN',
'failed',
'login',
'for',
'kim01',
'from',
'203.0.113.5'
]
따라서 사용자 이름은 다음처럼 꺼낼 수 있다.
print(parts[6])
결과:
kim01
문제는 로그 문장의 형태가 조금만 달라져도 발생한다.
예를 들어 다음 로그가 있다고 하자.
2026-09-29 09:12:00 WARN failed user login for kim01 from 203.0.113.5
중간에 user라는 단어 하나가 추가됐다.
그러면 모든 위치가 한 칸씩 밀린다.
그런데 파이썬은 오류를 내지 않는다.
그냥 다른 값을 꺼낸다.
이것이 더 위험하다.
프로그램이 멈추는 오류보다, 틀린 값을 정상 값처럼 처리하는 오류가 더 찾기 어렵다.
4. 정규표현식에서 자주 쓰는 기호
파이썬에서 정규표현식을 사용하려면 re 모듈을 불러온다.
import re
오늘 사용한 주요 기호는 다음과 같다.
기호의미예
| \d | 숫자 한 글자 | 3, 9 |
| \w | 문자·숫자·밑줄 한 글자 | a, 7, _ |
| [ ] | 대괄호 안 문자 중 하나 | [\d.] |
| + | 바로 앞 패턴이 1개 이상 반복 | \d+ |
| . | 아무 글자 한 글자 | a, 1, - 등 |
| {1,3} | 앞 패턴이 1~3개 반복 | \d{1,3} |
5. \d와 +
\d는 숫자 한 글자를 의미한다.
\d
예를 들어 다음 문자열에서
실패 3회
\d는 3을 찾을 수 있다.
여러 자리 숫자를 찾으려면 +를 붙인다.
\d+
예를 들어
실패 123회
에서 123 전체를 찾는다.
+는 왼쪽 패턴을 하나 이상 계속 이어서 찾는다는 의미다.
6. [\d.]의 의미
다음 패턴을 보자.
r"[\d.]+"
[\d.]는
숫자 또는 점
한 글자를 의미한다.
여기에 +가 붙었기 때문에 숫자나 점이 이어지는 동안 계속 찾는다.
따라서 다음 IP를 찾는 데 사용할 수 있다.
203.0.113.5
7. raw string의 r
정규표현식 앞에는 보통 r을 붙인다.
r"\d+"
이것을 raw string이라고 한다.
정규표현식에는 \d, \w처럼 역슬래시를 많이 사용한다.
r을 붙이면 파이썬이 역슬래시를 먼저 해석하지 않고 정규표현식에 그대로 전달한다.
따라서 정규표현식을 작성할 때는 다음처럼 쓰는 습관이 좋다.
re.search(r"\d+", text)
8. re.search()
re.search()는 문자열 안에서 처음으로 패턴과 맞는 부분 하나를 찾는다.
import re
m = re.search(r"\d+", "확인 필요: admin 실패 3회")
print(m.group())
결과:
3
re.search() 자체는 찾은 정보를 담고 있는 객체를 반환한다.
실제 문자열을 꺼내려면 .group()을 사용한다.
m.group()
9. re.findall()
re.findall()은 패턴에 맞는 값을 전부 찾아 리스트로 돌려준다.
import re
numbers = re.findall(r"\d+", "실패 3회, 성공 2회")
print(numbers)
결과:
['3', '2']
결과가 하나만 있어도 리스트다.
['3']
찾는 값이 없으면 빈 리스트가 나온다.
[]
10. re.search와 re.findall 비교
함수결과
| re.search() | 처음 맞는 것 하나 |
| re.findall() | 맞는 것 전부 |
| re.search() 실패 | None |
| re.findall() 실패 | [] |
11. 그룹(Group)이란?
정규표현식으로 한 줄 전체를 찾는 것만으로는 부족하다.
다음 로그에서
2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5
우리가 원하는 것은 각각 다음 값이다.
- 시간
- 로그 레벨
- 사용자
- IP
이처럼 정규표현식 안에서 따로 꺼내고 싶은 부분을 소괄호로 묶는 것을 그룹이라고 한다.
12. 그룹 번호로 꺼내기
다음처럼 괄호를 사용할 수 있다.
pattern = r"(\d+)회"
m = re.search(pattern, "실패 3회")
print(m.group(1))
결과:
3
첫 번째 괄호는 group(1)이다.
하지만 그룹이 많아지면
1번이 뭐였지?
2번이 뭐였지?
3번이 IP였나?
처럼 헷갈릴 수 있다.
그래서 이름 붙인 그룹(named group) 을 사용한다.
13. named group
이름 붙인 그룹은 다음 형태를 사용한다.
(?P<이름>패턴)
예를 들어 사용자라면
(?P<user>[\w.]+)
와 같이 작성한다.
14. 비정형 로그 한 줄에서 네 값 꺼내기
다음 로그를 사용해 보자.
line = "2026-09-29 09:12:00 WARN failed login for kim01 from 203.0.113.5"
패턴은 다음과 같다.
PATTERN = r"(?P<time>[\d:]+) (?P<level>\w+) \w+ login for (?P<user>[\w.]+) from (?P<ip>[\d.]+)"
찾는다.
m = re.search(PATTERN, line)
사용자를 꺼내면
print(m.group("user"))
결과:
kim01
15. groupdict()
이름을 붙인 그룹을 한 번에 딕셔너리로 만들 수 있다.
print(m.groupdict())
결과:
{
'time': '09:12:00',
'level': 'WARN',
'user': 'kim01',
'ip': '203.0.113.5'
}
즉,
비정형 문자열 한 줄
↓
정규표현식
↓
딕셔너리 한 개
로 바뀐다.
이 과정이 로그 정규화에서 매우 중요하다.
16. 괄호 밖의 문자열은 왜 필요한가?
패턴을 다시 보자.
PATTERN = r"(?P<time>[\d:]+) (?P<level>\w+) \w+ login for (?P<user>[\w.]+) from (?P<ip>[\d.]+)"
여기서
login for
from
은 그룹이 아니다.
따라서 결과 딕셔너리에는 나오지 않는다.
하지만 해당 문장이 실제 로그 안에 존재해야 전체 패턴이 맞는다.
즉,
괄호 안 = 가져올 값
괄호 밖 = 패턴이 맞는지 판단하기 위한 조건
이라고 이해하면 된다.
17. 못 찾으면 None
re.search()는 패턴을 못 찾으면 None을 반환한다.
m = re.search(r"\d+", "숫자가 없는 문장")
print(m)
결과:
None
이 상태에서 다음 코드를 실행하면 오류가 난다.
m.group()
따라서 항상 먼저 확인해야 한다.
if m:
print(m.group())
18. 안 맞는 줄을 버리지 않는 이유
정규표현식과 맞지 않는 로그가 있다고 해서 그냥 버리면 문제가 있다.
그 로그가
- 원래 다른 종류의 정상 로그인지
- 정규표현식 패턴이 틀린 것인지
- 로그 자체가 깨진 것인지
알 수 없기 때문이다.
그래서 안 맞는 로그는 따로 남겨 두는 것이 좋다.
예:
unmatched_logs.txt
이렇게 하면 나중에 패턴을 수정하거나 분석할 수 있다.
19. 오전 정규화 흐름
오전까지의 전체 흐름은 다음과 같다.
비정형 로그
↓
re.search()
↓
패턴 일치?
↓
Yes → groupdict() → 딕셔너리
No → unmatched_logs.txt
↓
normalized_logs.json
20. 탐지 룰이란?
정규화가 끝났다면 이제 데이터를 이용해 수상한 행동을 찾아야 한다.
탐지 룰은
이런 형태가 나타나면 수상하다고 판단한다
라는 기준을 코드로 만든 것이다.
보안 관제에서는 로그를 단순히 저장하는 것이 목적이 아니라,
어떤 행동이 이상한가?
를 찾는 것이 중요하다.
21. 브루트포스란?
브루트포스(Brute Force) 는 비밀번호나 인증 정보를 알아내기 위해 가능한 값을 반복적으로 시도하는 공격이다.
로그에서는 같은 계정에 대해 로그인 실패가 반복되는 형태로 나타날 수 있다.
예:
admin LOGIN_FAIL
admin LOGIN_FAIL
admin LOGIN_FAIL
admin LOGIN_FAIL
22. 임계값
몇 번부터 공격으로 볼지는 프로그램이 스스로 결정하지 않는다.
사람이 정한다.
예를 들어
THRESHOLD = 3
이라면
로그인 실패가 3회 이상이면 확인 필요
라는 규칙이 된다.
이렇게 상태가 달라지는 경계값을 임계값이라고 한다.
23. 룰 ① 브루트포스 — 계정별 실패 횟수
먼저 딕셔너리를 이용해 값을 세는 방법을 다시 보자.
fruits = ["사과", "배", "사과"]
count = {}
for name in fruits:
if name in count:
count[name] = count[name] + 1
else:
count[name] = 1
print(count)
결과:
{
'사과': 2,
'배': 1
}
로그에서도 같은 방법을 사용할 수 있다.
count = {}
for row in rows:
if row["level"] == "WARN":
user = row["user"]
if user in count:
count[user] = count[user] + 1
else:
count[user] = 1
이후 임계값과 비교한다.
THRESHOLD = 3
for user in count:
if count[user] >= THRESHOLD:
print(user, count[user])
24. 룰 ①의 시선
룰 ①은 사용자 계정 기준으로 본다.
admin → 몇 번 실패했는가?
kim01 → 몇 번 실패했는가?
lee02 → 몇 번 실패했는가?
즉,
한 계정을 얼마나 여러 번 두드렸는가?
를 보는 것이다.
25. 룰 ② 의심 IP
이번에는 시선을 반대로 바꾼다.
한 IP가 여러 계정을 시도한다면 수상할 수 있다.
예:
203.0.113.5 → admin
203.0.113.5 → kim01
203.0.113.5 → lee02
하나의 IP가 여러 계정을 건드리고 있다.
26. 딕셔너리 안에 리스트 넣기
다음 데이터를 보자.
pairs = [
("A", "kim"),
("A", "lee"),
("B", "kim")
]
목표는 다음처럼 만드는 것이다.
{
'A': ['kim', 'lee'],
'B': ['kim']
}
코드는 다음과 같다.
table = {}
for key, name in pairs:
if key in table:
if name not in table[key]:
table[key].append(name)
else:
table[key] = [name]
27. 같은 계정을 두 번 넣지 않는 이유
다음처럼 같은 계정에 여러 번 로그인했다고 하자.
IP A → kim
IP A → kim
IP A → kim
이것을 그대로 세면 계정 수가 3개처럼 보인다.
하지만 실제로는 하나의 계정만 시도했다.
그래서 다음 조건을 사용한다.
if name not in table[key]:
table[key].append(name)
즉, 서로 다른 계정만 저장한다.
28. 룰 ②의 시선
룰 ②는 IP 기준이다.
IP A
├─ admin
├─ kim
└─ lee
한 IP에서 시도한 계정 수가 일정 개수 이상이라면 의심할 수 있다.
예:
if user_count >= 2:
print("의심 IP")
이 2 역시 사람이 정한 임계값이다.
실제 환경에서는 정상 사용 패턴을 분석해서 조정해야 한다.
29. 탐지 룰 ①② 비교
룰기준무엇을 보는가
| ① 브루트포스 | 계정 | 같은 계정의 반복 실패 |
| ② 의심 IP | IP | 하나의 IP가 여러 계정 시도 |
오늘 오후의 순서
- 탐지 룰 ③ 심야 성공 접속
- 세 룰 한 번에 실행하기
- API란 무엇인가
- URL 구조
- HTTP 메서드
- 상태코드
- 401과 403
- API 키와 토큰
30. 룰 ③ 심야 성공 접속
오전의 룰 ①과 ②는 모두 실패를 기준으로 했다.
그런데 보안에서는 실패보다 성공이 더 위험한 경우도 있다.
예를 들어 공격자가 다음처럼 여러 번 로그인에 실패했다고 하자.
03:12 LOGIN_FAIL
03:13 LOGIN_FAIL
03:15 LOGIN_FAIL
03:17 LOGIN_OK
앞의 실패는 막힌 것이다.
하지만 마지막 성공은 실제로 계정에 들어간 것이다.
따라서 비정상적인 시간대의 로그인 성공을 탐지해야 한다.
31. 심야 접속을 의심하는 이유
심야 접속 자체가 반드시 공격이라는 뜻은 아니다.
하지만 일반적인 업무 시간대와 크게 다른 시간의 로그인은 정상 행동 패턴에서 벗어날 수 있다.
따라서 탐지 대상으로 삼을 수 있다.
예를 들어 오늘은
NIGHT_END = 6
으로 정하고
오전 6시 이전의 로그인 성공
을 탐지한다.
이 역시 환경에 따라 달라질 수 있는 임계값이다.
32. 문자열 시간에서 시(hour) 꺼내기
다음 시간 문자열이 있다고 하자.
time = "03:17:09"
콜론 기준으로 나눈다.
print(time.split(":"))
결과:
['03', '17', '09']
0번째 값이 시간이다.
hour = time.split(":")[0]
현재는 문자열이므로 숫자로 변환한다.
hour = int(time.split(":")[0])
결과:
3
33. 심야인지 확인하기
NIGHT_END = 6
if hour < NIGHT_END:
print("심야 접속")
hour가 3이라면 조건이 참이다.
34. 성공 로그인까지 함께 확인하기
심야라고 해서 모든 로그가 위험한 것은 아니다.
로그인 성공일 때만 확인한다.
예:
if row["level"] == "INFO" and hour < NIGHT_END:
print("심야 로그인 성공")
즉,
성공 로그인
AND
06시 이전
두 조건을 모두 만족해야 한다.
35. 세 탐지 룰 한눈에 보기
룰무엇을 본다예시 기준
| ① 브루트포스 | 계정별 로그인 실패 | 3회 이상 |
| ② 의심 IP | IP별 시도한 계정 수 | 2개 이상 |
| ③ 심야 접속 | 심야 로그인 성공 | 06시 이전 |
중요한 점은 세 룰 모두 같은 로그 데이터 rows를 다른 시각으로 보는 것이라는 점이다.
36. 기준값은 코드 위에 모아 두기
임계값을 코드 안 여기저기에 직접 적지 않는 것이 좋다.
다음처럼 맨 위에 모아 둔다.
BRUTE_FORCE_THRESHOLD = 3
SUSPICIOUS_IP_THRESHOLD = 2
NIGHT_END = 6
그러면 기준을 바꿀 때 한 곳만 수정하면 된다.
37. 오전 산출물
오전 작업을 완료하면 다음과 같은 파일을 만들 수 있다.
agent_core/
├─ normalize_logs.py
├─ normalized_logs.json
└─ unmatched_logs.txt
각 역할은 다음과 같다.
파일역할
| normalize_logs.py | 비정형 로그를 분석하고 정규화 |
| normalized_logs.json | 정규화된 로그 저장 |
| unmatched_logs.txt | 패턴과 맞지 않은 로그 저장 |
38. API란?
지금까지는 내가 가지고 있는 파일 안의 데이터만 사용했다.
하지만 로그에 이런 IP가 있다고 하자.
185.220.101.34
이 IP가 실제로 위험한 IP인지 여부는 우리 로그 파일만으로는 알 수 없다.
외부 IP 분석 서비스에 물어봐야 한다.
이때 사용하는 것이 API다.
39. API를 쉽게 이해하면
API는
내 프로그램과 다른 프로그램이 대화하기 위해 미리 정해 둔 공식 창구
라고 생각하면 된다.
식당으로 비유하면 다음과 같다.
손님
↓
메뉴판
↓
주문
↓
주방
↓
음식
프로그램에서는
내 프로그램
↓
API 요청
↓
외부 서버
↓
API 응답
이 된다.
40. HTTP란?
API에서 요청과 응답을 주고받을 때 많이 사용하는 규칙이 HTTP다.
기본 구조는 다음과 같다.
클라이언트
↓ 요청(Request)
서버
↓ 응답(Response)
클라이언트
오늘은 실제 HTTP 요청을 보내는 것은 아니고, 요청과 응답의 구조를 이해하는 단계다.
실제로 요청을 보내는 requests는 다음 단계에서 사용한다.
41. API 응답은 문자열로 올 수 있다
서버에서 다음과 같은 JSON 문자열이 왔다고 하자.
text = '{"city": "seoul", "count": 3}'
현재는 문자열이다.
따라서 바로 딕셔너리처럼 사용할 수 없다.
json.loads()를 사용한다.
import json
data = json.loads(text)
이제 딕셔너리가 된다.
print(data["city"])
결과:
seoul
숫자도 원래 자료형으로 변환된다.
print(data["count"] + 1)
결과:
4
42. URL 구조
다음 주소를 보자.
https://api.example.com/v1/ip/185.220.101.34?full=true
URL은 크게 네 부분으로 볼 수 있다.
부분예의미
| 방식 | https:// | 어떤 방식으로 통신할지 |
| 호스트 | api.example.com | 어느 서버에 요청할지 |
| 경로 | /v1/ip/185.220.101.34 | 서버 안의 어느 기능인지 |
| 질의 | ?full=true | 추가 조건 |
43. HTTPS
주소 앞의
https://
는 HTTPS 방식을 사용한다는 뜻이다.
HTTPS는 통신 내용을 암호화해 전송한다.
즉, HTTP보다 안전하게 데이터를 주고받기 위한 방식이다.
44. 호스트
다음 부분은 호스트다.
api.example.com
쉽게 말하면
어느 서버에게 요청을 보낼 것인가?
를 나타낸다.
45. 경로
다음 부분은 경로다.
/v1/ip/185.220.101.34
같은 서버에서도 여러 기능을 제공할 수 있기 때문에 경로로 원하는 기능을 구분한다.
여기서는 특정 IP 정보를 조회하는 경로라고 볼 수 있다.
46. 질의(Query)
다음 부분은 질의다.
?full=true
? 뒤에 추가 조건을 적는다.
조건이 여러 개라면 &로 연결한다.
?full=true&lang=ko
47. HTTP 메서드
같은 URL이라도 무엇을 하려는지 표시해야 한다.
그 역할을 하는 것이 HTTP 메서드다.
대표적인 두 가지는 다음과 같다.
메서드의미
| GET | 데이터를 조회 |
| POST | 새로운 데이터를 생성 |
48. GET
GET은 데이터를 읽거나 조회할 때 사용한다.
예를 들어 IP 정보를 조회한다면 GET이 적합하다.
GET /ip/185.220.101.34
일반적인 조회 요청은 같은 요청을 다시 보내도 데이터를 하나 더 생성하지 않는다.
49. POST
POST는 새로운 데이터를 만들거나 서버에 데이터를 제출할 때 많이 사용한다.
예:
- 회원가입
- 주문 생성
- 게시글 작성
같은 요청을 여러 번 보내면 데이터가 중복 생성될 수 있다.
따라서 GET과 성격이 다르다.
50. GET과 POST 비교
구분GETPOST
| 주요 목적 | 조회 | 생성·제출 |
| 예 | 검색, IP 조회 | 회원가입, 주문 |
| 반복 요청 | 일반적으로 같은 정보 조회 | 중복 생성 가능 |
51. 상태코드란?
HTTP 요청을 보내면 서버는 결과와 함께 숫자를 돌려준다.
이 숫자를 HTTP 상태코드라고 한다.
예:
200
404
500
세 자리 숫자의 첫 번째 자리만 봐도 대략적인 상태를 알 수 있다.
52. 상태코드 앞자리
앞자리의미대표 예
| 2 | 성공 | 200, 201 |
| 4 | 요청한 쪽 문제 | 401, 403, 404 |
| 5 | 서버 문제 | 500 |
예를 들어 상태코드가 다음과 같다면
code = 404
문자열로 바꾼다.
str(code)
결과:
"404"
첫 번째 글자를 꺼낸다.
print(str(code)[0])
결과:
4
53. 주요 상태코드
코드의미
| 200 | 요청 성공 |
| 201 | 새로운 데이터 생성 성공 |
| 401 | 인증 필요 또는 인증 실패 |
| 403 | 인증됐지만 권한 없음 |
| 404 | 요청한 자원을 찾을 수 없음 |
| 500 | 서버 내부 오류 |
54. 인증과 인가
401과 403을 이해하려면 인증과 인가의 차이를 알아야 한다.
인증(Authentication)
당신이 누구인가?
를 확인하는 과정이다.
예:
아이디 + 비밀번호
토큰
API 키
인가(Authorization)
확인된 당신에게 이 기능을 사용할 권한이 있는가?
를 확인하는 과정이다.
즉,
인증
↓
누구인지 확인
↓
인가
↓
무엇을 할 수 있는지 확인
순서로 이해할 수 있다.
55. 401과 403의 차이
둘 다 요청이 거부되는 상태지만 이유가 다르다.
비교401403
| 의미 | 누구인지 확인하지 못함 | 누구인지는 알지만 권한 없음 |
| 문제 | 인증 | 인가 |
| 예 | 토큰 만료 | 관리자 기능 접근 |
| 대응 | 다시 인증 | 권한 확인·요청 |
쉽게 말하면 다음과 같다.
401
"누구세요?"
403
"누군지는 아는데 들어갈 수 없습니다."
56. API 키
API를 이용할 때 사용자를 확인하기 위해 API 키를 사용할 수 있다.
API 키는 서비스에서 발급해 주는 긴 문자열이다.
요청마다 이 값을 함께 보낸다.
장점은 간단하다는 것이다.
하지만 키가 유출되면 직접 변경하기 전까지 계속 악용될 수 있기 때문에 관리가 중요하다.
57. 토큰
토큰도 사용자를 확인하기 위해 사용하는 문자열이다.
API 키와 비슷해 보이지만 보통 유효 기간이 있다.
예를 들어 로그인 후 토큰을 발급받고 일정 시간이 지나면 만료된다.
따라서 토큰이 유출되더라도 피해 시간이 제한될 수 있다.
58. Authorization 헤더
토큰은 보통 HTTP 요청의 헤더에 넣는다.
대표적인 형태가 다음이다.
headers = {
"Authorization": "Bearer " + token
}
실제 전송 형태는 다음과 비슷하다.
Authorization: Bearer abcdef123456...
59. API 키와 토큰 비교
방식특징주의점
| API 키 | 발급받은 긴 문자열을 요청마다 사용 | 유출되면 교체 전까지 사용 가능 |
| 토큰 | 인증 후 발급받아 사용 | 유효 기간 관리 필요 |
60. 오늘 전체 흐름
오늘 배운 내용을 하나로 연결하면 다음과 같다.
비정형 로그
↓
정규표현식
↓
named group
↓
딕셔너리
↓
정규화
↓
normalized_logs.json
↓
탐지 룰 실행
┌──────────────┬──────────────┬──────────────┐
│ │ │
브루트포스 의심 IP 심야 성공 접속
│ │ │
└──────────────┴──────────────┘
↓
경보
↓
외부 정보가 필요하면
↓
API
↓
HTTP 요청 / 응답
↓
상태코드 · 인증 · 인가 확인
핵심 정리
정규표현식
정규표현식은 문자열의 위치가 아니라 모양을 이용해 원하는 값을 찾는 방법이다.
re.search()
는 처음 맞는 값 하나를 찾고,
re.findall()
은 맞는 값을 모두 찾는다.
named group을 사용하면 비정형 문자열을 바로 딕셔너리 형태로 바꿀 수 있다.
(?P<user>...)
m.groupdict()
탐지 룰
정규화된 로그를 여러 관점에서 분석하면 서로 다른 이상 행동을 찾을 수 있다.
룰관점
| 브루트포스 | 계정별 실패 횟수 |
| 의심 IP | IP별 접근 계정 수 |
| 심야 접속 | 비정상 시간대의 로그인 성공 |
탐지 기준은 절대적인 값이 아니라 실제 환경에 맞게 조정하는 임계값이다.
API와 HTTP
내 파일에 없는 정보를 얻으려면 외부 프로그램에 물어봐야 한다.
API는 다른 프로그램에 요청하기 위한 공식 창구이고, HTTP는 요청과 응답을 주고받는 규칙이다.
내 프로그램
↓
HTTP 요청
↓
API
↓
외부 서버
↓
HTTP 응답
상태코드
HTTP 응답의 상태코드를 보면 요청 결과를 빠르게 판단할 수 있다.
2xx → 성공
4xx → 요청 쪽 문제
5xx → 서버 문제
특히
401 → 인증 문제
403 → 권한 문제
라는 차이를 구분하는 것이 중요하다.
와
문제가 계속있어요진짜
저는 오늘도 문제한테 졌습니다..
그럼 아됴
'공부 > SKT ALEPH' 카테고리의 다른 글
| AI·자동화 기초(에이전트·API·스크립팅) - 3 (0) | 2026.09.28 |
|---|---|
| AI·자동화 기초(에이전트·API·스크립팅) - 2 (0) | 2026.09.23 |
| AI·자동화 기초(에이전트·API·스크립팅) - 1 (0) | 2026.09.22 |


