비밀번호 확인하기
Coddy Node.js 여정의 인증과 보안 섹션에 포함된 레슨. 24개 중 6번째.
로그인은 저장된 솔트와 원래 키 길이를 사용해 입력한 비밀번호에서 키를 도출합니다. 새 솔트를 사용하면 올바른 비밀번호를 입력해도 다른 키가 생성됩니다. 원래 비밀번호는 저장된 데이터에서 절대로 복호화되지 않습니다.
// 초대 코드가 salt.keyHex 형식으로 저장되었습니다
const [salt, keyHex] = savedInvite.split('.');
const expected = Buffer.from(keyHex, 'hex');
const attempted = await scryptAsync(inviteCode, salt, 32);길이가 같은지 확인한 다음, 결과 바이트 시퀀스를 timingSafeEqual을 사용해 비교합니다. 이 함수는 콘텐츠에 따라 조기에 종료되어 타이밍을 통해 정보가 노출될 수 있는 상황을 방지합니다. Buffer의 ===는 동일성을 확인하며, 일반적인 문자열 비교는 상수 시간 처리를 보장하지 않습니다. 비밀번호 함수는 비밀번호 레코드의 콜론 구분자와 64바이트 키를 사용합니다. 추가하는 POST /login 경로는 결과를 사용해 200 또는 401을 반환합니다.
챌린지
쉬움이 단계에서는 Never Store Passwords의 프로젝트 파일( passwords.js 포함)이 아니라, 강좌용으로 준비된 비공개 노트 API 프로젝트를 엽니다. 이전 작업은 해당 강좌에 그대로 남아 있습니다. passwords.js를 수정하기 전에 제공된 파일을 읽어 보세요. 이 복사본에는 비밀번호 해싱 도우미와 제공된 디버깅 경로가 들어 있습니다.
이제 회원 가입 시 솔트가 적용된 해시를 저장합니다. 회원 가입 시 저장한 값과 비밀번호를 대조하는 로그인을 추가하세요.
passwords.js에서async함수verifyPassword(password, stored)를 추가하고 내보내세요.stored는 회원 가입 시 저장된 값으로, 예를 들면f2bb...f065:c3f7...dbd2와 같습니다.password가 해당 값을 만든 비밀번호라면true를 반환하고, 그렇지 않으면false를 반환하세요.- 콜론 앞에서 솔트를 가져오세요.
hashPassword와 같은 방식으로 해당 솔트를 사용해password를 64바이트 키로 해싱하세요.- 콜론 뒤에 저장된 키를 16진수에서 디코딩하세요. 두 키의 바이트 길이가 다르면, 키 바이트 비교에서처럼
timingSafeEqual을 호출하지 말고false를 반환하세요. 그렇지 않으면 비교 결과를 반환하세요.
server.js에서hashPassword옆에verifyPassword를 가져오세요.POST /login을 추가하세요. 이 경로는 회원 가입과 같은 본문을 받습니다. 예를 들면{"username": "ada", "password": "correct horse"}입니다.- 해당 사용자 이름으로 사용자를 찾으세요.
verifyPassword는 느리고 프라미스를 반환하므로, 완료될 때까지 기다리세요. - 사용자가 있고 비밀번호가 맞으면 상태 코드 200과
{"username": "ada"}를 응답으로 보내세요. - 그렇지 않으면 상태 코드 401과
{"error": "Wrong username or password"}를 응답으로 보내세요. 401 Unauthorized는 요청자가 누구인지 증명하지 못했다는 뜻입니다. - 비밀번호가 틀린 경우와 가입한 적 없는 사용자 이름인 경우에 정확히 같은 401 응답을 보내세요. 그러면 응답 본문으로는 어떤 사용자 이름이 존재하는지 알 수 없습니다.
- 해당 사용자 이름으로 사용자를 찾으세요.
직접 해 보기: 실행을 누르세요. POST /signup에 {"username": "ada", "password": "correct horse"}를 보내세요. 상태 코드는 201입니다. 같은 본문으로 POST /login을 보내세요. 상태 코드 200과 {"username": "ada"}가 응답됩니다. {"username": "ada", "password": "wrong horse"}를 사용해 POST /login을 보내세요. 상태 코드 401과 {"error": "Wrong username or password"}가 응답됩니다. {"username": "nobody", "password": "correct horse"}를 사용해 POST /login을 보내세요. 똑같이 401이 응답됩니다.
직접 해보기
import { randomBytes, scrypt } from 'node:crypto';
import { promisify } from 'node:util';
const scryptAsync = promisify(scrypt);
// 비밀번호를 signup이 저장하는 텍스트로 변환합니다.
export async function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const key = await scryptAsync(password, salt, 64);
return `${salt}:${key.toString('hex')}`;
}
인증과 보안의 모든 레슨
직접 연습해 보세요: Express 플레이그라운드