feat: 토큰 만료 시 자동 로그아웃 기능 추가 및 테이블명 수정

🔐 토큰 만료 시 자동 로그아웃 기능:
1. JWT 토큰 만료 시간 대폭 연장:
   - 액세스 토큰: 24시간 → 7일
   - 리프레시 토큰: 7일 → 30일
   - 사용자 편의성 크게 향상

2. 토큰 만료 감지 및 처리:
   - isTokenExpired() 함수 추가
   - JWT 페이로드 파싱하여 exp 확인
   - 현재 시간과 비교하여 만료 여부 판단

3. 자동 로그아웃 처리:
   - API 호출 시 401 오류 감지
   - 주기적 토큰 만료 확인 (5분마다)
   - 만료 시 자동 인증 데이터 정리
   - 사용자 알림 후 로그인 페이지 리다이렉트

4. 개선된 인증 데이터 관리:
   - clearAuthData() 함수로 통합 관리
   - token, user, userInfo, currentUser 모두 정리
   - 메모리 누수 방지

🐛 데이터베이스 테이블명 수정:
1. projectModel.js:
   - Projects → projects (대문자 → 소문자)
   - 실제 DB 테이블명과 일치

2. taskModel.js:
   - Tasks → tasks (대문자 → 소문자)
   - 실제 DB 테이블명과 일치

3. API 오류 해결:
   - '테이블이 존재하지 않습니다' 오류 수정
   - projects, tasks API 정상 작동

 사용자 경험 개선:
- 토큰 만료로 인한 예상치 못한 오류 방지
- 명확한 만료 알림 메시지
- 자동 로그아웃으로 보안 강화
- 더 긴 세션 유지로 편의성 향상

🔧 기술적 개선:
- JWT 페이로드 안전한 파싱
- 에러 핸들링 강화
- 주기적 백그라운드 확인
- 전역 함수로 재사용성 향상

🎯 결과:
- 안정적인 인증 시스템
- 사용자 친화적인 세션 관리
- 보안성과 편의성의 균형
- API 호출 오류 해결

테스트:
- 토큰 만료 후 자동 로그아웃 확인
- projects, tasks API 정상 작동 확인
This commit is contained in:
Hyungi Ahn
2025-11-03 12:49:25 +09:00
parent 2445b3b29e
commit 790d12fe13
6 changed files with 82 additions and 37 deletions

View File

@@ -34,15 +34,45 @@ window.API_BASE_URL = API_URL;
function ensureAuthenticated() {
const token = localStorage.getItem('token');
if (!token || token === 'undefined') {
alert('로그인이 필요합니다');
localStorage.removeItem('token');
window.location.href = '/';
return null;
if (!token || token === 'undefined' || token === 'null') {
console.log('🚨 인증되지 않은 사용자. 로그인 페이지로 이동합니다.');
clearAuthData(); // 만약을 위해 한번 더 정리
window.location.href = '/index.html';
return false; // 이후 코드 실행 방지
}
// 토큰 만료 확인
if (isTokenExpired(token)) {
console.log('🚨 토큰이 만료되었습니다. 로그인 페이지로 이동합니다.');
clearAuthData();
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
window.location.href = '/index.html';
return false;
}
return token;
}
// 토큰 만료 확인 함수
function isTokenExpired(token) {
try {
const payload = JSON.parse(atob(token.split('.')[1]));
const currentTime = Math.floor(Date.now() / 1000);
return payload.exp < currentTime;
} catch (error) {
console.error('토큰 파싱 오류:', error);
return true; // 파싱 실패 시 만료된 것으로 간주
}
}
// 인증 데이터 정리 함수
function clearAuthData() {
localStorage.removeItem('token');
localStorage.removeItem('user');
localStorage.removeItem('userInfo');
localStorage.removeItem('currentUser');
}
function getAuthHeaders() {
const token = localStorage.getItem('token');
return {
@@ -72,11 +102,11 @@ async function apiCall(url, options = {}) {
// 인증 만료 처리
if (response.status === 401) {
console.error(' 인증 만료');
localStorage.removeItem('token');
alert('인증이 만료되었습니다. 다시 로그인해주세요.');
window.location.href = '/';
return;
console.error('🚨 인증 실패: 토큰이 만료되었거나 유효하지 않습니다.');
clearAuthData();
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
window.location.href = '/index.html';
throw new Error('인증에 실패했습니다.');
}
// 응답 실패 처리
@@ -143,10 +173,23 @@ window.ensureAuthenticated = ensureAuthenticated;
window.getAuthHeaders = getAuthHeaders;
window.apiCall = apiCall;
window.testApiConnection = testApiConnection;
window.isTokenExpired = isTokenExpired;
window.clearAuthData = clearAuthData;
// 개발 모드에서 자동 테스트
if (window.location.hostname === 'localhost' || window.location.hostname.startsWith('192.168.')) {
setTimeout(() => {
testApiConnection();
}, 1000);
}
}
// 주기적으로 토큰 만료 확인 (5분마다)
setInterval(() => {
const token = localStorage.getItem('token');
if (token && isTokenExpired(token)) {
console.log('🚨 주기적 확인: 토큰이 만료되었습니다.');
clearAuthData();
alert('세션이 만료되었습니다. 다시 로그인해주세요.');
window.location.href = '/index.html';
}
}, 5 * 60 * 1000); // 5분마다 확인