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 에러
- 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를 서블릿으로 변환하는 과정을 거쳐서 응답객체가 브라우저에 보내지면 쿠키 생성
* (참고)디버그 진행시 웹브라우저에서 새로고침을 하면 요청 남아있는 것이므로 처리하려고 컨트롤러가 계속 작동함
▷ 쿠키와 세션을 같이 활용하기
- 필터에 의해 /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 |