3단계: 2FA(TOTP) 와 로그인 흐름 통합
3단계: 2FA(TOTP) 와 로그인 흐름 통합
비밀번호가 유출돼도 로그인이 안 되게 만드는 두 번째 요소, Google Authenticator 가 보여주는 6자리 숫자의 정체가 TOTP(RFC 6238) 입니다. HMAC-SHA1 한 번으로 그 숫자를 직접 계산해 RFC 테스트 벡터와 맞춰 보고, QR 등록(otpauth URI)·시간 윈도우·재생 방지·백업 코드·실패 횟수 제한까지 서버 쪽 전부를 만듭니다. 후반부는 1단계(비밀번호)·2단계(Access/Refresh)·3단계(2FA)를 하나의 로그인 흐름으로 통합합니다. "비밀번호는 맞았지만 아직 코드는 안 넣은" 어중간한 상태를 어떤 토큰으로 표현하고, 그 토큰으로 API 를 못 부르게 어떻게 막는지가 핵심입니다. 마지막에 Spring Security 필터 체인에서 이 다단계가 어디에 끼어드는지, 회사 SAML SSO 와 어떻게 공존하는지 정리합니다.
1. 왜 배우는가
1·2단계를 거친 로그인은 토큰 쪽은 튼튼해졌지만 입구는 여전히 비밀번호 하나입니다. 비밀번호는 다른 사이트에서 유출된 것이 재사용되고(크리덴셜 스터핑), 피싱 페이지에 입력되고, 어깨너머로 보입니다. PBKDF2 로 아무리 잘 해시해도 사용자가 다른 곳에 같은 비밀번호를 쓰면 우리 DB 와 무관하게 뚫립니다.
그래서 "아는 것"(비밀번호) 에 "가진 것"(휴대폰) 을 더합니다. 공격자가 비밀번호를 알아도 사용자의 휴대폰에 지금 떠 있는 6자리를 모르면 못 들어옵니다. 이 6자리는 네트워크 없이 휴대폰 안에서 계산되고, 30초마다 바뀌며, 서버는 SMS 를 보낼 필요도 없습니다. 어떻게 그게 가능한지 이해하면 "OTP 앱은 어떻게 오프라인에서 서버와 같은 숫자를 아는가" 라는 질문에 답할 수 있게 됩니다.
실무에서 2FA 는 관리자 계정·정산·개인정보 열람 같은 고위험 기능의 필수 조건으로 요구됩니다(ISMS-P, 전자금융감독규정). 회사 SSO(SAML) 가 있어도 외부 협력사·운영 계정처럼 IdP 밖의 로컬 계정이 남고, 그 계정에는 우리가 직접 2FA 를 붙여야 합니다.
그리고 이 단계는 통합입니다. 1단계의 PasswordHasher 와 잠금 방식, 2단계의 TokenService 를 그대로 가져와 그 사이에 2FA 를 끼웁니다. 실무 로그인 서버도 정확히 이 모양입니다.