Dev/SW Engineering

36. Authentication - Cookie

HJChung 2020. 9. 6. 12:59

인증(Authentication)을 할 때는 보통 (로그인을 통해) user를 판별한다.

이때 Session, Cookie가 필요하다고 했는데 왜 필요한 것일까?

 

로그인 한 후 새로고침(새로운 요청)을 할 때마다 초기화된다면? 그래서 로그아웃 된다면? 계속해서 아이디와 비밀번호를 쳐야할 것이다. 그런데 현재 우리가 사용하는 웹사이트에서 그렇지않은 이유는 클라이언트가 서버에게 사용자(내)가 누구인지 지속적으로 알려주기 때문이고, 새로운 요청을 하고 응답을 할 때 서버는 쿠키라는 것을 같이 보내 준다.

 

정리하면, 새로운 요청/응답이 오갈때마다  사용자(내)가 누구인지 지속적으로 기억하는 방법은

  1.  처음에 서버는 클라이언트에게 요청자(사용자)를 추적할 만한 정보를 쿠키로 만들어서 브라우저로 보내고 (우리가 코딩해야함)
  2.  그 다음부터는 웹 브라우전는 쿠키를 저장해두었다가 요청할 때마다 쿠키를 동봉해서 보내준다. (브라우저는 쿠키가 있을 경우 자동으로 동봉해서 보내주므로 우리가 코딩하지 않음)
  3.  그러면 서버는 요청에 들어있는 쿠키를 읽어서 사용자가 누군지 파악한다. 
    출처: https://nesoy.github.io/articles/2017-03/Session-Cookie

1. 쿠키란

웹 서버가 웹 브라우저에게 보냈다가 요청이 있을 때 저장되었다가 브라우저가 다시 서버로 보내주는 문자열 정보로 name='hyeonjeong'과 같이 단순한 키-값의 쌍이다. 

 

2. 쿠키의 위치

저장되었다가? 쿠키는 어디 저장되는 것일까? 

브라우저는 서버에게 쿠키를 보낼 때 어디 저장해서 보내고, 서버는 브라우저에 응답할 때 어디 저장해서 보내는 걸까?

bit.ly/2R0xT4w

 

Server&Node Sprint - Node.js를 이용한 서버 구축(HTTP 트랜잭션 해부)

지금까지의 내용을 정리하면, "Client는 요청하는 주체 - API가 이 요청/응답을 정리해서 상호작용 매개, 이때 상호작용은 HTTP라는 규약에 맞게! - Server는 응답하는 주체이다. Client가 Server로부터 요�

libertegrace.tistory.com

예전에 HTTP에 대해 배울 때 요청(Request)와 응답(Response) 구성에 대해 정리해보았다. 요청과 응답은 header와 body가 있는데 body는 실제 주고 받는 데이터, header는 데이터에 대한 데이터를 저장한다. 쿠키도 실제 데이터라기보다 부과적인 데이터이기 때문에 header에 저장되어 있다. 

그래서 요청시 쿠키가 들어있는 곳은 request.headers.cookie이고, 응답에 쿠키를 기록할 때는 response.writeHead 메서드를 사용한다. 

3. 쿠키 생성(구현)

앞에서 살펴보았던

<< 정리하면, 새로운 요청/응답이 오갈때마다 사용자(내)가 누구인지 지속적으로 기억하는 방법은

1. 처음에 서버는 클라이언트에게 요청자(사용자)를 추적할 만한 정보를 쿠키로 만들어서 브라우저로 보내고 (우리가 코딩해야함)

2. 그 다음부터는 웹 브라우전는 쿠키를 저장해두었다가 요청할 때마다 쿠키를 동봉해서 보내준다. (브라우저는 쿠키가 있을 경우 자동으로 동봉해서 보내주므로 우리가 코딩하지 않음)

3. 그러면 서버는 요청에 들어있는 쿠키를 읽어서 사용자가 누군지 파악한다. >> 이 부분에서 

 

=> 처음에 서버의 응답과 합께 사용자의 브라우저에 쿠키를 심는것을 구현해야 한다. 이 때에는 set-cookie 가 사용되며 이는 '브라우저야~ 다음과 같은 값의 쿠키를 저장해! 라는 의미'이다. 

 

'Node.js 교과서 - 조현영 지음' 에서 set-cookie과 다양한 옵션들에 대해 잘 나와있었다.  이를 바탕으로 정리해보면,,

  • 쿠키명 = [쿠키값]: 기본적인 쿠키의 값이다. 'Set-Cookie': `login='Grace Chung'` 과 같이 설정 할 수 있다. 
  • Expires = [날짜]: 만료 기한이다. 이 기한이 지나면 쿠키가 제거된다. 기본값은 클라이언트가 종료 될 때 까지이다. 
  • Max-age = [초]: Expires와 비슷하지만 날짜 대신 초를 입력할 수 있다. Expires보다 우선되며 해당 시간이 지나면 쿠키가 제거된다. 
  • Domine = [도메인명]: 쿠키가 전송될 도메인을 특정할 수 있다. 기본값은 현재 도메인이다. 
  • Path = [URL]: 쿠키가 전송될 도메인을 측정할 수 있다. 기본값은 '/'이고, 이 경우 모든 URL에서 쿠키를 전송할 수 있다. 
  • Secure: HTTPS 일 때만 쿠키가 전송된다. 
  • HttpOnly: 설정 시 자바스크립트에서 쿠키에 접근할 수 없다. 쿠키(특히 로그인관련 쿠키)는 JavaScript로 접근가능하게 되면 보안이 위험하기때문에 설정한다.

코드)

const http = require('http')
const fs = require('fs')

http
  .createServer((request, response) => {
    request.on('error', (err) => {
      console.error(err)
      response.statusCode = 400
      response.end()
    })
    response.on('error', (err) => {
      console.error(err)
    })
    if (request.method === 'GET' && request.url === '/login') {
      //1. request.url인 /login인 경우 cookie가 있으면 '마이페이지 [name]님 환영합니다'이 있는 client/mypage.html이 보여야함
      console.log('req.headers.cookie: ', request.headers.cookie) //req.headers.cookie확인
      const expires = new Date()
      expires.setMinutes(expires.getMinutes() + 5) //쿠키 유효시간: 현재시간+5분
      response.writeHead(200, {
        'Set-Cookie': `name=HyeonJeong;Expires=${expires.toGMTString()};HttpOnly`, //서버 응답과 함께 쿠키를 헤더에 넣는다.(using writeHead with Set-Cookie)
      })
      response.write(fs.readFileSync('client/mypage.html'))
      request.pipe(response)
    } else if (request.method === 'GET' && request.url === '/') {
   	//2. request.url인 /인 경우
      response.write(fs.readFileSync('client/index.html'))
      request.pipe(response)
    } else {
      response.statusCode = 404
      response.end()
    }
  })
  .listen(8080)

console.log('open http://localhost:8080')

<Node.js 교과서 - 조현영 지음> 공부 후 코드)

구현 문제)

  • parseCookies: 쿠키 문자열을 객체로 변환

  • 주소가 /login인 경우와 /인 경우로 나뉨

  • /login인 경우 쿼리스트링으로 온 이름을 쿠키로 저장

  • 그 외의 경우 쿠키가 있는지 없는지 판단 (쿠키가 있냐/없냐고 분기처리를 해줌) ⇒if문이 너무 많아지고 복잡해진다는 단점이 있다. ⇒ express필요!

    • 있으면 환영인사
    • 없으면 로그인 페이지로 리다이렉트

코드)

//cookie2.js
const http = require('http');
const fs = require('fs').promises;
const url = require('url');
const qs = require('querystring');

const parseCookies = (cookie = '') => 
	cookie
		.split(';')
		.map(v => v.split('='))
		.reduce((acc, [k, v]) => {
			acc[k.trim()] = decodeURIComponent(v);
			return acc;
		}, {});

http.createServer(async(req, res) => {
	const cookies = parseCookies(req.headers.cookie); //1. req.headers.cookie 문자열을 받아와서 객체로 만들어주는 parseCookies 함수를 만들고 넣어준다. 

	//1. 주소가 /login인 경우 쿼리스트링으로 온 이름을 쿠키로 저장하고 /로 리다이렉트 (리다이렉트해준는 status는 301이나 302)
	if(req.url.startWith('./login')){
		const{query} = url.parse(req.url);
		const{name} = qs.parse(query);
		const expires = new Date() ; //쿠키 유효시간을 현재시간 + 5분으로 설정해주기 위한것
		expires.setMinutes(expires.getMinutes() + 5);
		
		res.writeHead(302, {
			Location: '/', //encodURIComponent는 한글때문에 해주는 것임 //Expires는 쿠키의 만료기간(쿠키의 만료기간을 안넣어주면 브라우저를 끄는 순간 쿠키가 사라지는 세션쿠키가 되버린다)
			'Set-Cookie': `name=${encodeURIComponent(name)};Expires = ${expires.toGMTStirng()}; HttpOnly; Path=/`,
///HttpOnly: 쿠키(특히 로그인관련 쿠키)는 JavaScript로 접근가능하게 되면 보안이 위험하기때문에 설정한다. 
		});
		res.end();
	}
	//2. name이라는 쿠키가 있는 경우 ([name]님 안녕하세요 페이지 보이게함)
	else if(cookies.name){
		res.writeHead(200, {'Content-Type':'text/plain; charset=utf-8'});
		res.end(`${cookies.name}님 안녕하세요`);
	}
	//3. name이라는 쿠키가 없는 경우 (로그인 페이지 보이게함)
	else{
		try{
			const data = await fs.readFile('./cookie2.html');
			res.writeHead(200, {'Content-Type':'text/html; charset=utf-8'});
			res.end(data);
		}.catch(err){
			res.writeHead(500, {'Content-Type':'text/plain; charset=utf-8'});
			res.end(err.message);
		}
	}
}

reference

lovefor-you.tistory.com/247

Node.js 교과서 - 조현영 지음