2025. 2. 14. 20:46ㆍ프로젝트
지금 현재 기능 개발은 어느정도 완성이 되었고 이제는 보안을 신경을 쓸 차례입니다.
우리가 알아야할 보안쪽을 알아보겠습니다.
1. 인증 및 세션 보안 (필수)
- 세션 보안 강화:
- HttpOnly, Secure, SameSite=Strict 속성 적용
- 세션 타임아웃: 유휴 30분, 최대 2시간
- 세션 재발급(Session Fixation 방지): 로그인 시 기존 세션 파기 후 새 세션 발급
- 2FA(이중 인증): 관리자 및 슈퍼유저 로그인 시 필수
- 브루트 포스 공격 방지: 로그인 실패 횟수 제한 및 CAPTCHA 적용
- 다중 로그인 차단: 한 계정으로 여러 기기에서 접속 시 이전 세션 종료
1-1 HttpOnly, Secure, SameSite=Strict 속성 적용
// 세션 미들웨어 설정 강화
app.use(
session({
secret: process.env.SESSION_SECRET || "default_secret",
resave: false,
saveUninitialized: false,
cookie: {
httpOnly: true, // 자바스크립트를 통한 쿠키 접근 차단 (XSS 방지)
secure: process.env.NODE_ENV === "production", // HTTPS 에서만 쿠키 전송
sameSite: "strict", // CSRF 방지 (엄격한 쿠키 정책)
maxAge: 1000 * 60 * 30, // 세션 유지시간 (30분)
},
})
);
httpOnly: JavaScript로 접근 불가 → XSS 방지
secure: HTTPS에서만 전송
sameSite: 크로스사이트 요청 방지 (CSRF 방지)
maxAge: 세션 유효시간 명확히 지정
1. httpOnly가 왜 중요한가
XSS(크로스사이트 스크립트) 공격 방지
- XSS 공격은 해커가 웹사이트에 악성 JavaScript 코드를 삽입하여 쿠키(Session ID)를 탈취하는 공격입니다.
- httpOnly가 적용된 쿠키는 JavaScript(document.cookie)**를 통해 읽을 수 없기 때문에 세션 도용(Session Hijacking)을 예방할 수 있습니다.
2. secure 옵션 차이점
secure: false (HTTP 및 HTTPS 모두 쿠키 전송)
- HTTP: 해커가 네트워크 패킷을 가로채서 세션 쿠키(Session ID)를 탈취할 수 있음.
- 결과: 해커가 세션 도용(Session Hijacking)을 통해 관리자 권한을 탈취할 위험이 있음.
secure: true (HTTPS에서만 쿠키 전송)
- HTTP: 브라우저가 쿠키를 전송하지 않음 → 해커가 세션 쿠키를 탈취할 기회 차단
- HTTPS: 암호화된 연결을 통해서만 쿠키가 전송됨 → 세션 도용 방지
3. sameSite가 중요할까?
CSRF(Cross-Site Request Forgery) 공격이란?
- 해커가 사용자의 인증된 세션을 도용해, 원치 않는 요청을 서버에 보내는 공격입니다.
- 사용자가 로그인 상태(세션 유지)**일 때, 해커는 악성 사이트에서 자동 요청을 보내어 사용자의 의도와 상관없이 중요한 작업(계정 탈퇴, 송금, 데이터 삭제)을 수행할 수 있습니다.
1 - 2. 세션 타임아웃 및 재인증
- 세션 만료 설정: 세션은 일정 시간 후 자동 만료
- 재로그인 처리: 세션 만료 시 재로그인 요구
// 로그인 시 마지막 접속 시간 저장
req.session.lastAccess = Date.now();
// 미들웨어로 세션 타임아웃 처리
app.use((req, res, next) => {
const now = Date.now();
const sessionMaxAge = 1000 * 60 * 30; // 30분
if (req.session.lastAccess && now - req.session.lastAccess > sessionMaxAge) {
req.session.destroy(() => {
res.status(401).send('세션이 만료되었습니다. 다시 로그인해 주세요.');
});
} else {
req.session.lastAccess = now;
next();
}
});
1 - 3 CSRF(Cross-Site Request Forgery) 방지
CSRF 토큰 발급 및 검증: 세션 기반 CSRF 방어
SameSite 쿠키 설정: sameSite: 'Strict'로 외부 도메인 요청 차단
const csrf = require('csurf');
const csrfProtection = csrf({ cookie: true });
app.use(csrfProtection);
app.get('/csrf-token', (req, res) => {
res.json({ csrfToken: req.csrfToken() });
});
// 예제: 자가진단 제출 시 CSRF 토큰 검사
app.post('/self-assessment', csrfProtection, (req, res) => {
res.send('자가진단 제출 완료');
});
csrfProtection이 보호하는 방법
CSRF 토큰을 생성하여 클라이언트에 제공
- 서버는 사용자가 요청을 보낼 때마다 고유한 CSRF 토큰을 생성해.
- 이 토큰은 사용자 세션과 연결되며, GET 요청 시 클라이언트에게 전달됨.
- 클라이언트는 이 CSRF 토큰을 POST, PUT, DELETE 요청에 함께 포함해야 해.
서버에서 CSRF 토큰을 확인 후 요청 처리
- 클라이언트가 API 요청을 보낼 때 CSRF 토큰을 함께 보냄.
- 서버는 클라이언트가 보낸 CSRF 토큰이 세션에 저장된 토큰과 일치하는지 검사.
- 일치하면 요청을 처리하고, 일치하지 않으면 403 Forbidden 오류 반환.
- 이를 통해 해커가 요청을 위조하여 중요한 작업(회원탈퇴, 송금, 데이터 변경 등)을 수행하는 것을 막을 수 있어!
1 - 4 세션 고정 공격(Session Fixation) 방지
로그인 후 세션 재발급: 로그인 시 기존 세션을 파기하고 새 세션 생성
app.post('/login', (req, res) => {
req.session.regenerate((err) => {
if (err) return res.status(500).send('세션 재생성 실패');
req.session.user = { id: user.id, role: user.role };
res.send('로그인 성공');
});
});
1 - 5. 세션 종료 및 로그아웃 보안 강화
로그아웃 시 세션 파기 및 쿠키 제거
로그아웃 후 캐시된 페이지 접근 차단
// 로그아웃 처리
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) return res.status(500).send('로그아웃 실패');
res.clearCookie('connect.sid');
res.send('로그아웃 완료');
});
});
// 캐시 방지
app.use((req, res, next) => {
res.setHeader('Cache-Control', 'no-store, no-cache, must-revalidate, proxy-revalidate');
res.setHeader('Pragma', 'no-cache');
next();
});
1 - 6 2FA(이중 인증) 구현 – 관리자 및 슈퍼유저 로그인 시 필수
목표
슈퍼유저 및 관리자 로그인 시 2FA(이중 인증)**을 적용해 보안 강화
OTP(One-Time Password) 방식으로 Google Authenticator와 연동
- 1단계: 관리자/슈퍼유저 로그인 (기존 세션 인증)
- 2단계: OTP(6자리) 입력 요청 (Google Authenticator 등)
- 검증 완료 후: 세션에 2FA_PASSED 플래그 저장
- 2FA 미완료 시: 모든 관리자 API 요청 차단
최종 동작 시나리오
관리자 로그인: /login → 세션 생성 (twoFactorAuthPassed = false)
QR 코드 발급: /2fa/generate-qr → Google Authenticator 등록
2FA 인증: /2fa/verify → OTP 입력 및 검증
API 접근: require2FA 미들웨어를 통과해야만 접근 가능
1 - 7 브루트 포스 공격 방지 및 다중 로그인 차단
목표
- 브루트 포스 공격 방지
- 로그인 시도 횟수 제한 (express-rate-limit)
- 일정 횟수 초과 시 일시적인 차단
- 다중 로그인 차단
- 세션 ID 기반으로 동일 계정 중복 로그인 시 이전 세션 강제 파기
- 현재 세션만 유지 (최근 로그인 우선)
브루트 포스 공격 방지 & 다중 로그인 차단
💡 로그인 시도 횟수 제한 (express-rate-limit) + 다중 로그인 차단 (세션 관리)
📌 목표:
1️⃣ 로그인 시도 횟수 제한: express-rate-limit을 활용하여 로그인 실패 횟수 제한
2️⃣ 일정 횟수 초과 시 차단: 로그인 실패 5회 초과 시 10분간 차단
3️⃣ 다중 로그인 차단: 동일 계정이 여러 번 로그인하면 이전 세션 자동 삭제
1. express-rate-limit 적용 (브루트 포스 공격 방지)
🚀 로그인 시도를 제한하여 브루트 포스 공격 방지
설정:
- 5회 로그인 실패 시 10분 동안 로그인 차단
📌 📂 middlewares/rateLimit.js (새 파일 생성)
import rateLimit from "express-rate-limit";
const loginLimiter = rateLimit({
windowMs: 10 * 60 * 1000, // 🔥 10분 동안
max: 5, // 🔥 최대 5번 시도 가능
message: { resultCode: "F-2", msg: "로그인 시도가 너무 많습니다. 10분 후 다시 시도해주세요." },
standardHeaders: true, // 🛡️ Rate-Limit 헤더 제공
legacyHeaders: false, // 🛡️ X-Rate-Limit 헤더 제거
});
export default loginLimiter;
2. 로그인 API에 express-rate-limit 적용
📌 middlewares/rateLimit.js에서 만든 loginLimiter를 로그인 라우트에 적용
routes/auth.js
import express from "express";
import { login, logout } from "../controllers/auth.js";
import loginLimiter from "../middlewares/rateLimit.js"; // ✅ 브루트 포스 방지 미들웨어 추가
const router = express.Router();
router.post("/login", loginLimiter, login); // 🔥 로그인 시도 횟수 제한 적용
router.post("/logout", logout);
export default router;
3. 다중 로그인 차단 (이전 세션 강제 종료)
📌 세션 ID를 저장하고, 중복 로그인 시 기존 세션을 삭제하는 방식
✅ 동일 계정 중복 로그인 시, 가장 최근 세션만 유지
middlewares/sessionManager.js
const activeSessions = new Map(); // 🛡️ 사용자 ID별 활성 세션 저장
const preventMultipleSessions = (req, res, next) => {
const userId = req.session?.user?.id || req.session?.expert?.id || req.session?.superuser?.id;
if (userId) {
// ✅ 동일 유저가 기존 세션으로 로그인한 경우, 기존 세션 제거
if (activeSessions.has(userId)) {
console.log(`🔴 [SESSION] 기존 세션 강제 종료: User ${userId}`);
activeSessions.get(userId).destroy(); // 🔥 기존 세션 강제 파기
}
// ✅ 현재 세션을 저장하여 이후 로그인 시 제거할 수 있도록 함
activeSessions.set(userId, req.session);
}
next();
};
export default preventMultipleSessions;
4. 로그인 API에 다중 로그인 차단 적용
📌 middlewares/sessionManager.js에서 만든 **preventMultipleSessions**를 로그인 시 실행
controllers/auth.js
import pool from "../config/db.js";
import bcrypt from "bcrypt";
import preventMultipleSessions from "../middlewares/sessionManager.js"; // ✅ 다중 로그인 차단 미들웨어 추가
// ✅ 로그인
const login = async (req, res) => {
const { email, password } = req.body;
try {
const [user] = await pool.query("SELECT * FROM User WHERE email = ?", [email]);
if (!user || user.length === 0) {
return res.status(400).json({ message: "이메일 또는 비밀번호가 잘못되었습니다." });
}
const isMatch = await bcrypt.compare(password, user[0].password);
if (!isMatch) {
return res.status(400).json({ message: "이메일 또는 비밀번호가 잘못되었습니다." });
}
req.session.user = {
id: user[0].id,
email: user[0].email,
name: user[0].representative_name,
member_type: "user",
};
preventMultipleSessions(req, res, () => { // ✅ 다중 로그인 차단 미들웨어 실행
res.status(200).json({
resultCode: "S-1",
message: "로그인 성공",
data: req.session.user,
});
});
} catch (error) {
console.error("❌ [LOGIN] 로그인 오류:", error);
res.status(500).json({ resultCode: "F-1", msg: "서버 에러 발생", error: error.message });
}
};
export { login };
인증 및 세션 보안 점검표
세션 보안 강화: httpOnly, secure, sameSite | 완료 | 세션 쿠키 보안 설정 (XSS, CSRF 방지) |
세션 타임아웃 및 재인증 (30분 유휴, 최대 2시간) | 완료 | 일정 시간 후 자동 로그아웃 및 재로그인 요구 |
CSRF 방지 (csurf 토큰 및 sameSite) | 완료 | 크로스사이트 요청 방지 (CSRF 방어) |
세션 고정 공격(Session Fixation) 방지 | 완료 | 로그인 시 req.session.regenerate()로 세션 재발급 |
로그아웃 보안 강화: 세션 파기 및 캐시 무효화 | 완료 | req.session.destroy() 및 캐싱 방지 |
2FA(이중 인증) – 관리자 및 슈퍼유저 필수 | 완료 | speakeasy 기반 OTP 및 2FA 미들웨어 적용 |
브루트 포스 공격 방지 (express-rate-limit) | 완료 | 로그인 시도 횟수 제한 및 속도 제한(slowDown) |
다중 로그인 차단 | 완료 | 서버 메모리(activeSessions) 기반 중복 로그인 차단 |
'프로젝트' 카테고리의 다른 글
개인정보-컴플라이언스-웹애플리케이션 (3. 데이터베이스 및 개인정보 보호) (0) | 2025.02.15 |
---|---|
개인정보-컴플라이언스-웹애플리케이션 (2. 입력값 검증 및 웹 보안 ) (0) | 2025.02.15 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 7) (0) | 2025.02.02 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 6) (0) | 2025.01.29 |
개인정보-컴플라이언스-웹애플리케이션 (수정사항 5) (0) | 2025.01.29 |