스미싱 피해는 문자를 받은 직후 몇 분 안에 결정됩니다. 기존 대응에는 두 가지 한계가 있습니다.
- 판단 근거가 없습니다. "위험합니다"라는 결과만으로는 확신하지 못하고, 결국 링크를 누릅니다.
- 혼자 판단해야 합니다. 정작 피해가 집중되는 고령층일수록 판단이 어렵습니다.
SafeFam은 왜 위험한지 근거를 제시하고, 가족이 함께 개입할 수 있는 경로를 만듭니다.
flowchart TB
subgraph CL["클라이언트"]
direction LR
APP["SafeFam_FE<br/><sub>Flutter</sub>"]
WEB["SafeFam_Web<br/><sub>React · Vercel</sub>"]
end
subgraph AS["애플리케이션 서버"]
direction TB
BE["SafeFam_BE<br/><sub>Spring Boot</sub>"]
PG[("PostgreSQL<br/><sub>Flyway</sub>")]
RD[("Redis")]
end
MQ{{"RabbitMQ<br/><sub>request queue · DLQ</sub>"}}
subgraph AE["분석 엔진"]
AI["SafeFam_AI<br/><sub>FastAPI</sub>"]
end
subgraph EX["외부 연동"]
direction LR
BR["AWS Bedrock"]
VT["VirusTotal"]
GSB["Safe Browsing"]
end
APP -->|REST · JWT| BE
WEB -->|REST · JWT| BE
BE <--> PG
BE <--> RD
BE -->|분석 요청 발행| MQ
MQ -->|소비| AI
AI -->|결과 발행| MQ
MQ -->|소비| BE
BE -->|챗봇 · 동기| AI
AI --> BR & VT & GSB
BE -.->|FCM 푸시| APP
분석은 메시지 큐 기반 비동기입니다. 외부 LLM·평판 조회는 초 단위 지연이 발생하므로 접수(202 Accepted)와 결과 조회를 분리했고, 실패 메시지는 Dead Letter Queue로 격리해 재처리합니다. 챗봇만 응답성이 중요해 동기 호출을 씁니다.
문자 하나를 5개 레이어가 독립 평가하고, 가중 합산으로 최종 위험도를 산출합니다.
flowchart LR
IN["문자 원문"] --> PRE["전처리<br/><sub>정규화 · 난독화 복원<br/>Kiwi 형태소 분석</sub>"]
PRE --> L1["규칙<br/><sub>패턴 · 키워드</sub>"]
PRE --> L2["텍스트 ML<br/><sub>Stacking 앙상블</sub>"]
PRE --> L3["LLM<br/><sub>Bedrock Claude</sub>"]
PRE --> L4["URL<br/><sub>단축 URL 추적 → 평판</sub>"]
PRE --> L5["기관 사칭<br/><sub>공식 도메인 대조</sub>"]
L1 & L2 & L3 & L4 & L5 --> SC["스코어링<br/><sub>가중 합산 · 기여도 분해</sub>"]
SC --> EV["증거 카드 생성"]
EV --> OUT["위험 등급<br/>+ 판단 근거"]
| 레이어 | 방식 | 탐지 대상 |
|---|---|---|
| 규칙 | 정규식·키워드 패턴 | 개인정보 요구, 긴급성 압박, 앱 설치 유도 |
| 텍스트 ML | Naive Bayes · Linear SVM · Logistic Regression을 base로 한 Stacking 앙상블 + 구조적 특징 | 학습된 스미싱 문체·구성 |
| LLM | AWS Bedrock (Claude) | 규칙으로 못 잡는 맥락·의도 |
| URL | 단축 URL 최종 목적지 추적 후 VirusTotal · Google Safe Browsing 조회 | 악성 링크, 리다이렉트 은닉 |
| 기관 사칭 | 발신처 표기와 공식 도메인 레지스트리 대조 | 은행·공공기관 사칭 |
한국어 문자 특성상 조사 변형과 띄어쓰기 오류가 많아, Kiwi 형태소 분석기와 char_wb 문자 n-gram을 함께 사용합니다.
점수만이 아니라 증거 카드로 반환합니다.
{
"riskScore": 87,
"riskLevel": "HIGH",
"category": "FINANCIAL_INSTITUTION",
"evidenceCards": [
{ "category": "INSTITUTION_IMPERSONATION", "title": "기관 사칭",
"description": "국민은행을 언급했지만 공식 도메인이 아닙니다." },
{ "category": "DANGEROUS_URL", "title": "위험 URL",
"description": "단축 URL의 최종 목적지가 위험한 것으로 확인됐습니다." }
],
"failedTracks": []
}sequenceDiagram
autonumber
participant U as 사용자
participant FE as SafeFam_FE
participant BE as SafeFam_BE
participant MQ as RabbitMQ
participant AI as SafeFam_AI
U->>FE: 의심 문자 입력
FE->>BE: POST /api/v1/analyses
BE->>MQ: 분석 요청 발행
BE-->>FE: 202 Accepted (analysisId)
MQ->>AI: 요청 소비
AI->>AI: 5개 레이어 실행
Note over AI: 일부 실패 시<br/>failedTracks에 기록
AI->>MQ: 결과 발행
MQ->>BE: 결과 소비 · 저장
loop 종료 상태까지 폴링
FE->>BE: GET /api/v1/analyses/{id}
BE-->>FE: 진행 중 / 완료 / 부분 성공
end
FE-->>U: 3중 스코어 게이지 + 판단 근거
BE-->>U: 위험 시 보호자에게 FCM 푸시
| 저장소 | 역할 | 핵심 기술 |
|---|---|---|
| SafeFam_FE | 사용자 모바일 앱 | Flutter · FCM · Secure Storage · TTS · QR |
| SafeFam_BE | API 서버, 인증·가족·알림 오케스트레이션 | Spring Boot · JPA · RabbitMQ · Redis |
| SafeFam_AI | 스미싱 탐지 모델·분석 API | FastAPI · scikit-learn · Bedrock |
| SafeFam_Web | 웹 클라이언트 | React 19 · TypeScript · Vite |
| 분류 | 기술 |
|---|---|
| 프레임워크 | Flutter · Dart |
| 인증 | Kakao SDK · flutter_secure_storage |
| 알림 | Firebase Cloud Messaging · flutter_local_notifications |
| 기능 | mobile_scanner (QR 스캔) · qr_flutter · flutter_tts (음성 안내) · share_plus · url_launcher |
| 분류 | 기술 |
|---|---|
| 프레임워크 | Spring Boot 4.1 · Spring MVC · Spring Security |
| 데이터 | PostgreSQL · Spring Data JPA · Flyway (스키마 마이그레이션) · Redis |
| 메시징 | Spring AMQP · RabbitMQ |
| 인증·보안 | JJWT · Bucket4j (Rate Limiting) · Nurigo SDK (SMS 인증) |
| 연동 | Firebase Admin SDK |
| 문서·운영 | SpringDoc OpenAPI (Swagger) · Spring Actuator |
| 분류 | 기술 |
|---|---|
| 프레임워크 | FastAPI · Uvicorn · Pydantic v2 |
| 머신러닝 | scikit-learn 1.8 · NumPy · pandas · SciPy · joblib |
| 자연어 처리 | kiwipiepy (한국어 형태소 분석) |
| LLM | AWS Bedrock (boto3) |
| 메시징 | aio-pika (비동기 AMQP) |
| 외부 API | VirusTotal · Google Safe Browsing (httpx · 재시도 정책) |
| 분류 | 기술 |
|---|---|
| 프레임워크 | React 19 · TypeScript · Vite |
| 스타일 | Tailwind CSS · lucide-react |
| 통신·라우팅 | Axios (JWT 인터셉터 · 토큰 자동 재발급) · React Router 7 |
| 배포 | Vercel |
| 분류 | 기술 |
|---|---|
| 컨테이너 | Docker · Docker Compose (로컬·운영 분리) |
| CI/CD | GitHub Actions |
| 배포 | AWS EC2 · nginx · Vercel |
문자 위험도 분석 — 5개 레이어 종합 판단, 위험 근거 카드와 기여도 분해 제공
가족 보호 — 초대 코드·QR로 보호자–피보호자 연결, 피보호자 탐지 이력 열람, 위험 탐지 시 보호자 푸시
대응 챗봇 — 분석 결과를 컨텍스트로 받는 멀티턴 상담, 지급정지·신고 절차(112 · 118 · 1332) 안내
신뢰 발신자 — 자주 받는 정상 발신처를 화이트리스트로 등록해 오탐 감소
탐지 통계 — 위험 등급·피싱 유형별 분포와 월간 트렌드
외부 장애를 '안전'으로 오인하지 않습니다
외부 LLM이나 평판 API가 실패했을 때 해당 레이어를 0점 처리하면 위험한 문자가 안전해 보입니다. 실패한 레이어를 failedTracks로 응답에 담아 클라이언트가 부분 성공 안전모드로 렌더하고, 어떤 분석이 빠졌는지 알린 뒤 보수적으로 대응하도록 안내합니다.
데이터 누수를 구조적으로 차단합니다
숫자나 URL만 다른 유사 문자가 train과 test에 흩어지면 성능이 과대평가됩니다. 정규화 텍스트의 fingerprint로 완전 중복을 제거하고, 유사 문자를 template group으로 묶어 그룹 단위로 분할합니다. 분할 후 그룹·fingerprint 중복이 0건인지 매 학습마다 검증합니다.
평가셋은 실제 문자로만 구성합니다
합성 데이터로 만든 평가셋에서는 ROC-AUC가 1.0으로 측정됐지만, 같은 모델을 실제 문자에 적용하면 0.72~0.83이었습니다. 합성 데이터는 템플릿에서 생성돼 자명하게 분리되기 때문입니다. 주 평가셋을 실제 문자로 재구성하고, 합성 셋은 오탐 스트레스 테스트 보조 지표로만 사용합니다.
학습 파이프라인이 데이터 변경을 감지합니다
데이터셋 fingerprint, 행 수, split 크기를 상수로 고정하고 학습 시작 시 검증합니다. 데이터가 바뀌면 학습이 중단되어, 의도치 않은 데이터로 만들어진 모델이 배포 경로에 올라가지 않습니다.
프롬프트 인젝션을 전제로 설계합니다
챗봇에 전달되는 분석 컨텍스트와 대화 내용은 신뢰할 수 없는 참고 데이터로 명시합니다. 시스템 프롬프트에 지침 변경 시도를 따르지 않도록 규정하고, 비밀번호·인증번호·계좌번호는 어떤 경우에도 요청하지 않습니다.
개인정보는 저장 전에 제거합니다
문자 원문 마스킹은 서버에서 처리합니다. 모델 평가 보고서에는 원문에서 파생된 문자열을 남기지 않고 재현 가능한 fingerprint만 기록합니다.
| 항목 | 내용 |
|---|---|
| CI | GitHub Actions — 저장소별 테스트·빌드·Docker 이미지 검증 |
| 테스트 | 전 계층 단위·통합 테스트 |
| 코드 리뷰 | CodeRabbit 자동 리뷰 + 팀 리뷰 |
| DB 마이그레이션 | Flyway 버전 관리 |
| API 문서 | SpringDoc OpenAPI |
| 브랜치 전략 | develop 기반, 이슈 단위 브랜치 → PR 병합 |
