[ 로그인 로직 설계 ]
웹 프로젝트에서 로그인 기능은
가장 기본이면서도 가장 중요한 기능이다.
단순히 ID와 비밀번호가 맞는지 검사하는 것만으로는
실제 서비스에 사용할 수 없다.
이번 포스팅에서는
- 로그인 처리
- 탈퇴 계정 차단
- 관리자 권한 분기
- 쿠키를 통한 자동 로그인
(보안을 고려한 HttpOnly 도입)
까지 포함한 로그인 로직 설계 방식을 정리한다.
1. 회원 상태 및 권한 설계
회원 테이블은 다음과 같이
member_role 컬럼을 사용한다.
MEMBER_ROLE ENUM
('ACTIVE','WITHDRAWN','ADMIN')
DEFAULT 'ACTIVE' NOT NULL
각 값의 의미는 다음과 같다.
| 값 | 의미 |
| ACTIVE | 활동 회원 |
| ADMIN | 관리자 |
| WITHDRAWN | 탈퇴 회원 |
로그인 로직에서는 이 값에 따라
접근 가능 여부와 이동 페이지를 제어한다.
2. 로그인 처리의 전체 흐름
로그인 처리 흐름은 다음 순서로 진행된다.
- 클라이언트로부터 ID / 비밀번호 수신
- DAO를 통한 회원 인증
- 탈퇴 회원 여부 검사
- 세션 정보 저장
- 관리자 / 일반 사용자 분기
- 자동 로그인 요청 시 보안 설정을 고려한 쿠키 생성
이 순서를 지키는 것이
안정적인 로그인 로직의 핵심이다.
3. LoginAction 코드
아래는 실제 사용한 LoginAction 코드이다.
로그인 액션은 단순히 로그인 인증만 수행하는 것이 아니라,
탈퇴 회원 차단, 관리자 권한 분기, 자동 로그인 처리까지
함께 고려하여 설계하였다.
특히 인증 성공 이후에도
회원의 상태(member_role)를 한 번 더 검사하여
서비스 이용 가능 여부를 판단하도록 구성하였다.
public class LoginAction implements Action {
@Override
public ActionForward execute(HttpServletRequest request, HttpServletResponse response) {
// DAO / DTO 생성
// Controller(Action)는 요청을 받아 Model(DAO)에게 처리를 위임하는 역할
MemberDAO memberDAO = new MemberDAO();
MemberDTO memberDTO = new MemberDTO();
// 로그인 성공 시 사용자 정보를 유지하기 위한 세션 객체
HttpSession session = request.getSession();
// 1. 로그인 요청 데이터 설정
// JSP(View)에서 전달받은 ID / PW를 DTO에 담아 DAO로 전달
memberDTO.setCondition("LOGIN"); // DAO에서 로그인 쿼리 분기용
memberDTO.setMemberName(request.getParameter("memberName"));
memberDTO.setMemberPassward(request.getParameter("memberPassward"));
System.out.println("로그인 액션 로그 : [" + memberDTO + "]");
// 회원 단건 조회 (로그인)
MemberDTO data = memberDAO.selectOne(memberDTO);
// 이동 경로와 방식(redirect/forward)을 담을 객체
ActionForward forward = new ActionForward();
// 2. 로그인 성공 여부 판단
if (data != null) { // ID / PW 일치 → 로그인 성공
System.out.println("로그인 성공 로그 - ID : [" + data.getMemberName() + "]");
// 3️. 탈퇴 회원 차단
// 로그인에 성공했더라도 탈퇴한 계정은 서비스 이용 불가
// 세션 저장 이전에 반드시 차단해야 함
if ("WITHDRAWN".equals(data.getMemberRole())) {
request.setAttribute("msg", "탈퇴한 계정입니다.");
request.setAttribute("location", "mainPage.do");
forward.setPath("message.jsp");
forward.setRedirect(false);
return forward; // 이후 로직 실행 방지
}
// 4️. 로그인 성공 → 세션 저장
// 인증이 완료된 사용자 정보는 세션에 저장하여
// 이후 요청에서 로그인 상태를 유지
session.setAttribute("userId", data.getMemberId());
session.setAttribute("userName", data.getMemberName());
session.setAttribute("userNickName", data.getMemberNickName());
session.setAttribute("userMemberRole", data.getMemberRole());
session.setAttribute("userMemberProfileImage", data.getMemberProfileImage());
session.setAttribute("userMemberEmail", data.getMemberEmail());
session.setAttribute("userMemberPhoneNumber", data.getMemberPhoneNumber());
// 5️. 자동 로그인 쿠키 처리
// autoLogin 체크 시 7일간 유지되는 쿠키 생성
String autoLogin = request.getParameter("autoLogin");
if ("Y".equals(autoLogin)) {
//Y와 비교를 먼저 시작해야 널포인트익셉션이 일어나지 않는다.
Cookie cookie = new Cookie("autoLogin", data.getMemberName());
cookie.setMaxAge(60 * 60 * 24 * 7); // 7일
cookie.setPath("/"); // 모든 경로에서 유효
cookie.setHttpOnly(true); // JS 접근 차단 (보안)
response.addCookie(cookie);
System.out.println("자동 로그인 쿠키 생성됨 : " + cookie.getValue());
}
// 6️. 관리자 권한 분기
// 로그인 성공 후 권한에 따라 이동 페이지 분기
if ("ADMIN".equals(data.getMemberRole())) {
// 관리자 계정 → 관리자 페이지
forward.setPath("adminPage.do");
} else {
// 일반 사용자 → 메인 페이지
forward.setPath("mainPage.do");
}
// 로그인 성공 후에는 새로운 요청으로 이동
forward.setRedirect(true);
} else {
// 7️. 로그인 실패 처리
// 인증 실패 시 메시지를 유지해야 하므로 forward 사용
System.out.println("로그인 실패 로그");
request.setAttribute("msg", "로그인 실패...");
request.setAttribute("location", "mainPage.do");
forward.setPath("message.jsp");
forward.setRedirect(false);
}
return forward;
}
}
4. 탈퇴 회원을 먼저 차단해야 하는 이유
if ("WITHDRAWN".equals(data.getMemberRole())) {
...
return forward;
}
탈퇴 회원은 세션을 생성하기 전에
반드시 차단해야 한다.
만약 세션을 먼저 생성한다면,
이후 로직에서 권한 체크가 꼬일 수 있다.
즉,
- 로그인 성공 여부와
- 서비스 이용 가능 여부
는 서로 다른 개념이다.
5. 관리자 권한 분기 처리
if ("ADMIN".equals(data.getMemberRole())) {
forward.setPath("adminPage.do");
} else {
forward.setPath("mainPage.do");
}
관리자는 일반 사용자와 다른 화면과 기능을 제공받는다.
따라서 로그인 성공 이후 반드시
권한에 따른 이동 페이지 분기가 필요하다.
문자열 비교 시에는 다음과 같은 형태를 사용한다.
"ADMIN".equals(role)
문자열 비교 시에는 "ADMIN".equals(role) 형태를
사용하여 NullPointerException을 예방한다.
이는 NullPointerException을
방지하는 안전한 방식이다.
6. 자동 로그인과 보안 고려 (HttpOnly)
로그인 시 자동 로그인을 선택한 경우 쿠키를 생성한다.
Cookie cookie = new Cookie("autoLogin", data.getMemberName());
cookie.setMaxAge(60 * 60 * 24 * 7);
cookie.setPath("/");
cookie.setHttpOnly(true);
response.addCookie(cookie);
여기서 중요한 보안 포인트는 HttpOnly 옵션이다.
- HttpOnly가 설정된 쿠키는
JavaScript에서 document.cookie로 접근할 수 없다. - 이를 통해 XSS 공격으로 인한 쿠키 탈취 위험을 줄일 수 있다.
즉,
자동 로그인 기능을 제공하면서도
최소한의 보안 장치를 함께 고려한 설계이다.
7. redirect와 forward 선택 기준
| 상황 | 방식 | 이유 |
| 로그인 성공 | redirect | 새 요청 생성, URL 정리 |
| 로그인 실패 | forward | 메시지 유지(실패 안내) |
로그인 성공 후에는 새 요청으로
페이지를 이동하는 것이 적절하며,
실패 시에는 요청 데이터를
유지해야 하므로 forward를 사용한다.
8. 관리자 페이지 접근 보안
로그인 시 분기 처리만으로는 보안이 완성되지 않는다.
관리자 페이지 Action에서도 반드시 권한 검사를 수행해야 한다.
String role = (String) session.getAttribute("userMemberRole");
if (!"ADMIN".equals(role)) {
response.sendRedirect("mainPage.do");
return;
}
이러한 로직은 URL 조작을 통한
직접 접근을 막기 위한 2중 방어 로직이다.
9. 마무리
이번에 작업한 로그인 로직은
다음 요소를 모두 고려하여 설계해본 로직이다.
- 인증과 권한의 분리
- 탈퇴 계정 사전 차단
- 관리자 / 일반 사용자 분기
- redirect / forward 기준 정립
- 자동 로그인 시 HttpOnly를 통한 보안 고려
JSP/Servlet 환경에서도 충분히 안정적인
로그인 시스템을 구현할 수 있으며,
핵심은 흐름과 책임을 명확히 나누는 것이다.
'개주 훈련일지 > 🏋️ 전집중 호흡 훈련' 카테고리의 다른 글
| 게시글 댓글 정렬(최신순/오래된순) 비동기 서블릿 구현 (0) | 2025.12.19 |
|---|---|
| 게시글 상세보기 구현 설계(조회수 중복 방지, 쿠키 생성, URL 조작 방어) (0) | 2025.12.18 |
| Hexagonal Architecture 리뷰를 보고 느낀 점 (1) | 2025.12.16 |
| CKEditor5 기본 문법 정리 (0) | 2025.12.14 |
| CKEditor5 커스텀 빌드 수정하는법 (0) | 2025.12.14 |