ASP.NET Web API와 HTTP 쿠키
- 본 번역문서의 원문은 HTTP Cookies www.asp.net 입니다.
- 본 번역문서는 ASP.NET Web API와 HTTP 쿠키 www.taeyo.net 에서도 함께 제공됩니다.
본문에서는 Web API에서 HTTP 쿠키가 전송되고 수신되는 방식을 살펴봅니다.
HTTP 쿠키의 기본개념
이번 절에서는 HTTP 수준의 쿠키 구현 방식을 간단하게 살펴봅니다. 보다 자세한 정보는 RFC 6265를 참고하시기 바랍니다.
쿠키는 서버가 HTTP 응답의 일부로 전송하는 데이터 조각입니다. 클라이언트는 쿠키를 받아서 (선택적으로) 저장했다가, 그 다음 요청 시 이 쿠키를 다시 서버로 재전송합니다. 이런 과정들을 통해서 클라이언트와 서버가 서로 상태를 공유할 수 있습니다. 서버는 응답에 Set-Cookie 헤더를 포함시키는 방식으로 쿠키를 설정합니다. 쿠키는 이름-값 쌍과 선택적인 어트리뷰트들로 구성됩니다. 가령, 다음과 같은 구조를 갖고 있습니다:
Set-Cookie: session-id=1234567
그리고, 다음은 몇 가지 어트리뷰트들이 추가된 경우입니다:
Set-Cookie: session-id=1234567; max-age=86400; domain=example.com; path=/;
클라이언트에서 이 쿠키를 서버로 재전송하려면, 요청에 다음과 같은 Cookie 헤더를 포함시킵니다.
Cookie: session-id=1234567
HTTP 응답은 Set-Cookie 헤더를 여러 개 포함할 수 있습니다.
Set-Cookie: session-token=abcdef; Set-Cookie: session-id=1234567;
반면, 클라이언트는 단일 Cookie 헤더를 사용해서 여러 개의 쿠키를 재전송합니다.
Cookie: session-id=1234567; session-token=abcdef;
쿠키의 범위와 존속 기간은 Set-Cookie 헤더에 뒤이어 지정되는 어트리뷰트로 제어가 가능합니다:
- Domain: 클라이언트에서 어떤 도메인에 대해 해당 쿠키를 받아야 할지 알려줍니다. 가령, 이 값이 "example.com"인 경우라면 클라이언트는 example.com의 모든 서브 도메인에서 쿠키를 재전송합니다. 값이 지정되지 않으면 현재 서버가 기본값으로 사용됩니다.
- Path: 쿠키를 도메인 하위의 특정 경로로만 제한합니다. 값이 지정되지 않으면 기본값으로 요청 URI 경로가 사용됩니다.
- Expires: 쿠키 만료일자를 지정합니다. 만료일자에 도달하면 클라이언트가 쿠키를 삭제합니다.
- Max-Age: 쿠키의 최대 나이(초)를 지정합니다. 최대 나이에 도달하면 클라이언트가 쿠키를 삭제합니다. (역주: IE의 구버전에서는 이 어트리뷰트가 동작하지 않는 것으로 알고 있습니다. 최신 버전에서는 테스트 해보지 못했습니다.)
만약, Expires
어트리뷰트와 Max-Age
어트리뷰트가 모두 설정되어 있다면 Max-Age
어트리뷰트가 더 높은 우선순위를 갖습니다.
반대로, 두 어트리뷰트가 모두 설정되어 있지 않으면 현재 세션이 종료될 때 클라이언트가 쿠키를 삭제합니다.
(여기서 "세션"에 대한 정확한 개념은 사용자-에이전트에 의해서 정의됩니다.)
그렇지만, 클라이언트가 쿠키를 아예 무시해버릴 수도 있으므로 주의해야 합니다. 가령, 사용자가 개인 정보 보호를 위해서 쿠키를 비활성화 시켜버릴 수도 있습니다. 또는, 만료가 되기도 전에 클라이언트가 쿠키를 삭제해 버릴 수도 있고 쿠키 저장소의 개수를 제한할 수도 있습니다. 때로는 개인 정보 보호를 위해서 클라이언트가 현재 서버 이외의 다른 도메인의 "3사 쿠키"를 거부하는 경우도 많습니다. 간단하게 말해서, 근본적으로 서버에서 설정한 쿠키가 반드시 재전송 될 것이라고 단정지어서는 안된다는 말입니다.
Web API와 쿠키
쿠키를 HTTP 응답에 추가하려면 먼저 쿠키 자체를 의미하는 CookieHeaderValue 클래스의 인스턴스를 생성해야 합니다. 그런 다음, System.Net.Http.HttpResponseHeadersExtensions 클래스에 정의되어 있는 AddCookies 확장 메서드를 사용해서 쿠키를 추가합니다.
가령, 다음 코드는 컨트롤러에서 쿠키를 추가하는 예제입니다:
public HttpResponseMessage Get() { var resp = new HttpResponseMessage(); var cookie = new CookieHeaderValue("session-id", "12345"); cookie.Expires = DateTimeOffset.Now.AddDays(1); cookie.Domain = Request.RequestUri.Host; cookie.Path = "/"; resp.Headers.AddCookies(new CookieHeaderValue[] { cookie }); return resp; }
이 코드에서 AddCookies 메서드가 CookieHeaderValue 인스턴스의 배열을 전달받는다는 점에 유의하시기 바랍니다.
반대로 클라이언트 요청에서 쿠키를 추출하려면 GetCookies 메서드를 호출합니다:
string sessionId = ""; CookieHeaderValue cookie = Request.Headers.GetCookies("session-id").FirstOrDefault(); if (cookie != null) { sessionId = cookie["session-id"].Value; }
이 코드에서 CookieHeaderValue의 개체는 CookieState 인스턴스들의 컬렉션을 갖고 있습니다. 각각의 CookieState는 하나의 쿠키를 나타내며, 예제 코드에서 볼 수 있는 것처럼 인덱서 메서드에 이름을 지정해서 CookieState를 가져올 수 있습니다.
쿠키 데이터의 구조
많은 브라우저들이 전체 개수 및 도메인 당 개수 모두를 기준으로, 저장할 수 있는 쿠키를 최대량을 제한하고 있습니다. 따라서, 여러 개의 쿠키를 사용하는 대신 하나의 쿠키에 데이터를 구조적으로 설정하는 것이 유용할 수 있습니다.
CookieHeaderValue 클래스를 사용하면 이름-값 쌍의 목록을 쿠키 데이터로 전달할 수 있습니다. 이 때, 설정된 이름-값 쌍들은 URL-인코딩 된 폼 데이터의 형태로 Set-Cookie 헤더에 인코딩됩니다:
var resp = new HttpResponseMessage(); var nv = new NameValueCollection(); nv["sid"] = "12345"; nv["token"] = "abcdef"; nv["theme"] = "dark blue"; var cookie = new CookieHeaderValue("session", nv); resp.Headers.AddCookies(new CookieHeaderValue[] { cookie });
이 코드는 다음과 같은 Set-Cookie 헤더를 만들어내게 됩니다:
Set-Cookie: session=sid=12345&token=abcdef&theme=dark+blue;
물론, CookieState 클래스도 요청값으로부터 쿠키의 서브-값들을 읽어올 수 있는 인덱서 메서드를 제공해줍니다:
string sessionId = ""; string sessionToken = ""; string theme = ""; CookieHeaderValue cookie = Request.Headers.GetCookies("session").FirstOrDefault(); if (cookie != null) { CookieState cookieState = cookie["session"]; sessionId = cookieState["sid"]; sessionToken = cookieState["token"]; theme = cookieState["theme"]; }
예제: 메시지 헨들러를 이용해서 쿠키를 설정하고 가져오기
지금까지는 Web API 컨트롤러에서 쿠키를 다루는 방법을 살펴봤습니다. 선택할 수 있는 한 가지 또 다른 방법은 메시지 핸들러(Message Handler)를 사용하는 것입니다. 메시지 핸들러는 파이프라인 상에서 컨트롤러보다 먼저 호출됩니다. 메시지 헨들러는 요청이 컨트롤러까지 도달하기 전에 먼저 쿠키를 읽거나, 컨트롤러가 응답을 생성한 후에 응답에 쿠키를 추가할 수도 있습니다.
다음 코드는 세션 ID를 생성해주는 메시지 핸들러 예제로, 세션 ID를 쿠키에 저장하고 있습니다. 헨들러는 먼저 요청을 검사해서 세션 쿠키가 존재하는지 여부를 확인한 다음, 쿠키가 존재하지 않으면 새로운 세션 ID를 생성합니다. 그와 반대로 이미 쿠키가 존재하는 경우에는 세션 ID를 HttpRequestMessage.Properties 프로퍼티 백에 저장합니다. 더불어, 세션 쿠키를 HTTP 응답에 추가하는 역할도 수행합니다.
using System; using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Headers; using System.Threading; using System.Threading.Tasks; using System.Web.Http; public class SessionIdHandler : DelegatingHandler { static public string SessionIdToken = "session-id"; async protected override Task<HttpResponseMessage> SendAsync( HttpRequestMessage request, CancellationToken cancellationToken) { string sessionId; // 요청에서 세션 ID를 가져오려고 시도하고, 없으면 새 ID를 생성한다. var cookie = request.Headers.GetCookies(SessionIdToken).FirstOrDefault(); if (cookie == null) { sessionId = Guid.NewGuid().ToString(); } else { sessionId = cookie[SessionIdToken].Value; try { Guid guid = Guid.Parse(sessionId); } catch (FormatException) { // 유효하지 않은 세션 ID. 새로운 ID를 생성한다. sessionId = Guid.NewGuid().ToString(); } } // 세션 ID를 요청의 프로퍼티 백에 저장한다. request.Properties[SessionIdToken] = sessionId; // HTTP 요청을 계속 처리한다. HttpResponseMessage response = await base.SendAsync(request, cancellationToken); // 응답 메시지에 세션 ID를 쿠키로 추가한다. response.Headers.AddCookies(new CookieHeaderValue[] { new CookieHeaderValue(SessionIdToken, sessionId) }); return response; } }
컨트롤러 내부에서는 HttpRequestMessage.Properties 프로퍼티 백을 통해서 세션 ID를 가져올 수 있습니다.
public HttpResponseMessage Get() { string sessionId = Request.Properties[SessionIdHandler.SessionIdToken] as string; return new HttpResponseMessage() { Content = new StringContent("Your session ID = " + sessionId) }; }
- 여러분의 첫 번째 ASP.NET Web API (C#) 2013-05-27 20:30
- CRUD 작업을 지원하는 Web API 작성하기 2013-06-14 20:30
- ASP.NET 웹폼에서 Web API 사용하기 2013-06-25 20:30
- ASP.NET Web API와 라우팅 2013-07-10 20:30
- ASP.NET Web API 도움말 페이지 작성하기 2013-08-14 15:43
- .NET 클라이언트에서 Web API 호출하기 (C#) 2013-08-21 16:26
- WPF 응용 프로그램에서 Web API 호출하기 (C#) 2013-08-28 22:41
- HttpClient 메시지 처리기 2013-09-04 17:16
- ASP.NET Web API 예외 처리 2013-09-11 21:23
- HTML 폼 데이터 전송하기 - 파트 1 2013-09-18 21:23
- HTML 폼 데이터 전송하기 - 파트 2 2013-09-25 01:11
- ASP.NET Web API와 HTTP 쿠키 2013-10-02 09:00
- 자체-호스트(Self-Host) Web API (C#) 2013-10-09 15:59
- OWIN을 이용한 ASP.NET Web API 자체 호스트(Self-Host) 2014-01-17 08:00
- 보안: ASP.NET Web API의 인증(Authentication)과 권한(Authorization) 2014-01-20 08:00
- 보안: 기본 인증 2014-01-22 08:00
- 보안: ASP.NET Web API와 개별 사용자 계정 2014-01-24 08:00
- 보안: 폼 인증 2014-01-27 08:00
- 보안: 통합 Windows 인증 2014-01-29 08:00
- 보안: 크로스 사이트 요청 위조(Cross-Site Request Forgery) 공격 방지하기 2014-02-03 08:00
- 보안: Web API에서 SSL 사용하기 2014-02-05 08:00
- 보안: 외부 인증 서비스 (C#) 2014-02-07 08:00
- 보안: ASP.NET Web API 교차-원본 요청(Cross-Origin Request) 활성화하기 2014-02-10 08:00