<< 학습 목표 >>

1. 리엑트 디버깅 툴을 설치할 수 있다.

2. XX


내가 만든 프로그램이 항상 내가 의도한대로만 동작하면 너무 좋겠지만 대부분은 내가 의도한대로 동작하지 않음

그래서 어디에 문제가 있는지 찾는 과정을 디버깅(debugging)이라고 함

 

디버깅을 하는 고전적이면서 기본적인 방법은 코드 사이 사이에 분기점을 출력하는 것임

Chapter02에서 마지막에 실습했던 주소록 컴포넌트에 분기점을 출력하는 코드를 넣어보자

( 실제 입력하지는 말고 보기만 하기 )

 

위와 같이 console.log 를 사용해 파악하고 싶은 분기점 마다 출력문을 넣어 두면 어디서 문제가 생기는지 파악할 수 있음

소스코드가 간단할 때는 이와 같이 출력문으로 디버깅을 할 수 있지만 일반적인 상황에서는 소스코드가 간단하지 않으므로 출력문으로 디버깅하는데는 한계가 있음

 

보통 개발툴에서는 디버깅툴까지 제공하지만 vs code는 별도로 디버깅툴을 제공하지 않음

vs code에 디버깅툴을 설치할 수도 있지만 우리는 웹 브라우저에 리엑트 디버깅툴을 설치하자


Edge(엣지) 브라우저를 사용할 경우 리엑트 디버깅 툴이 자동으로 설치되지만 어떤 상황에 의해서 설치가 안됬거나 다른 브라우저를 사용해서 설치가 안된 경우가 있으므로 리엑트 디버깅 툴을 설치하는 방법을 알아보자

리엑트 디버깅 툴을 설치하는 방법은 엣지, 크롬 브라우저만 설명함

혹시 다른 브라우저를 사용하는 사람은 두 브라우저 중 하나를 설치하자

 

<< 엣지 브라우저에 리엑트 디버깅 툴 설치 >>

더보기

브라우저 오른쪽 상단에 ... 메뉴(1) 클릭 -> 확장(2) -> [ Mircrosft Edge 추가 기능 웹 사이트 열기 ] (3) 클릭

만약 이미 다른 확장 프로그램이 설치되 있어 아래의 3번과 다른 메뉴라면 [ Mircrosoft Edge 추가 기능 열기 ] (4) 클릭

 

추가 기능 사이트 내 [ react developer tools ] 검색(1) -> 리엑트 디버깅 툴 [ 다운로드 ] (2) -> [ 확장 추가 ] (3)


 

<< 크롬 브라우저에 리엑트 디버깅툴 설치 >>

더보기

브라우저 오른쪽 상단에 ... 메뉴(1) 클릭 -> 도구 더보기(2) -> 확장 프로그램(3)

 

왼쪽 상단에 메뉴 버튼(1) 클릭 -> 메뉴 내 하단에 [ Chrome 웹 스토어 열기 ] (2)

 

chrome 웹 스토어 페이지 내 [ react developer tools ] 검색(1) -> 리엑트 디버깅툴(2) 클릭 -> 

 

리엑트 디버깅툴 chrome에 추가(1) -> 확장 프로그램 추가(2)



이제 엣지나 크롬을 닫고 Chapter02 에서 만든 주소록 컴포넌트를 웹 페이지에 출력하자

( 만약 특정 컴포넌트를 출력하자 라는 말이 기억나지 않으면 https://codingaja.tistory.com/75 전 글 다시 보고 오기 )

 

웹 페이지 내에서 F12 를 눌러 개발자 모드를 열어보자

개발자 모드를 보면 이제 Components(1), Profiler(2) 탭이 보임

이 탭은 리엑트 웹 페이지에서만 보이는 것

 

개발자 모드를 연 상태에서 F5 키를 눌러 페이지를 새로고침 하자

이제 Components 탭에 다음과 같이 페이지를 구성하고 있는 컴포넌트들이 보임

 

이제 이를 통해서 문제가 생겼을 때 상태를 확인할 수 있음


디버깅 얘기가 나온 김에 마지막으로 Chapter02 에서 만들었던 주소록 컴포넌트에서 발생하는 문제를 해결하고 마무리 하자

 

Components 탭은 현재 페이지를 구성하고 있는 컴포넌트들을 확인할 수 있는 탭임

그외에 다른 문제들은 Console 탭(1)에서 확인할 수 있음

 

Console탭을 보면 지금 다음과 같이 빨간색 글씨로 출력된 문장이 있는데 Warning 으로 시작하므로 경고 메세지임

심각한 오류는 아니므로 현재 페이지가 제대로 보이지만 경고 메세지도 뜨지 않게 해주는게 좋음

 

이 경고 메세지는 map을 사용할 때 태그를 반환한다면 그 태그에 key 를 넣어줘야한다는 것임

 

이 경고 메세지를 따라 AddressList 컴포넌트 코드로 가서 다음과 같이 key를 추가하자

 

키를 추가해야하는 이유는 리엑트가 컴포넌트를 빠르게 랜더링하기 위해 필요한 요소임

728x90
LIST

<< 학습 목표 >>

1. 회원 탈퇴 기능을 개선할 수 있다.


 

지난 글의 마지막에 다음과 같은 말을 했음

실제 서비스에서는 회원 탈퇴가 회원 정보를 삭제하지 않음
실제 서비스에서는 회원 정보 테이블에 회원 정보에 회원 탈퇴 플래그(Flag) 칼럼을 둠
그리고 회원 탈퇴 시 회원 탈퇴 플래그를 true로 바꿈


실제 서비스에서 회원 정보를 삭제하지 않는 이유는 여러 이유가 있지만 회원 정보는 그 서비스의 굉장히 중요한 자산 중 하나임
보통 실제 서비스는 특정 회원이 우리 서비스에서 어떤 활동을 하는지 기록해둠
그리고 그것들을 활용해서 서비스를 개선함
회원 탈퇴 시 회원 정보를 삭제하면 그것들 모두 지워야하므로 서비스를 개선 수 없음

우리 서비스도 실제 서비스와 가깝게 회원 정보 테이블에 회원 탈퇴 플래그를 두자

member 테이블 내 isDel 칼럼 추가(1)

 

회원 탈퇴 처리를 하는 MemberDao로 가서 deleteMemberByIdx 메서드의 쿼리를 DELETE 가 아닌 UPDATE 로 바꿔야함

// ...

public void deleteMemberByIdx(int idx) {
    Connection conn = null;
    PreparedStatement pstmt = null;

    try {
        conn = DBUtil.getConnection();

        String sql = "UPDATE member SET isDel = true WHERE idx = ?";

        pstmt = conn.prepareStatement(sql);

        pstmt.setInt(1, idx);

        pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.closes(conn, pstmt);
    }
}

// ...

 

<< 코드 설명 >>

이제 회원 정보 삭제가 아닌 회원 정보 수정이고 회원 탈퇴 플래그인 isDel 칼럼의 값을 true 로 바꿈


이렇게 하면 회원 정보를 삭제하지 않고 회원 탈퇴 처리를 할 수 있음

컨트롤러나 서비스는 바꿀 필요 없음

특히 컨트롤러에서는 회원 탈퇴 처리가 완료 됬을 때 세션을 삭제하므로 회원 탈퇴를 한 사용자는 로그인이 풀리므로 회원 탈퇴가 되었다고 느끼게됨

 

서버를 실행시키고 개선한 회원 탈퇴를 확인하자


회원 탈퇴는 잘 되지만 탈퇴한 ID, PW로 로그인을 해보자

여전히 로그인이 됨

왜? 실제로 삭제한게 아니라 탈퇴 플래그만 바꾼 것이므로...

그래서 로그인을 할 때 탈퇴한 회원(탈퇴 플래그가 true인 회원)의 ID, PW 로 로그인을 하려고 할 때는 "탈퇴한 회원입니다" 를 출력하도록 하자

 

수정 순서는 다음과 같음

1. 회원 정보를 담는 DTO

2. 회원 정보를 조회하는 DAO 메서드

3. 로그인 서블릿

4. 로그인 페이지

 

<< 1. 회원 정보를 담는 DTO >>

기존에는 탈퇴 플래그가 없었으므로 당연히 회원 정보를 담는 DTO에 탈퇴 플래그 값을 저장할 수 있는 멤버 변수가 없음

MemberDto에 isDel 멤버 변수 추가 한 후 getter, setter까지 추가하자

getter, setter 를 추가할 때 조심해야할 점은 is로 시작하는 멤버 변수의 getter, setter명이 제대로 설정되지 않음

우선 getter, setter를 추가한 후 이름을 getIsDel, setIsDel 로 바꿔주자

 

 

<< 2. 회원 정보를 조회하는 DAO 메서드 >>

기존에는 탈퇴 플래그가 없었으므로 회원 정보를 조회할 때 탈퇴 플래그를 담지 않음

회원 정보 조회 후 탈퇴 플래그를 회원 정보에 담도록 하자

 

MemberDao 클래스 -> selectOneById 메서드, selectOneByTel 메서드 내 다음과 같이 코드 추가

라인 번호는 다르지만 위치는 동일하므로 어렵지 않게 찾아서 추가할 수 있을 것

 

탈퇴 플래그를 Boolean 타입 데이터로 가져옴(1)

탈퇴 플래그를 회원 정보에 저장(2)

 

<< 3. 로그인 서블릿 >>

로그인 서블릿(member패키지 -> MemberLogin 서블릿) 에 로그인에 성공했다면 부분에 다음과 같이 코드를 추가하자

로그인 시 아이디, 비밀번호를 올바르게 입력했지만 탈퇴 한 계정이라면 403 상태 코드를 응답함

 

<< 4. 로그인 페이지 >>

로그인 페이지에서 서버가 보내는 403 상태 코드(탈퇴한 회원)을 인식해 "탈퇴한 회원입니다" 를 출력해야함

로그인 페이지(WEB-INF 폴더 -> member 폴더 -> login.html)에 로그인 요청 후 응답을 받는 부분에 아래 코드 추가

 

여기까지 회원 탈퇴 기능을 개선했음


길고 길었던 CRUD 프로젝트가 거의 완성됐음

여러분의 프로젝트를 만들 때 똑같은 로그인 기능이지만 여러분의 상황에 따라 달라질 수 있음

회원가입, 회원 정보 수정, 회원 탈퇴 역시 마찬가지...

 

지금까지 진행한 CRUD 프로젝트는 코드를 외울 순 없음

CRUD 프로젝트에서 회원가입 기능을 구현하기 위한 과정이 어떻게 됬는지를 이해하는게 중요함

 

개인적으로 게으른 개발자가 성공할 수 있다고 생각함

그렇다고 생활이 게으르면 안되고 개발을 하면서 "아~ 이거 번거로운데... 더 나은 방법 또는 덜 번거로운 방법 없나?" 를 꼭 찾아야하고 이를 개선할 수 있는 공부를 끊임 없이 해야함

728x90
LIST

<< 학습 목표 >>

1. 회원 탈퇴 기능을 구현할 수 있다.


드디어 이번 챕터의 마지막인 회원 탈퇴 기능을 구현하자

회원 탈퇴 기능은 매우 간단함

 

회원 탈퇴 기능을 개발할 때는 어떤걸 먼저해야한다???

더보기

기능에 대한 정의, 스토리보드, 인터페이스

이 세 가지는 앞서 ( https://codingaja.tistory.com/49 ) 이미 했으므로 전 글을 보고 오자


회원 탈퇴 기능을 구현하기 위해서는

1. 메인 페이지의 회원 탈퇴 버튼에 "회원 탈퇴 버튼 클릭 시 회원 탈퇴 요청이 들어가도록 추가"

2. 회원 탈퇴 요청을 받아 회원 탈퇴 처리

 2-1. 회원 탈퇴 요청을 받을 컨트롤러

 2-2. 회원 탈퇴 요청을 처리할 서비스

 2-3. 회원 탈퇴 처리를 할 DAO

 

<< 1. 메인 페이지의 회원 탈퇴 버튼에 "회원 탈퇴 버튼 클릭 시 회원 탈퇴 요청이 들어가도록 추가" >>

메인 페이지를 출력하는 서블릿(member 패키지 -> MemberMain 서블릿)으로 가서 닫는 main 태그(</main>)와 닫는 body태그(</body>) 사이에 다음과 같이 JS 코드를 추가하자

// ...

pw.print("<script src=\"https://code.jquery.com/jquery-3.6.3.min.js\" integrity=\"sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=\" crossorigin=\"anonymous\"></script>");
pw.print("<script>");
pw.print("	$(\"#delete_btn\").on(\"click\", function() {");
pw.print("		if(confirm(\"탈퇴하시겠습니까?\")) {");
pw.print("			$.ajax({");
pw.print("				url: $(this).attr(\"href\"),");
pw.print("				type: \"POST\",");
pw.print("				success: function() {");
pw.print("					location.reload();");
pw.print("				},");
pw.print("				error: function() {");
pw.print("				}");
pw.print("			});");
pw.print("		}");
pw.print("		return false;");
pw.print("	});");
pw.print("</script>");

// ...

 

<< 코드 설명 >>

탈퇴 버튼을 클릭(1)하면 탈퇴 여부를 물어봄(2)

탈퇴를 한다고 했다면(3) ajax로 탈퇴 요청을 보냄(4)

탈퇴가 되었다면(5) 페이지를 새로고침(6)해 메인 페이지를 다시 보여줌


<< 2. 회원 탈퇴 요청을 받아 회원 탈퇴 처리 >>

2-1. 회원 탈퇴 요청을 받을 컨트롤러

더보기

member 패키지 -> MemberDelete 서블릿 추가 후 아래 코드 추가

package member;

import java.io.IOException;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member/delete")
public class MemberDelete extends HttpServlet {
	
	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		
		if(session.getAttribute("member") == null) {
			response.sendRedirect("/member/login");
			return ;
		}
		
		MemberDto member = (MemberDto) session.getAttribute("member");
		
		MemberService service = new MemberService();
		service.deleteMemberByIdx(member.getIdx());
		
		session.invalidate();
	}

}

 

<< 코드 설명 >>

1. 로그인한 사용자만 탈퇴할 수 있으므로 로그인하지 않은 사용자라면 로그인 페이지로 리다이렉트

  로그인 후 메인 페이지에 몇 시간 이상, 장시간 머물렀을 경우 로그인이 풀릴 수 있음

  사용자는 로그인이 풀렸다는걸 모르는 상태로 회원 탈퇴 버튼을 눌렀을 수 있으므로 회원 탈퇴 컨트롤러에서 로그인 여부를 확인하는 것

2. 로그인한 사용자의 회원 번호로 회원 탈퇴 처리

3. 회원 탈퇴를 했으므로 세션을 삭제해 세션에 저장된 로그인 정보 삭제


 

2-2. 회원 탈퇴 요청을 처리할 서비스

더보기

회원 정보 처리 서비스(member 패키지 -> MemberService 클래스) 내 회원 탈퇴 처리 서비스 메서드 추가

package member;

public class MemberService {
	public void deleteMemberByIdx(int idx) {
		MemberDao dao = new MemberDao();
		
		dao.deleteMemberByIdx(idx);
	}
    
 // ...

 

<< 코드 설명 >>

기능에 대한 정의에서 회원 탈퇴란 회원 정보 삭제 라고 했으므로 DAO를 통해 DB에 저장된 회원 정보 삭제(1)


 

2-3. 회원 탈퇴 처리를 할 DAO

더보기

회원 정보 처리 DAO(member패키지 -> MemberDao 클래스) 내 회원 탈퇴 메서드 추가

// ...

public void deleteMemberByIdx(int idx) {
    Connection conn = null;
    PreparedStatement pstmt = null;

    try {
        conn = DBUtil.getConnection();

        String sql = "DELETE FROM member WHERE idx = ?";

        pstmt = conn.prepareStatement(sql);

        pstmt.setInt(1, idx);

        pstmt.executeUpdate();
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        DBUtil.closes(conn, pstmt);
    }

}

// ...

 

<< 코드 설명 >>

idx로 회원 정보를 삭제할 쿼리 준비(1)

?에 탈퇴할 회원의 idx를 저장(2)

회원 정보 삭제 쿼리 보내기 & 실행(3)


 

서버를 시작하고 로그인 후 회원 탈퇴를 해보자


여기까지 회원 탈퇴를 구현했음

실제 서비스에서는 회원 탈퇴가 회원 정보를 삭제하지 않음

실제 서비스에서는 회원 정보 테이블에 회원 정보에 회원 탈퇴 플래그(Flag) 칼럼을 둠

그리고 회원 탈퇴 시 회원 탈퇴 플래그를 true로 바꿈

 

실제 서비스에서 회원 정보를 삭제하지 않는 이유는 여러 이유가 있지만 회원 정보는 그 서비스의 굉장히 중요한 자산 중 하나임

보통 실제 서비스는 특정 회원이 우리 서비스에서 어떤 활동을 하는지 기록해둠

그리고 그것들을 활용해서 서비스를 개선함

회원 탈퇴 시 회원 정보를 삭제하면 그것들 모두 지워야하므로 서비스를 개선 수 없음

 

우리의 회원 탈퇴 처리를 실제와 가깝게 회원 탈퇴 플래그로 핸들링 하려면 회원 탈퇴 뿐만 아니라 다른 코드들도 수정해야하므로 다음 글에서 바꿔보자

728x90
LIST

<< 학습 목표 >>

1. 회원 정보 수정 페이지와 기능을 구현할 수 있다.

2. 동적 쿼리를 구현할 수 있다.

3. 서블릿으로 웹 페이지를 출력하는게 불편하다는걸 인지하고 있다.


드디어 지금까지 배운 것들을 활용해 회원 정보 수정 페이지와 기능을 개발할 수 있음

 

앞 Chapter의 글 ( https://codingaja.tistory.com/48 ) 에서도 언급했듯 회원 정보 수정 페이지는 로그인한 사용자의 정보를 보여줘야하므로 동적인 페이지임

 

동적인 페이지를 보여주는 방법은 알지만 "로그인한 사용자의 정보가 들어간 회원 정보 수정 페이지" 는 보여줄 수 없었음

그러나 이제 우리가 세션에 로그인한 사용자의 정보를 저장하고 꺼내는 방법을 익혔으므로 "로그인한 사용자의 정보가 들어간 회원 정보 수정 페이지"를 보여줄 수 있음

 

회원 정보 수정 페이지와 기능을 개발하자


<< 회원 정보 수정 페이지 >>

현재는 member 패키지 -> MemberUpdate 서블릿이 회원 정보 수정 페이지를 달라는 요청이 들어오면 회원 정보 수정 페이지로 포워딩 하고 있지만 이제는 해당 서블릿에서 현재 로그인한 사용자의 정보가 들어간 회원 정보 수정 페이지를 출력하면 됨

만약 로그인한 사용자의 정보를 쿠키에 저장했으면 HTML페이지에서 JS를 사용했으면 되겠지만 로그인한 사용자의 정보는 세션에 저장되어있으므로 서블릿에서 꺼내 보여줘야함

 

member 패키지 -> MemberUpdate 서블릿을 다음과 같이 수정하자

package member;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member/update")
public class MemberUpdate extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		
		if(session.getAttribute("member") == null) {
			response.sendRedirect("/member");
		}
		
		MemberDto loginMember = (MemberDto) session.getAttribute("member");
		String id = loginMember.getId();
		String nickname = loginMember.getNickname();
		String tel = loginMember.getTel();
		
		response.setCharacterEncoding("UTF-8");
		
		PrintWriter pw = response.getWriter();
		
		pw.print("<!DOCTYPE html>");
		pw.print("<html>");
		pw.print("<head>");
		pw.print("	<meta charset=\"UTF-8\">");
		pw.print("	<title>연습 프로젝트</title>");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/public/common.css\">");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/member/join.css\">");
		pw.print("</head>");
		pw.print("<body>");
		pw.print("	<header>&#60;&#60; 회원 정보 수정 &#62;&#62;</header>");
		pw.print("	<main>");
		pw.print("		<form action=\"/member/join\" method=\"POST\">");
		pw.print("			<fieldset>");
		pw.print("				<label for=\"id\">아이디</label>");
		pw.print("				<input type=\"text\" name=\"id\" id=\"id\" required=\"required\" dp-name=\"아이디\" readonly=\"readonly\" disabled=\"disabled\" value=\""+id+"\">");
		pw.print("			</fieldset>");
		pw.print("			<fieldset>");
		pw.print("				<div>");
		pw.print("					<label for=\"pw\">비밀번호</label>");
		pw.print("					<input type=\"password\" name=\"pw\" id=\"pw\" required=\"required\" dp-name=\"비밀번호\">");
		pw.print("				</div>");
		pw.print("				<div>");
		pw.print("					<label for=\"pwchk\">비밀번호 확인</label>");
		pw.print("					<input type=\"password\" name=\"pw\" id=\"pwchk\" required=\"required\" dp-name=\"비밀번호 확인\">");
		pw.print("				</div>");
		pw.print("			</fieldset>");
		pw.print("			<fieldset>");
		pw.print("				<label for=\"nickname\">닉네임</label>");
		pw.print("				<input type=\"text\" name=\"nickname\" id=\"nickname\" required=\"required\" dp-name=\"닉네임\" value=\""+nickname+"\">");
		pw.print("			</fieldset>");
		pw.print("			<fieldset>");
		pw.print("				<label for=\"tel\">연락처</label>");
		pw.print("				<input type=\"tel\" name=\"tel\" id=\"tel\" required=\"required\" dp-name=\"연락처\" value=\""+tel+"\">");
		pw.print("			</fieldset>");
		pw.print("			<fieldset>");
		pw.print("				<button type=\"button\" role=\"submit\">정보 수정</button>");
		pw.print("			</fieldset>");
		pw.print("		</form>");
		pw.print("	</main>");
		pw.print("</body>");
		pw.print("</html>");
	}
}


코드가 길긴하지만 조금만 집중해서 보면 어렵지 않게 이해할 수 있는 코드임

1. 로그인하지 않은 상태로 회원 정보 수정 페이지에 접근했다면 메인 페이지로 이동

2. 세션에 저장된 로그인한 사용자의 정보를 꺼낸 후 보여줘야할 데이터(아이디, 닉네임, 연락처)를 별도의 변수에 저장

3. 아이디를 화면에 보여줌

여기서 비밀번호를 보여주지 않는 이유는 보안상의 이유 보다는 암호화 때문임

비밀번호는 단방향 암호화를 했기 때문에 DB에 저장된, 사용자가 가입할 때 입력한 비밀번호, 는 절대 알 수 없음

비밀번호를 제외한 나머지는 사용자가 가입할 때 입력한 그대로 DB에 저장 되어있기 때문에 사용자에게 보여줄 수 있는 것


이제 회원 정보 수정 기능을 추가하자

시간이 흘러 기억이 흐릿하겠지만 기능(서블릿)을 만들 때는 먼저 기능에 대한 정의, 스토리보드, 인터페이스를 작성한 후 개발 해야함

 

우리는 전 Chapter의 회원 정보 수정 글 ( https://codingaja.tistory.com/48 ) 에서 이미 기능에 대한 정의, 스토리보드, 인터페이스를 작성했음

다시 한번 확인하고 오자

 

이제 기능에 대한 정의, 스토리보드, 인터페이스에 맞게 회원 정보 수정 기능을 개발하자

회원 정보 수정 기능을 개발하기 위해 필요한 것은

1. 회원 정보 수정 요청을 받을 컨트롤러

2. 회원 정보 수정을 수행할 서비스 메서드

3. DB의 member 테이블에 update 쿼리를 수행하고 결과를 받아 서비스 메서드로 전달할 DAO

 

<< 1. 회원 정보 수정 요청을 받을 컨트롤러 >>

더보기

<< 회원 정보 수정 컨트롤러 (member 패키지 -> MemberUpdate 서블릿) >>

코드가 길어 일부만 가져옴

// ...

@WebServlet("/member/update")
public class MemberUpdate extends HttpServlet {
	@Override
	protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
		HttpSession session = req.getSession();
		
		if(session.getAttribute("member") == null) {
			resp.sendRedirect("/member");
			return ;
		}
		
		MemberDto oldMember = (MemberDto) session.getAttribute("member");
		
		req.setCharacterEncoding("UTF-8");
		
		String pw = ParameterUtil.getString(req, "pw");
		String nickname = ParameterUtil.getString(req, "nickname");
		String tel = ParameterUtil.getString(req, "tel");
		
		if(pw != null && !ParameterUtil.isPw(pw)) {
			resp.setStatus(400);
			return ;
		} else if(!ParameterUtil.isNickname(nickname)) {
			resp.setStatus(400);
			return ;
		} else if(!ParameterUtil.isTel(tel)) {
			resp.setStatus(400);
			return ;
		}
		
		if(pw != null) {
			CryptoUtil cu = new CryptoUtil();
			pw = cu.onewayEncryption(pw);
		}
		
		MemberDto newMember = new MemberDto(oldMember.getId(), pw, nickname, tel);
		newMember.setIdx(oldMember.getIdx());
		
		MemberService service = new MemberService();
		
		try {
			newMember = service.updateMember(newMember, oldMember);
			
			session.setAttribute("member", newMember);
		} catch(DuplicateDataException e) {
			resp.setStatus(409);
		}
	}
    
 // ...

 

<< 코드 설명 >>

1. 회원 정보 수정은 로그인한 사용자만 사용할 수 있으므로 회원 정보 수정 기능이 본격적으로 동작하기 전 로그인 여부 확인

  이해하면서 따라오고 있다면 "회원 정보 수정 페이지에 로그인한 사용자만 들어갈 수 있는거 아닌가?" 싶겠지만 회원 정보 수정 페이지에 접속해 있다가 어떤 일이 있어 몇 시간 뒤에 정보를 수정할 수 있음

2. 클라이언트(사용자) 가 보낸 값을 꺼낸 후 검증

  비밀번호는 변경하지 않을 경우 입력하지 않으므로 비밀번호를 변경하지 않았다면 비밀번호를 보내지 않을 예정임

  닉네임과 연락처는 변경하지 않더라도 기존의 닉네임과 연락처를 보낼 예정임

  그래서 클라이언트가 보낸 값을 검증하는 if문의 조건식이 약간 다름

3. 비밀번호를 변경한다면 비밀번호를 암호화 함

4. 정보를 수정할 때 사용할 newMember 객체 생성

  회원 번호와 아이디는 변경할 수 없으므로 기존 회원 정보(oldMember)의 회원 번호와 아이디로 지정하고 나머지(비밀번호, 닉네임, 연락처)는 클라이언트가 전달한 새로운 데이터를 저장

5. 회원 정보를 수정한 후 서비스 메서드가 정보가 수정된 새로운 회원의 정보를 반환함

  메인 페이지, 회원 정보 수정 페이지에서 세션에 들어있는 로그인한 사용자의 정보를 꺼내서 사용하므로 수정된 회원 정보를 세션에 다시 저장해 해당 페이지에서 수정된 회원 정보를 사용하도록 함



 

<< 2. 회원 정보 수정을 수행할 서비스 메서드 >>

더보기

<< 회원 정보 서비스 (member패키지 -> MemberService 클래스) >>

코드가 길어 일부만 가져옴

package member;

public class MemberService {
	public MemberDto updateMember(MemberDto newMember, MemberDto oldMember) throws DuplicateDataException {
		MemberDao dao = new MemberDao();
		
		String newPw = newMember.getPw();
		String oldPw = oldMember.getPw();
		
		String newNickname = newMember.getNickname();
		String oldNickname = oldMember.getNickname();
		
		String newTel = newMember.getTel();
		String oldTel = oldMember.getTel();
		
		if(newPw != null && (oldPw.equals(newPw))) {
			newMember.setPw(null);
		} else if(newPw != null && (!oldPw.equals(newPw))) {
			oldMember.setPw(newPw);
		}
		
		if(oldNickname.equals(newNickname)) {
			newMember.setNickname(null);
		} else {
			oldMember.setNickname(newNickname);
		}
		
		if(oldTel.equals(newTel)) {
			newMember.setTel(null);
		} else {
			MemberDto selectedMember = dao.selectOneByTel(newMember.getTel());
			if(selectedMember != null) {
				throw new DuplicateDataException();
			}
			
			oldMember.setTel(newTel);
		}
		
		dao.updateMember(newMember);
		
		return oldMember;
	}
    
// ...

 

<< 코드 설명 >>

1. 수정할 회원 정보와 기존 회원 정보를 모두 꺼냄

2. 비밀번호를 변경하는데 기존의 비밀번호와 동일하게 입력했다면 DAO에서 비밀번호를 변경하지 않도록 하게 만들기 위해 수정할 회원 정보의 비밀번호를 지움

  비밀번호를 변경하는데 기존의 비밀번호와 다르다면 기존 회원 정보(oldMember)의 비밀번호를 변경할 비밀번호로 변경

  서비스 메서드의 oldMember 객체는 회원 정보를 수정한 후 컨트롤러로 반환하는 객체로 (4) 까지 동작하고 나면 기존 회원 정보가 변경된 최신 회원 정보를 갖고 있게 됨

3. 닉네임을 변경하지 않는다면 수정할 회원 정보의 닉네임을 지움

  닉네임을 변경한다면 기존 회원 정보의 닉네임을 수정할 닉네임으로 변경

4. 연락처를 변경하지 않는다면 수정할 회원 정보의 연락처를 지움

  연락처를 변경한다면 연락처 중복 여부 확인

  연락처가 중복됬다면 DuplicateDataException 예외 발생

  연락처가 중복되지 않았다면 기존 회원 정보의 연락처를 수정할 연락처로 변경

5. 회원 정보 수정

6. 변경된 최신 회원 정보를 컨트롤러로 반환

 

<< member패키지 내 DuplicateDataException 예외 클래스 추가 >>

package member;

public class DuplicateDataException extends RuntimeException {

}


 

<< 3. DB의 member 테이블에 update 쿼리를 수행하고 결과를 받아 서비스 메서드로 전달할 DAO >>

더보기

<< member 패키지 -> MemberDao 클래스 >>

코드가 길어 일부만 가져옴

// ...

public void updateMember(MemberDto member) {
	Connection conn = null;
	PreparedStatement pstmt = null;
	
	int idx = member.getIdx();
	String pw = member.getPw();
	String nickname = member.getNickname();
	String tel = member.getTel();
	
	if(pw == null && nickname == null && tel == null) {
		return ;
	}
	
	try {
		conn = DBUtil.getConnection();
		
		int count = 1;
		
		StringJoiner sql = new StringJoiner(",", "UPDATE member SET ", " WHERE idx = ?");
		if(pw != null) {
			sql.add("pw = ?");
			count++;
		}
		if(nickname != null) {
			sql.add("nickname = ?");
			count++;
		}
		if(tel != null) {
			sql.add("tel = ?");
			count++;
		}
		
		pstmt = conn.prepareStatement(sql.toString());
		
		pstmt.setInt(count--, idx);
		
		if(tel != null) {
			pstmt.setString(count--, tel);
		}
		if(nickname != null) {
			pstmt.setString(count--, nickname);
		}
		if(pw != null) {
			pstmt.setString(count, pw);
		}
		
		pstmt.executeUpdate();
	} catch (SQLException e) {
		e.printStackTrace();
	} finally {
		DBUtil.closes(conn, pstmt);
	}
}

// ...

 

<< 코드 설명 >>

1. 수정할 회원 정보의 데이터를 꺼냄

2. 서비스 메서드가 잘못 호출해 수정할 회원 정보가 비어있다면 메서드 흐름을 끊음

3. StringJoiner를 사용해 상황에 맞는 update 쿼리 생성

  StringJoiner를 모른다면 ChatGPT에 물어보기

4. 생성한 쿼리를 DB로 보내기 위한 준비

5. 준비된 쿼리에 ? 부분을 채움

6. 쿼리 보내기 & 실행

이 DAO에서 상황에 맞는 쿼리를 생성하므로 이런 쿼리를 동적 쿼리라고 함



이제 마지막으로 회원 정보 수정 페이지에서 [ 정보 수정 ] 버튼을 눌렀을 때 회원 정보 수정 컨트롤러로 요청이 들어가도록 회원 정보 수정 페이지 내 [ 정보 수정 ] 버튼에 기능을 추가하자

 

회원 정보 수정 페이지는 동적인 페이지이므로 회원 정보 수정 컨트롤러(member 패키지 -> MemberUpdate 서블릿)에서 직접 출력하고 있음

더보기

회원 정보 수정 컨트롤러(member패키지 -> MemberUpdate 서블릿) 내 doGet 메서드 수정

닫는 main 태그( </main> ) 와 닫는 body 태그 ( </body> ) 사이에 코드가 추가된 것

// ...

protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	HttpSession session = request.getSession();
	
	if(session.getAttribute("member") == null) {
		response.sendRedirect("/member");
	}
	
	MemberDto loginMember = (MemberDto) session.getAttribute("member");
	String id = loginMember.getId();
	String nickname = loginMember.getNickname();
	String tel = loginMember.getTel();
	
	response.setCharacterEncoding("UTF-8");
	
	PrintWriter pw = response.getWriter();
	
	pw.print("<!DOCTYPE html>");
	pw.print("<html>");
	pw.print("<head>");
	pw.print("	<meta charset=\"UTF-8\">");
	pw.print("	<title>연습 프로젝트</title>");
	pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/public/common.css\">");
	pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/member/join.css\">");
	pw.print("</head>");
	pw.print("<body>");
	pw.print("	<header>&#60;&#60; 회원 정보 수정 &#62;&#62;</header>");
	pw.print("	<main>");
	pw.print("		<form action=\"/member/update\" method=\"POST\">");
	pw.print("			<fieldset>");
	pw.print("				<label for=\"id\">아이디</label>");
	pw.print("				<input type=\"text\" name=\"id\" id=\"id\" required=\"required\" dp-name=\"아이디\" readonly=\"readonly\" disabled=\"disabled\" value=\""+id+"\">");
	pw.print("			</fieldset>");
	pw.print("			<fieldset>");
	pw.print("				<div>");
	pw.print("					<label for=\"pw\">비밀번호</label>");
	pw.print("					<input type=\"password\" name=\"pw\" id=\"pw\" required=\"required\" dp-name=\"비밀번호\">");
	pw.print("				</div>");
	pw.print("				<div>");
	pw.print("					<label for=\"pwchk\">비밀번호 확인</label>");
	pw.print("					<input type=\"password\" name=\"pw\" id=\"pwchk\" required=\"required\" dp-name=\"비밀번호 확인\">");
	pw.print("				</div>");
	pw.print("			</fieldset>");
	pw.print("			<fieldset>");
	pw.print("				<label for=\"nickname\">닉네임</label>");
	pw.print("				<input type=\"text\" name=\"nickname\" id=\"nickname\" required=\"required\" dp-name=\"닉네임\" value=\""+nickname+"\">");
	pw.print("			</fieldset>");
	pw.print("			<fieldset>");
	pw.print("				<label for=\"tel\">연락처</label>");
	pw.print("				<input type=\"tel\" name=\"tel\" id=\"tel\" required=\"required\" dp-name=\"연락처\" value=\""+tel+"\">");
	pw.print("			</fieldset>");
	pw.print("			<fieldset>");
	pw.print("				<button type=\"button\" role=\"submit\">정보 수정</button>");
	pw.print("			</fieldset>");
	pw.print("		</form>");
	pw.print("	</main>");
	
	pw.print("<script src=\"https://code.jquery.com/jquery-3.6.3.min.js\" integrity=\"sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=\" crossorigin=\"anonymous\"></script>");
	pw.print("<script>");
	pw.print("	$(\"button\").on(\"click\", function() {");
	pw.print("		let pw = $(\"#pw\").val();");
	pw.print("		let pwchk = $(\"#pwchk\").val();");
	pw.print("		let nickname = $(\"#nickname\").val();");
	pw.print("		let tel = $(\"#tel\").val();");
	
	pw.print("		let formData = {\"nickname\": nickname, \"tel\": tel};");
	
	pw.print("		if((pw.length != 0) && (pw != pwchk)) {");
	pw.print("			alert(\"비밀번호와 비밀번호 확인이 일치하지 않습니다.\");");
	pw.print("			$(\"#pwchk\").focus();");
	pw.print("			return false;");
	pw.print("		} else if(pw.length != 0) {");
	pw.print("			formData[\"pw\"] = pw;");
	pw.print("		}");
	
	pw.print("		$.ajax({");
	pw.print("			url: $(\"form\").attr(\"action\"),");
	pw.print("			type: $(\"form\").attr(\"method\"),");
	pw.print("			data: formData,");
	pw.print("			success: function() {");
	pw.print("				alert(\"회원 정보를 수정했습니다.\");");
	pw.print("			},");
	pw.print("			error: function(response) {");
	pw.print("				if(response.status == 400) {");
	pw.print("					alert(\"수정할 정보를 입력해주세요.\");");
	pw.print("				} else if(response.status == 409) {");
	pw.print("					alert(\"연락처가 중복되었습니다.\");");
	pw.print("				} else {");
	pw.print("					alert(\"서버에 문제가 생겼습니다.\\n잠시 후 다시 시도해주세요.\");");
	pw.print("				}");
	pw.print("			}");
	pw.print("		});");
	pw.print("	});");
	pw.print("</script>");
	
	pw.print("</body>");
	pw.print("</html>");
}

// ...

 

<< 코드 설명 >>

서블릿이 직접 동적인 페이지를 출력했을 때는 이와 같이 굉장히 불편함

이를 개선한 툴? 언어?가 JSP임

우리는 아직 JSP를 배우지 않았으니 어쩔 수 없이 JS를 이와 같이 추가해야함

 

추가된 JS 부분만 설명함

1. 회원 정보 수정 버튼을 클릭했을 때

2. 화면에 입력한 비밀번호 ~ 연락처까지 가져옴

3. 회원 정보 수정 컨트롤러로 보낼 데이터를 JSON으로 생성

4. 비밀번호를 변경하는데 비밀번호와 비밀번호 확인이 일치하지 않는다면 안내 문구 출력

  비밀번호를 변경하는데 비밀번호와 비밀번호 확인이 일치한다면 회원 정보 수정 컨트롤러로 보낼 데이터에 비밀번호 추가

5. 회원 정보 수정 컨트롤러로 요청을 보내고 결과를 받아 적절한 안내 문구 출력



여기까지 다소 길고 복잡하지만 회원 정보 수정 페이지와 기능을 구현했음

이제 서버를 실행시키고 회원 정보 수정 기능이 잘 동작하는지 확인하자

 

여기서 가장 불편한 부분은 서블릿이 직접 웹 페이지를 출력하면 웹 페이지에 무언가를 수정하거나 그 웹 페이지에 JS를 추가할 때 굉장히 불편하다는 점임

 

컨트롤러, 서비스, DAO 에서 이해가 안가는 부분이 있다면 그건 자바와 서블릿 복습이 부족하다는 것

728x90
LIST

<< 학습 목표 >>

1. 다른 서블릿에서 필요한 정보를 세션에 저장할 수 있다.

2. 세션에 저장된 정보를 꺼내 활용할 수 있다.


전 글에서 세션에 저장해둔 로그인 정보를 활용 해보자

 

이번에 우리가 할 것은 다음과 같음

1. 로그인을 한 사용자가 메인 페이지에 접속했다면 "! (nickname)님 환영합니다 !" 를 출력

2. 회원 가입을 한 사용자의 전체 정보를 세션에 저장해 회원 가입 겸 로그인 성공 처리

3. 회원 가입 성공 페이지에 접속했다면 "! (nickname)님 가입되었습니다 !" 를 출력

 

4. 로그인 상태를 확인하는 서블릿에서 확인 방식 변경

 

이전까지는 하나씩 수정한 후 서버를 켜 확인했지만

이번에는 4번까지 모두 다 진행한 후 서버를 켜고 확인하자


<< 1. 로그인을 한 사용자가 메인 페이지에 접속했다면 "! (nickname)님 환영합니다 !" 를 출력 >>

 

이와 같은 작업을 하려면 메인 페이지를 보여주는 서블릿에서 로그인 상태 정보를 꺼내는 방식 변경해야 하고 로그인에 성공했다면 ! (nickname)님 환영합니다 ! 를 출력하도록 해야함

더보기

메인 페이지를 보여주는 서블릿(member 패키지 -> MemberMain 서블릿)의 코드를 아래와 같이 수정

package member;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member")
public class MemberMain extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		
		MemberDto loginMember = null;
		if(session.getAttribute("member") != null) {
			loginMember = (MemberDto) session.getAttribute("member");
		}

		response.setCharacterEncoding("UTF-8");
		PrintWriter pw = response.getWriter();
		
		pw.print("<!DOCTYPE html>");
		pw.print("<html>");
		pw.print("<head>");
		pw.print("	<meta charset=\"UTF-8\">");
		pw.print("	<title>연습 프로젝트</title>");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/public/common.css\">");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/index.css\">");
		pw.print("</head>");
		pw.print("<body>");
		pw.print("	<header>&#60;&#60; 메인 &#62;&#62;</header>");
		pw.print("	<main>");
		pw.print("		<ul>");
		
		if(loginMember != null) {
			pw.print("			<li><h3>! "+loginMember.getNickname()+" 님 환영합니다 !</h3></li>");
			pw.print("			<li><a href=\"/member/logout\">로그아웃</a></li>");
			pw.print("			<li><a href=\"/member/update\">회원 정보 수정</a></li>");
			pw.print("			<li><a href=\"#\">회원 탈퇴</a></li>");
		} else {
			pw.print("			<li><a href=\"/member/join\">회원가입</a></li>");
			pw.print("			<li><a href=\"/member/login\">로그인</a></li>");
		}
		
		pw.print("		</ul>");
		pw.print("	</main>");
		pw.print("</body>");
		pw.print("</html>");
	}
}

 

세션에서 로그인한 사용자의 전체 정보를 꺼내야하므로 이를 담을 수 있는 loginMember 객체 선언(1)

세션에 로그인한 사용자의 전체 정보가 들어있다면 꺼내 loginMember 객체에 저장

 

로그인한 사용자의 정보가 있다면(2) 로그인 화면에서 ! (nickname) 님 환영합니다 ! 를 출력



<< 2. 회원 가입을 한 사용자의 전체 정보를 세션에 저장해 회원 가입 겸 로그인 성공 처리 >>

 

이와 같은 작업을 하려면 회원 가입 서블릿에서 회원 가입에 성공했다면 세션에 로그인한 사용자의 전체 정보를 저장해야함

더보기

<< 회원 가입 서블릿 (member 패키지 -> MemberJoin 서블릿) 을아래와 같이 수정 >>

수정할 부분은 적으나 코드는 매우 길기 때문에 수정할 부분만 가져옴

// ...

if(result.equals("OK")) {
	// 상태 코드를 설정하지 않으면 자동으로 200으로 설정되므로
	// 설정 생략
	// response.setStatus(200);
	
	HttpSession session = request.getSession();
	session.setAttribute("member", member);
} else if(result.startsWith("duplicate")) {
	response.setStatus(409);
} else {
	response.setStatus(500);
}

// ...

 

회원 가입에 성공했을 경우 세션에 사용자의 전체 정보를 저장(1)

로그인했을 때와 같은 방법으로 세션에 사용자의 전체 정보를 저장했으므로 로그인 성공 처리가 된 것



<< 3. 회원 가입 성공 페이지에 접속했다면 "! (nickname)님 환영합니다 !" 를 출력 >>

 

이와 같은 작업을 하려면 회원 가입 완료 페이지로 포워딩 하는 서블릿에서 회원 가입에 성공했다면 세션에 저장된 사용자의 전체 정보를 꺼내 보여줘야함

 

이때 페이지는 동적인 페이지임

왜? 회원 가입 하는 사람들 마다 닉네임이 다르므로...

쿠키에 사용자의 전체 정보가 저장되 있었다면 HTML 페이지 그대로, JS를 추가해 닉네임을 보여줄 수 있겠지만 세션에 저장된 사용자의 전체 정보를 꺼내 그 닉네임을 출력해야하므로 Servlet에서 동적인 페이지를 출력해야함

더보기

<< 회원 가입 완료 페이지로 포워딩하는 서블릿(member 패키지 -> MemberJoinSuccess 서블릿) >>

package member;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member/joinSuccess")
public class MemberJoinSuccess extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		
		MemberDto loginMember = null;
		if(session.getAttribute("member") != null) {
			loginMember = (MemberDto) session.getAttribute("member");
		}
		
		if(loginMember != null) {
			response.setCharacterEncoding("UTF-8");
			
			PrintWriter pw = response.getWriter();
			pw.print("<!DOCTYPE html>");
			pw.print("<html>");
			pw.print("<head>");
			pw.print("	<meta charset=\"UTF-8\">");
			pw.print("	<title>연습 프로젝트</title>");
			pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/public/common.css\">");
			pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/member/joinSuccess.css\">");
			pw.print("</head>");
			pw.print("<body>");
			pw.print("	<header>&#60;&#60; 회원 가입 완료 &#62;&#62;</header>");
			pw.print("	<main>");
			pw.print("		<div id=\"content_wrapper\">");
			pw.print("			<h1>! "+loginMember.getNickname()+"님 가입되었습니다 !</h1>");
			pw.print("			<div id=\"image_wrapper\">");
			pw.print("				<div><img src=\"/images/signupSuccess_left.png\"></div>");
			pw.print("				<div><img src=\"/images/signupSuccess_right.png\"></div>");
			pw.print("			</div>");
			pw.print("			<a href=\"/member/login\">로그인 페이지로 이동</a>");
			pw.print("		</div>");
			pw.print("	</main>");
			pw.print("</body>");
			pw.print("</html>");
		} else {
			response.sendRedirect("/member");
		}
	}
	
}

 

세션에 저장된 사용자의 전체 정보를 저장할 loginMember 객체 생성(1)

세션에 사용자의 전체 정보가 저장되있다면 꺼내서 loginMember 객체에 저장

 

세션에 사용자의 전체 정보가 저장되있다면 회원 가입 성공 페이지에 그 사용자의 닉네임을 출력함(2)


 

이렇게 했을 경우 생기는 단점은 로그인을 한 사람이 회원 가입 성공 페이지의 URL로 직접 접근했을 때도 회원 가입에 성공했다는 메세지를 볼 수 있다는 것

 

회원 가입 후 회원 가입 성공 페이지로 왔을 경우에만 회원 가입 성공 메세지를 보여주고 싶을 때는 회원 가입 서블릿에서 추가로 여러분만의 상태 정보, 이를 테면 successJoin=true, 를 저장한 뒤 회원 가입 성공 페이지에서 successJoin도 함께 꺼내 2차로 확인하면 됨

 

로그인한 사용자가 회원 가입 성공 페이지로 왔다고 해서 서비스에 심각한 문제가 생기는건 아니므로 이대로 두자


<< 4. 로그인 상태를 확인하는 서블릿에서 확인 방식 변경 >>

각 페이지로 포워딩 해주는 서블릿에서 로그인 여부를 확인하는 부분이 있다면 해당 부분들을 모두 바꿔야함

 

ㄱ. 회원 가입 페이지 포워딩 서블릿

 

ㄴ. 로그인 페이지 포워딩 서블릿

 

이제 서버를 시작하고 메인 페이지, 회원 가입, 회원 가입 완료, 로그인 페이지를 하나씩 확인해보자

728x90
LIST

<< 학습 목표 >>

1. 세션에 민감한 개인 정보를 저장할 수 있다.


전 글을 통해서 세션에 로그인 상태를 기록했음

그러나 사실 전 글처럼 단순한 데이터는 굳이 세션에 저장할 필요가 없음

쿠키에 저장해도 문제 없음

왜? 로그인에 성공했을 때 세션에 isLogin에 true 를 저장했는데 이는 로그인 성공 실패 여부만 나타내는 데이터이지 사용자와 관련된 민감한 개인 정보는 없음

아이디와 비밀번호를 저장한 것도 아니고 연락처나 주민등록 번호 등 유출 되면 큰 문제가 생기는 개인 정보를 저장한 게 아니기 때문에...

 

이번에는 로그인에 성공했을 때 반드시 세션에 저장해야하는 중요한 상태 정보를 저장해보자

로그인에 성공했을 때 세션에 로그인한 사용자의 정보 전체를 저장하자

만약 이를 쿠키에 저장했다면 해커가 쿠키를 통해 "음~ 로그인한 사용자의 아이디, 비밀번호는 이거군, 연락처는 저거군" 하면서 빼갈 수 있기 때문에 로그인한 사용자의 정보 전체는 반드시 세션에 저장해야함


구체적으로 우리는 로그인에 성공했다면 세션에 로그인한 사용자의 전체 정보(아이디, 비밀번호, 닉네임, 연락처)를 저장 할 것

 

세션에 무언가를 저장하는 역할은 컨트롤러가 하므로 로그인한 사용자의 전체 정보를 세션에 저장하려면 컨트롤러에서 해야함

그럼 우선 현재 컨트롤러를 분석해보자

1. 로그인 성공, 실패 여부를 판단하기 위해 아이디, 비밀번호를 담은 객체 생성

2. 로그인 성공, 실패 여부를 판단할 서비스 메서드로 아이디, 비밀번호를 담은 객체를 전달한 후 반환 값을 result 변수에 담음

3. 로그인에 성공했다면

4. 세션에 로그인 성공 상태 저장

 

여기서 바꿔야할 부분을 생각해보면 4번임

4. 세션에 로그인에 성공한 사용자의 전체 정보를 저장

 

로그인에 성공한 사용자의 전체 정보를 어떻게 저장할까?

로그인에 성공했을 때 컨트롤러가 알고 있는건 아이디, 비밀번호 뿐이므로 서비스 또는 DAO를 통해서 로그인에 성공한 사용자의 전체 정보를 받아와야함

 

컨트롤러의 주된 역할은?

더보기

1. 클라이언트의 요청을 받는다.

2. 파라미터(클라이언트가 보낸 값)를 꺼낸다.

3. 파라미터를 검증한다.

4. 클라이언트의 요청을 처리할 서비스 메서드를 호출한다.

5. 서비스 메서드가 반환한 처리 결과에 맞는 결괏값(+상태코드) 또는 결과 페이지를 응답한다.

 

따라서 로그인에 성공했을 때 서비스를 사용해 로그인에 성공한 사용자의 전체 정보를 가져오면 됨

그. 러. 나. 더 간단한 방법은 2번

 

로그인 성공, 실패 여부를 판단할 서비스 메서드로 아이디, 비밀번호를 담은 객체를 전달한 후 반환 값을 result 변수에 담음

 

에서 로그인 성공, 실패 여부를 판단할 서비스 메서드(correctIdNPw 메서드) 안으로 들어가보면 DAO에서 사용자의 전체 정보를 받아오고 있음

이 서비스 메서드가 boolean을 반환하는게 아닌 로그인에 성공했다면 사용자의 전체 정보를 반환하고 로그인에 실패했다면 null을 반환하도록 처리하는게 더 간단함

 

따. 라. 서 ! correctIdNPW 메서드의 반환 타입을 MemberDto로 바꾸고 로그인에 성공했다면 사용자의 전체 정보를 반환하고 로그인에 실패했다면 null을 반환하도록 바꾸자

멤버 서비스 클래스(member 패키지 -> MemberService 클래스) 내 correctIdNPw 메서드를 다음과 같이 수정

package member;

public class MemberService {
	public MemberDto correctIdNPw(MemberDto member) {
		MemberDao dao = new MemberDao();
		
		MemberDto selectedMember = dao.selectOneById(member.getId());
		if(selectedMember == null) {
			return null;
		}
		
		// 사용자가 입력한 비밀번호
		String userInputPw = member.getPw();
		// 사용자가 입력한 아이디로 조회한 회원 정보의 비밀번호
		String storedUserPw = selectedMember.getPw();
		
		if(!userInputPw.equals(storedUserPw)) {
			return null;
		} else {
			return selectedMember;
		}
	}
    
    ...
}

 

 

그 후 로그인 서블릿(member 패키지 -> MemberLogin 서블릿의 코드를 다음과 같이 수정하자

 

(1). 로그인 성공, 실패 여부를 판단하는 서비스가 이제 사용자 정보를 반환하므로 사용자 정보를 받을 수 있게 수정

(2). 로그인에 성공했다면 로그인에 성공한 사용자 정보를 저장하고 있으므로 조건식을 다음과 같이 변경

(3). 로그인에 성공했다면 로그인에 성공한 사용자 정보를 세션에 저장

728x90
LIST

<< 학습 목표 >>

1. 세션을 활용해 로그인 상태를 기억할 수 있다.

3. 세션에 저장된 상태 정보를 여러 서블릿에서 활용할 수 있다.

3. 서블릿으로 동적인 웹 페이지를 전달할 수 있다.


프로젝트(서비스)가 상태 정보를 저장해야되는 상황, 저장된 상태 정보를 사용해야되는 상황은 빈번히 발생함

 

먼저 네이버를 보자

네이버는 사용자의 로그인 상태를 기억하고 사용자가 로그인 전 이라면 로그인 전 화면을 보여주고 로그인 후 라면 로그인 후 화면을 보여주고 있음

 

 

우리가 만든 사이트에서도 로그인 상태를 기억하고 이를 활용해보자

로그인 서블릿에서 로그인에 성공했을 경우 세션에 로그인에 성공했다는 상태를 저장하자

 

지난 Chapter에서 만든 프로젝트의 서블릿은 member 패키지 안에 있음

member 패키지 내 MemberLogin 서블릿에서 로그인에 성공했을 때에 아래와 같이 추가하자

( 코드가 길어 바꿔야되는 부분만 캡쳐 )

 

로그인에 성공했다면 세션에 isLogin에 true를 저장했음(1)

이렇게 사용자가 로그인에 성공했다는 상태 정보를 저장할 수 있음

 

이제 로그인이 필요한 많은 곳에서 로그인 상태 정보를 활용할 수 있음


사용자가 회원 가입 페이지로 접속한다는건 회원 가입을 하지 않았다는 것

그렇다는건 당연히 로그인도 하지 않았다는 것

그러므로 회원 가입 페이지로 이동할 수 있는 회원은 로그인을 하지 않은 회원 뿐!

그러나 우리 서비스에서는 로그인을 했어도 회원 가입 페이지로 이동할 수 있음

 

회원 가입 페이지로 이동시켜주는 서블릿(member 패키지 -> MemberJoin서블릿)에서 세션을 활용해

1. 세션을 활용해 로그인 여부를 확인함

2. 로그인한 사용자는 메인 페이지로 리다이렉트 시키고

3. 로그인을 하지 않은 사용자만 회원 가입 페이지로 포워딩 시킴

더보기

만약 왜? 로그인을 한 상태라면 리다이렉트를 시키고 왜? 로그인을 하지 않은 상태라면 포워딩을 시키는 거지? 라는 생각이 들면 리다이렉트, 포워딩과 관련된 내용들을 더 복습해야함


 

로그인 페이지도 회원 가입 페이지와 마찬가지임

로그인 페이지로 접속하는 사용자는 로그인을 하지 않은 사용자임

로그인을 한 사용자가 다시 로그인 페이지로 접속하지 않을 것

 

로그인 페이지로 이동시켜주는 서블릿(member 패키지 -> MemberLogin서블릿)에서 세션을 활용해

1. 세션을 활용해 로그인 여부를 확인함

2. 로그인한 사용자는 메인 페이지로 리다이렉트 시키고

3. 로그인을 하지 않은 사용자만 로그인 페이지로 포워딩 시킴


이번에는 세션을 활용해서 아예 메인 페이지를 개선하자

로그인을 하지 않은 사용자는 메인 페이지에 회원가입, 로그인 버튼만 보이도록...

로그인을 한 사용자는 메인 페이지에 회원 정보 수정, 회원 탈퇴 버튼만 보이도록...

 

이제 동적인 메인 페이지를 만들어야함

앞 글에서 쿠키를 사용한 웹 페이지를 만들기 위해 JS 라이브러리를 추가하고 JS를 사용해 동적인 로그인 페이지를 만들었음

이번에도 JS를 사용해 동적인 메인 페이지를 만들 수 있을까?

그렇지 않음

쿠키는 클라이언트에 저장되므로 클라이언트 사이드 언어인 JS로 핸들링 할 수 있음

세션은 서버에 저장되므로 서버 사이드 언어인 Servlet, JSP만 핸들링 할 수 있음

JSP는 배우지 않았으니 Servlet으로 동적인 메인 페이지를 만들어보자

( 이 부분을 보면 앞 글에서 쿠키를 사용한 동적인 로그인 페이지를 패쓰한 이유를 알 수 있음 )

 

메인 페이지로 포워딩 시켜주는 서블릿에서 이제 포워딩을 시켜주면 안되고 동적인 웹 페이지를 전달해줘야함

과정을 먼저 설명하면

1. 세션을 활용해 로그인 여부를 확인함

2-1. 메인 페이지를 출력함

2-2. 메인 페이지의 메뉴 버튼을 출력할 때 로그인을 한 사용자 라면

2-3. 회원 정보 수정, 회원 탈퇴 버튼을 출력함

2-4. 메인 페이지의 메뉴 버튼을 출력할 때 로그인을 하지 않은 사용자 라면

2-5. 회원 가입, 로그인 버튼을 출력함

 

코드가 기므로 더보기를 참고해 메인 서블릿(member 패키지 -> MemberMain 서블릿)에 아래 코드를 추가하자

이제 메인 페이지 (WEB-INF 폴더 -> member -> index.html) 이 필요 없음

왜? MemberMain 서블릿에서 메인 페이지를 직접 출력하고 있으므로

더보기

package member;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member")
public class MemberMain extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		boolean isLogin = session.getAttribute("isLogin") != null;

		response.setCharacterEncoding("UTF-8");
		PrintWriter pw = response.getWriter();
		
		pw.print("<!DOCTYPE html>");
		pw.print("<html>");
		pw.print("<head>");
		pw.print("	<meta charset=\"UTF-8\">");
		pw.print("	<title>연습 프로젝트</title>");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/public/common.css\">");
		pw.print("	<link rel=\"stylesheet\" type=\"text/css\" href=\"/css/index.css\">");
		pw.print("</head>");
		pw.print("<body>");
		pw.print("	<header>&#60;&#60; 메인 &#62;&#62;</header>");
		pw.print("	<main>");
		pw.print("		<ul>");
		
		if(isLogin) {
			pw.print("			<li><a href=\"/member/update\">회원 정보 수정</a></li>");
			pw.print("			<li><a href=\"#\">회원 탈퇴</a></li>");
		} else {
			pw.print("			<li><a href=\"/member/join\">회원가입</a></li>");
			pw.print("			<li><a href=\"/member/login\">로그인</a></li>");
		}
		
		pw.print("		</ul>");
		pw.print("	</main>");
		pw.print("</body>");
		pw.print("</html>");
	}
}

 

이제 서버를 시작한 후 메인 페이지로 들어가보자

로그인 하기 전이라면 회원 가입, 로그인 버튼만 보이고 로그인 한 후라면 회원 정보 수정, 회원 탈퇴 버튼이 보일 것

 

또 일부로 회원 가입 페이지의 URL(http://localhost/member/join)을 입력해보고 로그인 페이지의 URL(http://localhost/member/login)을 입력해보자

 

이렇게 로그인 상태 정보를 사용해 더 실제와 같은 서비스로 발전시킬 수 있음


마지막으로 두 가지만 더 적용해보자

 

첫번째, 회원 가입 완료 페이지는 회원 가입을 한 후 보는 화면임

그러나 지금 우리는 회원 가입 완료 페이지 URL을 입력하면 접속할 수 있음

1. 회원 가입 서블릿에서 회원 가입을 완료 했을 경우 세션에 회원 가입 완료 상태를 저장

더보기

<< 회원 가입 서블릿 ( member 패키지 -> MemberJoin 서블릿 )

회원 가입에 성공했다면 세션에 회원 가입 완료 상태 저장

코드가 길어 일부만 캡쳐함


 

2. 회원 가입 완료 페이지로 포워딩 하기 전 회원 가입 완료 상태인 사용자인지 확인

더보기

package member;

import java.io.IOException;

import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member/joinSuccess")
public class MemberJoinSuccess extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		boolean successJoin = session.getAttribute("successJoin") != null;
		
		if(successJoin) {
			RequestDispatcher rd = request.getRequestDispatcher("/WEB-INF/member/joinSuccess.html");
			rd.forward(request, response);
		} else {
			response.sendRedirect("/member");
		}
	}
	
}

 

두 번째, 페이지 마다 로그인 상태 정보를 활용하므로 [ 로그아웃 ] 메뉴도 필요함

로그아웃은 로그인을 한 사용자에게 보여지는 메뉴임

1. 메인 페이지에 로그아웃 버튼 추가

메인 페이지를 출력하는 메인 서블릿(member 패키지 -> MemberMain 서블릿)에 로그인을 한 회원에게 보여줄 메뉴 부분에 다음과 같이 추가(1)

 

2. 로그아웃 기능 추가

Member 패키지 -> MemberLogout 서블릿 추가 후 아래 코드를 추가하자

package member;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

@WebServlet("/member/logout")
public class MemberLogout extends HttpServlet {
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		HttpSession session = request.getSession();
		session.invalidate();
		
		response.sendRedirect("/member");
	}

}

 

로그아웃 기능에서는 세션을 삭제(1) 하고 메인 페이지로 리다이렉트 하고 있음

세션에 여러 정보가 많이 들어있다면 로그인 여부를 확인하는 데이터만 삭제해야하지만 우리 세션에는 로그인 여부를 확인하는데 필요한 데이터만 들어있으므로 세션을 삭제했음


지난 글( https://codingaja.tistory.com/59 ) 에서 요즘에는 [ 아이디 기억하기 ] 대신 [ 로그인 상태 유지 ] 를 구현하는 추세라고 했음

 

로그인 상태 유지는 한번 로그인을 하고 난 이후에 로그아웃을 하기 전까지는 평생 쭉 로그인 상태로 유지되는 것으로 내일 또는 일주일 뒤 또는 일년 뒤에 다시 이 서비스에 접속하더라도 번거롭게 로그인 할 필요가 없는 것을 말함

서블릿으로 [ 로그인 상태 유지 ] 를 구현하기에는 한계가 있음

 

인터넷에 [ 로그인 상태 유지 ] 또는 [ 자동 로그인 ] 키워드로 검색하면 세션 아이디를 쿠키에 저장해 사용자가 다음번에 우리 서비스에 접속했을 때 쿠키에 저장된 세션 아이디를 가져와 로그인 상태를 유지하는것으로 구현되어있음

그러나 이는 좋은 생각은 아닌 듯 함

쿠키의 단점은 사용자 컴퓨터에 저장됨

따라서 쿠키에는 중요한 개인 정보는 저장하면 안됨

 

특정 사용자의 로그인 상태 정보를 취득할 수 있는 세션 아이디를 쿠키에 저장해두면 해커가 손쉽게 사용자의 로그인 상태 정보를 취득할 수 있음

제가 생각했을 때는 사용자가 우리 서비스에 접속했을 때 사용자가 보내는 어떤 고유한 키값을 활용해 세션 아이디를 암호화 하는 과정이 필요해보임

보안, 암호화 관련된 기초 지식이 많이 필요한 기능 같은데 다른 블로그들은 너무 쉽게 생각하는거 아닐까...

 

내가 너무 과하게 생각하고 있나 싶어 chatGPT에게 물어보니 위와 같은 방식은 보안에 큰 문제가 있다고 함

서블릿으로는 불가능 하지만 스프링 시큐리티에 RememberMe 기능을 사용하면 보안을 강화할 수 있다고 함

chatGPT가 항상 정답은 아니지만 내 생각과 같으므로 정답을 알려줬다고 생각함

 

따라서 Servlet 을 사용해서는 보안이 강화된 자동 로그인은 구현할 수 없을 듯 함

728x90
LIST

<< 학습 목표 >>

1. 쿠키를 활용해 아이디 기억하기 기능을 구현할 수 있다.

2. JS 를 활용해 웹 페이지에서 쿠키를 활용할 수 있다.


예전에는 대부분의 웹 사이트에서 로그인을 할 때 [ 아이디 기억하기 ] 가 있었음

아이디 기억하기 를 체크 한 후 로그인에 성공하면 다음에 다시 그 사이트에 로그인할 때 아이디 부분이 자동으로 채워져있었음

요즘은 아이디 기억하기 대신 [ 로그인 상태 유지 ] 로 바뀌는 추세임

( 로그인 상태 유지에 대한 얘기는 다음 글, https://codingaja.tistory.com/60, 에서 하겠음 )

 

서블릿으로 [ 로그인 상태 유지 ] 를 구현하기에는 한계가 뚜렷하기 때문에 [ 아이디 기억하기 ] 까지만 구현하자

 

먼저 아이디 기억하기 를 구현하려면 로그인 화면에서 아이디 기억하기 체크 버튼이 있어야함

사용자가 원치 않는데 아이디가 기억된다면 공공 장소(피씨방, 학교 컴퓨터)에서 로그인을 할 때 사용자에게 불편함을 줄 수 있기 때문에...

 

회원 가입 페이지(WEB-INF  폴더 -> member 폴더 -> login.html) 에 로그인 버튼 앞에 다음과 같이 아이디 기억하기 체크 박스를 추가하자

그 후 로그인 버튼을 눌렀을 때 서버로 remeberId 체크 여부를 함께 전달하자

<< 해당 부분만 캡쳐한 이미지 >>

 

이제 로그인 서블릿(member패키지 -> memberLogin 서블릿)에 아이디 기억하기 체크 값을 받도록 코드를 추가하자

( 코드가 길어 doPost 부분만 짤라왔음 )

// ...

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
	// 파라미터 검증
	String id = ParameterUtil.getString(request, "id");
	String pw = ParameterUtil.getString(request, "pw");
	String rememberIdTemp = ParameterUtil.getString(request, "rememberId");
	
	if(!ParameterUtil.isId(id)) {
		response.setStatus(400);
		return ;
	} else if(!ParameterUtil.isPw(pw)) {
		response.setStatus(400);
		return ;
	} else if(!ParameterUtil.isRememberId(rememberIdTemp)) {
		response.setStatus(400);
		return ;
	}
	
	boolean rememberId = rememberIdTemp == null ? false : Boolean.valueOf(rememberIdTemp);
	
	CryptoUtil cu = new CryptoUtil();
	pw = cu.onewayEncryption(pw);
	
	MemberDto member = new MemberDto(id, pw, null, null);
	
	MemberService service = new MemberService();
	boolean result = service.correctIdNPw(member);
	
	if(result) {
		if(rememberId) {
			Cookie cookie = new Cookie("userId", id);
			cookie.setMaxAge(60 * 60 * 24 * 30);
			response.addCookie(cookie);
		}
	} else {
		response.setStatus(400);
	}
}

// ...

 

rememberId 파라미터 검증을 하기 위한 코드를 넣자

( util패키지 -> ParameterUtil 클래스 / 코드가 너무 길어 isRememberId 메서드만 짤라왔음 )

// ...

public static boolean isRememberId(String rememberId) {
	return rememberId == null || rememberId.equals("true");
}

 

코드를 설명하면...

파라미터 검증을 하기 위해 우선 전달 받은 값을 꺼냄(1)

그 후 rememberId 파라미터 값을 검증함(2)

로그인에 성공한 다음 사용자가 아이디 기억하기를 체크했다면(3) 쿠키에 저장할 아이디를 담아 반환(4)


여기까지 아이디 기억하기를 추가한 로그인 과정의 흐름을 살펴보자

아이디 기억하기를 체크하고 로그인 요청을 했을 때의 상황임

 

아직 아이디 기억하기가 완성되지 않았음

아이디 기억하기를 체크하고 로그인에 성공한 후 다음번에 접속했을 때 로그인 페이지에 아이디가 기입되있어야함

 

이제 로그인 페이지가 동적으로 바뀌어야함

동적인 페이지를 만드는 방법

1. JS를 사용

2. Servlet을 사용

3. JSP를 사용

여기서 우린 아직 JSP를 배우지 않았으므로 1, 2번을 사용해 동적인 로그인 페이지를 만들 수 있음

가장 간단한 방법은 1번임

 

<< 1. JS를 사용 >>

JS를 사용해 동적인 로그인 페이지를 만들려면 JS에서 이름이 userId 쿠키가 있는지 확인을 함

userId 쿠키가 있다면 해당 쿠키 값을 꺼내 아이디 입력란에 아이디를 표시하고 아이디 기억하기 체크 박스를 체크해야함

userId 쿠키가 없다면 현재 상태 그대로 아이디가 비어있고 아이디 기억하기가 체크 되어있지 않은 상태로 두면 됨

 

JS에서 쿠키를 사용하려면 Cookie 라이브러리를 추가해야함

Cookie 라이브러리를 추가하고 위에 설명한 과정을 구현한 코드를 보자

아래 코드는 로그인 페이지(WEB-INF 폴더 -> member 폴더 -> login.html)에 추가하면 됨

( 코드가 너무 길어 바꿔야하는 부분만 캡쳐 )

// ...

<script src="https://code.jquery.com/jquery-3.6.3.min.js" integrity="sha256-pvPw+upLPUjgMXY0G+8O0xUf+/Im1MZjXxxgOcBQBXU=" crossorigin="anonymous"></script>
<script src="https://cdn.jsdelivr.net/npm/js-cookie@3.0.1/dist/js.cookie.min.js"></script>

<script>
	let value = Cookies.get('userId');
	if(value != undefined) {
		$("#id").val(value);
		$("#rememberId").prop("checked", true);
	}
	
	$("button[role='submit']").on("click", function() {
		let id = $("#id").val();
		let pw = $("#pw").val();
		let rememberId = $("#rememberId").prop("checked");
		
		$.ajax({
			url: $("form").attr("action"),
			type: $("form").attr("method"),
			data: {"id": id, "pw": pw, "rememberId": rememberId},
			success: function() {
				location.href = "/member";
			},
			error: function() {
				alert("아이디 또는 비밀번호를 확인해주세요.");
			}
		});
	});
</script>

// ...

 

위에 설명했듯

JS 라이브러리를 추가(1)하고 userId 쿠키를 꺼냄(2)

userId 쿠키가 있다면 아이디 입력란에 아이디를 표시하고 아이디 기억하기 체크 박스를 체크함(3)

 

이제 서버를 시작하고 로그인을 할 때 [ 아이디 기억하기 ] 체크하지 않고 로그인을 해본 다음 로그인 페이지로 가보자

그 다음 [ 아이디 기억하기 ] 를 체크한 뒤 로그인을 해본 다음 로그인 페이지로 가보자


Servlet을 사용한 동적 페이지는 중요하지 않으므로 패쓰~!

728x90
LIST

<< 학습 목표 >>

1. 사용자들의 개인정보를 암호화해야하는 이유를 설명할 수 있다.

2. 데이터를 암호화할 수 있다.


CRUD 프로젝트를 통해서 사용자들은 우리 서비스에 가입을 할 수 있음

가입을 할 때 사용자들의 민감한 개인정보를 함께 입력함

사용자들은 우리를 믿고 가입을 할 때 민감한 개인 정보를 입력했고 우리는 이를 DB에 보관하고 있음

만약 해커가 우리 DB를 해킹해 사용자들의 민감한 개인 정보를 훔쳐간다면?

 

 

한국맥도날드, 488만 고객 개인정보 유출…과징금 7억 제재

【서울=뉴시스】송혜리 기자 = 487만6106명의 고객 개인정보가 유출된 한국맥도날드가 개인정보보호위원회로부터 7억원 상당의 과징금 제재를 받았다

www.newsis.com

 

가장 중요한건 해커가 DB를 해킹하지 못하게 막는게 중요함

그러나 세상에 모든 것을 막을 수 있는 방패는 없듯 아무리 보안에 신경을 쓴다고 해도 해킹을 하는 해커들이 있음

그래서 2차적으로 사용자들의 민감한 개인 정보를 암호화해 저장함

암호화해 저장해두면 해커가 개인 정보를 훔쳐가더라도 이게 무슨 데이터인지 알 수 없기 때문에...

 

밑으로 내려 가기 전 암호화와 관련된 부분들을 보고 오자

 

AES & SHA-256 암호화란 ? (개념 / 종류 / 특징)

서론 이번글에서는 AES란 암호화에 대해 정리해볼려 한다. 우선 프로그래밍에 앞서 사용자의 개인 정보에 대해서 암호화는 필수라고 생각한다. 필자는 이번에 대표적인 AES의 암 복호화에 대한

jeongkyun-it.tistory.com

 

 

 

자바 암호화와 복호화

자바에서 암호화와 복호화는 어떻게 구현할까? 암호화에 사용되는 알고리즘, 운용 방식, 패딩이란 무엇일까?

madplay.github.io

 

 

자바 비밀번호 암호화 (SHA256,SHA512) + Salt

SHA (Secure Hash Algorithm, 안전한 해시 알고리즘)은 해시 함수들의 모음이라고 보면 되요. 복호화가 불가능한 단방향 암호화 기법으로 데이터를 안전하게 저장할 수 있습니다. SHA256과 SHA512도 그 중

gofnrk.tistory.com


우리는 간단한 프로젝트라 민감한 개인정보는 없지만 일반적으로 비밀번호는 암호화해 저장해둠

우리도 비밀번호를 암호화해 저장해두자

 

util 패키지 -> CryptoUtil 클래스를 선언하고 아래 코드를 추가하자

package utility;

import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

public class CryptoUtil {
	public String onewayEncryption(String str) {
		String encodedStr = null;
		
		try {
			MessageDigest md = MessageDigest.getInstance("SHA-512");
			md.update(str.getBytes(StandardCharsets.UTF_8));
			encodedStr = String.format("%128x", new BigInteger(1, md.digest()));
		} catch (NoSuchAlgorithmException e) {
			e.printStackTrace();
		}
		
		return encodedStr;
	}
}

 

회원 가입을 할 때 비밀번호를 암호화해 저장하자

파라미터 검증이 끝난 후와 회원 가입을 하기 전 사이에서 암호화를 해야함

 

이제 비밀번호가 암호화 되서 회원 가입이 이뤄질 것

여기서 회원 가입을 해보기 전에 DB에 비밀번호 칼럼의 길이를 늘려야함

우리가 암호화에 사용한 SHA-512 알고리즘은 암호화된 문자열의 길이가 128이기 때문에...

이제 비밀번호를 모두 같은 128자로 암호화 하기 때문에 위와 같이 pw 칼럼의 데이터 유형을 CHAR로 바꾸고 길이를 128로 바꾸자

 

이제 서버를 시작하고 회원 가입을 한 후 DB를 확인해보자


회원 가입할 때 비밀번호를 암호화해서 저장했으므로 로그인할 때도 비밀번호를 암호화해야함

 

로그인 역시 파라미터 검증을 한 후와 로그인 하기 전 사이에 비밀번호를 암호화 하도록 바꾸자

 

서버를 재시작 하고 로그인이 정상적으로 이뤄지는지 확인해보자

 

비밀번호를 암호화 했기 때문에 비밀번호를 암호화 하기 전 계정은 이제 접속할 수 없음

 

728x90
LIST

<< 학습 목표 >>

1. 내가 만든 서비스의 URL에 포트 번호를 생략할 수 있다.

2. 내가 만든 서비스의 URL에 프로젝트 명을 바꿀 수 있다.


다시 한번 네이버의 URL을 보자

네이버의 URL과 우리가 만든 서비스의 URL이 다른 점이 있음

네이버의 URL
https://www.naver.com
우리가 만든 서비스의 URL
메인 페이지 http://localhost:8080/studyProject/member
회원 가입 페이지 http://localhost:8080/studyProject/member/join
회원 가입 완료 페이지 http://localhost:8080/studyProject/member/joinSuccess
로그인 페이지 http://localhost:8080/studyProject/member/login
회원 정보 수정 페이지 http://localhost:8080/studyProject/member/update

 

1. 네이버의 URL은 https 로 시작하지만 우리의 URL은 http 로 시작함

2. 네이버는 www가 붙어있지만 우리는 www가 붙어있지 않음

3. 네이버는 com이 붙어있지만 우리는 com이 붙어있지 않음

4. 우리는 8080이 붙어있음

5. 우리는 서버 IP 주소 다음에 studyProject 가 붙어있음

 

등 여러 가지 차이가 있음

 

여기서 1번은 우리가 해결할 수 없는 차이임

https 는 http 보다 보안이 강화된 프로토콜로 https 프로토콜을 사용하는 프로젝트가 되려면 돈을 내고 보안 관련 설정을 해야함

네이버, 쿠팡 같은 서비스는 사람들이 사용할 서비스이므로 반드시 돈을 내고 https 프로토콜을 사용해야하지만 우리는 공부를 위해서 만든 프로젝트이므로 굳이 돈을 내고 https 프로토콜을 사용할 필요는 없음

혹시 "나는 네이버, 쿠팡처럼 실제와 가까운 서비스를 꼭 만들고 싶다" 하시는 분은 네트워크 과목을 공부하고 SSL/TLS 인증서 관련된 내용을 인터넷에서 찾아보자

 

2, 3번도 우리가 해결할 수 없는 차이임

예전 글에서 언급했듯 네이버가 숫자로 된 서버 아이피 주소 대신 www.naver.com 을 사용할 수 있는 이유는 이름이 www.naver.com 인 도메인을 구매했기 때문임

도메인 역시 돈을 주고 구매해야하기 때문에 우리는 굳이 돈을 내고 도메인을 구매할 필요는 없음

 

드디어 우리가 해결할 수 있는 차이인 4, 5번!

4, 5번은 우리가 돈을 내지 않아도 어렵지 않게 차이를 없앨 수 있음


4. 우리는 8080이 붙어있음

이는 잘 알고 있듯 포트 번호로 서버 IP 주소 다음 포트 번호를 써야함

포트 번호를 생략할 수 있음

포트 번호를 생략하면 프로토콜에 맞는 기본 포트 번호가 기입됨

http 프로토콜은 80번 포트가 기본 포트 번호임

https 프로토콜은 443번 포트가 기본 포트 번호임

왜 그럴까? 라는 생각이 드는분은 https://johngrib.github.io/wiki/why-http-80-https-443/ 이 블로그 글을 보시길

따라서 우리 웹 서버의 포트 번호를 80번으로 바꾸면 사용자들이 접속할 때 포트 번호를 입력하지 않아도됨

서버의 포트 번호를 바꿔보자

 

이클립스 -> [ Servers ](1) -> 서버 더블클릭(2) -> Overview 화면 내 HTTP/1.1의 PortNumber 를 80 으로 변경(3) -> 저장(Ctrl + S)

 

이제 서버를 실행시키자

혹시 서버가 실행되지 않으면서 [ ~ Already ~ ] 메세지가 보인다면 여러분의 컴퓨터에 어떤 프로그램이 이미 80번 포트를 사용하고 있다는 것

해당 프로그램이 어떤 프로그램인지 안다면 그 프로그램을 끄면 됨

만약 모른다면 컴퓨터를 재부팅 해보자

그래도 안되면 아쉽지만 80번 포트 말고 다시 변경하기 전 포트 번호를 사용하자

 

서버의 포트 번호가 80번으로 바꼈으니 80번 포트 번호를 기입해 접속해보자

 

난 분명히 포트 번호를 기입하고 접속했는데 사라진걸 볼 수 있음

포트 번호가 사라진게 아닌 생략된것임

http의 기본 포트 번호가 80번이므로 브라우저가 포트 번호를 생략한 것

 

이렇게 이제 우리도 네이버처럼 포트 번호 없이 접속할 수 있는 서비스가 되었음

우리가 만든 서비스의 URL
메인 페이지 http://localhost/studyProject/member
회원 가입 페이지 http://localhost/studyProject/member/join
회원 가입 완료 페이지 http://localhost/studyProject/member/joinSuccess
로그인 페이지 http://localhost/studyProject/member/login
회원 정보 수정 페이지 http://localhost/studyProject/member/update

5. 우리는 서버 IP 주소 다음에 studyProject 가 붙어있음

이는 프로젝트명으로 사용자의 요청을 받을 프로젝트 명임

프로젝트를 서버에 올리면 서버는 기본적으로 프로젝트명을 그대로 사용함

그러나 서버에 올라가는 프로젝트명을 바꿀 수 있음

 

서버에 올라가는 프로젝트 명을 바꿀 때는 이클립스 -> [ Servers 탭 ](1) -> 서버 더블클릭(2) -> Modules(3) -> 프로젝트 클릭(4) -> [ Edit ](5)

 

이렇게 서버에 올라가는 프로젝트 명을 바꿀 수 있음

여기서 Path 부분에 프로젝트명을 sp로 바꿔보자

sp로 변경(1) -> [ OK ](2) -> 저장(Ctrl + S)

 

그리고 서버를 재시작한 다음 메인 페이지에 접속해보자

서버에 올라간 프로젝트 명을 바꿨으니 메인 페이지에 접속할 때 프로젝트 명도 바꿔야함

 

이러한 방법을 활용해서 프로젝트 명을 완전히 지워버리면 우리 서비스에 접속할 때 프로젝트 명을 생략할 수 있음

다시 프로젝트 명을 바꾸자

완전히 지우는대 맨 앞에 있는 / 는 반드시 남겨둬야함

 

이제 서버를 재시작하고 다시 메인 페이지에 접속해보자

서버에 올라간 프로젝트 명을 바꿨으니 메인 페이지에 접속할 때 프로젝트 명도 바꿔야함

 

이로써 우리가 만든 서비스의 URL도 네이버 서비스의 URL과 제법 비슷해졌음

우리가 만든 서비스의 URL
메인 페이지 http://localhost/member
회원 가입 페이지 http://localhost/member/join
회원 가입 완료 페이지 http://localhost/member/joinSuccess
로그인 페이지 http://localhost/member/login
회원 정보 수정 페이지 http://localhost/member/update

서버를 재시작 하고 메인 페이지에 접속했을 때는 잘 보임

그러나 메인 페이지에서 각 버튼들을 누르면 404 NotFound 페이지가 보임

 

간단히 해결할 수 있는 문제이지만 여기서 많은 분들이 당황해서 "왜 이렇지?" 생각하며 머리가 백지가 됨

우리가 메인 페이지에서 각 버튼을 눌러 이동하는 URL에 프로젝트 명이 들어갔기 때문임

따라서 우리 프로젝트 내 모든 URL에서 프로젝트 명을 빼야함

 

하나씩 찾아서 프로젝트 명을 빼자

간단하고 단순한 작업이기 때문에 따로 코드를 첨부하진 않음

 

! 여기서 한가지 팁 !

<< 내가 원하는 키워드를 갖고 있는 파일을 쉽게 찾는 방법 >>

더보기

이클립스 내 [ Search 메뉴 ](1) -> [ Search 기능 ](2) -> 찾고자 하는 키워드 입력(3) -> 검색(4)

 

가령 현재 우리가 찾고자 하는 키워드는 /studyProject 이므로 아래와 같이 검색어를 입력하고 검색을 하면 이클립스 하단에 [ Search ] 패널이 생기면서 검색 결과가 보임

 

[ Search 패널 ] 내 보이는 파일에 들어가서 바꿔주면 됨

 

 

728x90
LIST