-
[Back End] 상태유지기술 - SessionWeb Development/부스트코스 - Back-End(Java) 2020. 5. 18. 15:37
✔️ 웹에서의 상태 유지 기술
◾ HTTP 프로토콜은 상태 유지가 안되는 프로토콜이다.
- 클라이언트가 이전에 무엇을 했고, 지금 무엇을 했는지에 대한 정보를 가지고 있지 않다.
- 웹 브라우저(클라이언트)의 요청에 대한 응답을 하고 나면 해당 클라이언트와의 연결을 지속하지 않는다.
◾ 클라이언트의 상태 정보 유지를 위해 Cookie와 Session 기술이 등장했다.
✔️ Session (세션)
📋 정의
◾ 클라이언트 별로 서버에 저장되는 정보이다.
📋 사용 방법
◾ 웹 클라이언트가 서버측에 요청을 보내게 되면 서버는 클라이언트를 식별하는 session id를 생성한다.
◾ 서버는 session id를 사용해서 key와 value를 이용한 저장소인 HttpSession을 생성한다.
◾ 클라이언트는 서버측에 요청을 보낼때 session id를 가지고 있는 쿠키를 전송한다.
◾ 서버는 쿠키에 있는 session id를 이용해서 그 전 요청에서 생성한 HttpSession을 찾고 사용한다.
📋 javax.servlet.http.HttpSession
🍪 세션 생성 및 얻기
HttpSession session = request.getSession(); HttpSession session = request.getSession(true);
◾ request의 getSession() 메소드는 서버에 생성된 세션이 있다면 세션을 반환하고, 없다면 새롭게 세션을 생성하여 반환한다.
◾ 새롭게 생성된 세션여부는 HttpSession이 가지고 있는 isNew() 메소드를 통해 알 수 있다.
HttpSession session = request.getSession(false);
◾ request의 getSession()메소드에 파라미터로 false를 전달하면, 이미 생성된 세션이 있다면 반환하고 없으면 null을 반환한다.
🍪 세션에 값 저장
setAttribute(String name, Object value)
◾ name과 value의 쌍으로 객체 Object를 저장하는 메소드이다.
◾ 세션이 유지되는 동안 저장할 자료를 저장한다.
🍪 세션으로부터 값 조회
getAttribute(String name) 메소드
◾ 세션에 저장된 자료는 다시 getAttribute(String name) 메소드를 이용해 조회한다.
◾ 반환 값은 Object 유형이므로 저장된 객체로 자료형 변환이 필요하다.
◾ 메소드 setAttribute()에 이용한 name인 “id”를 알고 있다면 바로 아래와 같이 바로 조회한다.
String value = (String) session.getAttribute("id");
🍪 세션에서 값 삭제
◾ removeAttribute(String name) 메소드
- name값에 해당하는 세션 정보를 삭제한다.
◾ invalidate() 메소드
- 모든 세션 정보를 삭제합니다.🍪 세션은 클라이언트가 서버에 접속하는 순간 생성된다.
◾ 특별히 지정하지 않으면 세션의 유지 시간은 기본 값으로 30분 설정한다.
◾ 세션의 유지 시간이란 서버에 접속한 후 서버에 요청을 하지 않는 최대 시간이다.
◾ 30분 이상 서버에 전혀 반응을 보이지 않으면 세션이 자동으로 끊어진다.
◾ 이 세션 유지 시간은 web.xml파일에서 설정 가능하다.
<session-config> <session-timeout>30</session-timeout> </session-config>
🍪 javax.servlet.http.HttpSession 정리
✔️ 세션 실습
◾ /guess로 요청을 하면 컴퓨터가 1부터 100 사이의 임의의 값 중의 하나를 맞춰보라는 메시지가 출력된다.
◾ 해당 값은 세션에 저장한다.
◾ 사용자는 1부터 100 사이의 값을 입력한다.
◾ 입력한 값이 세션 값보다 작으면, 입력한 값이 작다고 출력한다.
◾ 입력한 값이 세션 값보다 크면, 입력한 값이 크다고 출력한다.
◾ 입력한 값이 세션 값과 같다면 몇 번째에 맞췄다고 출력한다.
💾 GuessNumberController.java
더보기package kr.or.connect.guestbook.controller; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestParam; @Controller public class GuessNumberController { @GetMapping("/guess") public String guess(@RequestParam(name="number", required=false) Integer number, HttpSession session, ModelMap model) { String message = null; // get방식으로 /guess 를 요청하는데 파라미터 number가 없을 경우에는 session에 count를 0으로 randomNumber엔 1부터 100사이의 값을 저장합니다. if(number == null) { session.setAttribute("count", 0); session.setAttribute("randomNumber", (int)(Math.random() * 100) + 1); // 1 ~ 100사이의 random값 message = "내가 생각한 숫자를 맞춰보세요."; }else { // number파라미터가 있을 경우 세션에서 값을 읽어들인 후, number와 세션에 저장된 값을 비교합니다. // 값을 비교해서 작거나 크다면 카운트를 1증가시켜주고 // 값이 같다면 세션 정보를 삭제합니다. // 각 상황에 맞는 메시지를 message변수에 저장을 한 후 jsp에게 전달하기 위해서 ModelMap의 addAttribute메소드를 통해 전달하게 됩니다. int count = (Integer)session.getAttribute("count"); int randomNumber = (Integer)session.getAttribute("randomNumber"); if(number < randomNumber) { message = "입력한 값은 내가 생각하고 있는 숫자보다 작습니다."; session.setAttribute("count", ++count); }else if(number > randomNumber) { message = "입력한 값은 내가 생각하고 있는 숫자보다 큽니다."; session.setAttribute("count", ++count); }else { message = "OK " + ++count + " 번째 맞췄습니다. 내가 생각한 숫자는 " + number + " 입니다."; session.removeAttribute("count"); session.removeAttribute("randomNumber"); } } model.addAttribute("message", message); return "guess"; } }
💾 guess.jsp
더보기<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>숫자 맞추기 게임</title> </head> <body> <h1> 숫자 맞추기 게임.</h1> <hr> <h3>${message }</h3> <c:if test="${sessionScope.count != null}"> <form method="get" action="guess"> 1부터 100사이의 숫자로 맞춰주세요.<br> <input type="text" name="number"><br> <input type="submit" value="확인"> </form> </c:if> <a href="guess">게임 다시 시작하기.</a> </body> </html>
✔️ 세션 실습 - Spring MVC 세션 사용
📋 @SessionAttributes & @ModelAttribute
◾ @SessionAttributes 파라미터로 지정된 이름과 같은 이름이 @ModelAttribute에 지정되어 있을 경우 메소드가 반환되는 값은 세션에 저장된다.
◾ 아래의 예제는 세션에 값을 초기화하는 목적으로 사용되었다.
@SessionAttributes("user") public class LoginController { @ModelAttribute("user") public User setUpUserForm() { return new User(); } }
◾ @SessionAttributes의 파라미터와 같은 이름이 @ModelAttribute에 있을 경우 세션에 있는 객체를 가져온 후, 클라이언트로 전송받은 값을 설정합니다.
@Controller @SessionAttributes("user") public class LoginController { ...... @PostMapping("/dologin") public String doLogin(@ModelAttribute("user") User user, Model model) { ...... } }
📋 @SessionAttribute
◾ 메소드에 @SessionAttribute가 있을 경우 파라미터로 지정된 이름으로 등록된 세션 정보를 읽어와서 변수에 할당합니다.
@GetMapping("/info") public String userInfo(@SessionAttribute("user") User user) { //... //... return "user"; }
📋 SessionStatus
◾ SessionStatus 는 컨트롤러 메소드의 파라미터로 사용할 수 있는 스프링 내장 타입입니다.
◾ 이 오브젝트를 이용하면 현재 컨트롤러의 @SessionAttributes에 의해 저장된 오브젝트를 제거할 수 있습니다.
@Controller @SessionAttributes("user") public class UserController { ...... @RequestMapping(value = "/user/add", method = RequestMethod.POST) public String submit(@ModelAttribute("user") User user, SessionStatus sessionStatus) { ...... sessionStatus.setComplete(); ...... } }
✔️ 세션 실습 - Spring MVC 세션 예제
◾ 관리자는 /loginform에서 암호를 입력해 로그인을 한다.
◾ 관리자가 암호를 맞게 입력할 경우 세션에 로그인 정보가 저장된다.
◾ 세션에 로그인 정보가 있을 경우 방명록에는 "삭제" 링크가 보여진다.
◾ 삭제 링크를 누르면 삭제가 된다. 삭제 작업에서도 로그인 정보가 있는지를 검사해야 한다.
💾 GuestbookAdminController.java
더보기package kr.or.connect.guestbook.controller; import javax.servlet.http.HttpSession; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.support.SessionStatus; import org.springframework.web.servlet.mvc.support.RedirectAttributes; @Controller public class GuestbookAdminController { /* 패스워드를 입력 받는 loginform.jsp를 보여주는 컨트롤러 */ @GetMapping(path="/loginform") public String loginform() { return "loginform"; } /* POST 방식으로 패스워드를 인자로 받아 로그인을 처리하는 컨트롤러 */ @PostMapping(path="/login") public String login(@RequestParam(name="passwd", required=true) String passwd, HttpSession session, RedirectAttributes redirectAttr) { if("1234".equals(passwd)) { // 인자로 받은 패스워드가 1234 라면, session.setAttribute("isAdmin", "true"); //isAdmin이라는 이름의 세션을 true로 설정한다. }else { // 인자로 받은 패스워드가 1234가 아니라면, // redirectAttr의 addFlashAttribute에 에러 메세지를 저장한다. redirectAttr.addFlashAttribute("errorMessage","암호가 틀렸습니다."); // 패스워드가 틀렸다면 loginform으로 리다이렉트 한다. return "redirect:/loginform"; } return "redirect:/list"; } @GetMapping(path="/logout") public String login(HttpSession session) { session.removeAttribute("isAdmin"); return "redirect:/list"; } }
💾 loginform.jsp
더보기<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>loginform</title> </head> <body> <h1>관리자 로그인</h1> <br><br> ${errorMessage}<br> <form method="post" action="login"> 암호 : <input type="password" name="passwd"><br> <input type="submit"> </form> </body> </html>
💾 GuestbookController.java
더보기package kr.or.connect.guestbook.controller; import java.util.ArrayList; import java.util.List; import javax.servlet.http.Cookie; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.ui.ModelMap; import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.ModelAttribute; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.SessionAttribute; import org.springframework.web.servlet.mvc.support.RedirectAttributes; import kr.or.connect.guestbook.dto.Guestbook; import kr.or.connect.guestbook.service.GuestbookService; @Controller public class GuestbookController { @Autowired GuestbookService guestbookService; @GetMapping(path="/list") public String list(@RequestParam(name="start", required=false, defaultValue="0") int start, ModelMap model, @CookieValue(value="count", defaultValue="1", required=true) String value, HttpServletResponse response) { try { int i = Integer.parseInt(value); value = Integer.toString(++i); }catch(Exception ex){ value = "1"; } Cookie cookie = new Cookie("count", value); cookie.setMaxAge(60 * 60 * 24 * 365); // 1년 동안 유지. cookie.setPath("/"); // / 경로 이하에 모두 쿠키 적용. response.addCookie(cookie); List<Guestbook> list = guestbookService.getGuestbooks(start); int count = guestbookService.getCount(); int pageCount = count / GuestbookService.LIMIT; if(count % GuestbookService.LIMIT > 0) pageCount++; List<Integer> pageStartList = new ArrayList<>(); for(int i = 0; i < pageCount; i++) { pageStartList.add(i * GuestbookService.LIMIT); } model.addAttribute("list", list); model.addAttribute("count", count); model.addAttribute("pageStartList", pageStartList); model.addAttribute("cookieCount", value); return "list"; } @PostMapping(path="/write") public String write(@ModelAttribute Guestbook guestbook, HttpServletRequest request) { String clientIp = request.getRemoteAddr(); System.out.println("clientIp : " + clientIp); guestbookService.addGuestbook(guestbook, clientIp); return "redirect:list"; } @GetMapping(path="/delete") public String delete(@RequestParam(name="id", required=true) Long id, @SessionAttribute("isAdmin") String isAdmin, HttpServletRequest request, RedirectAttributes redirectAttr) { if(isAdmin == null || !"true".equals(isAdmin)) { // 세션값이 true가 아닐 경우 redirectAttr.addFlashAttribute("errorMessage", "로그인을 하지 않았습니다."); return "redirect:loginform"; } String clientIp = request.getRemoteAddr(); guestbookService.deleteGuestbook(id, clientIp); return "redirect:list"; } }
💾 list.jsp
더보기<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> <title>방명록 목록</title> </head> <body> <h1>방명록</h1> <br> 방명록 전체 수 : ${count }, 방문한 수 : ${cookieCount }<br><br> <c:forEach items="${list}" var="guestbook"> ${guestbook.id }<br> ${guestbook.name }<br> ${guestbook.content }<br> ${guestbook.regdate }<br> <!-- isAdmin 세션이 true 일경우 "삭제"출력.--> <c:if test="${sessionScope.isAdmin == 'true'}"> <a href="delete?id=${guestbook.id}">삭제</a><br><br></c:if> </c:forEach> <br> <c:forEach items="${pageStartList}" var="pageIndex" varStatus="status"> <a href="list?start=${pageIndex}">${status.index +1 }</a> </c:forEach> <br><br> <form method="post" action="write"> name : <input type="text" name="name"><br> <textarea name="content" cols="60" rows="6"></textarea><br> <input type="submit" value="등록"> </form> </body> </html>
'Web Development > 부스트코스 - Back-End(Java)' 카테고리의 다른 글
[Back End] Argument Resolver (0) 2020.05.28 [Back End] Interceptor (0) 2020.05.28 [Back End] 상태유지기술 - Cookie (0) 2020.05.11 [Back End] Layered Architecture and Rest Controller (0) 2020.04.06 [Back End] Spring MVC (0) 2020.04.03