2022-03-29(화)

Jeongyun Heo·2022년 3월 29일
0

세션과 쿠키

90-MyList프로젝트2 / 55 페이지

Web Browser <------요청, 응답------> Web Application Server (WAS)

Web Application Server - 예) Spring Boot
Spring Boot는 Tomcat Server와 Spring Framework를 포함하고 있다.
Tomcat Server는 웹 서버와 서블릿 컨테이너를 포함하고 있다.

🔹 세션 (session)
서버 측 값 저장소
저장 형태는 key(문자열)=value(객체)
서버측 값 저장소
주로 key는 문자열, value는 객체를 쓴다

🔹 쿠키 (cookie)
클라이언트 측 값 저장소
key, value, 유효기간, 사용범위
key는 문자열, value도 문자열
사용범위는 url
쿠키는 객체를 저장 못 한다. 문자열만 저장한다.

Web Application 시스템 아키텍처와 스프링 부트

90-MyList프로젝트2 / 56 페이지

시스템 아키텍처: S/W + H/W

Web Browser --> Web Server --> Servlet Container --> Servlet

Application Server ------ App

Application Server을 Servlet Container 라고 부른다.

Application을 자바에서 Servlet이라고 부른다.

Web Server --- 실행 위임 ---> Servlet Container
실행을 위임한다.

Web Server와 Servlet Container를 포장한 게 Tomcat Server

Servlet과 기타 등등을 포장한 게 Spring Framework

Tomcat Server와 Spring Framework를 포장한 게 Spring Boot

Spring Boot는 패키징 제품이다
결국 내부적으로 기술은 Spring Framework를 이용하고 있다
서버는 Tomcat Server를 이용하고 있다
서버를 Tomcat Server 대신 다른 걸로 교체 가능

Spring Boot가 없으면 Tomcat Server를 다운로드 받아서 따로 설치해야 한다
Spring Framework로 따로 프로젝트를 만들어서 실행시켜야 한다
이런 번거로움을 대신한 게 Spring Boot

내부적으로 Tomcat Server를 포함하고 있다

Spring Boot는 패키징 제품이다
톰캣 서버를 다운로드 받을 필요 없음. 세팅할 필요도 없음.
우리가 할 건 SpringApplication.run() 만 하면 됨
개발자가 설정을 좀 더 간결하게 할 수 있다.
application.properties 파일 하나만 하면 됨

eomcs-java/eomcs-spring-webmvc/app/src/main/java/bitcamp/config

스프링 부트는 스프링 프레임워크를 좀 더 쉽게 사용할 수 있도록 한 번 포장한 제품에 불과하다.

스프링 프레임워크 기반으로 프로그래밍을 한다는 게 맞는 말임

Spring Boot 프로그래밍 (X)
Spring Framework 기반으로 웹 프로그램을 개발하는 프로그래밍을 배운다 (O)

스프링 프레임워크를 직접 세팅하는 기술

세션과 클라이언트

90-MyList프로젝트2 / 57 페이지

각각의 클라이언트가 자신만의 쿠키를 갖고 있다

     Local                            Remote
쿠키 - Client A                  A Client Session 
쿠키 - Client B       WAS        B Client Session
쿠키 - Client C                  C Client Session

    환자             병원          환자의 진료 기록
    
    고객             은행            고객의 통장

어떤 진료 기록이 A 환자의 기록인지 알 수 있을까
주민번호 or 환자에게 부여된 번호

서버에서 각 고객의 진료 기록이나 통장을 구분할 때 주민번호 또는 고객 번호로 구분한다.

WAS에서 각 클라이언트의 세션을 구분할 때 세션 아이디로 구분한다.

서버에서 클라이언트를 구분하는 방법

90-MyList프로젝트2 / 58 페이지

고객 A ---- ① 방문 ----> 은행 ---- ② 개설 ----> 고객 A 통장
고객 A <---- ③ 통장(계좌번호, 고객정보)을 리턴 ---- 은행

처음에 갈 때는 통장 없이 가서 은행에서 통장을 받아온다

① 방문
② 개설
③ 통장을 리턴 (계좌번호, 고객정보)

통장 개설 후 다시 은행 방문

고객 A ---- ① 방문 + 통장(계좌번호, 고객정보) ----> 은행
은행 ---- ② (계좌번호, 고객정보)를 가지고 통장을 찾는다 ----> 고객 A 통장
고객 A <---- ③ 결과 리턴 ---- 은행

① 방문 + 통장 (계좌번호, 고객정보)
② 계좌번호, 고객정보를 가지고 통장을 찾는다
    -> 입금 또는 출금한다
③ 결과 리턴

계좌번호를 은행에서 준다
다음에 올 때는 이 통장을 가지고 오세요

서버에서 클라이언트를 구분하는 방법

90-MyList프로젝트2 / 59 페이지

Web Browser -------> WAS

세션ID 없이 요청
① 세션ID 없이 요청
② 요청한 클라이언트가 사용할 세션 생성
③ 세션ID 리턴
④ 웹 브라우저는 쿠키 테이블에 세션ID를 저장한다

세션 생성 후
① 쿠키에서 세션ID를 읽는다
② 요청 + 세션ID
③ 세션ID를 이용하여 클라이언트가 이용할 세션을 찾는다
    -> 세션에 값 저장/조회/변경/삭제
④ 결과 응답

세션을 사용하려면 반드시 쿠키라는 기술이 필요
서버가 세션ID를 리턴하면 웹 브라우저가 쿠키에 보관한다.

세션 생성 과정

90-MyList프로젝트2 / 60 페이지

① 세션ID 없이 요청
② new
③ 응답 (+ 세션ID)
④ 쿠키에 세션ID 저장

세션 생성 과정 - 세션을 사용하지 않는 request handler 실행할 때

90-MyList프로젝트2 / 60 페이지

세션을 사용하지 않는 request handler를 호출할 때는 세션을 생성하지 않는다.

http://localhost:8080/session/test1

주소창에 직접 입력하면 GET 요청

세션 생성 과정 - 세션을 사용하는 request handler를 실행할 때

90-MyList프로젝트2 / 61 페이지

  // 2) 세션을 사용하는 request handler
  // => HttpSession 객체를 달라고 파라미터에 선언하라!
  //
  @RequestMapping("/session/test2")
  public Object test2(HttpSession session) {
    System.out.printf("test2() => %s\n", session.getId());
    return "test2() 실행!";
  }

http://localhost:8080/session/test2

JSESSIONID ← 세션ID를 의미하는 이름은 서버마다 다를 수 있다
(Tomcat, WebLogic, JEUS 등)
Tomcat은 JSESSIONID 이라는 이름으로 보낸다

⑤ 쿠키에 세션ID를 저장한다

세션 생성 과정 - 세션을 사용하는 request handler를 실행할 때

90-MyList프로젝트2 / 62 페이지

  // 3) 세션을 사용하는 request handler
  // => 세션이 생성된 상태에서 요청하면 기존 세션 객체를 그대로 사용한다.
  // => 세션이 없는 상태에서 요청하면 새 세션 객체를 만든다.
  // => 세션이 있지만 무효한 상태일 경우 새 세션 객체를 만든다.
  //
  @RequestMapping("/session/test3")
  public Object test3(HttpSession session) {
    System.out.printf("test3() => %s\n", session.getId());
    return "test3() 실행!";
  }

http://localhost:8080/session/test3

세션을 사용하지 않는 request handler
세션을 사용하지 않아도 응답할 수 있는 업무
병원 운영시간 물어보는데 이름, 주민번호를 알려달라고 하진 않음
그 환자만을 위한 업무가 아닌 일반 업무
진료기록이 필요 없는 업무

세션ID 없이 요청했다고 무조건 세션을 만드는 게 아니라 세션을 사용하는 요청을 했을 경우 만든다

http://localhost:8080/session/test2

호출하는 메서드가 세션ID를 사용하는 메서드일 때

http://localhost:8080/session/test3

응답 헤더에 세션ID 없음
세션ID에 해당하는 기존 객체 사용
기존 세션을 사용하기 때문에 세션ID를 리턴할 필요가 없다

test2 test3 세션ID가 같다

다시 새 시크릿 창 켜서
http://localhost:8080/session/test3

세션ID가 다르다

세션 생성 과정 - 무효한 세션을 사용하려 할 때

90-MyList프로젝트2 / 63 페이지

  // 4) 세션 무효화시키기
  @RequestMapping("/session/test4")
  public Object test4(HttpSession session) {
    System.out.printf("test4() => %s\n", session.getId());
    session.invalidate(); // 요청한 클라이언트가 사용하는 현재 세션을 무효화시킨다.
    return "test4() 실행!";
  }

다시 test3() 호출해보기

http://localhost:8080/session/test3

✔ 클라이언트가 보낸 세션ID가 무효한 세션을 가리킨다면 새로 세션 객체를 생성한다.

새로 만든 세션 객체의 ID 리턴

서버에서 새로 받은 세션ID를 저장

웹 브라우저를 동시에 여러 개 실행할 때
같은 PC : 쿠키 공유
쿠키를 공유하기 때문에 같은 클라이언트로 묶인다.

시크릿 창끼리도 쿠키를 공유한다

창을 다 닫아야
크롬을 완전히 닫고 새로 브라우저를 띄우면 다른 브라우저로 간주한다

세션을 활용하는 법을 알아보자

Controller1 복사해서 Controller2 만들기

com.eomcs.web.session.Controller2

public Object step1(String name, HttpSession session)
이름을 입력받고 세션에 보관

session.setAttribute("name", name);
name 이라는 이름으로 name 값을 저장한다

  @RequestMapping("/session/step1")
  public Object step1(String name, HttpSession session) {
    // 세션에 값 저장
    session.setAttribute("name", name);
    return "step1() 실행!";
  }

public Object step2(int age, HttpSession session)
나이를 입력받아서 세션에 값을 저장

session.setAttribute("age", age);
int 값을 저장할 수 없다

void javax.servlet.http.HttpSession.setAttribute(String name, Object value)

int 값이 Integer 객체로 오토박싱 돼서 저장된다

int 값이 Integer 객체로 오토박싱 돼서 저장된다

  @RequestMapping("/session/step2")
  public Object step2(int age, HttpSession session) {
    session.setAttribute("age", age);
    return "step2() 실행!";
  }

working 여부 저장

  @RequestMapping("/session/step3")
  public Object step3(boolean working, HttpSession session) {
    session.setAttribute("working", working);
    return "step3() 실행!";
  }

모든 걸 출력한다

  @RequestMapping("/session/step4")
  public Object step4(HttpSession session) {
    String name = (String) session.getAttribute("name");
    int age = (int) session.getAttribute("age");
    boolean working = (boolean) session.getAttribute("working");
    
    return String.format("step() 실행: %s, %d, %b\n", name, age, working);
  }
  @RequestMapping("/session/step4")
  public Object step4(HttpSession session) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", session.getAttribute("name"));
    map.put("age", session.getAttribute("age"));
    map.put("working", session.getAttribute("working"));

    return map;
  }

페이지가 바뀌더라도 요청이 바뀌더라도 이전 요청에서 작업했던 내용을
계속 보관할 수 있다

세션 활용

90-MyList프로젝트2 / 65 페이지

http://localhost:8080/session/step1?name=aaa
step1() 호출

http://localhost:8080/session/step2?age=20
step2() 호출
기존의 HttpSession 객체에 저장한다

http://localhost:8080/session/step3?working=true
step3() 호출
기존의 HttpSession 객체에 저장한다

http://localhost:8080/session/step4
step4() 호출
기존의 HttpSession 객체에서 데이터 조회
응답 (aaa, 20, true)

요청이 달라도 같은 세션 사용

요청이 달라도 같은 HttpSession 객체 사용

뭐 가입할 때
지금까지 입력하신 게 이게 맞습니까? 맞으면 동의하세요
전형적으로 세션 사용한 거임

세션을 로그인할 때 활용한다

단, 시크릿창으로 띄우면
http://localhost:8080/session/step4
시크릿 창에서 하면 해당되는 값이 없어서 에러남

AJAX 세션이 그대로 적용되는지 확인

web/src/main/resources/static/javascript
ex08 만들기

session-step1.html - web/src/main/resources/static/javascript/ex08

http://localhost:8080/javascript/ex08/session-step1.html

// AJAX 코딩
fetch("", {}).then(function(){}).then(function(){});
<script>
document.querySelector("#btn1").onclick = function() {
	var fd = new FormData(document.forms.namedItem("form1"));
	
	fetch("/session/step1", {
		method: "POST",
		body: fd
	}).then(function(response){
		// 응답 객체를 텍스트로 파싱해서 리턴한다.
		return response.text();
	}).then(function(text){
		console.log(text);
	});
};
</script>

세션을 테스트할 때는 새 시크릿 창 열어서 확인

http://localhost:8080/javascript/ex08/session-step1.html

session-step1.html 복사해서 session-step2.html 만들기

게시글, 연락처, 독서록, todo
여러 사람이 쓰는 서비스를 만들려면 회원별로 데이터를 구분해야 됨
기존의 게시글, 연락처, 독서록, todo에 회원 정보를 추가시켜야 한다
그 구조로 바꿀 거임

session-step2.html 복사해서 session-step3.html 만들기

radio 버튼

input type="radio"

name이 같아야 둘 중 하나만 선택 가능

<input type="radio" name="working" value="true"> 직장인
<input type="radio" name="working" value="false" checked> 미취업자

name이 같아야 같은 그룹으로 묶여서 하나만 선택 가능

여러 개 선택하고 싶으면 type="checkbox"

radio 버튼 default 값 지정해주기
아무것도 선택 안 하면 값이 안 넘어감
다음 버튼 누르면 payload가 없음
default 값을 지정해준다
미취업자를 기본으로 선택되게 해준다

<input type="radio" name="working" value="false" checked> 미취업자

구매경험: 있음
체크를 빼고 싶으면 어떻게 하나요?
radio 버튼이 아니라 체크박스를 써야 됨!!!

session-step3.html 복사해서 session-step4.html 만들기

우리는 일반 데이터를 넘기는 건데
파일을 보내는 것도 아닌데 multipart로 넘어가고 있음

body로 보내서 그렇다는데...

javascript fetch post x-www-form-urlencoded 검색

https://stackoverflow.com/questions/35325370/how-do-i-post-a-x-www-form-urlencoded-request-using-fetch

Content-Type: application/x-www-form-urlencoded
직접 써주기?

javascript formdata 검색

https://developer.mozilla.org/ko/docs/Web/API/FormData

간단한 GET 전송을 사용하는 경우에는 <form>이 수행하는 방식으로 쿼리 매개변수를 생성할 수 있습니다. 이 경우 URLSearchParams 생성자에 직접 전달할 수 있습니다.

URLSearchParams
https://developer.mozilla.org/ko/docs/Web/API/URLSearchParams

document.querySelector("#btn1").onclick = function() {
	var fd = new FormData(document.forms.namedItem("form1"));
	
	fetch("/session/step1", {
		method: "POST",
		body: new URLSearchParams(fd)

console.log(new URLSearchParams(fd));

console.log(new URLSearchParams(fd).toString());

이름에 '홍길동' 입력해보기
url 인코딩까지 자동으로 된다!!

URLSearchParams에 감싸서 주면 편하다

toString()을 호출한 다음에 그 리턴값을 body로 넘긴다
서버에 보낼 때 multipart 형식이 아니라 x-www-form-urlencoded 타입으로 보낸다

session-step4.html

fetch 그냥 요청하면 GET 방식임

<script>
  fetch("/session/step4").then(function(response){
    return response.text(); 
  }).then(function(text){
    console.log(text);
  });
</script>

이렇게 나오면 이름, 나이, 재직여부 값을 추출하기가 힘들다
이렇게 서버에서 텍스트로 받으면 데이터를 추출하기 힘들다
서버에서 데이터를 보낼 때 문자열로 보내지 말고 json 형식으로 보내달라고 한다

객체에 담아서 보내는 걸로 바꾸기

도메인 따로 만들지 말고 HashMap에 담기

HashMap<String, Object> map = new HashMap<>();

형변환 할 필요 없이 그냥 저장하면 됨

  @RequestMapping("/session/step4")
  public Object step4(HttpSession session) {
    HashMap<String, Object> map = new HashMap<>();
    map.put("name", session.getAttribute("name"));
    map.put("age", session.getAttribute("age"));
    map.put("working", session.getAttribute("working"));

    return map;
  }

Map에 객체를 담아서 리턴하면 json 형식으로 간다

리턴하는 객체가 Map 객체면 json 문자열로 만들어서 리턴한다
누가? 스프링 부트가 자동으로 한다

이 상태에서 추출하면 번거로움
문자열에서 데이터를 추출하는 건 번거로움
양쪽에 중괄호 날리고 콤마로 자르고....

그런데 fetch가 대신 해준다

response.json()

서버에서 받은 JSON 형식의 문자열을 분해하여 자바스크립트 객체로 만들어 리턴한다.

자바스크립트 객체가 되는 순간 값을 꺼내기 쉬워진다.

<script>
  fetch("/session/step4").then(function(response){
    return response.json(); // 서버에서 받은 JSON 형식의 문자열을 분해하여 자바스크립트 객체로 만들어 리턴한다.
  }).then(function(obj){
    console.log(obj);
  });
</script>

<script>
  fetch("/session/step4").then(function(response){
    return response.json(); // 서버에서 받은 JSON 형식의 문자열을 분해하여 자바스크립트 객체로 만들어 리턴한다.
  }).then(function(obj){
    console.log(obj.name);
    console.log(obj.age);
    console.log(obj.working);
  });
</script>

CSV, XML, JSON
JSON이 제일 편함
그래서 서버에서 클라이언트쪽에 데이터를 보낼 때는 JSON 형식으로 보내고
클라이언트는 JSON 형식으로 받았으면 그걸 자바스크립트 객체로 바꾸기가 쉽다
자바스크립트 객체로 바꾼 걸 쓰면 됨

서버에서 받은 값을 화면에 어떻게 보여줄까
span 태그는 화면에 영향을 안 끼친다
span 태그를 사용하여 특정 자리의 값을 뽑아내자

<script>
  fetch("/session/step4").then(function(response){
    return response.json(); // 서버에서 받은 JSON 형식의 문자열을 분해하여 자바스크립트 객체로 만들어 리턴한다.
  }).then(function(obj){
	  document.querySelector("#x-name").innerHTML = obj.name;
	  document.querySelector("#x-age").innerHTML = obj.age;
	  document.querySelector("#x-working").innerHTML = obj.working;
  });
</script>

이전 화면에서 보낸 데이터를 세션에 보관하고 있다가 마지막 화면에서는 세션에 보관된 데이터를 가져와서 뿌린 거

세션은 여러 화면에서 작업한 데이터를 공통으로 사용할 때 쓴다

mylist에 회원가입, 로그인, 로그아웃을 구현해보자

18.1 세션과 쿠키의 활용: 회원가입/로그인/로그아웃 처리하기

회원가입 후 세션을 이용한 로그인/로그아웃을 다룬다.

1단계 - 회원 테이블을 추가한다.

create table ml_member (
  no int not null,
  name varchar(50) not null,
  email varchar(100) not null,
  password varchar(255) not null,
  regist_date datetime default now()
);

Primary Key면서 자동증가하게 만들기

alter table ml_member 
  add constraint primary key (no), 
  modify column no int not null auto_increment;

2단계 - 회원 데이터를 표현할 도메인 클래스를 정의한다.

com.eomcs.mylist.domain.Member 정의

package com.eomcs.mylist.domain;

import java.sql.Date;
import lombok.Data;

@Data
public class Member {
  int no;
  String name;
  String email;
  String password;
  Date registDate;
}

3단계 - 회원 데이터를 다룰 DAO 인터페이스를 정의한다.

com.eomcs.mylist.domain.MemberDao 인터페이스 정의

insert(Member), findAll(), findByNo(int), get(int no), update(), delete() 메서드 추가

package com.eomcs.mylist.dao;

import org.apache.ibatis.annotations.Mapper;
import com.eomcs.mylist.domain.Member;

@Mapper  
public interface MemberDao {

  int insert(Member member);

}

4단계 - DAO가 사용할 SQL Mapper 파일을 추가한다.

src/main/resources/com/eomcs/mylist/dao/MemberDao.xml 파일 추가

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper
  PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- 
  namespace => 인터페이스의 패키지 및 이름과 namespace의 이름이 일치해야 한다.
 -->
<mapper namespace="com.eomcs.mylist.dao.MemberDao">

자동으로 디폴트 값 들어가는 건 안 써줘도 됨

  <insert id="insert" parameterType="member">
    insert into ml_member(name,email,password) 
    values(#{name},#{email},password(#{password}))
  </insert>

데이터베이스에 비밀번호 그대로 집어넣으면 안 됨
데이터베이스에 넣기 전에 암호화 시켜서 넣어야 됨

mariadb password() 검색

select password('abc123');

MariaDB [studydb]> select password('abc123');

단방향이라 원래 형태로 못 바꿈

Data 인코딩

90-MyList프로젝트2 / 66 페이지

Data ----단방향 알고리즘(암호화)----> Data'
원래의 데이터로 디코딩(복호화) 불가
예) 암호처리

Data ----양방향 알고리즘(암호화)----> Data'
복호화 가능
예) BASE64, URL Encoding, ZIP(압축)
같은 알고리즘으로 푼다

Data -------공개키(자물통)로 암호화----> Data'
Data <-----개인키(열쇠)로 복호화-------- Data'
암호화 시키는 알고리즘 따로, 복호화 시키는 알고리즘 따로임

암호 처리는 단방향 알고리즘이다. 원상태로 복호화가 안 된다.

암호화 시켜서 저장한다.
password('abc123')

insert into ml_member(name,email,password) 
  values('user1','user1@test.com',password('abc123'));

5단계 - 서비스 객체를 추가한다.

MemberService 인터페이스 추가
DefaultMemberService 클래스 추가

package com.eomcs.mylist.service;

import com.eomcs.mylist.domain.Member;

public interface MemberService {

  int add(Member member);

}

MemberService 구현체 만들기

@Service
public class DefaultMemberService implements MemberService {

  @Autowired
  MemberDao memberDao;

  @Override
  public int add(Member member) {
    return memberDao.insert(member);
  }

}

6단계 - 페이지 컨트롤러를 추가한다

MemberController 클래스 추가

@RestController 붙이기. 무조건 JSON 형식으로 리턴된다.

@RestController
public class MemberController {

  @Autowired
  MemberService memberService;

  @RequestMapping("/member/signup")
  public Object signup(Member member) {
    return memberService.add(member);
  }
}

http://localhost:8080/member/signup?name=user3&email=user3@test.com&password=1111

delete from ml_member where no=3;

프론트엔드 개발

1단계 - 회원 가입 페이지 만들기

mylist-boot/src/main/resources/static/member/form.html

http://localhost:8080/member/form.html

return false

  document.querySelector("form[name=form1]").onsubmit = function() {
    if (xName.value == "" || 
    		xEmail.value == "" ||
    		xPassword.value == "") {
      window.alert("필수 입력 항목이 비어 있습니다.");
      return false;
    }

false를 리턴하게 되면 서버에 요청하지 않는다.

이메일 형식이 올바르지 않으면 에러가 뜬다.

올바르게 입력했을 때 비로소 서버에 요청을 보낸다.

브라우저에서 submit
개입
그 전까지는 브라우저가 개입하도록 한다

무조건 return false

입력 항목
웹브라우저가 개입

서버에 요청을 보내는 건 안 한다
우리가 직접 AJAX로 보낸다

브라우저가 할 수 있는 일은 맡긴다

서버에 요청하는 건 우리가 따로 한다

0개의 댓글