인증 기술 명세서
1. 인증 서비스 구조
1.1 인증 서비스 인터페이스
@Injectable()
export class AuthenticationService {
constructor(
private readonly userService: UserService,
private readonly tokenService: TokenService,
private readonly securityPolicyService: SecurityPolicyService,
private readonly pubSubClient: PubSubClient,
) {}
async login(
email: string,
password: string,
deviceInfo: DeviceInfo
): Promise<AuthResult> {
// 구현 세부사항
}
async validateToken(token: string): Promise<TokenPayload> {
// 구현 세부사항
}
async refreshToken(refreshToken: string): Promise<AuthResult> {
// 구현 세부사항
}
async logout(token: string): Promise<void> {
// 구현 세부사항
}
async changePassword(
userId: string,
oldPassword: string,
newPassword: string
): Promise<void> {
// 구현 세부사항
}
}
1.2 인증 결과 DTO
export class AuthResult {
@ApiProperty()
accessToken: string;
@ApiProperty()
refreshToken: string;
@ApiProperty()
expiresIn: number;
@ApiProperty()
tokenType: string = 'Bearer';
@ApiProperty({ type: UserDto })
user: UserDto;
}
1.3 인증 요청 DTO
export class LoginDto {
@ApiProperty()
@IsEmail()
email: string;
@ApiProperty()
@IsString()
@MinLength(8)
password: string;
@ApiProperty({ type: DeviceInfoDto })
@ValidateNested()
@Type(() => DeviceInfoDto)
deviceInfo: DeviceInfoDto;
}
2. 인증 프로세스
2.1 로그인 프로세스
async login(email: string, password: string, deviceInfo: DeviceInfo): Promise<AuthResult> {
// 1. 사용자 조회
const user = await this.userService.findByEmail(email);
if (!user) {
throw new UnauthorizedException('Invalid credentials');
}
// 2. 계정 상태 확인
if (user.status !== UserStatus.ACTIVE) {
throw new UnauthorizedException('Account is not active');
}
// 3. 비밀번호 검증
const isValid = await this.validatePassword(password, user.password);
if (!isValid) {
await this.handleFailedLogin(user);
throw new UnauthorizedException('Invalid credentials');
}
// 4. 토큰 발급
const tokens = await this.tokenService.generateTokens(user, deviceInfo);
// 5. GCP Pub/Sub을 통한 이벤트 발행
const messageId = uuidv4();
const message = {
eventId: messageId,
eventType: 'user-authenticated',
timestamp: new Date().toISOString(),
data: {
userId: user.id,
deviceInfo,
}
};
const messageBuffer = Buffer.from(JSON.stringify(message));
await this.pubSubClient.topic('auth-events').publish(messageBuffer);
return {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresIn: tokens.expiresIn,
tokenType: 'Bearer',
user: UserDto.fromEntity(user),
};
}
2.1.1 PIN 코드를 활용한 로컬 인증 프로세스
PIN 코드는 규제 준수(ISO27001, GDPR, DiGA)를 위해 서버로 전송되거나 서버에 저장되지 않습니다. 대신, 모바일 디바이스 내에서 안전하게 처리됩니다.
PIN 코드 설정 흐름
- 사용자가 최초 로그인 또는 회원가입 후 PIN 코드 설정
- 입력된 PIN 코드로 생성된 SHA256 해시값을 안전한 저장소 키값으로 사용 (iOS: 키체인, Android: DataStore)
- 사용자 비밀번호를 SHA256으로 Hasing 해서 저장
- Hash된 인증 정보를 디바이스의 안전한 저장소에 저장 (iOS Keychain, Android Keystore 등)
- PIN 코드 자체는 저장하지 않음 (매번 사용자 입력으로 검증)
PIN 코드 검증 흐름
- 사용자가 앱 접근 시 PIN 코드 입력
- 입력된 PIN 코드로부터 동일한 알고리즘으로 암호화 키 생성
- 생성된 키로 저장된 인증 정보 복호화 시도
- 복호화 성공 시 PIN 코드 검증 완료, 실패 시 오류 표시
- 성공적으로 복호화된 인증 정보로 서버 로그인 수행
보안 고려사항
- PIN 코드 입력 시도 제한 (5회 실패 시 잠금)
- 키 생성 시 솔트 적용 (디바이스 고유 ID 활용)
- 생체 인증과 PIN 코드 인증 병행 지원
- 디바이스 변경 시 이전 인증 정보 자동 무효화
이 방식은 PIN 코드가 서버로 전송되거나 서버에 저장되지 않으므로, 데이터 최소화 원칙(GDPR)을 준수하고 민감 정보 보호를 강화합니다.
모바일 앱 재설치 시나리오 처리
모바일 앱 재설치 시에는 기존의 PIN 코드와 암호화된 인증 정보가 모두 소실됩니다. 이 경우 다음과 같은 흐름으로 처리합니다:
- 필수 재로그인: 사용자는 이메일과 비밀번호를 사용한 전체 로그인 과정을 거쳐야 합니다.
- PIN 코드 재설정: 로그인 성공 후, 앱이 사용자에게 PIN 코드 재설정을 요청합니다.
- 복구 메커니즘 옵션:
- 클라우드 백업: 사용자의 동의 하에 암호화된 자격 증명을 안전하게 클라우드에 백업할 수 있습니다.
- 복구 코드: 초기 설정 시 복구 코드를 생성하여 사용자에게 제공할 수 있습니다.
- 디바이스 전환 지원: 사용자가 새 디바이스로 전환할 때, 안전한 방식으로 자격 증명을 이전할 수 있습니다. (기존 디바이스는 자동 로그아웃)
이러한 재설치 시나리오에서 중요한 것은 사용자 경험과 보안 사이의 균형을 맞추는 것입니다. 사용자가 불편함을 느낄 수 있지만, 민감한 PIN 코드 데이터를 서버에 저장하지 않는 현재 방식은 규제 준수와 보안 강화에 더 적합합니다.
2.2 토큰 검증 프로세스
async validateToken(token: string): Promise<TokenPayload> {
// 1. 토큰 디코딩 및 검증
const payload = await this.tokenService.verifyToken(token);
// 2. 블랙리스트 확인
const isBlacklisted = await this.tokenService.isTokenBlacklisted(token);
if (isBlacklisted) {
throw new UnauthorizedException('Token has been revoked');
}
// 3. 사용자 상태 확인
const user = await this.userService.findById(payload.sub);
if (!user || user.status !== UserStatus.ACTIVE) {
throw new UnauthorizedException('User is not active');
}
return payload;
}
2.3 토큰 갱신 프로세스
async refreshToken(refreshToken: string): Promise<AuthResult> {
// 1. 리프레시 토큰 검증
const payload = await this.tokenService.verifyRefreshToken(refreshToken);
// 2. 블랙리스트 확인
const isBlacklisted = await this.tokenService.isTokenBlacklisted(refreshToken);
if (isBlacklisted) {
throw new UnauthorizedException('Refresh token has been revoked');
}
// 3. 사용자 조회
const user = await this.userService.findById(payload.sub);
if (!user || user.status !== UserStatus.ACTIVE) {
throw new UnauthorizedException('User is not active');
}
// 4. 새로운 토큰 발급
const deviceInfo = await this.extractDeviceInfoFromRefreshToken(refreshToken); // 리프레시 토큰에서 디바이스 정보 추출
const tokens = await this.tokenService.generateTokens(user, deviceInfo);
// 5. 이전 리프레시 토큰 블랙리스트 처리 (선택적, 보안 강화)
await this.tokenService.blacklistToken(refreshToken);
return {
accessToken: tokens.accessToken,
refreshToken: tokens.refreshToken,
expiresIn: tokens.expiresIn,
tokenType: 'Bearer',
user: UserDto.fromEntity(user),
};
}
2.4 로그아웃 프로세스
async logout(token: string): Promise<void> {
// 1. 토큰 검증 (액세스 토큰)
const payload = await this.tokenService.verifyToken(token);
// 2. 액세스 토큰 블랙리스트 추가
await this.tokenService.blacklistToken(token);
// 3. 연관된 리프레시 토큰도 블랙리스트 처리 (선택적, 보안 강화)
const refreshToken = await this.tokenService.findRefreshTokenForAccessToken(token);
if (refreshToken) {
await this.tokenService.blacklistToken(refreshToken);
}
// 4. GCP Pub/Sub을 통한 이벤트 발행 (선택적)
// ...
}
3. 비밀번호 관리
3.1 비밀번호 해싱
- 알고리즘: bcrypt (권장) 또는 Argon2
- 솔트 라운드: bcrypt의 경우 10 이상
- 저장: 해시된 비밀번호만 저장 (원본 비밀번호 저장 금지)
async function hashPassword(password: string): Promise<string> {
const saltRounds = 10;
return await bcrypt.hash(password, saltRounds);
}
async function validatePassword(password: string, hash: string): Promise<boolean> {
return await bcrypt.compare(password, hash);
}
3.2 비밀번호 변경 프로세스
async changePassword(userId: string, oldPassword: string, newPassword: string): Promise<void> {
// 1. 사용자 조회
const user = await this.userService.findById(userId);
if (!user) {
throw new NotFoundException('User not found');
}
// 2. 현재 비밀번호 검증
const isValid = await this.validatePassword(oldPassword, user.password);
if (!isValid) {
throw new UnauthorizedException('Invalid current password');
}
// 3. 새 비밀번호 해싱 및 저장
const newPasswordHash = await this.hashPassword(newPassword);
await this.userService.updatePassword(userId, newPasswordHash);
// 4. 모든 세션 무효화 (선택적, 보안 강화)
await this.tokenService.revokeAllUserTokens(userId);
// 5. 이벤트 발행
// ...
}
4. 2단계 인증 (2FA)
4.1 2FA 활성화 프로세스
- 사용자가 2FA 활성화 요청
- 서버에서 TOTP 시크릿 키 생성 및 QR 코드 또는 텍스트로 제공
- 사용자가 인증 앱(Google Authenticator 등)에 시크릿 키 등록
- 사용자가 인증 앱에서 생성된 OTP 코드 입력하여 검증
- 검증 성공 시 사용자의 2FA 상태 활성화 및 시크릿 키 저장 (암호화)
4.2 2FA 로그인 프로세스
- 이메일/비밀번호 로그인 성공
- 사용자 계정에 2FA가 활성화되어 있는지 확인
- 2FA 활성화 시 OTP 코드 입력 요청
- OTP 코드 검증
- 검증 성공 시 최종 로그인 완료 및 토큰 발급
5. 보안 정책 및 규정 준수
5.1 일반 보안 정책
- 강력한 비밀번호 정책 강제 (길이, 복잡성, 변경 주기)
- 로그인 시도 제한 및 계정 잠금
- 세션 타임아웃 관리
- 민감 정보 암호화 (전송 중, 저장 시)
- 정기적인 보안 감사 및 취약점 점검
5.2 GDPR 및 DiGA 준수
- 데이터 최소화 원칙 준수
- 사용자 동의 관리 (명시적 동의, 철회 용이성)
- 개인 정보 접근/수정/삭제 권리 보장
- 데이터 유출 시 알림 의무
- 개인 정보 영향 평가 (DPIA) 수행
6. 기술 스택
- 언어: TypeScript
- 프레임워크: NestJS
- 데이터베이스: PostgreSQL (또는 유사 RDBMS)
- 캐싱: Redis
- 메시지 큐: GCP Pub/Sub
- 비밀번호 해싱: bcrypt 또는 Argon2
- JWT 라이브러리: jsonwebtoken
7. 이벤트 기반 아키텍처 (예시)
인증 관련 주요 이벤트 (예: user-registered, user-authenticated, password-changed)는 GCP Pub/Sub을 통해 발행되어 다른 서비스(감사 로그, 알림 등)에서 구독하여 처리할 수 있습니다.
8. API 엔드포인트 (Auth 도메인)
상세 API 엔드포인트 명세는 인증 API 엔드포인트 문서를 참조하세요.
9. 오류 처리
일관된 오류 응답 형식을 사용하며, 주요 오류 코드는 인증 API 엔드포인트 문서에 정의되어 있습니다.
10. 로깅 및 모니터링
- 모든 인증 시도, 성공, 실패, 오류를 상세히 로깅
- 주요 인증 지표 모니터링 (로그인 성공률, 실패율, 2FA 사용률 등)
- 이상 징후 감지 시 알림 설정
11. 테스트 전략
- 단위 테스트: 각 서비스 및 모듈의 핵심 로직 검증
- 통합 테스트: 서비스 간 상호작용 및 데이터 흐름 검증
- E2E 테스트: 실제 사용자 시나리오 기반 전체 인증 플로우 검증
- 보안 테스트: 취약점 점검, 침투 테스트
12. 디바이스 ID 관리
앱 토큰 발급 및 검증 시 사용되는 디바이스 ID는 클라이언트 앱에서 고유하게 생성되어 서버로 전달됩니다. 이 ID는 해시 처리되어 저장 및 비교에 사용됩니다.
- 상세 내용: 앱 토큰 기술 명세 및 앱 보안 인증 시스템 명세 참조.
변경 이력
| 버전 | 날짜 | 작성자 | 변경 내용 |
|---|---|---|---|
| 0.1.0 | 2025-03-10 | bok@weltcorp.com | 최초 작성 |
| 0.2.0 | 2025-03-28 | bok@weltcorp.com | PIN 코드 인증 프로세스 상세 내용 추가 |
| 0.3.0 | 2025-04-10 | bok@weltcorp.com | 앱 재설치 시나리오 및 복구 메커니즘 추가 |