본문 바로가기
백엔드/웹 개발

사용자 정의 쿠키(자동로그인)

by study_yeon 2023. 7. 3.

2023.06.29 ~ 2023.06.30수업 

교재 : 자바 웹 개발 워크북

 

(참고)

* stackoverflow
- 프로그래밍에 대한 질문을 하고 답변을 받는 사이트
- 필요한 쿠키만 허용

* 화면에 글자가 깨져보이는 경우
- UTF-8로 저장을 해야 UTF-8과 호환이 가능함
- ANSI로 읽으면 영어 전용이라 한글이 안읽힘
- 파일에서 Encoding을 UTF-8로 설정하기
- EUC-KR로 작성을 했다면 그 내용을 복사해서 새롭게 붙여넣어 유니코드를 변경하고 저장하여 해결
- 마름모가 많이 보인다면 EUC-KR일 확률 높음

*이전의 한글 코드
- EUC-KR
- KSC5601
- 한글완성형
- MS949


● 사용자 정의 쿠키 

○ 세션 쿠키 : JSESSIONID 
- 자동으로 발행(WAS에서)
- 사용자가 만들 수 없음

○ 사용자 정의 쿠키 
new Cookie()로 생성 (문자열로된 name, value 필요)

- value(값)은 일반적인 문자열로 저장이 불가능하여 URLEncoding(암호화)된 문자열로 저장해야함
- HttpServletResponse의 addCookie()를 통해 전송
- 유효기간 지정 가능
- 메모리 또는 파일 등에 보관
- 4kb의 크기


▶ 실습 


▷ 조회한 Todo 확인하기
- 번호(tno)들을 쿠키를 이용하여 보관
- 쿠키 이름 : viewTodos

- 문자열 내에 현재 Todo의 번호를 문자열로 연결 '1-3-2-' 와 같은 형태

- 이미 조회한 내용은 쿠키에 다시 추가하지 않음

- TodoReadController doGet(), findCookie() 작성

- 'viewTodos' 이름의 쿠키를 찾아 쿠키의 내용물을 검사한 다음에 조회한 적이 없는 번호라면 쿠키의 내용물을 갱신해 브라우저로 보내줌

package org.zerock.w2.controller;

import java.io.IOException;

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

import org.zerock.jdbcex.domain.TodoDTO;
import org.zerock.jdbcex.service.TodoService;

import lombok.extern.log4j.Log4j2;

@WebServlet(name = "todoReadController", value = "/todo/read")
@Log4j2
public class TodoReadController extends HttpServlet {
	// 비즈니스로직 처리는 todoService 객체에서 처리
	// 데이터베이스 작업은 todoDAO 객체에서 처리
	TodoService todoService = TodoService.INSTANCE;
	
	@Override
	protected void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {	
		try {
			Long tno = Long.parseLong(req.getParameter("tno"));
			log.info("1. tno 요청값 : {}", tno); // 로그방식(디버그로 대체 가능)
			// DB에서 자료 가져옴
            TodoDTO todoDTO = todoService.get(tno);
			// 얻어온 모델 데이터를 요청객체에 담는다
			// 실제화면 처리하는 뷰 read.jsp에 전달하기 위해 
			req.setAttribute("dto", todoDTO);
			log.info("2. DTO값을 요청값에 추가 : {}", todoDTO);
			// 요구사항에서 보았던 내용을 처리하기위해 쿠키 사용
			// 1. 쿠키찾기 findCookie 메소드 작성
			Cookie viewTodoCookie = findCookie(req.getCookies(), "viewTodos");
			log.info("3. 쿠키 검색 : {}", viewTodoCookie);
			// 2. 쿠키 값 읽어오기
			String todoListStr = viewTodoCookie.getValue();
			// 쿠키에서 읽은 문자열에 조회한 번호가 있는지 확인하는 flag변수
			// 처음에는 조회가 없으므로 exist = false
			boolean exist = false;
			
			// 조회하는 방법 : short circuit 방식 이용
			// 앞의 결과에 따라 뒤의 연산을 계속 할것인가 결정하는 방식
			if (todoListStr != null && 
				todoListStr.indexOf(tno+"-") >= 0) {
				exist = true; // 찾는 번호가 있음
			}
			
			log.info("쿠키에 조회한 번호가 있다 : {}", exist);
			
			// 3. 쿠키가 없다면 새로 만들기
			// 쿠키는 응답객체를 통해서 생성되므로 read.jsp를 부르기전에 만드는 것이 필요
			if (exist != true) {
				// 쿠키를 새로 만들거나 내용을 변경한다
				todoListStr += tno+"-";
				// 쿠키를 추가하는 순서
				// 1) 쿠키에 값을 대입
				viewTodoCookie.setValue(todoListStr);
				// 2) 쿠키의 만료시간 설정(하루 = 24시간 / 1시간 = 60분 / 1분 = 60초)
				viewTodoCookie.setMaxAge(60 * 60 * 24);  // 하루를 초로 계산				
				// 3) 쿠키를 사용할 경로의 시작 주소(uri) 추가
				viewTodoCookie.setPath("/");
				// 4) 요청객체에 만들어진 쿠키를 추가
				res.addCookie(viewTodoCookie);
			}
			// 요청객체와 응답객체가 다 준비되었으므로 read.jsp에게 뷰 생성 위임
			req.getRequestDispatcher("/WEB-INF/todo/read.jsp").forward(req, res);
			
		} catch (Exception e) {
			e.printStackTrace();
			log.error(e.getMessage());
			throw new ServletException("읽기 요청 에러!");
		}

	}

	private Cookie findCookie(Cookie[] cookies, String cookieName) {
		// 내가 찾을 쿠키를 가르키는 변수
		Cookie targetCookie = null;
		
		// 쿠키 배열이 없다 : 쿠키 배열의 내용물이 null / 쿠키 배열의 크키가 0
		if (cookies != null && cookies.length > 0) {
			
			// 쿠키 배열이 있으면 향상된 for(for~each)루프 이용
			// 주로 배열류(열거형 반복 자료형)
			// var : 로컬변수에서 자료형의 값을 자바가 스스로 알아내게하는 기법
			// 타입추론기법을 이용하여 호환성을 높이는 제네릭 프로그래밍 개념을 이용
			for (var ck : cookies) {
				// 쿠키를 읽어서 파라미터로 넘어온 쿠키이름과 비교하여 같다면 루프를 끝냄(break)
				if(ck.getName().equals(cookieName)) {
					targetCookie = ck;
					break;
				}
			}
		}
		// 만약 쿠키를 발견하지 못하면 빈 쿠키를 새로 만든다
		// 쿠키를 새로 만들때는 new Cookie()이용
		if (targetCookie == null) {
			targetCookie = new Cookie(cookieName, "");
			targetCookie.setPath("/");
			targetCookie.setMaxAge(60 * 60 * 24);
		}
		return targetCookie;
		
	}

}

* findCookie() 메소드
- private로 만들어 외부에서 접근 불가하게 하기
- 요청객체로 넘어온 쿠키들(쿠키배열) 중에서 내가 원하는 쿠키 찾기

* 요구사항에서 보았던 내용을 처리하기위해 쿠키 사용하는 법
1. 쿠키가 있는가(findCookie)
- 쿠키가 있으면 쿠키를 가져온다
2. 쿠키의 내용을 읽어서 찾는 문자열(tno-형식)이 있는가
- 있으면 쿠키의 내용이 있다고 exist 변수를 true로 설정
3. exist 변수가 거짓이면 쿠키가 없으므로 쿠키를 새로 만들고
    응답객체에 쿠키를 추가한다(나중에 read.jsp를 통해 전송)
4. 요청위임을 통하여 read.jsp 실행

* 문자열.indexOf("검색할 부분 문자열") 
- 현재 문자열에서 검색할 부분 문자열이 들어 있는가 검사
- 발견되면 현재 위치의 숫자를 반환. 0부터 카운트
- 없으면 -1을 반환

* 쿠키를 추가하는 순서
1) 쿠키에 값을 대입
2) 쿠키의 만료시간 설정
3) 쿠키를 사용할 경로의 시작 주소(uri) 추가
4) 요청객체에 만들어진 쿠키를 추가

* 쿠키는 하나의 문자열 배열
"쿠키이름", "쿠키내용", 도메인, 만료시간이 하나의 파일로 브라우저에 저장



※ TodoReadController 디버그 
* 중단점 
- req.setAttribute("dto", todoDTO);
- Cookie targetCookie = null;
- 웹필터 자동실행을 막을때 어노테이션 주석처리(디버그시 활용)

- get방식이라 파라미터(tno)가 null이라 500 에러

주소창에 tno가 지정되지 않음


- doGet if문 사용하여 500에러 발생하지 않게 수정하기 

-> tno가 없다면 1로 자동으로 설정해줌

// 읽기번호 저장용 변수 try 블럭에서 사용
Long tno = null;
// 요청으로부터 tno번호 얻기
if (req.getParameter("tno") != null) {
	tno = Long.parseLong(req.getParameter("tno"));
} else {
	tno = 1L; // tno가 없으면 1으로 세팅
}

 

* 중단점 삭제하는 법 

상단메뉴 Run > Remove All BreakPoints


▷ 브라우저의 쿠키 전부 삭제하고 중단점 걸기
- 중단점 : log.info("1. tno 요청값 : {}", tno) : 브레이스 안에 tno가 출력됨
- 디버그에서는 요청하고 응답이 진행될때 까지 브라우저에 쿠키가 생성되지 않음

->  jsp를 서블릿으로 변환하는 과정을 거쳐서 응답객체가 브라우저에 보내지면 쿠키 생성 

jsp를 서블릿으로 변환하는 과정


* (참고)디버그 진행시 웹브라우저에서 새로고침을 하면 요청 남아있는 것이므로 처리하려고 컨트롤러가 계속 작동함 


▷ 쿠키와 세션을 같이 활용하기
- 필터에 의해 /todo/.. 의 경로에 대해 매번 로그인을 해주어야함
이런 경우 쿠키를 이용한 '자동로그인'을 한다

* 자동로그인
- 로그인한 사용자의 정보를 쿠키에 보관하고 이를 이용해서 사용자의 정보를 HttpSession에 담음

* 로그인 구현 
- 사용자가 로그인할 때 임의의 문자열을 생성하고 데이터베이스에 보관
- 쿠키에 생성된 문자열을 값으로 하고 유효시간 설정(ex. 1주일)

* 로그인 체크 구현
- 현재 사용자의 HttpSession에 로그인 정보가 없는 경우에만 쿠키를 확인
- 쿠키의 값과 데이터베이스의 값을 비교하고 같다면 사용자의 정보를 읽어와서 HttpSession에 사용자 정보를 추가

○ 테이블 수정
- 자동로그인처리를 위한 uuid 컬럼 추가(alter의 add이용)
- uuid : 고유 값을 가짐

- uuid에 임의의 문자열 보관
* 구문

alter table 테이블이름 add column 컬럼명 컬럼 데이터타입 제약조건; 

-- 자동로그인처리를 위한 컬럼 추가
alter table tbl_member add column uuid varchar(50) comment '자동로그인시 토큰 생성'; 
desc tbl_member; // 내림차순

○ login.jsp 폼 수정
- 자동로그인 여부를 묻는 체크박스 추가

<form action="/login" method="post">
	아이디 : <input type="text" name="mid" value="" /><br />
	비밀번호 : <input type="password" name="mpw" value="" /><br />
	<!-- 자동로그인 추가 -->
	자동로그인 : <input type="checkbox" name="auto" /><br />
	<input type ="submit" value="로그인" />
</form>

○ LoginController의 doPost 수정
- auto라는 이름으로 체크박스에 전송되는 값이 'on'인지 확인
UUID : 라이브러리이므로 import해주기
- 현재상태와 관련한 유일한 키 생성

	protected void doPost(
			HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		log.info("login post ...");
		
		String mid = req.getParameter("mid");
		String mpw = req.getParameter("mpw");
		
		/// 0630 자동로그인 추가
		// 자동로그인 여부 체크
		String auto = req.getParameter("auto");
		// 자동로그인 설정상태 확인
		boolean rememberMe = auto != null && auto.equals("on");
		
		log.info("-----------------------------------");
		log.info("😊자동로그인 기능😊 : {}", rememberMe);
				
		/// 0630 끝
		
		try {
			MemberDTO memberDTO = MemberService.INSTANCE.login(mid, mpw);
			
			/// 0630 자동로그인 추가
			// 자동로그인 체크박스를 확인하여 쿠키(rememberMe)가 없으면 rememberMe 쿠키생성
			// 쿠키가 있으면 rememberMe 쿠키 업데이트
			if(rememberMe) { // 유일키(UUID)를 생성한다
				String uuid = UUID.randomUUID().toString();
			}
			/// 0630 끝
			
			// 세션정보에 로그인 성공 키 등록
			HttpSession session = req.getSession();
			session.setAttribute("userInfo", memberDTO);
			
			// 로그인 회원이용 페이지
			res.sendRedirect("/todo/list");
			
		} catch (Exception e) {
			res.sendRedirect("/login?result=error");
		}
	
	}


* 디버그
LoginController
- 체크박스 체크하지 않은 경우와 체크한 경우의 차이 확인
중단점 String mid = req.getParameter("mid");

(참고) 클라이언트에서 유니코드가 UTF-8로 작성되어 오는 것을 알려주어야 서버에서도 한글이 읽힘
- login.jsp에 '<html lang="ko">' 작성하여 해결

○ MemberVO, MemberDTO 수정
- 추가된 uuid 필드에 반영

public class MemberVO {
	
	private String mid; 
	private String mpw; 
	private String mname;
	private String uuid;
}
public class MemberDTO {

	private String mid; 
	private String mpw; 
	private String mname;
	private String uuid;
}


○ LoginController 내용추가

-  데이터베이스에 uuid값을 업데이트 한다.

- memberDTO uuid필드 업데이트
- 요청브라우저에 변경된 값 쿠키를 이용해 저장
- 자동로그인 시간설정 1주일간 유효, 영구적인 시간 설정 금지
- 쿠키가 적용될 도메인 주소 설정

package org.zerock.w2.controller;

import java.io.IOException;
import java.util.UUID;

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

import org.zerock.w2.domain.MemberDTO;
import org.zerock.w2.service.MemberService;

import lombok.extern.log4j.Log4j2;

@WebServlet(name = "loginController", value = "/login")
@Log4j2
public class LoginController extends HttpServlet {

	@Override
	protected void doGet(
			HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		log.info("login get ...");
		req.getRequestDispatcher("/WEB-INF/login.jsp").forward(req, res);
	}
	
	@Override
	protected void doPost(
			HttpServletRequest req, HttpServletResponse res) throws ServletException, IOException {
		동일
		try {
			MemberDTO memberDTO = MemberService.INSTANCE.login(mid, mpw);
			
			/// 0630 자동로그인 추가
			if(rememberMe) { // 유일키(UUID)를 생성한다
				String uuid = UUID.randomUUID().toString();
                
				// 데이터베이스에 uuid값을 업데이트 한다.
				MemberService.INSTANCE.updateUuid(mid, uuid); // 메소드 만들기
				// memberDTO uuid필드 업데이트
				memberDTO.setUuid(uuid);
				
				// 요청브라우저에 변경된 값 쿠키를 이용해 저장
				Cookie rememberMeCookie = new Cookie("remember-me", uuid);
				
				// 자동로그인 시간설정 1주일간 유효, 영구적인 시간 설정 금지
				rememberMeCookie.setMaxAge(60 * 60 * 24 * 7); // 1주일
				// 쿠키가 적용될 도메인 주소 설정
				rememberMeCookie.setPath("/");
				res.addCookie(rememberMeCookie);
			}
			/// 0630 끝
			동일	
	}
}


○ MemberService에 메소드 추가

	public void updateUuid(String mid, String uuid) throws Exception {
		dao.updateUuid(mid, uuid);	
	}


○  MemberDAO 수정
-(참고) 서비스에서 DAO를 부름
- uuid메소드 구현
- mid, uuid가 넘어옴

	public void updateUuid(String mid, String uuid) throws Exception {
		/// DAO에서 하는 작업
		// sql, connection, preparedStatement
		String sql = """
			UPDATE tbl_member 
				SET uuid =? 
				WHERE mid =?
			""";
		@Cleanup Connection conn = ConnectionUtil.INSTANCE.getConnection();
		@Cleanup PreparedStatement pstmt = conn.prepareStatement(sql);
		
		pstmt.setString(1, uuid);
		pstmt.setString(2, mid);
		
		pstmt.executeUpdate();
	}

자동로그인 체크를 안하면 쿠키가 생성되지 않음
자동로그인을 체크하면 쿠키가 생성됨


(참고)Filter는 속도 저하 리스너는 데이터 유실의 가능성이 있음

'백엔드 > 웹 개발' 카테고리의 다른 글

필터, 정규표현식  (0) 2023.08.03
리스너(Listener)  (0) 2023.07.04
로그인, 로그아웃 구현하기(필터)  (0) 2023.06.29
방문자 기억 - (세션/쿠키)  (0) 2023.06.28
14 게시판 만들기  (0) 2023.06.27