블로그 본문
[자격증명 보호] bcrypt 단방향 해시 함수와 솔트(Salt) 추가를 통한 암호학적 회원 가입 및 로그인(auth.js) 설계
니꼴라 클라우드 테크 9월 10, 2026📌 목차 바로가기
이번 포스팅 [자격증명 보호] bcrypt 단방향 해시 함수와 솔트(Salt) 추가를 통한 암호학적 회원 가입 및 로그인(auth.js) 설계]에서는 사용자 비밀번호를 평문으로 저장하는 위험성을 차단하고, bcrypt 단방향 해시 알고리즘과 솔트(Salt) 기법을 대입하여 회원가입, 로그인, 로그아웃 기능을 처리하는 auth.js 인증 라우터 모듈의 설계 로직을 설명해 드리겠습니다.
1. 평문 패스워드 저장의 위험성과 bcrypt 단방향 해싱/솔트(Salt)의 원리
사용자가 회원가입 시 입력한 비밀번호를 데이터베이스에 날것(Plain Text) 그대로 적재하는 것은 시스템 아키텍처 설계 및 개인정보 보호법상 엄격히 금지된 최악의 보안 결함입니다.
- 데이터베이스 유출 시의 2차 피해: 데이터베이스가 해킹이나 내부 유출로 공격자에게 넘어갈 경우, 평문 비밀번호는 사용자의 타 사이트 계정 탈취(Credential Stuffing) 2차 파국으로 직결됩니다.
- 단방향 해시 함수(One-Way Hash Function)의 특성: 복호화(Decryption)가 가능한 양방향 암호화와 달리, 단방향 해싱은 한 번 암호화하면 원래의 평문으로 되돌리는 것이 물리적으로 불가능합니다. 서버 관리자조차 사용자의 원래 비밀번호를 알 수 없도록 보호하는 암호학적 안전장치입니다.
- 레인보우 테이블(Rainbow Table) 공격과 솔트(Salt)의 대입:
- 공격자들은 자주 사용되는 비밀번호들의 해시값을 미리 계산해 둔 **'레인보우 테이블 사전'**을 가지고 해시값을 역추적합니다.
- 이를 무력화하기 위해 비밀번호 뒤에 무작위 난수 문자열인 **솔트(Salt)**를 추가하여 해싱합니다.
bcrypt알고리즘은 솔트 생성과 대입을 내부적으로 자동 처리하여, 두 사용자가 동일한 비밀번호('1234')를 사용하더라도 전혀 다른 해시 결과값을 생성해 냅니다.
- Cost Factor (Salt Rounds = 10):
bcrypt는 연산 해싱 비용(Round)을 지정할 수 있습니다. 스펙상10라운드를 지정하면 단일 해싱에 약 0.1초의 연산 시간이 소요되도록 설계되어, 공격자의 초당 수억 번의 무차별 무작위 대입(Brute-Force) 해킹 연산을 시스템적으로 차단합니다.
2. auth.js 라우터 모듈 설계 및 3대 핵심 인증 흐름 분석
교재 7.3절 및 8.1절 가이드라인에 맞춰, 우리 의류 쇼핑몰의 인증 전담 라우터인 auth.js가 수행하는 3대 핵심 비즈니스 흐름을 파헤쳐 보겠습니다.
① 회원가입 프로세스 (POST /auth/register_process)
- 입력값 수신 및 검증: 클라이언트가 제출한 이메일 아이디(
cust_id), 비밀번호(passwd), 성명(cust_name), 전화번호(m_phone) 및 약관 동의 값들을 접수합니다. - 중복 계정 사전 검사:
SELECT cust_id FROM Customers WHERE cust_id = ?쿼리를 실행하여 기존 가입 여부를 확인합니다. - bcrypt 단방향 해싱:
await bcrypt.hash(passwd, 10)메서드를 호출하여 평문 암호를 60자 이상의 복잡한 솔트 적용 해시 문자열로 변환합니다. - RDS 무결성 적재: 변환된 해시 문자열을
Customers테이블의passwd컬럼(VARCHAR(255))에INSERT연산으로 적재한 뒤, 회원가입 완료 안내 후/auth/login으로 리다이렉트합니다.
② 로그인 프로세스 (POST /auth/login_process)
- 사용자 레코드 조회: 입력받은 아이디(
cust_id)로Customers테이블을 검색하여 사용자 레코드를 수득합니다. 레코드가 없으면 즉시 로그인 실패 메시지를 반환합니다. - bcrypt.compare() 비동기 검증:
await bcrypt.compare(inputPassword, storedHash)메서드를 가동합니다.- 이 함수는 입력받은 평문 암호에 DB에 저장된 해시 내부의 솔트를 대입해 재해싱한 뒤, 저장된 해시값과 일치하는지 암호학적으로 비교 검증하여 결과(true/false)를 반환합니다.
- 세션 생성 및 확정:
- 검증 성공 시 세션 객체에 식별 정보를 할당합니다:
req.session.is_logined = true; req.session.nickname = `${user.cust_id}/${user.cust_name}/`; req.session.save()메서드를 명시적으로 호출하여session-file-store가 리눅스./sessions파일 시스템에 세션 데이터를 완전히 기록한 직후/main메인 쇼핑몰 화면으로 이송시킵니다.
- 검증 성공 시 세션 객체에 식별 정보를 할당합니다:
③ 로그아웃 프로세스 (GET /auth/logout)
req.session.destroy()함수를 호출하여 활성화된 세션 상태를 완전히 소멸시키고, 세션 저장소 파일 및 클라이언트 세션 쿠키를 무효화한 뒤 메인 첫 화면으로 리다이렉트합니다.
3. bcrypt 기반 인증 및 세션 저장 아키텍처 흐름도
사용자 회원가입 시 평문 암호가 솔트와 결합하여 bcrypt.hash()를 거쳐 RDS Customers 테이블에 적재되고, 로그인 시 bcrypt.compare() 검증을 거쳐 express-session 객체 및 파일 저장소에 안착하기까지의 전체 아키텍처 흐름도입니다.
4. [코드 분석] auth.js 핵심 구현 코드
실제 애플리케이션에서 사용자 가입 및 로그인을 처리하는 auth.js 라우터 모듈의 핵심 소스 코드입니다.
/**
* auth.js - 사용자 인증, 회원가입, 로그인 및 로그아웃 라우터 모듈
*/
const express = require('express');
const router = express.Router();
const bcrypt = require('bcrypt');
const db = require('./db');
const template = require('./template');
// 1. 로그인 화면 렌더링 (GET /auth/login)
router.get('/login', (req, res) => {
const title = '로그인 - FASHION STORE';
const html = template.HTML(title, `
<div class="auth-container">
<h2>로그인</h2>
<form action="/auth/login_process" method="post">
<input type="email" name="cust_id" placeholder="이메일 아이디" required /><br/>
<input type="password" name="passwd" placeholder="비밀번호" required /><br/>
<button type="submit">로그인</button>
</form>
<a href="/auth/register">아직 회원이 아니신가요? 회원가입</a>
</div>
`, '');
res.send(html);
});
// 2. 로그인 처리 (POST /auth/login_process)
router.post('/login_process', async (req, res) => {
const { cust_id, passwd } = req.body;
try {
const [rows] = await db.query('SELECT * FROM Customers WHERE cust_id = ?', [cust_id]);
if (rows.length === 0) {
return res.send('<script>alert("존재하지 않는 계정입니다."); location.href="/auth/login";</script>');
}
const user = rows;
// bcrypt 단방향 해시 일치 여부 암호학적 검증
const isMatch = await bcrypt.compare(passwd, user.passwd);
if (isMatch) {
// 세션 상태 저장
req.session.is_logined = true;
req.session.nickname = `${user.cust_id}/${user.cust_name}/`;
// 세션 파일에 안전하게 디스크 기록 후 리다이렉트
req.session.save(() => {
res.redirect('/main');
});
} else {
res.send('<script>alert("비밀번호가 일치하지 않습니다."); location.href="/auth/login";</script>');
}
} catch (err) {
console.error('로그인 처리 중 에러 발생:', err);
res.status(500).send('서버 내부 오류가 발생했습니다.');
}
});
// 3. 회원가입 처리 (POST /auth/register_process)
router.post('/register_process', async (req, res) => {
const { cust_id, passwd, cust_name, m_phone, a_term, a_privacy, a_marketing } = req.body;
try {
// 중복 계정 확인
const [existing] = await db.query('SELECT cust_id FROM Customers WHERE cust_id = ?', [cust_id]);
if (existing.length > 0) {
return res.send('<script>alert("이미 가입된 이메일 주소입니다."); location.href="/auth/register";</script>');
}
// bcrypt 10 라운드 솔트 단방향 해싱
const hashedPassword = await bcrypt.hash(passwd, 10);
// Customers 테이블 적재
await db.query(
'INSERT INTO Customers (cust_id, passwd, cust_name, m_phone, a_term, a_privacy, a_marketing) VALUES (?, ?, ?, ?, ?, ?, ?)',
[cust_id, hashedPassword, cust_name, m_phone, a_term || 'N', a_privacy || 'N', a_marketing || 'N']
);
res.send('<script>alert("회원가입이 완료되었습니다."); location.href="/auth/login";</script>');
} catch (err) {
console.error('회원가입 처리 중 에러 발생:', err);
res.status(500).send('서버 내부 오류가 발생했습니다.');
}
});
// 4. 로그아웃 처리 (GET /auth/logout)
router.get('/logout', (req, res) => {
req.session.destroy((err) => {
res.redirect('/');
});
});
module.exports = router;
마치며: 암호학적 안보망을 완벽히 수립했습니다!
오늘 우리는 백엔드 자격 증명 관리의 최고 원칙인 bcrypt 단방향 해시 함수와 솔트(Salt) 기법을 대입하여 평문 패스워드 유출 위험을 근본적으로 차단하고, 회원가입, 로그인, 로그아웃의 3대 핵심 인증 라우팅 로직을 auth.js 모듈로 완벽히 구현했습니다!
"사용자의 비밀번호를 날것으로 데이터베이스에 보관하던 위험천만한 과거를 지양하고, 무작위 난수 솔트와 암호학적 해싱 방패를 가동하여 데이터베이스가 유출되더라도 계정을 안전하게 보호하는 현대적인 DevSecOps 보안 아키텍처를 완성한 것입니다."
오늘 bcrypt.compare() 비동기 처리 중 오류가 발생했거나, req.session.save() 호출 관련 궁금한 점이 있으시다면 운영자 메일로 질문 남겨주세요. 오늘도 안전하고 똑똑한 클라우딩 라이프 하세요! 감사합니다! 😉
출처
- 이현호. 실무 프로젝트로 완성하는 클라우드 환경에서 DB 구축과 웹 개발. 길벗캠퍼스. 2026.04. (7.3장 '사용자 인증 및 라우팅 모듈(auth.js)', 180-188페이지 참조)
- 이현호. "07장. 애플리케이션_개발_-_DB_연결_및_사용자인증_설명오디오.m4a" 설명 오디오 가이드 스크립트 기반 평문 비밀번호 저장 금지 규칙, bcrypt 단방향 해시 및 솔트(Salt 10라운드) 연산 매커니즘, req.session.save() 세션 확정 및 auth.js 라우터 설계 비하인드 대화록 완벽 반영.
- npm bcrypt Package Documentation (https://www.npmjs.com/package/bcrypt) 및 OWASP Password Storage Cheat Sheet.
자주 묻는 질문 (FAQ)
Q. 회원가입 시 비밀번호를 DB에 저장할 때 평문 저장을 금지하고 bcrypt 단방향 해시를 사용하는 가장 결정적인 이유는 무엇인가요?
A. 데이터베이스 유출 시 사용자의 비밀번호 복호화를 차단하기 위해서입니다. 단방향 해시는 암호문에서 원래 평문을 역산해내는 것이 불가능하므로, DB가 해킹당하더라도 사용자의 타 사이트 계정 탈취 2차 피해를 근본적으로 방어해 줍니다.
Q. bcrypt 해시 연산 시 '솔트(Salt)'를 추가하는 암호학적 목적은 무엇인가요?
A. 레인보우 테이블(Rainbow Table) 사전 공격 무력화입니다. 무작위 난수인 솔트를 비밀번호 뒤에 결합해 해싱함으로써, 동일한 비밀번호('1234')를 입력한 사용자라도 완전히 서로 다른 해시 결과값을 생성하도록 만들어 역추적 공격을 무력화합니다.
Q. Express 로그인 처리 시 bcrypt.compare() 검증 성공 후 'req.session.save()'를 명시적으로 호출하는 이유는 무엇인가요?
A. 세션 저장소 파일 기록과 리다이렉트 간의 동기화 보장입니다. 'session-file-store'를 통해 세션 데이터가 ./sessions 디렉토리에 완전히 실시간 디스크 저장된 직후 메인 페이지로 이동시켜, 세션 유실로 인한 로그인 해제 현상을 방어합니다.