(책) 자바 웹을 다루는 기술

Chap 9 쿠키와 세션 알아보기

EunaSon 2023. 8. 23. 13:02

9.1 웹페이지 연결 기능

동시 사용자 수가 많아지면 데이터베이스 연동 속도 느려짐

=> 정보의 종류에 따라서 클라이언트 pc나 서버의 메모리에 저장해두고 사용하면 프로그램을 좀더 빠르게 실행시킬 수 있음

9장에서는 그 방법과 함께 로그인 시 사용자의 로그인 상태를 일정하게 유지시키는 기능에 대해 알아보자 

 

세션 트래킹(session tracking)

308p

http 프로토콜 방식으로 통신하는 웹페이지들은 사용자의 로그인 정보를 포함하여 어떤 정보도 서로 공유하지 않음

사용자 입장에서 웹 페이지 사이의 상태나 정보를 공유하려면 프로그래머가 세션 트래킹이라는 웹 페이지 연결 기능을 구현해야 함

 

- http 프로토콜로 웹 페이지를 요청해서 브라우저에 표시하는 과정

브라우저는 웹페이지 1, 2, 3과 각각 통신하므로 이전 웹 페이지들이 수행한 작업(로그인 등)을 다른 웹 페이지에서는 알 수 없음.  

http 프로토콜은 서버-클라이언트 통신시 각 웹 페이지의 상태나 정보를 다른 페이지들과 공유하지 않는 stateless 방식으로 통신함. 즉, 브라우저에서 새 웹페이지를 열면 기존 웹페이지나 서블릿에 관한 어떤 연결 정보도 새 웹 페이지에서는 알 수 없다는 것.

따라서 웹 페이지나 서블릿끼리 상태나 정보를 공유하려면 웹 페이지 연결 기능, 즉 세션 트래킹을 이용해야 함

 

-웹 페이지 연동 방법

<hidden> 태그 : html의 <hidden> 태그를 이용해 웹 페이지들 사이의 정보를 공유함

URL Rewriting : GET 방식으로 URL 뒤에 정보를 붙여서 다른 페이지로 전송함

쿠키 : 클라이언트 pc의 Cookie 파일에 정보를 저장한 후 웹 페이지들이 공유함

세션 : 서버 메모리에 정보를 저장한 후 웹 페이지들이 공유함

 

9.2 <hidden> 태그와 URL Rewriting 이용해 웹 페이지 연동하기

<hidden> 태그를 이용한 세션 트래킹 실습

310p

<hidden> 태그는 브라우저에는 표시되지 않지만 미리 저장된 정보를 서블릿으로 전송할 수 있음

 

[ login.html ]

로그인창. id와 비밀번호를 입력하면 미리 <hidden> 태그에 저장된 주소, 이메일, 휴대폰 번호를 서블릿으로 전송함

<input type="hidden" name="user-address" value="서울시 성북구" /> 등

 

[ LoginServlet 클래스 ]

getParameter() 메서드를 이용해 전송된 회원정보를 가져온 후 브라우저로 출력함

 

=> 브라우저에서 사용자가 입력한 id와 패스워드 외에 주소, 이메일, 전화번호도 화면에 함께 출력됨 

 

URL Rewriting을 이용한 세션 트래킹 실습

313p

url rewriting을 이용하여 로그인창에서 입력받은 id와 비밀번호를 다른 서블릿으로 전송하여 로그인 상태를 확인해보자

 

[ LoginServlet 클래스 ]

위의 LoginServlet에 추가로 <a> 태그의 '두번째 서블릿으로 보내기' 를 클릭하면 로그인 창에서 입력받은 아이디와 비밀번호 등 정보들을 GET 방식으로 두번째 서블릿에 전송함

 

user_address = URLEncoder.encode(user_address, "utf-8"); // GET방식으로 (주소창에) 한글을 전송하기 위해 인코딩함

out.print("<a href='/pro09/second?user_id=" + user_id + "&user_pw=" + user_pw + "&user_address=" + user_address + " ' >두번째 서블릿으로 보내기</a>");

// 진한 글자는 html 태그 출력하는 부분. 중간에 가는 글자는 변수이름임. '두번째 서블릿으로 보내기' 링크 클릭시 서블릿 /second로 정보를 전송함

 

[ SecondServlet 클래스 ]

첫번째 서블릿에서 전송한 데이터 중 id와 비밀번호를 가져왔다면 첫번째 서블릿에서 이미 로그인한것이므로 로그인 상태를 유지하도록 해주자

 

request.getParameter("user_id"); 등으로 id, pw, 주소를 가져와 변수에 저장함

 

if문을 사용하여 user_id != null && user_id.length() != 0 이면 '이미 로그인한 상태입니다' 라는 문구와 함께 넘겨받은 아이디, 비밀번호, 주소를 출력

조건이 false이면 '로그인하지 않았습니다' 문구와 함께 '로그인창으로 이동하기'라는 a태그 링크를 표시함

 

=> <hidden> 태그와 GET 방식으로 웹 페이지를 연동하는 방법은 여러 단점이 있음

웹 페이지가 많아지면 일일이 로그인 상태를 확인하기 위해 로그인 정보를 다른 웹 페이지로 전송해야하고,

 id와 비밀번호를 get 방식으로 전송하므로 브라우저에 노출되어 보안상 좋지 않음,

전송할 수 있는 데이터 용량에도 한계가 있음

따라서 이 방식들은 웹 페이지 사이에 간단한 정보 정도를 공유할 때만 사용하는 것이 좋음

 

9.3 쿠키를 이용한 웹 페이지 연동 기능

쿠키(Cookie)

: 317p 웹 페이지들 사이의 공유 정보를 클라이언트 pc에 저장해놓고 필요할 때 여러 웹페이지들이 공유해서 사용할 수 있도록 매개 역할을 하는 방법

-특징

정보가 클라이언트 pc에 저장됨

저장 정보 용량에 제한 있음(최대 4kb)

보안이 취약함 -> 보안과 무관한 경우에만 사용('팝업창 오늘은 더이상 보지않기' 등)

클라이언트 브라우저에서 사용 여부를 설정할 수 있음

도메인당(웹사이트당) 하나의 쿠키가 만들어짐 

 

속성 Persistence 쿠키 Session 쿠키
생성 위치 파일로 생성 브라우저 메모리에 생성
종료 시기 쿠키를 삭제하거나
쿠키 설정 값(지정한 사용만료시간)이 종료된 경우
브라우저를 종료한 경우
최초 접속 시
전송 여부
최초 접속 시 서버로 전송됨 최초 접속 시 서버로 전송되지 않음
용도 로그인 유무 또는 팝업창을 제한할 때 사이트 접속 시 Session 인증 정보를 유지할 때

* IE와 크롬의 쿠키 파일 생성 위치 다름

 

-쿠키 생성, 실행 과정

320p

<사이트 최초 접속시>

1. 브라우저에서 웹 사이트에 접속함

2. 서버- 정보를 저장한 쿠키를 생성함

3. 생성한 쿠키를 브라우저로 전송함

4. 브라우저 - 서버로부터 받은 쿠키 정보를 쿠키 파일에 저장함

 

<재접속 시>

5. 브라우저가 웹 사이트에 다시 접속

6. 서버 - 브라우저에게 쿠키 전송 요청함

7. 브라우저 - 쿠키 정보를 서버에 넘겨줌

8. 서버 - 넘겨받은 쿠키 정보를 이용해 작업함

 

-쿠키 API

321p

Cookie 클래스 객체를 생성 > 정보를 저장 > 서버에서 클라이언트로 전송 > 파일로 저장됨

-쿠키 관련 API의 특징

javax.servlet.http.Cookie를 이용함

HttpServletResponse의 addCookie() 메서드를 이용해 클라이언트 브라우저에 쿠키를 전송하여 저장됨

HttpServletRequest의 getCookie() 메서드를 이용해 쿠키를 서버로 가져옴

 

Cookie 클래스에서 제공하는 메서드 중 setMaxAge() 메서드 인자값의 종류를 통해 파일에 저장하는 Persistence 쿠카를 만들거나 메모리에만 저장하는 Session 쿠키로 만들 수 있음. 인자값으로 음수를 주거나 setMaxAge() 메서드를 사용하지 않고 쿠키를 만들면 Session 쿠키로 저장됨, 인자값으로 양수를 지정하면 Persistence 쿠키로 저장됨.

 

-서블릿에서 쿠키 사용하기

322p

[ SetCookieValue 클래스 ]

Cookie 객체를 생성한 후 쿠키이름을 cookieTest로 저장 후 setMaxAge()로 쿠키 유효 시간을 24시간으로 설정, response의 addCookie() 메서드로 생성된 쿠키를 브라우저로 전송함

 

Cookie c = new Cookie("cookieTest", URLEncoder.encode("JSP프로그래밍입니다", "utf-8"));

// Cookie 객체를 생성한 후 cookieTest라는 이름으로 한글 정보를 인코딩해서 쿠키에 저장함

c.setMaxAge(24*60*60); // 유효 기간 설정

response.addCookie(c); // 생성된 쿠키를 브라우저로 전송함

 

[ GetCookieValue 클래스 ]

 

Cookie[] allValues = request.getCookies();

// request의 getCookies() 메서드를 호출해 브라우저에서 쿠키 정보를 요청한 후 쿠키 정보를 배열로 가져옴

 

for (int i=0; i < allValues.length; i++) {

  if (allValues[i].getName().equals("cookieTest")) {

    out.println("<h2>Cookie값 가져오기: " + URLDecoder.decode(allValues[i].getValue(), "utf-8"));

  }

}

// 가져온 쿠키들 중 이름이 cookieTest인 것을 찾아서 utf-8로 디코딩

 

=> 브라우저에서 첫번째 서블릿(매핑이름 set)으로 요청 : 쿠키에 cookieTest라는 이름으로 문자열("JSP 프로그래밍입니다") 를 저장함

get으로 두번째 서블릿 요청 : cookieTest라는 이름으로 저장된 쿠키 값을 가져와 브라우저에 출력함

 

* 쿠키 생성 상태 확인하기

324p

크롬에서 클라이언트 쿠키의 생성상태 확인하는 방법

1. 크롬 실행 > F12 눌러 디버그창 나타냄 > 상단 메뉴바에서 Application을 클릭

2. 왼쪽 메뉴에서 Cookies를 선택 > 하위에 있는 http://localhost:8090을 클릭

3. 현재 애플리케이션에서 사용하고 있는 쿠키 정보가 표시됨

 

-세션 쿠키 사용하기

325p

[ SetCookieValue 클래스]

c.setMaxAge(-1); // 앞에서 양수(24*60*60)를 넣어준 것과 달리 음수를 넣어주면 세션 쿠키를 만듦

 

-쿠키 이용해 팝업창 제한하기

팝업창 제어는 서버에서 쿠키를 다루는 것이 아니라 자바스크립트를 이용해 쿠키에 직접 접근함

 

[ popupTest.html ]

웹 페이지가 브라우저에 로드될때(window.onload) pageLoad() 함수를 호출하여 실행함

pageLoad() 함수는 getCookieValue() 함수를 통해 notShowPop의 쿠키값을 얻음,

notShowPop이 false이면 팝업창을 띄움(popUp.html)

 

[ popUp.html ]

'오늘 더이상 팝업창 띄우지 않기'에 체크하면 자바스크립트 함수인 setPopUpStart() 함수를 호출하여 notShowPop의 값을 true로 설정함. 재접속 시 팝업창을 나타내지 않도록 함.


* 쿠키 비중은 점차 낮아지고 있지만 여전히 방문자 정보 파악, 임시 데이터 보관 등의 중요한 수단임

클라이언트 또는 웹 브라우저는 DOM 객체에 쿠키 정보를 담고 있는 쿠키 속성(document.cookie)을 제공함

document.cookie 속성은 DOM에 담고 있는 쿠키 문자열임. 이 정보를 읽어서 쿠키 값을 가져오거나, 반대로 쿠키 값을 저장할 수 있음. 객체가 아니기에 메서드도 제공되지 않으므로 쿠키 문자열을 읽어서 파싱하고, 쿠키정보를 구분하는 것은 어디까지나 자바스크립트의 몫임. 

 

* 쿠키 문자열 구조

쿠키key = 쿠키value; path=/; expires=Sat, 02 Oct 2021 17:46:04 GMT;

각 속성값을 세미콜론으로 구분해서 하나의 문자열로 만듦

쿠키 만료 표시 시각은 GMT 시각임(js의 Date 객체에서 제공하는 toGMTString() 메서드 이용해 날짜 객체를 GMT 시각 문자열로 변환해서 사용하는 것이 좋음)

 

* 쿠키 정보

"키=값"의 문자열 정보를 저장함

키로 접근해서 쿠키값에 해당하는 값을 가져옴

쿠키를 추가할 때는 document.cookie 속성에 새 쿠키 문자열을 대입하면 됨

쿠키 키와 값은 2byte 언어 지원이 잘 되기 때문에 한글로 사용할 수 있음

 

* 쿠키 경로(path)

도메인 하위로 하위 쿠키 경로를 지정할 수 있음

사이트에서 사용하는 쿠키 종류가 많고 하위 url단위로 쿠키를 따로 사용하는 경우 쿠키 경로를 추가로 지정해서 쿠키를 관리할 수 있음. 일반적으로 쿠키 개수가 많지 않으면 루트( / ) 경로만 사용함

 

* 만료일(expires)

GMT 시각 문자열.

쿠키는 삭제 기능이 없으며, 현재 시각 이전으로 만료일을 지정하면 자동으로 만료되어 삭제됨

보안 문제 등을 이유로 쿠키 만료 시간은 가능한 짧게 지정하는 것이 좋음. 3개월 이내 추천.

 

* 쿠키 저장

날짜 정보는 Date 객체를 사용해 toGMTString() 함수로 형식을 바꾸어 날짜 문자열을 생성하여 사용함

쿠키 값은 특수문자나 2byte 문자 처리를 위해 escape() 함수로 이스케이핑을 하는 것을 추천함.

 

* 쿠키 삭제

쿠키는 별도의 삭제 기능 없음, 만료 시각이 현재 시간 이전이 되면 만료되어 삭제됨

따라서 setCookie()와 마찬가지로 document.cookie에 기존 쿠키를 덮어쓰면 됨

만료시각은 현재 시각 이전으로 설정해야 쿠키가 만료되어 사라짐

 

* 쿠키 읽기

정규표현식을 사용해서 쿠키들을 분리하고, 분리한 쿠키에서 일치하는 쿠키 키를 가진 쿠키를 찾아서 쿠키 값을 반환함

setCookie() 함수에서 escape() 함수로 이스케이핑을 했기 때문에 가져온 쿠키 값도 unescape() 함수로 언이스케이핑을 해야함.

setCookie() 함수에서 escape() 함수를 사용하지 않았을 경우 반드시 getCookie() 함수에서도 unescape() 함수를 삭제해야함

 

출처 https://blogpack.tistory.com/1071


9.4 세션을 이용한 웹 페이지 연동 기능

329p

쿠키와 세션 모두 웹 페이지들 간 공유 정보를 서버에 저장해두고 웹페이지를 매개,

but 쿠키는 이 정보들이 클라이언트 pc에 저장(보안에 취약), 세션은 서버의 메모리에 생성되어 저장됨

 

-세션의 특징

서버의 메모리에 정보가 저장됨

세션 쿠키를 이용함

쿠키보다 보안에 유리함

서버에 부하를 줄 수 있음

브라우저(사용자)당 1개의 세션(세션 id, jsessionID)이 생성됨 - > 이 id를 통해 브라우저에 대한 세션을 구분하고 각 브라우저에 대한 세션 작업을 수행함

세션은 유효 시간을 가짐(기본 30분)

 

-세션 실행 과정

[ 최초 접속 시 ]

1. 클라이언트 - 브라우저를 통해 서버에 최초 접속

2. 서버 - 접속한 브라우저에 대한 세션 객체를 생성

3. 서버 - 생성된 세션에 대한 세션 id를 클라이언트 브라우저에 전송(응답)

4.  브라우저 - 서버로부터 받은 세션 id를 브라우저가 사용하는 세션 쿠키에 저장함(쿠키 이름은 jsessionID)

[ 재접속 시 ]

5. 브라우저 - 세션 쿠키에 저장된 세션 id를 서버에 전달함

6. 서버 - 전송된 세션 id를 이용해 해당 브라우저의 세션 객체에 접근하여 작업을 수행함

 

-세션 API

331p

서블릿에서 세션을 이용하려면 HttpSession 클래스 객체를 생성해서 사용해야 함

HttpSession 객체는 HttpServletRequest의 getSession() 메서드를 이용함

getSession() : 기존의 세션 객체가 존재하면 반환하고, 없으면 새로 생성함

getSession(true) : getSession()과 동일한 결과

getSession(false) : 기존의 세션 객체가 존재하면 반환하고, 없으면 null을 반환함

 

-HttpSession 클래스의 여러가지 메서드

331p

반환 타입 메서드 설명
Object getAttribute
(String name)
속성 이름이 name인 속성 값을 Object 타입으로 반환함
해당되는 속성 이름이 없을 경우 null을 반환함
Enumeration getAttributeNames() 세션 속성 이름들을 Enumeration 객체 타입으로 반환함
long getCreationTime() 1970년 1월 1일 0시 0초를 기준으로, 현재 세션이 생성된 시간까지 경과된 시간을 계산하여 1/1000초 단위로 반환함
String getId() 세션에 할당된 고유 식별자를 String 타입으로 반환함
int getMaxInactiveInterval() 현재 생성된 세션을 유지하기 위해 설정된 세션 유지 시간을 int 타입으로 반환함
void invalidate() 현재 생성된 세션을 소멸함
boolean inNew() 최초로 생성된 세션인지 기존에 생성되어 있었던 세션인지 판별함
void removeAttribute
(String name)
세션 속성 이름이 name인 속성을 제거함
void setAttribute
(String name, Object value)
세션 속성 이름이 name인 속성에 속성 값으로 value를 할당함
void setMaxInactiveInterval() 세션을 유지하기 위한 세션 유지 시간을 초 단위로 설정함

 

-세션을 이용한 로그인 정보 바인딩 실습

339p

실습 전, 톰캣이 종료된 후에도 세션이 메모리에서 삭제되지 않는 경우가 있으므로

톰캣 설정 파일인 context.xml을 열어 <Manager pathname=" " /> 태그의 주석을 해제함

 

[ login2.html ]

로그인창에서 id와 비밀번호를 입력한 후 서블릿으로 전송함

 

[ SessionTest4 클래스 ]

로그인창에서 입력한 id와 비밀번호 가져옴

최초 요청시(첫 로그인 시) 세션에 id를 바인딩 후( setAttribute() ) <a>태그로 서블릿 재요청하고, getAttribute()로 세션에서 user_id값 가져와 로그인 여부 확인

 

HttpSession session = request.getSession(); // 생성된 세션 가져오거나 새로 생성함

 

String user_id = request.getParameter("user_id");

String user_pw = request.getParameter("user_pw"); // 로그인창(login2.html)에서 전송된 id, pw가져옴

 

if (session.isNew()) { // 최초 요청시 수행

  if (user_id != null ) { // 로그인창에서 id 제대로 입력 후 서블릿 요청했다면

    session.setAttribute("user_id", user_id); // 세션에 "user_id" 라는 name으로 user_id 값 할당

    out.println("<a href='login'>로그인 상태 확인</a>"); // 해당 서블릿으로 재요청

  } ...

} else { // 재요청 시 수행

  user_id = (String)session.getAttribute("user_id"); // 세션에서 바인딩된 id 가져옴(getAttribute() 반환타입 Object)

  if ( user_id != null && user_id.length != 0 ) {

    out.print("안녕하세요 " + user_id + "님!"); // id 제대로 입력했으면 id 가져와서 출력함

  }

 

9.5 encodeURL() 사용법

343p

세션 역시 클라이언트의 세션 '쿠키'를 이용해 각 브라우저에 대한 세션 기능을 사용함

만약 브라우저에서 쿠키 기능을 사용할 수 없게 설정했다면 쿠키 기능은 물론 세션 기능도 사용할 수 없음

=> encodeURL() 메서드를 이용해 직접 서버에서 브라우저로 응답을 먼저 보낸 후

URL Rewriting 방법을 이용해 jsessionID를 서버로 전송하여 세션 기능을 사용함 

 

*** String encodeURL(String url) : 클라이언트가 쿠기를 지원하지 않을 때 세션id를 포함한 특정 url을 인코딩함

교재 179p, HttpServletResponse 의 여러가지 메서드 표, Chap 06 참고

 

-브라우저에서 쿠키 사용 금지하기

크롬 브라우저 > 오른쪽 상단 점세개(더보기) 아이콘 클릭 > 설정 > 하단의 '고급' > '개인정보 보호 및 보안'에서 콘텐츠 설정 클릭 > 쿠키 > 사이트에서 쿠키 데이터 저장 및 읽기 허용 > '차단됨' 옆의 옵션을 클릭해서 차단으로 설정함

 

-실습

세션 쿠키를 사용하지 않고 encodeURL()을 이용해서 세션id를 브라우저에 응답하여 세션 사용함

 

String url = response.encodeURL("login"); // 매핑 이름 뒤에 자동으로 세션id 붙여줌, url에 저장

out.println("<a href=" + url + ">로그인 상태 확인</a>"); // 링크 클릭시 세션id가 다시 서블릿으로 요청됨

 

9.6 세션을 이용한 로그인 예제

348p

1. DB 연동 관련 설정을 해줌

MemberDAO, MemberVO 클래스 복붙함

 

2. [ login3.html ]

사용자 id, pw 입력 후 /login 서블릿으로 전송하도록 함

 

3. [ LoginServlet 클래스 ]

로그인 창에서 전송된 id, pw 가져와 MemberVO 객체를 생성 후 속성에 id, pw 설정(setter 이용)

MemberDAO의 isExisted() 메서드를 통해 해당 id, pw를 가진 회원이 있는지 조회, true이면 isLogOn 속성을 ture로 세션에 저장&id와 pw도 세션에 저장

 

4. [ MemberDAO 클래스 ]

isExisted() 메서드는 인자로 MemberVO 객체를 받아서, MemberVO의 id와 pw를 getter를 통해 가져온 후

오라클의 decode() 함수를 통해 sql문으로 회원정보를 조회함,

정보가 존재하면 true, 존재하지 않으면 false 반환함

 

String query = "select decode(count(*), 1, 'true', 'false') as result from t_member";

query += " where id = ? and pw = ?";

* as 는 alias에 사용함(해당 결과 컬럼명을 result로 표시한다는 의미)

* from의 테이블에서 where의 조건 수행한 결과를 select에 맞게 보여줌

* decode(컬럼, 조건1, 결과1, 조건2, 결과2, ... , else)

->count(*)의 값이 1이면 true반환, ... 아니면 false반환

 

쿼리 결과값을 통해 변수 result의 값을 저장함(ture=회원정보 조회됨, false=회원정보가 테이블에 없음)

 

5. [ ShowMember 클래스 ]

사용자가 로그인 시 회원정보를 표시해주는 서블릿임

로그인 상태를 확인하기 위해 getSession(false)로 세션을 얻어와

getAttribute("isLogOn")으로 로그인 상태 가져옴,

isLogOn 값이 ture이면 세션에서 회원정보 가져와 출력함,

세션이 존재하지 않거나(session == null) false이면 다시 로그인창으로 이동함