11 KiB
11 KiB
📋 Phase 3.14: AuthService Raw Query 전환 계획
📋 개요
AuthService는 5개의 Prisma 호출이 있으며, 사용자 인증 및 권한 관리를 담당하는 서비스입니다.
📊 기본 정보
| 항목 | 내용 |
|---|---|
| 파일 위치 | backend-node/src/services/authService.ts |
| 파일 크기 | 335 라인 |
| Prisma 호출 | 0개 (이미 Phase 1.5에서 전환 완료) |
| 현재 진행률 | 5/5 (100%) ✅ 전환 완료 |
| 복잡도 | 높음 (보안, 암호화, 세션 관리) |
| 우선순위 | 🟡 중간 (Phase 3.14) |
| 상태 | ✅ 완료 (Phase 1.5에서 이미 완료) |
🎯 전환 목표
- ⏳ 5개 모든 Prisma 호출을
db.ts의query(),queryOne()함수로 교체 - ⏳ 사용자 인증 기능 정상 동작
- ⏳ 비밀번호 암호화/검증 유지
- ⏳ 세션 관리 기능 유지
- ⏳ 권한 검증 기능 유지
- ⏳ TypeScript 컴파일 성공
- ⏳ Prisma import 완전 제거
🔍 예상 Prisma 사용 패턴
주요 기능 (5개 예상)
1. 사용자 로그인 (인증)
- findFirst or findUnique
- 이메일/사용자명으로 조회
- 비밀번호 검증
2. 사용자 정보 조회
- findUnique
- user_id 기준
- 권한 정보 포함
3. 사용자 생성 (회원가입)
- create
- 비밀번호 암호화
- 중복 검사
4. 비밀번호 변경
- update
- 기존 비밀번호 검증
- 새 비밀번호 암호화
5. 세션 관리
- create, update, delete
- 세션 토큰 저장/조회
💡 전환 전략
1단계: 인증 관련 전환 (2개)
- login() - 사용자 조회 + 비밀번호 검증
- getUserInfo() - 사용자 정보 조회
2단계: 사용자 관리 전환 (2개)
- createUser() - 사용자 생성
- changePassword() - 비밀번호 변경
3단계: 세션 관리 전환 (1개)
- manageSession() - 세션 CRUD
💻 전환 예시
예시 1: 로그인 (비밀번호 검증)
변경 전:
async login(username: string, password: string) {
const user = await prisma.users.findFirst({
where: {
OR: [
{ username: username },
{ email: username },
],
is_active: true,
},
});
if (!user) {
throw new Error("User not found");
}
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
throw new Error("Invalid password");
}
return user;
}
변경 후:
async login(username: string, password: string) {
const user = await queryOne<any>(
`SELECT * FROM users
WHERE (username = $1 OR email = $1)
AND is_active = $2`,
[username, true]
);
if (!user) {
throw new Error("User not found");
}
const isPasswordValid = await bcrypt.compare(password, user.password_hash);
if (!isPasswordValid) {
throw new Error("Invalid password");
}
return user;
}
예시 2: 사용자 생성 (비밀번호 암호화)
변경 전:
async createUser(userData: CreateUserDto) {
// 중복 검사
const existing = await prisma.users.findFirst({
where: {
OR: [
{ username: userData.username },
{ email: userData.email },
],
},
});
if (existing) {
throw new Error("User already exists");
}
// 비밀번호 암호화
const passwordHash = await bcrypt.hash(userData.password, 10);
// 사용자 생성
const user = await prisma.users.create({
data: {
username: userData.username,
email: userData.email,
password_hash: passwordHash,
company_code: userData.company_code,
},
});
return user;
}
변경 후:
async createUser(userData: CreateUserDto) {
// 중복 검사
const existing = await queryOne<any>(
`SELECT * FROM users
WHERE username = $1 OR email = $2`,
[userData.username, userData.email]
);
if (existing) {
throw new Error("User already exists");
}
// 비밀번호 암호화
const passwordHash = await bcrypt.hash(userData.password, 10);
// 사용자 생성
const user = await queryOne<any>(
`INSERT INTO users
(username, email, password_hash, company_code, created_at, updated_at)
VALUES ($1, $2, $3, $4, NOW(), NOW())
RETURNING *`,
[userData.username, userData.email, passwordHash, userData.company_code]
);
return user;
}
예시 3: 비밀번호 변경
변경 전:
async changePassword(
userId: number,
oldPassword: string,
newPassword: string
) {
const user = await prisma.users.findUnique({
where: { user_id: userId },
});
if (!user) {
throw new Error("User not found");
}
const isOldPasswordValid = await bcrypt.compare(
oldPassword,
user.password_hash
);
if (!isOldPasswordValid) {
throw new Error("Invalid old password");
}
const newPasswordHash = await bcrypt.hash(newPassword, 10);
await prisma.users.update({
where: { user_id: userId },
data: { password_hash: newPasswordHash },
});
}
변경 후:
async changePassword(
userId: number,
oldPassword: string,
newPassword: string
) {
const user = await queryOne<any>(
`SELECT * FROM users WHERE user_id = $1`,
[userId]
);
if (!user) {
throw new Error("User not found");
}
const isOldPasswordValid = await bcrypt.compare(
oldPassword,
user.password_hash
);
if (!isOldPasswordValid) {
throw new Error("Invalid old password");
}
const newPasswordHash = await bcrypt.hash(newPassword, 10);
await query(
`UPDATE users
SET password_hash = $1, updated_at = NOW()
WHERE user_id = $2`,
[newPasswordHash, userId]
);
}
🔧 기술적 고려사항
1. 비밀번호 보안
import bcrypt from "bcrypt";
// 비밀번호 해싱 (회원가입, 비밀번호 변경)
const SALT_ROUNDS = 10;
const passwordHash = await bcrypt.hash(password, SALT_ROUNDS);
// 비밀번호 검증 (로그인)
const isValid = await bcrypt.compare(plainPassword, passwordHash);
2. SQL 인젝션 방지
// ❌ 위험: 직접 문자열 결합
const sql = `SELECT * FROM users WHERE username = '${username}'`;
// ✅ 안전: 파라미터 바인딩
const user = await queryOne(`SELECT * FROM users WHERE username = $1`, [
username,
]);
3. 세션 토큰 관리
import crypto from "crypto";
// 세션 토큰 생성
const sessionToken = crypto.randomBytes(32).toString("hex");
// 세션 저장
await query(
`INSERT INTO user_sessions (user_id, session_token, expires_at)
VALUES ($1, $2, NOW() + INTERVAL '1 day')`,
[userId, sessionToken]
);
4. 권한 검증
async checkPermission(userId: number, permission: string): Promise<boolean> {
const result = await queryOne<{ has_permission: boolean }>(
`SELECT EXISTS (
SELECT 1 FROM user_permissions up
JOIN permissions p ON up.permission_id = p.permission_id
WHERE up.user_id = $1 AND p.permission_name = $2
) as has_permission`,
[userId, permission]
);
return result?.has_permission || false;
}
✅ 전환 완료 내역 (Phase 1.5에서 이미 완료됨)
AuthService는 Phase 1.5에서 이미 Raw Query로 전환이 완료되었습니다.
전환된 Prisma 호출 (5개)
-
loginPwdCheck()- 로그인 비밀번호 검증- user_info 테이블에서 비밀번호 조회
- EncryptUtil을 활용한 비밀번호 검증
- 마스터 패스워드 지원
-
insertLoginAccessLog()- 로그인 로그 기록- login_access_log 테이블에 INSERT
- 로그인 시간, IP 주소 등 기록
-
getUserInfo()- 사용자 정보 조회- user_info 테이블 조회
- PersonBean 객체로 반환
-
updateLastLoginDate()- 마지막 로그인 시간 업데이트- user_info 테이블 UPDATE
- last_login_date 갱신
-
checkUserPermission()- 사용자 권한 확인- user_auth 테이블 조회
- 권한 코드 검증
주요 기술적 특징
- 보안: EncryptUtil을 활용한 안전한 비밀번호 검증
- JWT 토큰: JwtUtils를 활용한 토큰 생성 및 검증
- 로깅: 상세한 로그인 이력 기록
- 에러 처리: 안전한 에러 메시지 반환
코드 상태
- Prisma import 없음
- query 함수 사용 중
- TypeScript 컴파일 성공
- 보안 로직 유지
📝 원본 전환 체크리스트
1단계: Prisma 호출 전환 (✅ Phase 1.5에서 완료)
login()- 사용자 조회 + 비밀번호 검증 (findFirst)getUserInfo()- 사용자 정보 조회 (findUnique)createUser()- 사용자 생성 (create with 중복 검사)changePassword()- 비밀번호 변경 (findUnique + update)manageSession()- 세션 관리 (create/update/delete)
2단계: 보안 검증
- 비밀번호 해싱 로직 유지 (bcrypt)
- SQL 인젝션 방지 확인
- 세션 토큰 보안 확인
- 중복 계정 방지 확인
3단계: 테스트
- 단위 테스트 작성 (5개)
- 로그인 성공/실패 테스트
- 사용자 생성 테스트
- 비밀번호 변경 테스트
- 세션 관리 테스트
- 권한 검증 테스트
- 보안 테스트
- SQL 인젝션 테스트
- 비밀번호 강도 테스트
- 세션 탈취 방지 테스트
- 통합 테스트 작성 (2개)
4단계: 문서화
- 전환 완료 문서 업데이트
- 보안 가이드 업데이트
🎯 예상 난이도 및 소요 시간
- 난이도: ⭐⭐⭐⭐ (높음)
- 보안 크리티컬 (비밀번호, 세션)
- SQL 인젝션 방지 필수
- 철저한 테스트 필요
- 예상 소요 시간: 1.5~2시간
- Prisma 호출 전환: 40분
- 보안 검증: 40분
- 테스트: 40분
⚠️ 주의사항
보안 필수 체크리스트
- ✅ 모든 사용자 입력은 파라미터 바인딩 사용
- ✅ 비밀번호는 절대 평문 저장 금지 (bcrypt 사용)
- ✅ 세션 토큰은 충분히 길고 랜덤해야 함
- ✅ 비밀번호 실패 시 구체적 오류 메시지 금지 ("User not found" vs "Invalid credentials")
- ✅ 로그인 실패 횟수 제한 (Brute Force 방지)
상태: ⏳ 대기 중
특이사항: 보안 크리티컬, 비밀번호 암호화, 세션 관리 포함
⚠️ 주의: 이 서비스는 보안에 매우 중요하므로 신중한 테스트 필수!