ASP.NET Web API 中的 HTTP Cookie
本主題介紹如何在 Web API 中傳送和接收 HTTP Cookie。
HTTP Cookie 的背景
本節簡要概述 Cookie 如何在 HTTP 層級實作。 有關詳細資訊,請參閱 RFC 6265。
Cookie 是伺服器在 HTTP 回應中傳送的一段資料。 用戶端 (可選) 儲存 Cookie 並在後續要求中傳回它。 這可讓用戶端和伺服器共用狀態。 若要設定 Cookie,伺服器在回應中包含 Set-Cookie 標頭。 Cookie 的格式是名稱/值組,具有選擇性屬性。 例如:
Set-Cookie: session-id=1234567
以下一個具有屬性的範例:
Set-Cookie: session-id=1234567; max-age=86400; domain=example.com; path=/;
為了向伺服器傳回 Cookie,用戶端在後續要求中包含 Cookie 標頭。
Cookie: session-id=1234567
HTTP 回應可以包含多個 Set-Cookie 標頭。
Set-Cookie: session-token=abcdef;
Set-Cookie: session-id=1234567;
用戶端使用單一 Cookie 標頭傳回多個 Cookie。
Cookie: session-id=1234567; session-token=abcdef;
Cookie 的範圍和持續時間由 Set-Cookie 標頭中的下列屬性控制:
- 網域:告訴用戶端哪個網域應該接收 Cookie。 例如,如果網域是“example.com”,則用戶端會將 Cookie 傳回 example.com 的每個子網域。 如果不指定,則該網域為來源伺服器。
- 路徑:將 Cookie 限制為網域內的指定路徑。 如果未指定,則使用要求 URI 的路徑。
- 過期:設定 Cookie 的過期日期。 當 Cookie 過期時,用戶端會刪除該 Cookie。
- Max-Age:設定 Cookie 的最長期限。 當 Cookie 達到最大期限時,用戶端會刪除該 Cookie。
如果 Expires
和 Max-Age
均已設定,則 Max-Age
將優先。 如果兩者均未設定,則用戶端會在目前工作階段結束時刪除 Cookie。 (「工作階段」的確切含義由使用者代理程式決定。)
但是,請注意用戶端可能會忽略 Cookie。 例如,出於隱私原因,使用者可能會停用 Cookie。 用戶端可以在 Cookie 過期之前將其刪除,或限制儲存的 Cookie 數量。 出於隱私原因,用戶端經常拒絕「第三方」Cookie,其中網域名稱與來源伺服器不符。 簡而言之,伺服器不應該依賴取回它設定的 Cookie。
Web API 中的 Cookie
若要將 Cookie 新增至 HTTP 回應,請建立表示 Cookie 的 CookieHeaderValue 執行個體。 然後呼叫 AddCookies 擴充方法,該方法在 System.Net.Http. HttpResponseHeadersExtensions 類別中定義,用於新增 Cookie。
例如,以下程式碼在控制器動作中新增 Cookie:
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 執行個體陣列。
若要從用戶端要求中提取 Cookie,請呼叫 GetCookies 方法:
string sessionId = "";
CookieHeaderValue cookie = Request.Headers.GetCookies("session-id").FirstOrDefault();
if (cookie != null)
{
sessionId = cookie["session-id"].Value;
}
CookieHeaderValue 包含 CookieState 執行個體的集合。 每個 CookieState 代表一個 Cookie。 使用索引器方法按名稱取得 CookieState,如圖所示。
結構化 Cookie 資料
許多瀏覽器會限制其儲存的 Cookie 數量,包括總數和每個網域的數量。 因此,將結構化資料放入單一 Cookie 中而不是設定多個 Cookie 會很有用。
注意
RFC 6265 沒有定義 Cookie 資料的結構。
使用 CookieHeaderValue 類別,您可以傳遞 Cookie 資料的名稱/值組清單。 這些名稱/值組在 Set-Cookie 標頭中編碼為 URL 編碼的表單資料:
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 類別提供了一個索引器方法來從要求訊息中的 Cookie 讀取子值:
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"];
}
範例:在訊息處理常式中設定和檢索 Cookie
前面的範例展示如何在 Web API 控制器中使用 Cookie。 另一種選擇是使用訊息處理常式。 訊息處理常式在管道中會比控制器更早叫用。 訊息處理常式可以在要求到達控制器之前從要求中讀取 Cookie,或在控制器產生回應之後將 Cookie 新增至回應。
以下程式碼顯示了用於建立工作階段 ID 的訊息處理常式。 工作階段 ID 會儲存在 Cookie 中。 處理程序檢查工作階段 Cookie 的要求。 如果要求不包含 Cookie,則處理常式會產生新的工作階段 ID。 無論哪種情況,處理程序都會將工作階段 ID 儲存在 HttpRequestMessage.Properties 屬性包中。 它還會將工作階段 Cookie 新增到 HTTP 回應中。
此實作不會驗證來自用戶端的工作階段 ID 是否確實由伺服器發出。 不要把這當作一種驗證形式! 此範例的重點是展示 HTTP Cookie 管理。
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
{
public static string SessionIdToken = "session-id";
async protected override Task<HttpResponseMessage> SendAsync(
HttpRequestMessage request, CancellationToken cancellationToken)
{
string sessionId;
// Try to get the session ID from the request; otherwise create a new 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)
{
// Bad session ID. Create a new one.
sessionId = Guid.NewGuid().ToString();
}
}
// Store the session ID in the request property bag.
request.Properties[SessionIdToken] = sessionId;
// Continue processing the HTTP request.
HttpResponseMessage response = await base.SendAsync(request, cancellationToken);
// Set the session ID as a cookie in the response message.
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)
};
}