HttpURLConnection를 이용한 쿠키(Cookie), 세션(Session) 사용

 

개발 환경

Java : Java OpenJDK 8

IDE : STS4 (Spring Tool Suite 4)

Server Platform : Spring boot

 

 

http 통신 방법

stateless(=connectionless) 방식으로 http 통신

http 통신은 기본적으로 stateless 방식이기 때문에, 서버에서는 클라이언트의 요청에 대해 상태를 유지하지 않는다. 이 말은, 동일한 클라이언트에서 동일한 서버로 몇 번을 요청하더라도 서버는 모든 요청에 대해 독립적으로 처리한다는 말이다. 이 방식으로는 http 통신으로만 사용해서 로그인 등의 상태를 유지해야하는 기능들을 개발할 수 없다.

 

state(=connection) 방식으로 http 통신

http 통신이 기본적으로 stateless 방식을 따르고 있지만, 서버와 클라이언트의 코드에 추가작업을 한다면 state 방식으로 상태를 유지할 수 있게 만들 수 있다. 이 말은, 동일한 클라이언트에서 동일한 서버로 계속 요청을 하면 서버는 이전에 요청했던 클라이언트가 다시 요청한 것인지를 인지할 수 있다는 말이다. 이 방식은 http 통신으로만 사용해서도 로그인 등의 상태를 유지하면서 이메일, 온라인 서비스 등의 상태를 유지해야하는 기능들을 개발할 수 있다. 여기서 말하는 추가작업이라는 것은, 클라이언트에서는 쿠키(cookie)를 사용하도록 설정하고, 서버에서는 세션(session)을 사용하도록 설정하는 작업을 말한다.

 

 

http 통신에서 session을 사용한 개발

http 통신을 할 때 개발자에게 발생할 수 있는 상황은 보통 아래 4가지 경우 중 하나이다. 이 글은 클라이언트에서 사용하는 HttpURLConnection에 대한 설명이므로, 서버가 세션을 사용하는 상황에 초점을 두고 설명할 것이기 때문에 1번과 2번에 대해서만 설명한다.

 

  1. 서버에서 세션을 사용하고, 클라이언트에서 쿠키를 사용하는 경우

  2. 서버에서 세션을 사용하고, 클라이언트에서 쿠키를 사용하지 않는 경우

  3. 서버에서 세션을 사용하지 않고, 클라이언트에서 쿠키를 사용하는 경우

  4. 서버에서 세션을 사용하지 않고, 클라이언트에서 쿠키를 사용하지 않는 경우

 

서버에서 세션을 사용하고, 클라이언트에서 쿠키를 사용하지 않는 경우

서버 개발자와 클라이언트 개발자가 http 통신을 사용한 프로그램을 개발하는 경우 발생 할 수 있는 상황이다. 세션을 사용한 프로그램을 개발하는 프로젝트에서, 서버 개발자는 세션에 대한 처리를 모두 완료 했지만, 클라이언트에서는 서버에 http 요청을 몇번을 해도 서버에서는 세션이 달라서 요청을 거부한다는 응답만 하는 경우이다.

 

이 경우, 원인은 클라이언트에서 쿠키를 사용하지 않고 있기 때문이다. 현재 글쓴이는 클라이언트에서 http 통신을 위해 java 라이브러리 중 하나인 HttpURLConnection을 사용해서 개발 중이다. 이 HttpURLConnection 클래스는 기본적으로 쿠키를 사용하지 않기 때문에, 쿠키를 사용하겠다고 직접 설정해주어야 한다.

 

참고로, 클라이언트에서 쿠키를 사용하도록 설정해야 하는 이유는 세션은 쿠키를 기반으로 하는 방법이기 때문에, 서버 뿐만 아니라 클라이언트도 쿠키를 사용해야 한다. 아래 그림은 서버에서는 세션을 사용하지만, 클라이언트에서는 쿠키를 사용하지 않는 경우에 요청을 두번 했을 때 어떤 상황이 일어나는지를 설명하는 그림이다.

 

클라이언트의 첫번째 요청

 

 

  1. 클라이언트가 http request message를 서버에 요청

  2. 서버가 요청에 대한 session id가 없으면 새로 생성, 있으면 그대로 사용 (여기선 새로 생성)

  3. 서버가 (session id를 담은) http response message를 클라이언트에 응답

 

클라이언트의 두번째 요청

 

 

  1. 클라이언트가 http request message를 서버에 요청

  2. 서버가 요청에 대한 session id가 없으면 새로 생성, 있으면 그대로 사용 (여기선 새로 생성)

  3. 서버가 (session id를 담은) http response message를 클라이언트에 응답

 

클라이언트 및 서버 코드

Client Code (java / HttpURLConnection)

import java.net.HttpURLConnection;
...

public class JokerClient {
	// HTTP 요청 함수
	public String sendUsingHttp(...) {
		...
		HttpURLConnection conn = (HttpsURLConnection) url.openConnection();
		...
		conn.setRequestProperty("Content-Type", contentType);
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setUseCaches(false);
		conn.setDefaultUseCaches(false);
		conn.setRequestMethod(method);
		conn.connect();
		writer = conn .getOutputStream();
		writer.flush();
		writer.close();
		code = conn.getResponseCode();
		...
	}
    
	// Client Test 실행 코드
	public static void main(String[] args) throws HttpException, JSONException {
		JokerClient client = new JokerClient();
		
		Login user = new Login();
		user.setId("batman");
		user.setPwd("qwer1234");
		
		String recv1 = client.sendUsingHttp("http://localhost/page1", user.toJSON());
		System.out.println(recv1);
		
		String recv2 = client.sendUsingHttp("http://localhost/page2");
		System.out.println(recv2);
	}
}

 

Server Code (java / Spring Framework)

import org.springframework.web.bind.annotation.RestController;
...
@RestController
@RequestMapping(value = "/")
public class JokerController {
	...
	@RequestMapping(value = "/page1", method = RequestMethod.POST)
	public String req1(@RequestBody Login user, HttpSession session) throws Exception{
		String sid = session.getId();
		session.setAttribute("state", user.toJSON());

		// 반환 값 : response (152894)
		return "response (" + sid + ")";
	}
    
	@RequestMapping(value = "/page2", method = RequestMethod.POST)
	public String req2(HttpSession session) throws Exception{
		String state = (String) session.getAttribute("state");
		if(state == null)
		state = "fail";
		String sid = session.getId();

		// 반환 값 : response (561328 / fail)
		return "response (" + sid + " / " + state + ")";
	}
	...
}

 

서버에서 세션을 사용하고, 클라이언트에서 쿠키를 사용하는 경우

http 통신을 사용한 개발을 할 때, 세션을 정상적으로 사용하는 경우라면, 서버에서는 세션을 사용하고 클라이언트에서는 쿠키를 사용하도록 설정해서 개발해 주어야 한다. 만약에, 클라이언트에서도 쿠키를 사용한다면, 아래 그림처럼 클라이언트가 두 번 이상 요청을 할 때는 서버로부터 전달받은 세션 아이디를 쿠키에 담에서 전송하게 된다.

 

클라이언트의 첫번째 요청

 

 

  1. 클라이언트가 http request message를 서버에 요청

  2. 서버가 요청에 대한 session id가 없으면 새로 생성, 있으면 그대로 사용 (여기선 새로 생성)

  3. 서버가 (session id를 담은) http response message를 클라이언트에 응답

 

클라이언트의 두번째 요청

 

 

  1. 클라이언트가 (session id를 담은) http request message를 서버에 요청

  2. 서버가 요청에 대한 session id가 없으면 새로 생성, 있으면 그대로 사용 (여기선 그대로 생성)

  3. 서버가 (session id를 담은) http response message를 클라이언트에 응답

 

클라이언트 및 서버 코드

Client Code (java / HttpURLConnection)

import java.net.HttpURLConnection;
…
public class JokerClient {
	// HTTP Request Message에 쿠키 포함할지 여부 (HTTP Response Message에 있었다면)
	boolean m_session = false;

	// HTTP Response Message에서 획득한 쿠키 내용
	String m_cookies = “”;

	// HTTP 요청 함수
	public String sendUsingHttp(...) {
		...
		HttpURLConnection conn = (HttpsURLConnection) url.openConnection();
		...

		// HTTP Request Message에 쿠키 포함
		if(m_session) {
			conn.setRequestProperty("Cookie", m_cookies);
		}

		conn.setRequestProperty("Content-Type", contentType);
		conn.setDoInput(true);
		conn.setDoOutput(true);
		conn.setUseCaches(false);
		conn.setDefaultUseCaches(false);
		conn.setRequestMethod(method);
		conn.connect();
		writer = conn .getOutputStream();
		writer.flush();
		writer.close();
		code = conn.getResponseCode();

		// HTTP Response Message에서 쿠키 획득
		Map<String, List<String>> header = conn.getHeaderFields();
		if(header.containsKey("Set-Cookie")){
			List<String> cookie = header.get("Set-Cookie");
			for(int i=0; i<cookie.size(); i++) {
				m_cookies += cookie.get(i);
			}
			m_session = true;
		} else {
			m_session = false;
		}
	}
	...
    
	// Client Test 실행 코드
	public static void main(String[] args) throws HttpException, JSONException {
		JokerClient client = new JokerClient();
		
		Login user = new Login();
		user.setId("batman");
		user.setPwd("qwer1234");
		
		String recv1 = client.sendUsingHttp("http://localhost/page1", user.toJSON());
		System.out.println(recv1);
		
		String recv2 = client.sendUsingHttp("http://localhost/page2");
		System.out.println(recv2);
	}
}

 

Server Code (java / Spring Framework)

import org.springframework.web.bind.annotation.RestController;
...

@RestController
@RequestMapping(value = "/")
public class JokerController {
	...
	@RequestMapping(value = "/page1", method = RequestMethod.POST)
	public String req1(@RequestBody Login user, HttpSession session) throws Exception {    
		String sid = session.getId();
		session.setAttribute("state", user.toJSON());
        
		// 반환 값 : response (152894)	
		return "response (" + sid + ")";
	}

	@RequestMapping(value = "/page2", method = RequestMethod.POST)
	public String req2(HttpSession session) throws Exception {
		String state = (String) session.getAttribute("state");
		if(state == null)
			state = "fail";
		String sid = session.getId();

		// 반환 값 : response (152894 / {“id”:”batman”,”pwd”:”qwer1234”})
		return "response (" + sid + " / " + state + ")";
	}
	...
}